diff --git a/tile/docs/facades.md b/tile/docs/facades.md new file mode 100644 index 0000000..a421c89 --- /dev/null +++ b/tile/docs/facades.md @@ -0,0 +1,1272 @@ +# Facades (Database API) + +Sermon Browser provides six facade classes offering static methods for database operations. Facades provide a clean, type-safe API for CRUD operations on sermons, preachers, series, services, files, tags, and books without direct database access. + +## Overview + +All facade classes are located in the `\SermonBrowser\Facades\` namespace and extend `BaseFacade`. They proxy calls to repository classes which handle the actual database operations using WordPress's `$wpdb` object with prepared statements. + +```php { .api } +/** + * Base facade class + * + * Class: \SermonBrowser\Facades\BaseFacade + */ +abstract class BaseFacade { + /** + * Get the repository instance + * + * @return object Repository instance + */ + protected static function getRepository(): object; +} +``` + +## Capabilities + +### Sermon Facade + +Complete CRUD operations and advanced queries for sermon records. + +```php { .api } +use SermonBrowser\Facades\Sermon; + +/** + * Find sermon by ID + * + * @param int $id Sermon ID + * @return object|null Sermon object or null if not found + */ +Sermon::find(int $id): ?object; + +/** + * Find all sermons with optional criteria + * + * @param array $criteria Filter criteria: + * - 'preacher' (int): Filter by preacher ID + * - 'series' (int): Filter by series ID + * - 'service' (int): Filter by service ID + * - 'book' (string): Filter by Bible book name + * - 'tag' (string): Filter by tag name + * - 'date' (string): Filter by date (YYYY-MM-DD) + * - 'enddate' (string): End date for range (YYYY-MM-DD) + * - 'title' (string): Search in title + * @param int $limit Number of results (0 = unlimited) + * @param int $offset Result offset for pagination + * @param string $orderBy Column to sort by (default: 'sermon_date') + * @param string $order Sort direction: 'ASC' or 'DESC' (default: 'DESC') + * @return array Array of sermon objects + */ +Sermon::findAll( + array $criteria = [], + int $limit = 0, + int $offset = 0, + string $orderBy = 'sermon_date', + string $order = 'DESC' +): array; + +/** + * Count sermons matching criteria + * + * @param array $criteria Same as findAll() + * @return int Number of matching sermons + */ +Sermon::count(array $criteria = []): int; + +/** + * Create new sermon + * + * @param array $data Sermon data: + * - 'title' (string, required): Sermon title + * - 'preacher' (int): Preacher ID + * - 'series' (int): Series ID + * - 'service' (int): Service ID + * - 'sermon_date' (string, required): Date (YYYY-MM-DD) + * - 'sermon_date_time' (string): Time (HH:MM) + * - 'bible_passage' (string): Start passage reference + * - 'bible_passage_end' (string): End passage reference + * - 'description' (string): Sermon description + * - 'video_embed' (string): Video embed code + * - 'alternate_embed' (string): Alternate embed code + * @return int Created sermon ID + */ +Sermon::create(array $data): int; + +/** + * Update existing sermon + * + * @param int $id Sermon ID + * @param array $data Sermon data (same keys as create) + * @return bool True on success, false on failure + */ +Sermon::update(int $id, array $data): bool; + +/** + * Delete sermon + * + * @param int $id Sermon ID + * @return bool True on success, false on failure + */ +Sermon::delete(int $id): bool; + +/** + * Find sermons by preacher + * + * @param int $preacherId Preacher ID + * @param int $limit Number of results (default: 10) + * @return array Array of sermon objects + */ +Sermon::findByPreacher(int $preacherId, int $limit = 10): array; + +/** + * Find sermons by series + * + * @param int $seriesId Series ID + * @param int $limit Number of results (default: 10) + * @return array Array of sermon objects + */ +Sermon::findBySeries(int $seriesId, int $limit = 10): array; + +/** + * Find sermons by service + * + * @param int $serviceId Service ID + * @param int $limit Number of results (default: 10) + * @return array Array of sermon objects + */ +Sermon::findByService(int $serviceId, int $limit = 10): array; + +/** + * Find recent sermons + * + * @param int $limit Number of results (default: 10) + * @return array Array of sermon objects + */ +Sermon::findRecent(int $limit = 10): array; + +/** + * Find sermons in date range + * + * @param string $startDate Start date (YYYY-MM-DD) + * @param string $endDate End date (YYYY-MM-DD) + * @param int $limit Number of results (0 = unlimited) + * @return array Array of sermon objects + */ +Sermon::findByDateRange(string $startDate, string $endDate, int $limit = 0): array; + +/** + * Find sermon with all relations (files, tags, books) + * + * @param int $id Sermon ID + * @return object|null Sermon object with 'files', 'tags', 'books' properties + */ +Sermon::findWithRelations(int $id): ?object; + +/** + * Find all sermons with relations + * + * @param array $filter Filter criteria (same as findAll) + * @param int $limit Number of results (default: 0 = unlimited) + * @param int $offset Result offset (default: 0) + * @return array Array of sermon objects with relations + */ +Sermon::findAllWithRelations(array $filter = [], int $limit = 0, int $offset = 0): array; + +/** + * Search sermons by title + * + * @param string $search Search query + * @param int $limit Number of results (default: 10) + * @return array Array of sermon objects + */ +Sermon::searchByTitle(string $search, int $limit = 10): array; + +/** + * Find sermon for template rendering (includes formatted data) + * + * @param int $id Sermon ID + * @return object|null Sermon object with additional formatting + */ +Sermon::findForTemplate(int $id): ?object; + +/** + * Find sermons for frontend listing (optimized query) + * + * @param array $filter Filter criteria + * @param string $order Sort direction: 'ASC' or 'DESC' + * @param int $page Page number (1-based) + * @param int $limit Results per page + * @param bool $hideEmpty Hide sermons without files + * @return array Array of sermon objects + */ +Sermon::findForFrontendListing( + array $filter = [], + string $order = 'DESC', + int $page = 1, + int $limit = 10, + bool $hideEmpty = false +): array; + +/** + * Find next sermon by date + * + * @param string $datetime Reference datetime + * @param int $excludeId Sermon ID to exclude + * @return object|null Next sermon object or null + */ +Sermon::findNextByDate(string $datetime, int $excludeId): ?object; + +/** + * Find previous sermon by date + * + * @param string $datetime Reference datetime + * @param int $excludeId Sermon ID to exclude + * @return object|null Previous sermon object or null + */ +Sermon::findPreviousByDate(string $datetime, int $excludeId): ?object; + +/** + * Find sermons on same day + * + * @param string $datetime Reference datetime + * @param int $excludeId Sermon ID to exclude + * @return array Array of sermon objects on same day + */ +Sermon::findSameDay(string $datetime, int $excludeId): array; + +/** + * Find dates for sermon IDs + * + * @param array $sermonIds Array of sermon IDs + * @return array Array of dates associated with sermon IDs + */ +Sermon::findDatesForIds(array $sermonIds): array; + +/** + * Count filtered sermons + * + * @param array $filter Filter criteria (same as findAll) + * @return int Number of sermons matching filter + */ +Sermon::countFiltered(array $filter = []): int; + +/** + * Find sermons for admin list with filters + * + * @param array $filter Filter criteria + * @param int $limit Number of results + * @param int $offset Result offset + * @return array Array of sermon objects for admin display + */ +Sermon::findForAdminListFiltered(array $filter = [], int $limit = 0, int $offset = 0): array; + +/** + * Check if sermon exists + * + * @param int $id Sermon ID + * @return bool True if sermon exists + */ +Sermon::exists(int $id): bool; + +/** + * Find by column value + * + * @param string $column Column name + * @param mixed $value Value to match + * @return array Array of sermon objects + */ +Sermon::findBy(string $column, mixed $value): array; + +/** + * Find one by column value + * + * @param string $column Column name + * @param mixed $value Value to match + * @return object|null First matching sermon object or null + */ +Sermon::findOneBy(string $column, mixed $value): ?object; +``` + +**Usage Examples:** + +```php +use SermonBrowser\Facades\Sermon; + +// Get a single sermon +$sermon = Sermon::find(123); +if ($sermon) { + echo $sermon->title; +} + +// Get recent sermons +$recent = Sermon::findRecent(10); +foreach ($recent as $sermon) { + echo $sermon->title . '
'; +} + +// Search with criteria +$sermons = Sermon::findAll([ + 'preacher' => 5, + 'series' => 12, + 'date' => '2024-01-01', + 'enddate' => '2024-12-31' +], 20, 0, 'sermon_date', 'DESC'); + +// Get sermon with all related data +$sermon = Sermon::findWithRelations(123); +echo $sermon->title; +foreach ($sermon->files as $file) { + echo $file->stuff; +} +foreach ($sermon->tags as $tag) { + echo $tag->tag_name; +} + +// Create new sermon +$id = Sermon::create([ + 'title' => 'Grace and Truth', + 'sermon_date' => '2024-01-15', + 'preacher' => 5, + 'series' => 12, + 'bible_passage' => 'John 1:14-18', + 'description' => 'Exploring the incarnation...' +]); + +// Update sermon +Sermon::update($id, [ + 'title' => 'Grace and Truth (Updated)', + 'description' => 'New description...' +]); + +// Delete sermon +Sermon::delete($id); +``` + +### Preacher Facade + +CRUD operations for preacher records. + +```php { .api } +use SermonBrowser\Facades\Preacher; + +/** + * Find preacher by ID + * + * @param int $id Preacher ID + * @return object|null Preacher object or null + */ +Preacher::find(int $id): ?object; + +/** + * Find all preachers (unsorted) + * + * @return array Array of preacher objects + */ +Preacher::findAll(): array; + +/** + * Find all preachers sorted by name + * + * @return array Array of preacher objects sorted alphabetically + */ +Preacher::findAllSorted(): array; + +/** + * Create new preacher + * + * @param array $data Preacher data: + * - 'preacher_name' (string, required): Preacher name + * - 'preacher_image' (string): Image URL + * - 'preacher_description' (string): Biography text + * @return int Created preacher ID + */ +Preacher::create(array $data): int; + +/** + * Update existing preacher + * + * @param int $id Preacher ID + * @param array $data Preacher data (same keys as create) + * @return bool True on success + */ +Preacher::update(int $id, array $data): bool; + +/** + * Delete preacher + * + * @param int $id Preacher ID + * @return bool True on success + */ +Preacher::delete(int $id): bool; + +/** + * Find preacher by name (exact match) + * + * @param string $name Preacher name + * @return object|null Preacher object or null + */ +Preacher::findByNameLike(string $name): ?object; + +/** + * Find or create preacher by name + * + * @param string $name Preacher name + * @return int Preacher ID (existing or newly created) + */ +Preacher::findOrCreate(string $name): int; + +/** + * Find all preachers with sermon counts + * + * @return array Array of preacher objects with 'sermon_count' property + */ +Preacher::findAllWithSermonCount(): array; + +/** + * Find all preachers for filter display (formatted) + * + * @return array Array of preachers formatted for filter dropdowns + */ +Preacher::findAllForFilter(): array; + +/** + * Find preachers by sermon IDs with counts + * + * @param array $sermonIds Array of sermon IDs + * @return array Array of preacher objects with sermon counts + */ +Preacher::findBySermonIdsWithCount(array $sermonIds): array; +``` + +**Usage Examples:** + +```php +use SermonBrowser\Facades\Preacher; + +// Get all preachers sorted +$preachers = Preacher::findAllSorted(); +foreach ($preachers as $preacher) { + echo ''; +} + +// Create preacher +$id = Preacher::create([ + 'preacher_name' => 'John Smith', + 'preacher_image' => 'https://example.com/image.jpg', + 'preacher_description' => 'Biography text...' +]); +``` + +### Series Facade + +CRUD operations for sermon series. + +```php { .api } +use SermonBrowser\Facades\Series; + +/** + * Find series by ID + * + * @param int $id Series ID + * @return object|null Series object or null + */ +Series::find(int $id): ?object; + +/** + * Find all series (unsorted) + * + * @return array Array of series objects + */ +Series::findAll(): array; + +/** + * Find all series sorted by name + * + * @return array Array of series objects sorted alphabetically + */ +Series::findAllSorted(): array; + +/** + * Create new series + * + * @param array $data Series data: + * - 'series_name' (string, required): Series name + * - 'series_image' (string): Image URL + * - 'series_description' (string): Series description + * @return int Created series ID + */ +Series::create(array $data): int; + +/** + * Update existing series + * + * @param int $id Series ID + * @param array $data Series data (same keys as create) + * @return bool True on success + */ +Series::update(int $id, array $data): bool; + +/** + * Delete series + * + * @param int $id Series ID + * @return bool True on success + */ +Series::delete(int $id): bool; + +/** + * Find series by name (exact match) + * + * @param string $name Series name + * @return object|null Series object or null + */ +Series::findByNameLike(string $name): ?object; + +/** + * Find or create series by name + * + * @param string $name Series name + * @return int Series ID (existing or newly created) + */ +Series::findOrCreate(string $name): int; + +/** + * Find all series with sermon counts + * + * @return array Array of series objects with 'sermon_count' property + */ +Series::findAllWithSermonCount(): array; + +/** + * Find all series for filter display (formatted) + * + * @return array Array of series formatted for filter dropdowns + */ +Series::findAllForFilter(): array; + +/** + * Find series by sermon IDs with counts + * + * @param array $sermonIds Array of sermon IDs + * @return array Array of series objects with sermon counts + */ +Series::findBySermonIdsWithCount(array $sermonIds): array; +``` + +**Usage Examples:** + +```php +use SermonBrowser\Facades\Series; + +// Get all series for dropdown +$series = Series::findAllSorted(); +foreach ($series as $s) { + echo ''; +} + +// Create series +$id = Series::create([ + 'series_name' => 'Gospel of John', + 'series_description' => 'A verse-by-verse study...' +]); +``` + +### Service Facade + +CRUD operations for church service records. + +```php { .api } +use SermonBrowser\Facades\Service; + +/** + * Find service by ID + * + * @param int $id Service ID + * @return object|null Service object or null + */ +Service::find(int $id): ?object; + +/** + * Find all services (unsorted) + * + * @return array Array of service objects + */ +Service::findAll(): array; + +/** + * Find all services sorted by name + * + * @return array Array of service objects sorted alphabetically + */ +Service::findAllSorted(): array; + +/** + * Create new service + * + * @param array $data Service data: + * - 'service_name' (string, required): Service name + * - 'service_time' (string): Service time (HH:MM) + * @return int Created service ID + */ +Service::create(array $data): int; + +/** + * Update existing service + * + * @param int $id Service ID + * @param array $data Service data (same keys as create) + * @return bool True on success + */ +Service::update(int $id, array $data): bool; + +/** + * Delete service + * + * @param int $id Service ID + * @return bool True on success + */ +Service::delete(int $id): bool; + +/** + * Update service with time shift (updates time while preserving name) + * + * @param int $id Service ID + * @param string $name Service name + * @param string $time Service time (HH:MM) + * @return bool True on success + */ +Service::updateWithTimeShift(int $id, string $name, string $time): bool; + +/** + * Get service time + * + * @param int $id Service ID + * @return string|null Service time (HH:MM) or null if not found + */ +Service::getTime(int $id): ?string; + +/** + * Find all services with sermon counts + * + * @return array Array of service objects with 'sermon_count' property + */ +Service::findAllWithSermonCount(): array; + +/** + * Find all services for filter display (formatted) + * + * @return array Array of services formatted for filter dropdowns + */ +Service::findAllForFilter(): array; + +/** + * Find services by sermon IDs with counts + * + * @param array $sermonIds Array of sermon IDs + * @return array Array of service objects with sermon counts + */ +Service::findBySermonIdsWithCount(array $sermonIds): array; +``` + +**Usage Examples:** + +```php +use SermonBrowser\Facades\Service; + +// Get all services +$services = Service::findAllSorted(); +foreach ($services as $service) { + echo $service->service_name . ' (' . $service->service_time . ')'; +} + +// Create service +$id = Service::create([ + 'service_name' => 'Sunday Morning', + 'service_time' => '10:30' +]); +``` + +### File Facade + +Manage sermon file attachments and download statistics. + +```php { .api } +use SermonBrowser\Facades\File; + +/** + * Find file by ID + * + * @param int $id File ID + * @return object|null File object or null + */ +File::find(int $id): ?object; + +/** + * Find all files for a sermon + * + * @param int $sermonId Sermon ID + * @return array Array of file objects + */ +File::findBySermon(int $sermonId): array; + +/** + * Create new file record + * + * @param array $data File data: + * - 'sermon_id' (int, required): Sermon ID + * - 'stuff' (string, required): File path, URL, or embed code + * - 'stuff_type' (string, required): 'file', 'url', or 'code' + * @return int Created file ID + */ +File::create(array $data): int; + +/** + * Delete file + * + * @param int $id File ID + * @return bool True on success + */ +File::delete(int $id): bool; + +/** + * Increment download count for file by name + * + * @param string $filename File name + * @return bool True on success + */ +File::incrementCountByName(string $filename): bool; + +/** + * Get total download count for all files in sermon + * + * @param int $sermonId Sermon ID + * @return int Total download count + */ +File::getTotalDownloadsBySermon(int $sermonId): int; + +/** + * Find files by sermon and type + * + * @param int $sermonId Sermon ID + * @param string $type File type ('file', 'url', 'code') + * @return array Array of file objects + */ +File::findBySermonAndType(int $sermonId, string $type): array; + +/** + * Find all files by type + * + * @param string $type File type ('file', 'url', 'code') + * @return array Array of file objects + */ +File::findByType(string $type): array; + +/** + * Find unlinked files (not attached to any sermon) + * + * @param int $limit Number of results (0 = unlimited) + * @return array Array of unlinked file objects + */ +File::findUnlinked(int $limit = 0): array; + +/** + * Count unlinked files + * + * @return int Number of unlinked files + */ +File::countUnlinked(): int; + +/** + * Count linked files + * + * @return int Number of files attached to sermons + */ +File::countLinked(): int; + +/** + * Get total downloads across all files + * + * @return int Total download count + */ +File::getTotalDownloads(): int; + +/** + * Count files by type + * + * @param string $type File type ('file', 'url', 'code') + * @return int Number of files of this type + */ +File::countByType(string $type): int; + +/** + * Check if file exists by name + * + * @param string $name File name + * @return bool True if file exists + */ +File::existsByName(string $name): bool; + +/** + * Unlink file from sermon (remove association) + * + * @param int $sermonId Sermon ID + * @return bool True on success + */ +File::unlinkFromSermon(int $sermonId): bool; + +/** + * Link file to sermon + * + * @param int $fileId File ID + * @param int $sermonId Sermon ID + * @return bool True on success + */ +File::linkToSermon(int $fileId, int $sermonId): bool; + +/** + * Delete all non-file records (URLs/codes) for a sermon + * + * @param int $sermonId Sermon ID + * @return bool True on success + */ +File::deleteNonFilesBySermon(int $sermonId): bool; + +/** + * Delete files by IDs (batch delete) + * + * @param array $ids Array of file IDs + * @return bool True on success + */ +File::deleteByIds(array $ids): bool; + +/** + * Delete orphaned non-file records + * + * @return bool True on success + */ +File::deleteOrphanedNonFiles(): bool; + +/** + * Delete empty unlinked file records + * + * @return bool True on success + */ +File::deleteEmptyUnlinked(): bool; + +/** + * Get all file names + * + * @return array Array of file name strings + */ +File::findAllFileNames(): array; + +/** + * Find files for sermon or all unlinked files + * + * @param int $sermonId Sermon ID (0 for unlinked only) + * @return array Array of file objects + */ +File::findBySermonOrUnlinked(int $sermonId): array; + +/** + * Delete unlinked file by name + * + * @param string $name File name + * @return bool True on success + */ +File::deleteUnlinkedByName(string $name): bool; + +/** + * Find unlinked files with title search + * + * @param int $limit Number of results + * @param int $offset Result offset + * @return array Array of unlinked file objects + */ +File::findUnlinkedWithTitle(int $limit = 0, int $offset = 0): array; + +/** + * Find linked files with title search + * + * @param int $limit Number of results + * @param int $offset Result offset + * @return array Array of linked file objects + */ +File::findLinkedWithTitle(int $limit = 0, int $offset = 0): array; + +/** + * Search files by name + * + * @param string $search Search query + * @param int $limit Number of results + * @param int $offset Result offset + * @return array Array of file objects matching search + */ +File::searchByName(string $search, int $limit = 0, int $offset = 0): array; + +/** + * Count files matching search + * + * @param string $search Search query + * @return int Number of matching files + */ +File::countBySearch(string $search): int; + +/** + * Get file duration (for MP3/video files) + * + * @param string $name File name + * @return string|null Duration in HH:MM:SS format or null + */ +File::getFileDuration(string $name): ?string; + +/** + * Set file duration + * + * @param string $name File name + * @param string $duration Duration in HH:MM:SS format + * @return bool True on success + */ +File::setFileDuration(string $name, string $duration): bool; +``` + +**Usage Examples:** + +```php +use SermonBrowser\Facades\File; + +// Get all files for a sermon +$files = File::findBySermon(123); +foreach ($files as $file) { + if ($file->stuff_type === 'file') { + echo ''; + echo basename($file->stuff); + echo ' (' . $file->download_count . ' downloads)'; + } +} + +// Add file to sermon +$id = File::create([ + 'sermon_id' => 123, + 'stuff' => 'sermons/sermon-123.mp3', + 'stuff_type' => 'file' +]); + +// Track download +File::incrementCountByName('sermon-123.mp3'); + +// Get total downloads +$total = File::getTotalDownloadsBySermon(123); +echo "Total downloads: $total"; +``` + +### Tag Facade + +Access sermon tags. + +```php { .api } +use SermonBrowser\Facades\Tag; + +/** + * Find tag by ID + * + * @param int $id Tag ID + * @return object|null Tag object or null + */ +Tag::find(int $id): ?object; + +/** + * Find all tags + * + * @return array Array of tag objects + */ +Tag::findAll(): array; + +/** + * Find all tags for a sermon + * + * @param int $sermonId Sermon ID + * @return array Array of tag objects + */ +Tag::findBySermon(int $sermonId): array; + +/** + * Create new tag + * + * @param array $data Tag data: + * - 'tag_name' (string, required): Tag name + * @return int Created tag ID + */ +Tag::create(array $data): int; + +/** + * Delete tag + * + * @param int $id Tag ID + * @return bool True on success + */ +Tag::delete(int $id): bool; + +/** + * Find tag by name + * + * @param string $name Tag name + * @return object|null Tag object or null + */ +Tag::findByName(string $name): ?object; + +/** + * Find or create tag by name + * + * @param string $name Tag name + * @return int Tag ID (existing or newly created) + */ +Tag::findOrCreate(string $name): int; + +/** + * Find all tags sorted by name + * + * @return array Array of tag objects sorted alphabetically + */ +Tag::findAllSorted(): array; + +/** + * Attach tag to sermon (create relationship) + * + * @param int $sermonId Sermon ID + * @param int $tagId Tag ID + * @return bool True on success + */ +Tag::attachToSermon(int $sermonId, int $tagId): bool; + +/** + * Detach tag from sermon (remove relationship) + * + * @param int $sermonId Sermon ID + * @param int $tagId Tag ID + * @return bool True on success + */ +Tag::detachFromSermon(int $sermonId, int $tagId): bool; + +/** + * Detach all tags from sermon + * + * @param int $sermonId Sermon ID + * @return bool True on success + */ +Tag::detachAllFromSermon(int $sermonId): bool; + +/** + * Find all tags with sermon counts + * + * @param int $limit Number of results (0 = unlimited) + * @return array Array of tag objects with 'sermon_count' property + */ +Tag::findAllWithSermonCount(int $limit = 0): array; + +/** + * Delete unused tags (tags with no sermons) + * + * @return int Number of tags deleted + */ +Tag::deleteUnused(): int; + +/** + * Count non-empty tags (tags with at least one sermon) + * + * @return int Number of tags with sermons + */ +Tag::countNonEmpty(): int; +``` + +**Usage Examples:** + +```php +use SermonBrowser\Facades\Tag; + +// Get tags for a sermon +$tags = Tag::findBySermon(123); +foreach ($tags as $tag) { + echo '' . $tag->tag_name . ' '; +} + +// Get all tags +$allTags = Tag::findAll(); +``` + +### Book Facade + +Access Bible book names. + +```php { .api } +use SermonBrowser\Facades\Book; + +/** + * Find all Bible book names + * + * @return array Array of book name strings + */ +Book::findAllNames(): array; + +/** + * Truncate books table (delete all records) + * + * @return bool True on success + */ +Book::truncate(): bool; + +/** + * Insert a book record + * + * @param string $name Book name + * @return int Inserted book ID + */ +Book::insertBook(string $name): int; + +/** + * Update book name in all sermons + * + * @param string $newName New book name + * @param string $oldName Old book name to replace + * @return bool True on success + */ +Book::updateBookNameInSermons(string $newName, string $oldName): bool; + +/** + * Delete all book references for a sermon + * + * @param int $sermonId Sermon ID + * @return bool True on success + */ +Book::deleteBySermonId(int $sermonId): bool; + +/** + * Insert passage reference + * + * @param string $book Book name + * @param string $chapter Chapter number + * @param string $verse Verse number + * @param int $order Display order + * @param string $type Reference type ('start' or 'end') + * @param int $sermonId Sermon ID + * @return int Inserted reference ID + */ +Book::insertPassageRef(string $book, string $chapter, string $verse, int $order, string $type, int $sermonId): int; + +/** + * Find all book references for a sermon + * + * @param int $sermonId Sermon ID + * @return array Array of book reference objects + */ +Book::findBySermonId(int $sermonId): array; + +/** + * Reset books for locale (update book names to localized versions) + * + * @param array $books Localized book names + * @param array $engBooks English book names + * @return void + */ +Book::resetBooksForLocale(array $books, array $engBooks): void; + +/** + * Get all sermons with verse data + * + * @return array Array of sermon objects with verse data + */ +Book::getSermonsWithVerseData(): array; + +/** + * Update sermon verse data + * + * @param int $sermonId Sermon ID + * @param string $start Start verse reference + * @param string $end End verse reference + * @return bool True on success + */ +Book::updateSermonVerseData(int $sermonId, string $start, string $end): bool; + +/** + * Find all books with sermon counts + * + * @return array Array of book names with 'sermon_count' property + */ +Book::findAllWithSermonCount(): array; + +/** + * Find books by sermon IDs with counts + * + * @param array $sermonIds Array of sermon IDs + * @return array Array of book names with sermon counts + */ +Book::findBySermonIdsWithCount(array $sermonIds): array; +``` + +**Usage Examples:** + +```php +use SermonBrowser\Facades\Book; + +// Get all Bible books for dropdown +$books = Book::findAllNames(); +foreach ($books as $book) { + echo ''; +} +``` + +## Error Handling + +All facade methods return `null` or `false` on failure. Check return values: + +```php +$sermon = Sermon::find(999); +if ($sermon === null) { + // Sermon not found + wp_die('Sermon not found'); +} + +$success = Sermon::delete($id); +if (!$success) { + // Deletion failed + error_log('Failed to delete sermon ' . $id); +} +``` + +## Transaction Support + +For operations requiring multiple database changes, facades do not provide built-in transaction support. Use WordPress's `$wpdb` directly: + +```php +global $wpdb; + +$wpdb->query('START TRANSACTION'); + +try { + $sermonId = Sermon::create($sermonData); + File::create(['sermon_id' => $sermonId, 'stuff' => 'file.mp3', 'stuff_type' => 'file']); + $wpdb->query('COMMIT'); +} catch (Exception $e) { + $wpdb->query('ROLLBACK'); + error_log($e->getMessage()); +} +``` + +## Repository Layer + +Facades delegate to repository classes in `\SermonBrowser\Repositories\*`. Repositories handle: +- SQL query construction with `$wpdb->prepare()` +- Result mapping to objects +- Error handling +- Input validation + +Direct repository access is also available if needed: + +```php +use SermonBrowser\Repositories\SermonRepository; + +$repo = new SermonRepository(); +$sermons = $repo->findAll(['preacher' => 5], 10); +``` diff --git a/tile/docs/gutenberg-blocks.md b/tile/docs/gutenberg-blocks.md new file mode 100644 index 0000000..c5adf27 --- /dev/null +++ b/tile/docs/gutenberg-blocks.md @@ -0,0 +1,389 @@ +# Gutenberg Blocks + +Sermon Browser provides twelve Gutenberg blocks and five pre-built block patterns for Full Site Editing (FSE) support. All blocks use the `sermon-browser` namespace and integrate with the WordPress block editor. + +## Block Registration + +All blocks are registered via the `BlockRegistry` class: + +```php { .api } +/** + * Block registry and initialization + * + * Class: \SermonBrowser\Blocks\BlockRegistry + */ +class BlockRegistry { + /** + * Register all Sermon Browser blocks + * Called on 'init' hook + */ + public static function registerAll(): void; +} +``` + +## Capabilities + +### Sermon List Block + +Display a filterable, paginated list of sermons with AJAX-powered dynamic filtering. + +```php { .api } +/** + * Block: sermon-browser/sermon-list + * Class: \SermonBrowser\Blocks\SermonListBlock + * + * Block attributes: + * - limit (int): Number of sermons per page (default: 10) + * - filter (string): Filter type - "dropdown", "oneclick", "none" (default: "dropdown") + * - preacher (int): Filter by preacher ID (default: 0 = all) + * - series (int): Filter by series ID (default: 0 = all) + * - service (int): Filter by service ID (default: 0 = all) + * - showPagination (bool): Show pagination controls (default: true) + * - orderDirection (string): Sort order - "asc", "desc" (default: "desc") + */ +``` + +### Single Sermon Block + +Display details of a single sermon including title, preacher, date, Bible passage, description, and attached files. + +```php { .api } +/** + * Block: sermon-browser/single-sermon + * Class: \SermonBrowser\Blocks\SingleSermonBlock + * + * Block attributes: + * - sermonId (int): Sermon ID to display (default: 0 = latest) + * - showPreacher (bool): Display preacher name (default: true) + * - showDate (bool): Display sermon date (default: true) + * - showBiblePassage (bool): Display Bible passage (default: true) + * - showDescription (bool): Display description (default: true) + * - showFiles (bool): Display attached files (default: true) + */ +``` + +### Tag Cloud Block + +Display a tag cloud of sermon topics with font sizes based on usage frequency. + +```php { .api } +/** + * Block: sermon-browser/tag-cloud + * Class: \SermonBrowser\Blocks\TagCloudBlock + * + * Block attributes: + * - minFontSize (int): Minimum font size in pixels (default: 12) + * - maxFontSize (int): Maximum font size in pixels (default: 24) + */ +``` + +### Preacher List Block + +Display a list of all preachers with optional images and biographies. + +```php { .api } +/** + * Block: sermon-browser/preacher-list + * Class: \SermonBrowser\Blocks\PreacherListBlock + * + * Block attributes: + * - showImages (bool): Display preacher images (default: true) + * - showBios (bool): Display preacher biographies (default: false) + * - layout (string): Display layout - "grid", "list" (default: "grid") + */ +``` + +### Series Grid Block + +Display sermon series in a grid layout with series images and descriptions. + +```php { .api } +/** + * Block: sermon-browser/series-grid + * Class: \SermonBrowser\Blocks\SeriesGridBlock + * + * Block attributes: + * - columns (int): Number of columns (default: 3) + * - showImages (bool): Display series images (default: true) + * - showDescriptions (bool): Display series descriptions (default: true) + * - showSermonCount (bool): Display sermon count per series (default: true) + */ +``` + +### Sermon Player Block + +Display an audio/video player for sermon media files. + +```php { .api } +/** + * Block: sermon-browser/sermon-player + * Class: \SermonBrowser\Blocks\SermonPlayerBlock + * + * Block attributes: + * - sermonId (int): Sermon ID to play (default: 0 = latest) + * - autoplay (bool): Autoplay on load (default: false) + * - showPlaylist (bool): Show playlist of all files (default: true) + */ +``` + +### Recent Sermons Block + +Display a list of the most recent sermons. + +```php { .api } +/** + * Block: sermon-browser/recent-sermons + * Class: \SermonBrowser\Blocks\RecentSermonsBlock + * + * Block attributes: + * - limit (int): Number of sermons to display (default: 5) + * - showPreacher (bool): Display preacher name (default: true) + * - showDate (bool): Display sermon date (default: true) + * - showExcerpt (bool): Display excerpt (default: false) + */ +``` + +### Popular Sermons Block + +Display most popular sermons based on download counts. + +```php { .api } +/** + * Block: sermon-browser/popular-sermons + * Class: \SermonBrowser\Blocks\PopularSermonsBlock + * + * Block attributes: + * - limit (int): Number of sermons to display (default: 5) + * - showDownloadCount (bool): Display download count (default: true) + * - timeRange (string): Time range - "all", "week", "month", "year" (default: "all") + */ +``` + +### Sermon Grid Block + +Display sermons in a grid layout with featured images. + +```php { .api } +/** + * Block: sermon-browser/sermon-grid + * Class: \SermonBrowser\Blocks\SermonGridBlock + * + * Block attributes: + * - columns (int): Number of columns (default: 3) + * - limit (int): Number of sermons (default: 12) + * - showPreacher (bool): Display preacher (default: true) + * - showDate (bool): Display date (default: true) + * - showExcerpt (bool): Display excerpt (default: true) + */ +``` + +### Profile Block + +Display a preacher profile with image, biography, and recent sermons. + +```php { .api } +/** + * Block: sermon-browser/profile-block + * Class: \SermonBrowser\Blocks\ProfileBlock + * + * Block attributes: + * - preacherId (int): Preacher ID to display + * - showImage (bool): Display preacher image (default: true) + * - showBio (bool): Display biography (default: true) + * - showRecentSermons (bool): Display recent sermons (default: true) + * - recentLimit (int): Number of recent sermons (default: 5) + */ +``` + +### Sermon Media Block + +Display media attachments (audio/video/documents) for a sermon. + +```php { .api } +/** + * Block: sermon-browser/sermon-media + * Class: \SermonBrowser\Blocks\SermonMediaBlock + * + * Block attributes: + * - sermonId (int): Sermon ID (default: 0 = latest) + * - layout (string): Display layout - "list", "grid" (default: "list") + * - showIcons (bool): Display file type icons (default: true) + * - showDownloadCounts (bool): Display download counts (default: false) + */ +``` + +### Sermon Filters Block + +Display filter controls for sermon lists (used with Sermon List block). + +```php { .api } +/** + * Block: sermon-browser/sermon-filters + * Class: \SermonBrowser\Blocks\SermonFiltersBlock + * + * Block attributes: + * - filterType (string): Filter UI type - "dropdown", "oneclick" (default: "dropdown") + * - showPreacherFilter (bool): Show preacher filter (default: true) + * - showSeriesFilter (bool): Show series filter (default: true) + * - showServiceFilter (bool): Show service filter (default: true) + * - showBookFilter (bool): Show Bible book filter (default: true) + * - showDateFilter (bool): Show date filter (default: true) + * - showTagFilter (bool): Show tag filter (default: true) + */ +``` + +## Block Patterns + +Pre-built block patterns combining multiple blocks for common layouts: + +### Featured Sermon Hero + +Hero section with a featured sermon display. + +```php { .api } +/** + * Pattern: sermon-browser/featured-sermon-hero + * File: src/Blocks/patterns/featured-sermon-hero.php + * + * Contains: + * - Single Sermon Block (latest sermon) + * - Sermon Player Block + * - Large typography and spacing + */ +``` + +### Sermon Archive Page + +Full-page sermon archive layout with filters and pagination. + +```php { .api } +/** + * Pattern: sermon-browser/sermon-archive-page + * File: src/Blocks/patterns/sermon-archive-page.php + * + * Contains: + * - Page heading + * - Sermon Filters Block + * - Sermon List Block with pagination + * - Sidebar with Tag Cloud and Popular Sermons + */ +``` + +### Preacher Spotlight + +Highlighted preacher profile section. + +```php { .api } +/** + * Pattern: sermon-browser/preacher-spotlight + * File: src/Blocks/patterns/preacher-spotlight.php + * + * Contains: + * - Profile Block + * - Recent sermons by preacher + * - Call-to-action for more sermons + */ +``` + +### Popular This Week + +Display most popular sermons from the past week. + +```php { .api } +/** + * Pattern: sermon-browser/popular-this-week + * File: src/Blocks/patterns/popular-this-week.php + * + * Contains: + * - Section heading + * - Popular Sermons Block (timeRange: "week") + * - Styled card layout + */ +``` + +### Tag Cloud Sidebar + +Sidebar section with sermon topics tag cloud. + +```php { .api } +/** + * Pattern: sermon-browser/tag-cloud-sidebar + * File: src/Blocks/patterns/tag-cloud-sidebar.php + * + * Contains: + * - Sidebar heading + * - Tag Cloud Block + * - Optional "Browse all topics" link + */ +``` + +## Full Site Editing (FSE) Support + +FSE support provided through template parts and block templates: + +```php { .api } +/** + * FSE support class + * + * Class: \SermonBrowser\Blocks\FSESupport + * + * Template parts: + * - sermon-archive.html: Archive page template + * - single-sermon.html: Single sermon template + */ +class FSESupport { + /** + * Register template parts and block templates + * Called on 'init' hook + */ + public static function register(): void; +} +``` + +## Block Editor Integration + +Blocks are registered with WordPress using `register_block_type()`: + +```php { .api } +/** + * Register a block with WordPress + * + * @param string $name Block name (without namespace) + * @param array $args Block registration arguments + */ +register_block_type('sermon-browser/' . $name, $args); +``` + +## Block Assets + +Each block includes: +- **JavaScript**: Block editor functionality (in `assets/js/blocks/`) +- **CSS**: Block styling (in `assets/css/blocks/`) +- **PHP**: Server-side rendering (in `src/Blocks/`) + +Assets are automatically enqueued when blocks are used on a page. + +## Server-Side Rendering + +All blocks use server-side rendering for dynamic content: + +```php { .api } +/** + * Render block content on the server + * + * Each block class implements: + */ +public static function render($attributes, $content): string; +``` + +## Block Deprecation + +Blocks follow WordPress block deprecation patterns to maintain backward compatibility when block structure changes. Deprecated versions are stored in block metadata. + +## Block Variations + +Some blocks support variations for common use cases. For example, the Sermon List block has variations: +- **Default**: Standard list with all filters +- **By Preacher**: Pre-filtered by preacher +- **By Series**: Pre-filtered by series +- **Recent Only**: Recent sermons without filters diff --git a/tile/docs/index.md b/tile/docs/index.md new file mode 100644 index 0000000..a000faf --- /dev/null +++ b/tile/docs/index.md @@ -0,0 +1,260 @@ +# Sermon Browser + +Sermon Browser is a comprehensive WordPress plugin that enables churches to upload, manage, and display sermons on their websites with full podcasting capabilities. The plugin provides a complete sermon management system including search functionality by topic, preacher, bible passage, and date; support for multiple file types (MP3, PDF, PowerPoint, video embeds from YouTube/Vimeo); customizable sidebar widgets; built-in media player for MP3 files; RSS/iTunes podcast feeds with custom filtering; automatic ID3 tag parsing; and integration with multiple Bible versions. + +## Package Information + +- **Package Name**: sermon-browser +- **Package Type**: WordPress Plugin (Composer) +- **Language**: PHP 8.0+ +- **WordPress Version**: 6.0+ +- **Installation**: Install via Composer (`composer require sermon-browser/sermon-browser`) or upload plugin folder to `/wp-content/plugins/` and activate in WordPress admin + +## Core Imports + +```php +// Facade classes for database operations +use SermonBrowser\Facades\Sermon; +use SermonBrowser\Facades\Preacher; +use SermonBrowser\Facades\Series; +use SermonBrowser\Facades\Service; +use SermonBrowser\Facades\File; +use SermonBrowser\Facades\Tag; +use SermonBrowser\Facades\Book; + +// Template engine +use SermonBrowser\Templates\TemplateEngine; +use SermonBrowser\Templates\TemplateManager; + +// Block classes +use SermonBrowser\Blocks\BlockRegistry; + +// REST API controllers +use SermonBrowser\REST\SermonsController; +use SermonBrowser\REST\PreachersController; + +// Widget classes +use SermonBrowser\Widgets\SermonsWidget; +use SermonBrowser\Widgets\TagCloudWidget; +use SermonBrowser\Widgets\PopularWidget; + +// Plugin automatically initializes when WordPress loads +// Main initialization occurs on the 'plugins_loaded' and 'init' hooks +// No manual setup required - plugin is ready to use after activation +``` + +## Basic Usage + +```php +// Display sermons using shortcode in post/page content +echo do_shortcode('[sermons limit="10" filter="dropdown"]'); + +// Or use in template files +if (function_exists('sb_display_sermons')) { + sb_display_sermons([ + 'limit' => 10, + 'filter' => 'dropdown' + ]); +} + +// Get sermons programmatically +$sermons = sb_get_sermons( + ['preacher' => 5, 'series' => 2], // filter + 'DESC', // order + 1, // page + 10, // limit + false // hide_empty +); + +foreach ($sermons as $sermon) { + echo '

' . esc_html($sermon->title) . '

'; + echo '

' . esc_html($sermon->preacher_name) . '

'; +} +``` + +## Architecture + +The plugin uses a modern PSR-4 architecture with the following key components: + +- **Facades**: Static API for database operations (`\SermonBrowser\Facades\*`) +- **Repositories**: Database access layer (`\SermonBrowser\Repositories\*`) +- **REST API**: RESTful endpoints for AJAX and external access (`\SermonBrowser\REST\*`) +- **Blocks**: Gutenberg block components (`\SermonBrowser\Blocks\*`) +- **Widgets**: Traditional WordPress widgets (`\SermonBrowser\Widgets\*`) +- **Template System**: Customizable rendering engine (`\SermonBrowser\Templates\*`) +- **Security**: Input sanitization and CSRF protection (`\SermonBrowser\Security\*`) + +## Capabilities + +### Shortcodes + +Display sermons on any page or post using the `[sermons]` and `[sermon]` shortcodes with extensive filtering and display options including dropdown filters, one-click filters, and conditional display. + +```php { .api } +// Display multiple sermons with filtering +[sermons filter="dropdown" limit="10" preacher="5" series="2"] + +// Display single sermon +[sermon id="123"] +``` + +[Shortcodes](./shortcodes.md) + +### WordPress Widgets + +Three widget types for sidebar display: sermon list widget with filtering options, tag cloud widget for sermon topics, and popular sermons/series/preachers widget. + +```php { .api } +// Widgets registered via widgets_init hook +class SermonsWidget extends WP_Widget // Display recent sermons +class TagCloudWidget extends WP_Widget // Display tag cloud +class PopularWidget extends WP_Widget // Display popular content +``` + +[Widgets](./widgets.md) + +### Gutenberg Blocks + +Twelve Gutenberg blocks for Full Site Editing including sermon lists, single sermon display, tag clouds, series grids, media players, preacher profiles, and filter controls, plus five pre-built block patterns. + +```php { .api } +// Block namespace: sermon-browser +sermon-browser/sermon-list // Filterable sermon list +sermon-browser/single-sermon // Single sermon display +sermon-browser/tag-cloud // Tag cloud display +// ... and 9 more blocks +``` + +[Gutenberg Blocks](./gutenberg-blocks.md) + +### REST API + +RESTful API with seven controllers providing full CRUD operations for sermons, preachers, series, services, files, tags, and search, with rate limiting and authentication. + +```php { .api } +// REST namespace: sermon-browser/v1 +// Base URL: /wp-json/sermon-browser/v1/ + +GET /sermons // List sermons with filters +POST /sermons // Create sermon +GET /sermons/{id} // Get single sermon +PUT /sermons/{id} // Update sermon +DELETE /sermons/{id} // Delete sermon +// ... and 40+ more endpoints +``` + +[REST API](./rest-api.md) + +### Template Tag Functions + +Over 100 public PHP functions for retrieving and displaying sermon data, including sermon retrieval, display helpers, pagination, Bible text integration, file handling, and podcast support. + +```php { .api } +function sb_get_sermons($filter, $order, $page, $limit, $hide_empty): array; +function sb_display_sermons($options): void; +function sb_print_sermon_link($sermon, $echo): string|void; +function sb_print_filters($filter): void; +// ... and 100+ more functions +``` + +[Template Tag Functions](./template-tags.md) + +### Facade Database API + +Six facade classes providing static methods for database operations with type-safe interfaces for sermons, preachers, series, services, files, tags, and books. + +```php { .api } +use SermonBrowser\Facades\Sermon; + +$sermon = Sermon::find($id); +$sermons = Sermon::findAll($criteria, $limit, $offset); +$recent = Sermon::findRecent(10); +``` + +[Facades](./facades.md) + +### Template System + +Customizable template engine for rendering sermon lists and single sermon displays with tag-based templating, transient caching, and support for custom templates. + +```php { .api } +use SermonBrowser\Templates\TemplateEngine; + +$engine = new TemplateEngine(); +$html = $engine->render('search', $data); // Multi-sermon list +$html = $engine->render('single', $data); // Single sermon +``` + +[Template System](./template-system.md) + +### WordPress Hooks + +Actions and filters for extending plugin functionality including core initialization hooks, frontend display hooks, admin panel hooks, and custom filter hooks. + +```php { .api } +// Actions +add_action('init', 'sb_sermon_init'); +add_action('wp_head', function() { /* headers */ }); + +// Filters +add_filter('wp_title', 'sb_page_title'); +add_filter('widget_title', function($title) { /* filter */ }); +``` + +[WordPress Hooks](./wordpress-hooks.md) + +### Podcast Feed + +RSS 2.0 podcast feed with iTunes extensions supporting custom filtering by preacher, series, or service, automatic enclosure metadata, and full podcast directory compatibility. + +```php { .api } +// Podcast feed URLs +?podcast // All sermons +?podcast&preacher=ID // By preacher +?podcast&series=ID // By series +?podcast&service=ID // By service +``` + +[Podcast Feed](./podcast.md) + +## Database Schema + +The plugin creates nine database tables with the WordPress table prefix followed by `sb_`: + +- **sb_sermons**: Main sermon records with title, dates, Bible passages, descriptions +- **sb_preachers**: Preacher information with names, images, biographies +- **sb_series**: Sermon series with names, images, descriptions +- **sb_services**: Church services with names and times +- **sb_stuff**: Attached files, URLs, and embed codes linked to sermons +- **sb_tags**: Sermon topic tags +- **sb_books**: Bible books +- **sb_sermons_tags**: Many-to-many relationship between sermons and tags +- **sb_books_sermons**: Many-to-many relationship between sermons and books + +## Security + +The plugin includes comprehensive security features: + +- **Input Sanitization**: All user input sanitized via `\SermonBrowser\Security\Sanitizer` +- **CSRF Protection**: Token-based CSRF protection for admin forms +- **SQL Injection Prevention**: Prepared statements using `$wpdb->prepare()` +- **XSS Prevention**: All output escaped with `esc_html()`, `esc_attr()`, `esc_url()` +- **Rate Limiting**: REST API rate limits (60/min anonymous, 120/min authenticated) +- **Capability Checks**: WordPress capability verification for all privileged operations + +## Internationalization + +The plugin supports internationalization with text domain `sermon-browser` and includes translations for 9 languages. All strings use WordPress i18n functions (`__()`, `_e()`, `esc_html__()`). + +## Constants + +Key plugin constants defined in `\SermonBrowser\Constants`: + +```php { .api } +const REST_NAMESPACE = 'sermon-browser/v1'; +const CAP_MANAGE_SERMONS = 'edit_posts'; +const RATE_LIMIT_ANON = 60; // Requests per minute +const RATE_LIMIT_AUTH = 120; // Requests per minute +const RATE_LIMIT_SEARCH_ANON = 20; // Search requests per minute +const RATE_LIMIT_SEARCH_AUTH = 60; // Search requests per minute +``` diff --git a/tile/docs/podcast.md b/tile/docs/podcast.md new file mode 100644 index 0000000..7d09c93 --- /dev/null +++ b/tile/docs/podcast.md @@ -0,0 +1,445 @@ +# Podcast Feed + +Sermon Browser generates RSS 2.0 podcast feeds with iTunes extensions for distributing sermons via podcast directories like Apple Podcasts, Spotify, and Google Podcasts. The feeds include automatic enclosure metadata, support custom filtering, and are fully compatible with podcast standards. + +## Overview + +The podcast feed is automatically generated and accessible via URL parameters on the main sermons page. + +```php { .api } +/** + * Base podcast feed URL + * + * Format: {sermons_page_url}?podcast + * Content-Type: application/rss+xml + * Encoding: UTF-8 + */ + +// Example URLs: +// https://example.com/sermons/?podcast +// https://example.com/?page_id=123&podcast +``` + +## Capabilities + +### Basic Podcast Feed + +Generate a podcast feed containing all sermons. + +```php { .api } +/** + * All sermons podcast feed + * + * URL: ?podcast + * + * Returns RSS 2.0 feed with all sermons ordered by date (newest first) + * Includes enclosures for all MP3 files attached to sermons + */ + +// Example +$feed_url = sb_podcast_url(); +// Returns: https://example.com/sermons/?podcast +``` + +**Usage:** + +```php +// Get podcast feed URL +$url = sb_podcast_url(); +echo 'Subscribe to Podcast'; + +// Or use template tag + +``` + +### Filtered Podcast Feeds + +Generate podcast feeds filtered by preacher, series, or service. + +```php { .api } +/** + * Preacher-specific podcast feed + * + * URL: ?podcast&preacher={id} + * + * Returns RSS feed containing only sermons by specified preacher + */ + +/** + * Series-specific podcast feed + * + * URL: ?podcast&series={id} + * + * Returns RSS feed containing only sermons in specified series + */ + +/** + * Service-specific podcast feed + * + * URL: ?podcast&service={id} + * + * Returns RSS feed containing only sermons from specified service + */ +``` + +**Usage:** + +```php +// Preacher podcast feed +$preacher_id = 5; +$url = sb_podcast_url() . '&preacher=' . $preacher_id; +echo 'Subscribe to John Smith\'s sermons'; + +// Series podcast feed +$series_id = 12; +$url = sb_podcast_url() . '&series=' . $series_id; +echo 'Subscribe to Gospel of John series'; + +// Service podcast feed +$service_id = 1; +$url = sb_podcast_url() . '&service=' . $service_id; +echo 'Subscribe to Sunday Morning sermons'; +``` + +### Feed Metadata + +Each podcast feed includes channel-level metadata: + +```php { .api } +/** + * Channel metadata (RSS 2.0) + * + * - title: Site name + " Sermons" (or filtered version) + * - link: Site URL + * - description: Site description or custom podcast description + * - language: Site language (e.g., en-US) + * - copyright: Site name + * - lastBuildDate: RFC 2822 formatted date + * - generator: "Sermon Browser" + * + * iTunes extensions: + * - itunes:author: Site name + * - itunes:subtitle: Short description + * - itunes:summary: Full description + * - itunes:owner: Site email and name + * - itunes:image: Site icon or custom podcast image + * - itunes:category: Religion & Spirituality > Christianity + * - itunes:explicit: no + */ +``` + +### Item (Sermon) Data + +Each sermon in the feed includes: + +```php { .api } +/** + * Sermon item data (RSS 2.0) + * + * - title: Sermon title + * - link: Permalink to sermon page + * - description: Sermon description (HTML) + * - pubDate: RFC 2822 formatted sermon date + * - guid: Unique identifier (permalink) + * + * Enclosure: + * - url: Direct link to MP3 file + * - length: File size in bytes + * - type: MIME type (audio/mpeg) + * + * iTunes extensions: + * - itunes:author: Preacher name + * - itunes:subtitle: Bible passage + * - itunes:summary: Description + * - itunes:duration: HH:MM:SS format + * - itunes:keywords: Tags as comma-separated list + */ +``` + +## Podcast Functions + +Template tag functions for podcast feed generation: + +```php { .api } +/** + * Get podcast feed URL + * + * @return string Full URL to podcast feed + */ +function sb_podcast_url(): string; + +/** + * Print ISO 8601 formatted date for podcast + * + * @param object $sermon Sermon object + * @return void Outputs RFC 2822 date string + */ +function sb_print_iso_date($sermon): void; + +/** + * Get media file size for enclosure + * + * @param string $media_name File path or URL + * @param string $media_type Media type ('file', 'url', 'code') + * @return int File size in bytes (0 if unable to determine) + */ +function sb_media_size($media_name, $media_type): int; + +/** + * Get MP3 duration + * + * @param string $media_name File path + * @param string $media_type Media type ('file', 'url', 'code') + * @return string Duration in HH:MM:SS format + */ +function sb_mp3_duration($media_name, $media_type): string; + +/** + * XML entity encode string for RSS + * + * @param string $string String to encode + * @return string Encoded string safe for XML + */ +function sb_xml_entity_encode($string): string; + +/** + * Get podcast file URL + * + * @param string $media_name File path + * @param string $media_type Media type ('file', 'url', 'code') + * @return string Full URL to file for enclosure + */ +function sb_podcast_file_url($media_name, $media_type): string; + +/** + * Get MIME type for file + * + * @param string $media_name File path or URL + * @return string MIME type (e.g., 'audio/mpeg', 'video/mp4') + */ +function sb_mime_type($media_name): string; +``` + +## Feed Generation Example + +```php +// Generate custom podcast feed +header('Content-Type: application/rss+xml; charset=UTF-8'); + +echo ''; +?> + + + <?php echo get_bloginfo('name'); ?> Sermons + + + en-US + + + + + + + + + + + <?php echo sb_xml_entity_encode($sermon->title); ?> + + description; ?>]]> + + + + + + preacher_name); ?> + bible_passage); ?> + stuff, $file->stuff_type); ?> + + + + +``` + +## Podcast Optimization + +### File Size Caching + +File sizes are calculated on-demand but should be cached: + +```php +// Cache file size in transient +$file_size = get_transient('sb_file_size_' . md5($file_path)); +if ($file_size === false) { + $file_size = sb_media_size($file_path, 'file'); + set_transient('sb_file_size_' . md5($file_path), $file_size, DAY_IN_SECONDS); +} +``` + +### Duration Parsing + +MP3 duration is parsed from ID3 tags: + +```php +// Get duration from MP3 file +$duration = sb_mp3_duration('/path/to/sermon.mp3', 'file'); +// Returns: "01:23:45" (HH:MM:SS) + +// Duration is calculated using getID3 library or ffprobe if available +``` + +### Feed Caching + +Podcast feeds should be cached to reduce server load: + +```php +// Cache feed for 1 hour +$cache_key = 'sb_podcast_' . md5($_SERVER['QUERY_STRING']); +$feed_xml = get_transient($cache_key); + +if ($feed_xml === false) { + // Generate feed + ob_start(); + // ... feed generation code ... + $feed_xml = ob_get_clean(); + + // Cache for 1 hour + set_transient($cache_key, $feed_xml, HOUR_IN_SECONDS); +} + +echo $feed_xml; +``` + +## Podcast Directories + +### Apple Podcasts + +Submit podcast feed to Apple Podcasts: + +1. Get feed URL: `https://example.com/sermons/?podcast` +2. Open Podcasts Connect: https://podcastsconnect.apple.com +3. Submit feed URL +4. Verify ownership +5. Wait for approval + +### Spotify + +Submit to Spotify for Podcasters: + +1. Sign up at https://podcasters.spotify.com +2. Add podcast feed URL +3. Verify ownership +4. Complete metadata + +### Google Podcasts + +Submit to Google Podcasts Manager: + +1. Visit https://podcastsmanager.google.com +2. Add feed URL +3. Verify ownership +4. Publish + +## iTunes Tags + +The feed includes all required iTunes podcast tags: + +```xml +Church Name +Weekly sermons from Church Name +Full description of podcast content... + + Pastor Name + podcast@example.com + + + + + +no +``` + +## Feed Validation + +Validate podcast feed before submission: + +- **Cast Feed Validator**: https://castfeedvalidator.com +- **Podbase**: https://podba.se/validate +- **iTunes Podcast Spec**: Check against official spec + +Common validation errors: +- Missing or invalid enclosure URLs +- Incorrect MIME types +- Invalid XML characters +- Missing iTunes tags +- Incorrect date formats + +## Custom Podcast Settings + +Customize podcast metadata via WordPress options: + +```php +// Set custom podcast title +update_option('sb_podcast_title', 'My Church Sermons'); + +// Set custom podcast description +update_option('sb_podcast_description', 'Weekly messages from My Church'); + +// Set custom podcast image +update_option('sb_podcast_image', 'https://example.com/podcast-art.jpg'); + +// Set custom podcast author +update_option('sb_podcast_author', 'Pastor John Smith'); + +// Set podcast owner email +update_option('sb_podcast_email', 'podcast@example.com'); + +// Set podcast category +update_option('sb_podcast_category', 'Religion & Spirituality'); +``` + +## Troubleshooting + +### Feed Not Validating + +- Check XML validity with validator +- Ensure all enclosure URLs are accessible +- Verify MIME types are correct +- Check for invalid characters in titles/descriptions + +### Files Not Playing + +- Ensure MP3 files are publicly accessible +- Check file permissions (should be readable) +- Verify correct MIME type (audio/mpeg) +- Test file URLs directly in browser + +### Feed Not Updating + +- Clear WordPress transient cache +- Clear podcast directory cache (may take 24-48 hours) +- Verify lastBuildDate is current +- Check that new sermons have audio files + +### Missing Duration + +- Ensure getID3 library is available +- Check MP3 file has valid ID3 tags +- Verify file is readable by PHP +- Fallback: manually set duration in database diff --git a/tile/docs/rest-api.md b/tile/docs/rest-api.md new file mode 100644 index 0000000..dffe49d --- /dev/null +++ b/tile/docs/rest-api.md @@ -0,0 +1,706 @@ +# REST API + +Sermon Browser provides a comprehensive RESTful API for CRUD operations on sermons, preachers, series, services, files, tags, and search. All endpoints are namespaced under `sermon-browser/v1` and support standard HTTP methods. + +## API Configuration + +```php { .api } +// REST API namespace +const REST_NAMESPACE = 'sermon-browser/v1'; + +// Base URL +// https://example.com/wp-json/sermon-browser/v1/ + +// Authentication: WordPress authentication (cookies, application passwords, or OAuth) +// Rate limiting: Applied per IP address +// - Anonymous: 60 requests/minute (20 for search) +// - Authenticated: 120 requests/minute (60 for search) +``` + +## Rate Limiting + +```php { .api } +/** + * Rate limiter class + * + * Class: \SermonBrowser\REST\RateLimiter + */ +class RateLimiter { + const RATE_LIMIT_ANON = 60; // Anonymous requests per minute + const RATE_LIMIT_AUTH = 120; // Authenticated requests per minute + const RATE_LIMIT_SEARCH_ANON = 20; // Anonymous search requests per minute + const RATE_LIMIT_SEARCH_AUTH = 60; // Authenticated search requests per minute + + /** + * Check if request should be rate limited + * + * @param string $identifier User IP or user ID + * @param string $type Request type ('default' or 'search') + * @return bool True if rate limited + */ + public static function isRateLimited($identifier, $type = 'default'): bool; +} +``` + +## Capabilities + +### Sermons Controller + +Full CRUD operations for sermon management with filtering, pagination, and rendering. + +```php { .api } +/** + * Sermons REST controller + * + * Class: \SermonBrowser\REST\SermonsController + * Base route: /sermons + */ + +/** + * GET /wp-json/sermon-browser/v1/sermons + * + * List sermons with optional filtering and pagination + * + * Query parameters: + * - preacher (int): Filter by preacher ID + * - series (int): Filter by series ID + * - service (int): Filter by service ID + * - search (string): Search in sermon titles + * - page (int): Page number (default: 1) + * - per_page (int): Results per page (default: 10, max: 100) + * + * Response: Array of sermon objects + * { + * "id": 123, + * "title": "Grace and Truth", + * "preacher": 5, + * "preacher_name": "John Smith", + * "series": 12, + * "series_name": "Gospel of John", + * "service": 1, + * "service_name": "Sunday Morning", + * "sermon_date": "2024-01-15", + * "sermon_date_time": "10:30", + * "bible_passage": "John 1:14-18", + * "bible_passage_end": "", + * "description": "Exploring the incarnation...", + * "video_embed": "", + * "alternate_embed": "" + * } + */ +GET /sermons + +/** + * POST /wp-json/sermon-browser/v1/sermons + * + * Create a new sermon + * + * Required capability: edit_posts + * + * Request body: + * { + * "title": "string (required)", + * "preacher": "int (optional)", + * "series": "int (optional)", + * "service": "int (optional)", + * "sermon_date": "YYYY-MM-DD (required)", + * "sermon_date_time": "HH:MM (optional)", + * "bible_passage": "string (optional)", + * "bible_passage_end": "string (optional)", + * "description": "string (optional)", + * "video_embed": "string (optional)", + * "alternate_embed": "string (optional)" + * } + * + * Response: Created sermon object with ID + */ +POST /sermons + +/** + * GET /wp-json/sermon-browser/v1/sermons/{id} + * + * Get a single sermon by ID + * + * Path parameters: + * - id (int): Sermon ID + * + * Response: Single sermon object with all related data (files, tags, books) + */ +GET /sermons/{id} + +/** + * PUT /wp-json/sermon-browser/v1/sermons/{id} + * PATCH /wp-json/sermon-browser/v1/sermons/{id} + * + * Update an existing sermon + * + * Required capability: edit_posts + * + * Path parameters: + * - id (int): Sermon ID + * + * Request body: Partial or full sermon object (same as POST) + * + * Response: Updated sermon object + */ +PUT /sermons/{id} +PATCH /sermons/{id} + +/** + * DELETE /wp-json/sermon-browser/v1/sermons/{id} + * + * Delete a sermon + * + * Required capability: edit_posts + * + * Path parameters: + * - id (int): Sermon ID + * + * Response: Success message + * { + * "success": true, + * "message": "Sermon deleted successfully" + * } + */ +DELETE /sermons/{id} + +/** + * GET /wp-json/sermon-browser/v1/sermons/render + * + * Render sermon list as HTML (for AJAX dynamic filtering) + * + * Query parameters: Same as GET /sermons + * + * Response: HTML string + * { + * "html": "
...
" + * } + */ +GET /sermons/render +``` + +### Preachers Controller + +Manage preacher records with CRUD operations. + +```php { .api } +/** + * Preachers REST controller + * + * Class: \SermonBrowser\REST\PreachersController + * Base route: /preachers + */ + +/** + * GET /wp-json/sermon-browser/v1/preachers + * + * List all preachers + * + * Response: Array of preacher objects + * { + * "id": 5, + * "preacher_name": "John Smith", + * "preacher_image": "https://example.com/image.jpg", + * "preacher_description": "Biography text..." + * } + */ +GET /preachers + +/** + * POST /wp-json/sermon-browser/v1/preachers + * + * Create a new preacher + * + * Required capability: manage_categories + * + * Request body: + * { + * "preacher_name": "string (required)", + * "preacher_image": "string (optional, URL)", + * "preacher_description": "string (optional)" + * } + * + * Response: Created preacher object with ID + */ +POST /preachers + +/** + * GET /wp-json/sermon-browser/v1/preachers/{id} + * + * Get a single preacher by ID + * + * Path parameters: + * - id (int): Preacher ID + * + * Response: Single preacher object + */ +GET /preachers/{id} + +/** + * PUT /wp-json/sermon-browser/v1/preachers/{id} + * PATCH /wp-json/sermon-browser/v1/preachers/{id} + * + * Update an existing preacher + * + * Required capability: manage_categories + * + * Request body: Partial or full preacher object + * + * Response: Updated preacher object + */ +PUT /preachers/{id} +PATCH /preachers/{id} + +/** + * DELETE /wp-json/sermon-browser/v1/preachers/{id} + * + * Delete a preacher + * + * Required capability: manage_categories + * + * Response: Success message + */ +DELETE /preachers/{id} +``` + +### Series Controller + +Manage sermon series with CRUD operations and related sermons. + +```php { .api } +/** + * Series REST controller + * + * Class: \SermonBrowser\REST\SeriesController + * Base route: /series + */ + +/** + * GET /wp-json/sermon-browser/v1/series + * + * List all series + * + * Response: Array of series objects + * { + * "id": 12, + * "series_name": "Gospel of John", + * "series_image": "https://example.com/image.jpg", + * "series_description": "Series description..." + * } + */ +GET /series + +/** + * POST /wp-json/sermon-browser/v1/series + * + * Create a new series + * + * Required capability: manage_categories + * + * Request body: + * { + * "series_name": "string (required)", + * "series_image": "string (optional, URL)", + * "series_description": "string (optional)" + * } + * + * Response: Created series object with ID + */ +POST /series + +/** + * GET /wp-json/sermon-browser/v1/series/{id} + * + * Get a single series by ID + * + * Response: Single series object + */ +GET /series/{id} + +/** + * PUT /wp-json/sermon-browser/v1/series/{id} + * PATCH /wp-json/sermon-browser/v1/series/{id} + * + * Update an existing series + * + * Required capability: manage_categories + * + * Response: Updated series object + */ +PUT /series/{id} +PATCH /series/{id} + +/** + * DELETE /wp-json/sermon-browser/v1/series/{id} + * + * Delete a series + * + * Required capability: manage_categories + * + * Response: Success message + */ +DELETE /series/{id} + +/** + * GET /wp-json/sermon-browser/v1/series/{id}/sermons + * + * Get all sermons in a series + * + * Path parameters: + * - id (int): Series ID + * + * Query parameters: + * - page (int): Page number (default: 1) + * - per_page (int): Results per page (default: 10, max: 100) + * + * Response: Array of sermon objects + */ +GET /series/{id}/sermons +``` + +### Services Controller + +Manage church service records with CRUD operations. + +```php { .api } +/** + * Services REST controller + * + * Class: \SermonBrowser\REST\ServicesController + * Base route: /services + */ + +/** + * GET /wp-json/sermon-browser/v1/services + * + * List all services + * + * Response: Array of service objects + * { + * "id": 1, + * "service_name": "Sunday Morning", + * "service_time": "10:30" + * } + */ +GET /services + +/** + * POST /wp-json/sermon-browser/v1/services + * + * Create a new service + * + * Required capability: manage_categories + * + * Request body: + * { + * "service_name": "string (required)", + * "service_time": "HH:MM (optional)" + * } + * + * Response: Created service object with ID + */ +POST /services + +/** + * GET /wp-json/sermon-browser/v1/services/{id} + * + * Get a single service by ID + * + * Response: Single service object + */ +GET /services/{id} + +/** + * PUT /wp-json/sermon-browser/v1/services/{id} + * PATCH /wp-json/sermon-browser/v1/services/{id} + * + * Update an existing service + * + * Required capability: manage_categories + * + * Response: Updated service object + */ +PUT /services/{id} +PATCH /services/{id} + +/** + * DELETE /wp-json/sermon-browser/v1/services/{id} + * + * Delete a service + * + * Required capability: manage_categories + * + * Response: Success message + */ +DELETE /services/{id} +``` + +### Files Controller + +Manage sermon file attachments (audio, video, documents). + +```php { .api } +/** + * Files REST controller + * + * Class: \SermonBrowser\REST\FilesController + * Base route: /files + */ + +/** + * GET /wp-json/sermon-browser/v1/files + * + * List all files + * + * Query parameters: + * - sermon_id (int): Filter by sermon ID + * + * Response: Array of file objects + * { + * "id": 456, + * "sermon_id": 123, + * "stuff": "sermon-123.mp3", + * "stuff_type": "file", + * "download_count": 42 + * } + */ +GET /files + +/** + * POST /wp-json/sermon-browser/v1/files + * + * Upload a file attachment + * + * Required capability: upload_files + * + * Request body (multipart/form-data): + * { + * "sermon_id": "int (required)", + * "file": "file upload (required)", + * "stuff_type": "file|url|code (default: file)" + * } + * + * Response: Created file object with ID + */ +POST /files + +/** + * GET /wp-json/sermon-browser/v1/files/{id} + * + * Get a single file by ID + * + * Response: Single file object + */ +GET /files/{id} + +/** + * DELETE /wp-json/sermon-browser/v1/files/{id} + * + * Delete a file + * + * Required capability: delete_posts + * + * Response: Success message + */ +DELETE /files/{id} +``` + +### Tags Controller + +Retrieve sermon tags (read-only). + +```php { .api } +/** + * Tags REST controller + * + * Class: \SermonBrowser\REST\TagsController + * Base route: /tags + */ + +/** + * GET /wp-json/sermon-browser/v1/tags + * + * List all tags + * + * Response: Array of tag objects + * { + * "id": 78, + * "tag_name": "Grace" + * } + */ +GET /tags + +/** + * GET /wp-json/sermon-browser/v1/tags/{id} + * + * Get a single tag by ID + * + * Response: Single tag object + */ +GET /tags/{id} +``` + +### Search Controller + +Combined search across sermons with text and filter criteria. + +```php { .api } +/** + * Search REST controller + * + * Class: \SermonBrowser\REST\SearchController + * Base route: /search + */ + +/** + * GET /wp-json/sermon-browser/v1/search + * + * Search sermons with combined text and filters + * + * Query parameters: + * - q (string): Search query (searches title and description) + * - preacher (int): Filter by preacher ID + * - series (int): Filter by series ID + * - service (int): Filter by service ID + * - page (int): Page number (default: 1) + * - per_page (int): Results per page (default: 10, max: 100) + * + * Response: Array of sermon objects matching search criteria + */ +GET /search +``` + +## Authentication + +The REST API uses WordPress's built-in authentication: + +```php { .api } +/** + * Authentication methods: + * + * 1. Cookie Authentication (for logged-in users) + * - Requires nonce header: X-WP-Nonce + * + * 2. Application Passwords (WordPress 5.6+) + * - Use Basic Auth with application password + * + * 3. OAuth (via plugin) + * - Install OAuth plugin for token-based auth + */ + +// Example with fetch API and cookie auth +fetch('/wp-json/sermon-browser/v1/sermons', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': wpApiSettings.nonce + }, + body: JSON.stringify({ + title: 'New Sermon', + sermon_date: '2024-01-15' + }) +}); + +// Example with cURL and application password +// curl -u "username:application_password" \ +// -X POST https://example.com/wp-json/sermon-browser/v1/sermons \ +// -H "Content-Type: application/json" \ +// -d '{"title":"New Sermon","sermon_date":"2024-01-15"}' +``` + +## Error Responses + +All endpoints return standard WordPress REST API error responses: + +```php { .api } +/** + * Error response format + * + * HTTP Status: 400, 401, 403, 404, 500, etc. + * + * Response body: + * { + * "code": "error_code", + * "message": "Human-readable error message", + * "data": { + * "status": 400 + * } + * } + */ + +// Common error codes: +// - rest_forbidden: Insufficient permissions +// - rest_invalid_param: Invalid parameter value +// - rest_not_found: Resource not found +// - rest_rate_limit_exceeded: Rate limit exceeded +// - rest_missing_callback_param: Required parameter missing +``` + +## Pagination + +List endpoints support pagination with standard headers: + +```php { .api } +/** + * Pagination headers (included in response) + * + * X-WP-Total: Total number of items + * X-WP-TotalPages: Total number of pages + * + * Link header with rel="next" and rel="prev" URLs + */ + +// Example response headers: +// X-WP-Total: 150 +// X-WP-TotalPages: 15 +// Link: ; rel="next" +``` + +## CORS Support + +CORS is handled by WordPress core. Enable with: + +```php { .api } +// In wp-config.php or theme functions.php +add_filter('rest_pre_serve_request', function($served, $result, $request) { + header('Access-Control-Allow-Origin: *'); + header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS'); + header('Access-Control-Allow-Headers: Content-Type, Authorization, X-WP-Nonce'); + return $served; +}, 10, 3); +``` + +## JavaScript Example + +```javascript +// Fetch sermons with filtering +async function getSermons(filters = {}) { + const params = new URLSearchParams(filters); + const response = await fetch( + `/wp-json/sermon-browser/v1/sermons?${params}` + ); + return await response.json(); +} + +// Create a new sermon +async function createSermon(sermonData) { + const response = await fetch('/wp-json/sermon-browser/v1/sermons', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': wpApiSettings.nonce + }, + body: JSON.stringify(sermonData) + }); + return await response.json(); +} + +// Usage +const sermons = await getSermons({ preacher: 5, per_page: 20 }); +const newSermon = await createSermon({ + title: 'Grace and Truth', + sermon_date: '2024-01-15', + preacher: 5, + series: 12 +}); +``` diff --git a/tile/docs/shortcodes.md b/tile/docs/shortcodes.md new file mode 100644 index 0000000..29eb07a --- /dev/null +++ b/tile/docs/shortcodes.md @@ -0,0 +1,182 @@ +# Shortcodes + +Sermon Browser provides two shortcodes for displaying sermons in WordPress posts and pages: `[sermons]` for listing multiple sermons and `[sermon]` for displaying a single sermon. Both shortcodes support extensive filtering and customization options. + +## Capabilities + +### Sermons List Shortcode + +Display a filterable list of sermons with optional dropdown or one-click filters. + +```php { .api } +/** + * Display multiple sermons with filtering + * + * Shortcode: [sermons] + * Handler function: sb_shortcode() + */ +[sermons + filter="dropdown|oneclick|none" // Filter UI type (default: "dropdown") + filterhide="true|false" // Hide filter UI (default: false) + preacher="ID" // Filter by preacher ID + series="ID" // Filter by series ID + book="book_name" // Filter by Bible book name + service="ID" // Filter by service ID + date="YYYY-MM-DD" // Filter by date + enddate="YYYY-MM-DD" // End date for date range + tag="tag_slug" // Filter by tag slug + title="search_text" // Search by title text + limit="number" // Number of sermons to display + dir="asc|desc" // Sort direction (default: "desc") +] +``` + +**Examples:** + +```php +// Display 10 most recent sermons with dropdown filters +[sermons limit="10" filter="dropdown"] + +// Display sermons by specific preacher +[sermons preacher="5" limit="20"] + +// Display sermons in a series without filters +[sermons series="12" filter="none"] + +// Display sermons from a date range +[sermons date="2024-01-01" enddate="2024-12-31"] + +// Display sermons by Bible book +[sermons book="John" limit="15"] + +// Display sermons with one-click filter buttons +[sermons filter="oneclick" limit="10"] + +// Search sermons by title +[sermons title="grace" limit="10"] + +// Combined filters +[sermons preacher="5" series="12" service="1" limit="20" dir="asc"] +``` + +### Single Sermon Shortcode + +Display a single sermon's details including title, preacher, date, Bible passage, description, attached files, and embedded media. + +```php { .api } +/** + * Display single sermon details + * + * Shortcode: [sermon] + * Handler function: sb_shortcode() + */ +[sermon + id="ID|latest" // Sermon ID or "latest" for most recent sermon +] +``` + +**Examples:** + +```php +// Display specific sermon +[sermon id="123"] + +// Display most recent sermon +[sermon id="latest"] +``` + +## Shortcode Handler + +Both shortcodes are processed by the `sb_shortcode()` function defined in the main plugin file: + +```php { .api } +/** + * Process [sermons] and [sermon] shortcodes + * + * @param array $atts Shortcode attributes + * @param string|null $content Shortcode content (not used) + * @param string $tag Shortcode tag name + * @return string HTML output + */ +function sb_shortcode($atts, $content = null, $tag): string; +``` + +## Filter Types + +### Dropdown Filters + +The `filter="dropdown"` option displays dropdown select menus for filtering by: +- Preacher +- Series +- Bible book +- Service +- Date (year/month) +- Tag + +### One-Click Filters + +The `filter="oneclick"` option displays clickable buttons or links for quick filtering without dropdowns. + +### No Filters + +The `filter="none"` option displays only the sermon list without any filter UI. + +## Template Integration + +Shortcodes use the plugin's template system to render output. The templates can be customized via the WordPress admin panel under Sermons > Options > Templates. + +**Template Types:** +- **Search Template**: Used for `[sermons]` multi-sermon lists +- **Single Template**: Used for `[sermon]` single sermon display + +## Usage in Templates + +Shortcodes can be used programmatically in theme templates: + +```php +// In template files +echo do_shortcode('[sermons limit="10" filter="dropdown"]'); + +// With dynamic attributes +$preacher_id = 5; +echo do_shortcode('[sermons preacher="' . $preacher_id . '" limit="20"]'); + +// In widgets +echo do_shortcode('[sermon id="latest"]'); +``` + +## Pagination + +The `[sermons]` shortcode automatically includes pagination when the number of sermons exceeds the `limit` parameter. Pagination links are generated using the template system's `[pagination]` tag. + +## Caching + +Shortcode output is not cached by default, but the underlying template rendering uses WordPress transients for caching (60-minute TTL). Cache is automatically cleared when sermons are added, updated, or deleted. + +## Attributes Reference + +| Attribute | Type | Default | Description | +|-------------|--------|-------------|------------------------------------------------| +| filter | string | "dropdown" | Filter UI type: "dropdown", "oneclick", "none" | +| filterhide | bool | false | Hide filter UI completely | +| id | mixed | null | Sermon ID or "latest" | +| preacher | int | null | Filter by preacher ID | +| series | int | null | Filter by series ID | +| book | string | null | Filter by Bible book name | +| service | int | null | Filter by service ID | +| date | string | null | Filter by date (YYYY-MM-DD) | +| enddate | string | null | End date for range (YYYY-MM-DD) | +| tag | string | null | Filter by tag slug | +| title | string | null | Search by title text | +| limit | int | 10 | Number of sermons to display | +| dir | string | "desc" | Sort direction: "asc" or "desc" | + +## URL Parameters + +When filters are used, they add query parameters to the URL allowing for shareable filtered views: + +``` +?preacher=5&series=12&sb_action=display +``` + +These URL parameters work with both shortcodes and direct page access. diff --git a/tile/docs/template-system.md b/tile/docs/template-system.md new file mode 100644 index 0000000..e7bff07 --- /dev/null +++ b/tile/docs/template-system.md @@ -0,0 +1,453 @@ +# Template System + +Sermon Browser includes a powerful template engine for customizing the display of sermons. The template system uses tag-based templating with transient caching and supports both single sermon and multi-sermon list views. + +## Overview + +The template system consists of two main components: + +```php { .api } +/** + * Template engine - Renders templates with data + * + * Class: \SermonBrowser\Templates\TemplateEngine + */ +class TemplateEngine { + /** + * Render template with data + * + * @param string $templateType 'search' (multi-sermon) or 'single' (single sermon) + * @param array $data Template variables + * @return string Rendered HTML + */ + public function render(string $templateType, array $data): string; +} + +/** + * Template manager - Manages template CRUD operations + * + * Class: \SermonBrowser\Templates\TemplateManager + */ +class TemplateManager { + /** + * Get template HTML from options + * + * @param string $type 'search' or 'single' + * @return string Template HTML + */ + public function getTemplate(string $type): string; + + /** + * Save template HTML to options + * + * @param string $type 'search' or 'single' + * @param string $content Template HTML with tags + * @return bool True on success + */ + public function saveTemplate(string $type, string $content): bool; + + /** + * Get default template + * + * @param string $type 'search' or 'single' + * @return string Default template HTML + */ + public function getDefaultTemplate(string $type): string; + + /** + * Migrate templates from legacy format + * + * @return array Migration results + */ + public function migrateFromLegacy(): array; +} +``` + +## Capabilities + +### Template Types + +The system supports two template types: + +#### Search Template (Multi-Sermon List) + +Used for displaying multiple sermons with filtering and pagination. + +```php { .api } +/** + * Search template - Multi-sermon listing + * + * Used by: + * - [sermons] shortcode + * - Sermon archive pages + * - Filtered sermon lists + * - Sermon widgets + * + * Available data in $data array: + * - 'sermons' (array): Array of sermon objects + * - 'pagination' (string): Pagination HTML + * - 'filter_html' (string): Filter UI HTML + * - 'total' (int): Total sermon count + * - 'page' (int): Current page number + * - 'limit' (int): Sermons per page + */ +``` + +#### Single Template (Single Sermon) + +Used for displaying a single sermon's complete details. + +```php { .api } +/** + * Single template - Single sermon display + * + * Used by: + * - [sermon] shortcode + * - Single sermon pages + * - Sermon detail views + * + * Available data in $data array: + * - 'sermon' (object): Sermon object with all fields + * - 'files' (array): Array of file objects + * - 'tags' (array): Array of tag objects + * - 'books' (array): Array of Bible book names + * - 'preacher' (object): Preacher object + * - 'series' (object): Series object + * - 'service' (object): Service object + */ +``` + +### Template Tags + +Templates use placeholder tags that are replaced with dynamic content: + +```php { .api } +/** + * Core Template Tags + */ + +// Basic sermon information +[title] // Sermon title +[preacher] // Preacher name with link +[series] // Series name with link +[service] // Service name with link +[date] // Formatted sermon date +[description] // Sermon description/notes +[book] // Bible book name with link +[tags] // Tag list with links + +// Bible text tags (requires bible_passage field) +[esvtext] // ESV Bible text +[nettext] // NET Bible text +[kjvtext] // KJV Bible text +[nivtext] // NIV Bible text +[msgtext] // MSG Bible text + +// File and media tags +[files] // All attached files as links +[url] // First URL link +[mp3] // First MP3 file player +[video] // Video embed code +[code] // Alternate embed code + +// Navigation and metadata +[edit] // Edit link (if user has permission) +[permalink] // Link to single sermon page +[download_count] // Total download count + +// List-only tags (search template) +[pagination] // Pagination controls +[filters] // Filter UI controls +[sermon_count] // Total sermon count + +// Conditional tags +[if_files]...[/if_files] // Show if files exist +[if_description]...[/if_description] // Show if description exists +[if_tags]...[/if_tags] // Show if tags exist +``` + +### Using Template Engine + +```php { .api } +use SermonBrowser\Templates\TemplateEngine; + +$engine = new TemplateEngine(); + +// Render single sermon +$sermon = Sermon::findWithRelations(123); +$html = $engine->render('single', [ + 'sermon' => $sermon, + 'files' => $sermon->files, + 'tags' => $sermon->tags +]); +echo $html; + +// Render sermon list +$sermons = Sermon::findAll(['series' => 12], 10); +$html = $engine->render('search', [ + 'sermons' => $sermons, + 'pagination' => sb_print_pagination_html(), + 'filter_html' => sb_get_filter_html('dropdown'), + 'total' => count($sermons), + 'page' => 1, + 'limit' => 10 +]); +echo $html; +``` + +### Customizing Templates + +Templates can be customized via WordPress admin: + +**Location:** Sermons > Options > Templates + +**Admin Interface:** +- View current templates +- Edit template HTML +- Reset to defaults +- Preview changes + +**Programmatic Access:** + +```php { .api } +use SermonBrowser\Templates\TemplateManager; + +$manager = new TemplateManager(); + +// Get current template +$template = $manager->getTemplate('search'); + +// Modify template +$template = str_replace('[title]', '

[title]

', $template); + +// Save modified template +$manager->saveTemplate('search', $template); + +// Reset to default +$default = $manager->getDefaultTemplate('search'); +$manager->saveTemplate('search', $default); +``` + +### Default Search Template + +```html +
+ [filters] + +
+ +
+

[title]

+
+ Preacher: [preacher] + Date: [date] + Series: [series] + Book: [book] +
+
+ [description] +
+
+ Tags: [tags] +
+
+ [files] +
+
+ +
+ + [pagination] +
+``` + +### Default Single Template + +```html +
+
+

[title]

+
+ + Preacher: [preacher] + + + Series: [series] + + + Service: [service] + + + Date: [date] + +
+
+ +
+ Bible Passage: [book] + [esvtext] +
+ +
+ [description] +
+ +
+

Media Files

+ [files] + [video] +
+ +
+ Topics: [tags] +
+ +
+ [edit] +
+
+``` + +### Template Caching + +The template system uses WordPress transients for caching: + +```php { .api } +/** + * Cache configuration + * + * - Cache key format: 'sb_template_{type}_{hash}' + * - Cache TTL: 60 minutes (3600 seconds) + * - Cache cleared on: sermon create/update/delete + */ + +// Manual cache clearing +delete_transient('sb_template_search_' . md5($template)); +delete_transient('sb_template_single_' . md5($template)); + +// Clear all sermon browser caches +global $wpdb; +$wpdb->query( + "DELETE FROM {$wpdb->options} + WHERE option_name LIKE '_transient_sb_template_%'" +); +``` + +### Advanced Template Customization + +#### Loop Control + +The search template automatically loops through sermons. To customize the loop: + +```html + +
+ +
+
+

[title]

+ [date] +
+
+

[description]

+
+ +
+
+``` + +#### Conditional Content + +Use conditional tags to show content only when data exists: + +```html +[if_description] +
+ [description] +
+[/if_description] + +[if_files] +
+

Available Files

+ [files] +
+[/if_files] + +[if_tags] +
+ Topics: [tags] +
+[/if_tags] +``` + +#### Custom CSS Classes + +Add custom CSS classes for styling: + +```html +
+

[title]

+
+ [preacher] + [date] +
+
+``` + +### Template Migration + +The system includes automatic migration from legacy template formats: + +```php { .api } +use SermonBrowser\Templates\TemplateManager; + +$manager = new TemplateManager(); + +// Migrate legacy templates on plugin activation +$results = $manager->migrateFromLegacy(); + +// Results array contains: +// - 'search_migrated' (bool): Whether search template was migrated +// - 'single_migrated' (bool): Whether single template was migrated +// - 'errors' (array): Any migration errors +``` + +### Template Debugging + +Enable WordPress debug mode to see template rendering information: + +```php +// In wp-config.php +define('WP_DEBUG', true); +define('WP_DEBUG_LOG', true); + +// Template errors will be logged to wp-content/debug.log +``` + +### Best Practices + +1. **Always test changes**: Preview templates before saving +2. **Use conditional tags**: Show content only when it exists +3. **Keep templates simple**: Complex logic belongs in code, not templates +4. **Cache clearing**: Clear cache after template changes +5. **Backup templates**: Save custom templates before upgrading plugin +6. **Valid HTML**: Ensure templates produce valid HTML structure +7. **Accessibility**: Include proper ARIA labels and semantic HTML + +### Template Storage + +Templates are stored in WordPress options: + +```php { .api } +// Option keys +// - 'sb_template_search': Search template HTML +// - 'sb_template_single': Single template HTML + +// Direct access (use TemplateManager instead) +$search_template = get_option('sb_template_search'); +$single_template = get_option('sb_template_single'); +``` diff --git a/tile/docs/template-tags.md b/tile/docs/template-tags.md new file mode 100644 index 0000000..7249803 --- /dev/null +++ b/tile/docs/template-tags.md @@ -0,0 +1,1335 @@ +# Template Tag Functions + +Public PHP template tag functions that enable developers to display and manipulate sermon data in WordPress templates. These functions provide the foundational API for custom theme integration, allowing direct control over sermon display, filtering, navigation, and data retrieval. + +All template tag functions are globally available after plugin initialization and are defined in the main `sermon.php` file. + +## Basic Usage + +```php +' . esc_html($sermon->title) . ''; + echo '

By: ' . esc_html($sermon->preacher_name) . '

'; + sb_print_sermon_link($sermon, true); +} + +// Display single sermon with all details +$sermon = sb_get_single_sermon(123); +if ($sermon) { + echo '

' . esc_html($sermon->title) . '

'; + sb_print_bible_passage($sermon->bible_passage, $sermon->bible_passage_end); + sb_print_preacher_link($sermon); + sb_print_series_link($sermon); + + // Display sermon files + $files = sb_get_stuff($sermon, false); + foreach ($files as $file) { + sb_print_url($file->stuff); + } +} + +// Display complete sermon listing with pagination and filters +sb_display_sermons([ + 'filter' => 'dropdown', + 'limit' => 20, + 'preacher' => 5, + 'series' => 10 +]); +``` + +## Capabilities + +### Sermon Retrieval Functions + +Core functions for retrieving sermon data from the database with filtering, pagination, and relation loading. + +```php { .api } +function sb_get_sermons($filter, $order, $page, $limit, $hide_empty) +``` + +Get multiple sermons with filtering and pagination. + +**Parameters:** +- `$filter` (array): Filter criteria containing keys like 'preacher', 'series', 'service', 'book', 'tag', 'date', 'enddate', 'title' +- `$order` (string): Sort order - "asc" or "desc" +- `$page` (int): Page number for pagination +- `$limit` (int): Number of sermons per page +- `$hide_empty` (bool): Hide sermons without files + +**Returns:** array - Array of sermon objects + +```php { .api } +function sb_get_single_sermon($id) +``` + +Get single sermon by ID with all related data (files, tags, books). + +**Parameters:** +- `$id` (int): Sermon ID + +**Returns:** object|null - Sermon object with all relations or null if not found + +```php { .api } +function sb_get_stuff($sermon, $mp3_only) +``` + +Get sermon attachments (files, URLs, embed codes). + +**Parameters:** +- `$sermon` (object): Sermon object +- `$mp3_only` (bool): Return only MP3 files if true + +**Returns:** array - Array of file/URL/code objects with properties: id, sermon_id, stuff, stuff_type, download_count + +```php { .api } +function sb_sermon_stats($sermonid) +``` + +Get download statistics for a sermon. + +**Parameters:** +- `$sermonid` (int): Sermon ID + +**Returns:** object - Object with download statistics including total downloads and per-file counts + +**Example:** +```php +// Get recent sermons from a specific preacher +$sermons = sb_get_sermons( + ['preacher' => 5, 'series' => 10], + 'desc', + 1, + 10, + false +); + +// Get single sermon with all details +$sermon = sb_get_single_sermon(123); +if ($sermon) { + echo $sermon->title; // "The Gospel of Grace" + echo $sermon->preacher_name; // "John Smith" + + // Get all attachments + $files = sb_get_stuff($sermon, false); + + // Get only MP3 files + $mp3s = sb_get_stuff($sermon, true); + + // Get download stats + $stats = sb_sermon_stats($sermon->id); + echo "Total Downloads: " . $stats->total; +} +``` + +### Display Functions + +High-level functions for displaying sermons with complete formatting, filtering, and pagination. + +```php { .api } +function sb_display_sermons($options) +``` + +Display sermons in templates with all formatting, filtering, and pagination controls. + +**Parameters:** +- `$options` (array): Display options containing: + - `filter` (string): Filter UI type - "dropdown", "oneclick", "none" + - `filterhide` (bool): Hide filter UI + - `id` (mixed): Sermon ID or "latest" + - `preacher` (int): Filter by preacher ID + - `series` (int): Filter by series ID + - `book` (string): Filter by Bible book name + - `service` (int): Filter by service ID + - `date` (string): Filter by date (YYYY-MM-DD) + - `enddate` (string): End date for range + - `tag` (string): Filter by tag slug + - `title` (string): Search by title text + - `limit` (int): Number of sermons to display + - `dir` (string): Sort direction - "asc", "desc" + +**Returns:** void - Outputs HTML directly + +```php { .api } +function sb_print_sermon_link($sermon, $echo) +``` + +Print or return sermon detail page URL. + +**Parameters:** +- `$sermon` (object): Sermon object +- `$echo` (bool): Echo output if true, return if false + +**Returns:** string|void - URL string if $echo is false, otherwise outputs HTML + +```php { .api } +function sb_print_preacher_link($sermon) +``` + +Print preacher filter URL as clickable link. + +**Parameters:** +- `$sermon` (object): Sermon object + +**Returns:** void - Outputs HTML link + +```php { .api } +function sb_print_series_link($sermon) +``` + +Print series filter URL as clickable link. + +**Parameters:** +- `$sermon` (object): Sermon object + +**Returns:** void - Outputs HTML link + +```php { .api } +function sb_print_service_link($sermon) +``` + +Print service filter URL as clickable link. + +**Parameters:** +- `$sermon` (object): Sermon object + +**Returns:** void - Outputs HTML link + +```php { .api } +function sb_print_tags($tags) +``` + +Print tag list with clickable links to tag filter pages. + +**Parameters:** +- `$tags` (array): Array of tag objects + +**Returns:** void - Outputs HTML + +```php { .api } +function sb_print_tag_clouds($minfont, $maxfont) +``` + +Print tag cloud with weighted font sizes based on usage. + +**Parameters:** +- `$minfont` (int): Minimum font size in pixels +- `$maxfont` (int): Maximum font size in pixels + +**Returns:** void - Outputs HTML tag cloud + +```php { .api } +function sb_print_most_popular() +``` + +Print most popular sermons, series, and preachers widget. + +**Returns:** void - Outputs HTML widget + +**Example:** +```php + + 'dropdown', + 'limit' => 20, + 'preacher' => 5, + 'dir' => 'desc' +]); +?> + + +' . esc_html($sermon->title) . ''; +sb_print_preacher_link($sermon); // Links to preacher's sermons +sb_print_series_link($sermon); // Links to series page +sb_print_service_link($sermon); // Links to service page +sb_print_tags($sermon->tags); // Display tags with links +?> + + + + + + +``` + +### Pagination Functions + +Functions for displaying pagination controls for sermon listings. + +```php { .api } +function sb_print_next_page_link($limit) +``` + +Print next page navigation link. + +**Parameters:** +- `$limit` (int): Items per page + +**Returns:** void - Outputs HTML link or disabled state + +```php { .api } +function sb_print_prev_page_link($limit) +``` + +Print previous page navigation link. + +**Parameters:** +- `$limit` (int): Items per page + +**Returns:** void - Outputs HTML link or disabled state + +**Example:** +```php + +
+ + +
+``` + +### File Display Functions + +Functions for displaying sermon files, URLs, and embed codes. + +```php { .api } +function sb_print_url($url) +``` + +Print file download link with icon and download tracking. + +**Parameters:** +- `$url` (string): File URL or path + +**Returns:** void - Outputs HTML link + +```php { .api } +function sb_print_url_link($url) +``` + +Print external URL link with icon. + +**Parameters:** +- `$url` (string): External URL + +**Returns:** void - Outputs HTML link + +```php { .api } +function sb_print_code($code) +``` + +Print base64 decoded embed code (video/audio players). + +**Parameters:** +- `$code` (string): Base64 encoded embed code + +**Returns:** void - Outputs decoded HTML + +```php { .api } +function sb_first_mp3($sermon, $stats) +``` + +Get first MP3 file URL from sermon. + +**Parameters:** +- `$sermon` (object): Sermon object +- `$stats` (bool): Include download stats parameter in URL + +**Returns:** string - MP3 file URL or empty string if none found + +**Example:** +```php +stuff_type === 'file') { + sb_print_url($file->stuff); + } elseif ($file->stuff_type === 'url') { + sb_print_url_link($file->stuff); + } elseif ($file->stuff_type === 'code') { + sb_print_code($file->stuff); + } +} + +// Get first MP3 for audio player +$mp3_url = sb_first_mp3($sermon, true); +if ($mp3_url) { + echo ''; +} +?> +``` + +### Bible Text Functions + +Functions for displaying Bible references, passages, and retrieving Scripture text from online services. + +```php { .api } +function sb_get_bible_books() +``` + +Get list of all Bible books. + +**Returns:** array - Array of Bible book objects with id and book_name properties + +```php { .api } +function sb_tidy_reference($start, $end, $add_link) +``` + +Format Bible reference into readable format (e.g., "John 3:16-18"). + +**Parameters:** +- `$start` (string): Start reference +- `$end` (string): End reference +- `$add_link` (bool): Add links to book names + +**Returns:** string - Formatted reference + +```php { .api } +function sb_get_books($start, $end) +``` + +Get Bible book names with links. + +**Parameters:** +- `$start` (string): Start reference +- `$end` (string): End reference + +**Returns:** string - HTML with book names and links + +```php { .api } +function sb_get_book_link($book_name) +``` + +Get book filter link URL. + +**Parameters:** +- `$book_name` (string): Bible book name + +**Returns:** string - Filter URL for book + +```php { .api } +function sb_print_bible_passage($start, $end) +``` + +Print formatted Bible passage reference with links. + +**Parameters:** +- `$start` (string): Start reference +- `$end` (string): End reference + +**Returns:** void - Outputs HTML + +```php { .api } +function sb_add_bible_text($start, $end, $version) +``` + +Add Bible text to page from online sources. + +**Parameters:** +- `$start` (string): Start reference (e.g., "John 3:16") +- `$end` (string): End reference (e.g., "John 3:18") +- `$version` (string): Bible version code ("esv", "net", "kjv", "niv", etc.) + +**Returns:** void - Outputs HTML with Bible text + +```php { .api } +function sb_add_esv_text($start, $end) +``` + +Add ESV (English Standard Version) Bible text. + +**Parameters:** +- `$start` (string): Start reference +- `$end` (string): End reference + +**Returns:** void - Outputs HTML + +```php { .api } +function sb_add_net_text($start, $end) +``` + +Add NET (New English Translation) Bible text with study notes. + +**Parameters:** +- `$start` (string): Start reference +- `$end` (string): End reference + +**Returns:** void - Outputs HTML + +```php { .api } +function sb_add_other_bibles($start, $end, $version) +``` + +Add other Bible versions (KJV, NIV, NASB, etc.) via BibleGateway. + +**Parameters:** +- `$start` (string): Start reference +- `$end` (string): End reference +- `$version` (string): Bible version code + +**Returns:** void - Outputs HTML + +**Example:** +```php +bible_passage, $sermon->bible_passage_end); +// Output: "John 3:16-18" with links + +// Display formatted reference +$ref = sb_tidy_reference($sermon->bible_passage, $sermon->bible_passage_end, true); +echo $ref; // "John 3:16-18" + +// Add Bible text in different versions +sb_add_esv_text('John 3:16', 'John 3:18'); +sb_add_net_text('John 3:16', 'John 3:18'); +sb_add_other_bibles('John 3:16', 'John 3:18', 'NIV'); + +// Get all Bible books +$books = sb_get_bible_books(); +foreach ($books as $book) { + $link = sb_get_book_link($book->book_name); + echo '' . esc_html($book->book_name) . ''; +} +?> +``` + +### Helper Functions + +Utility functions for formatting, URL building, and accessing sermon metadata. + +```php { .api } +function sb_formatted_date($sermon) +``` + +Format sermon date according to WordPress date settings. + +**Parameters:** +- `$sermon` (object): Sermon object + +**Returns:** string - Formatted date string + +```php { .api } +function sb_build_url($arr, $clear) +``` + +Build filter URL with query parameters. + +**Parameters:** +- `$arr` (array): Query parameters as key-value pairs +- `$clear` (bool): Clear existing parameters if true + +**Returns:** string - Complete URL with parameters + +```php { .api } +function sb_get_tag_link($tag) +``` + +Get tag filter link URL. + +**Parameters:** +- `$tag` (object): Tag object with tag_name property + +**Returns:** string - Filter URL for tag + +```php { .api } +function sb_edit_link($id) +``` + +Display edit sermon link if user has permission. + +**Parameters:** +- `$id` (int): Sermon ID + +**Returns:** void - Outputs HTML link if user can edit + +```php { .api } +function sb_print_preacher_description($sermon) +``` + +Print preacher biography/description. + +**Parameters:** +- `$sermon` (object): Sermon object + +**Returns:** void - Outputs HTML + +```php { .api } +function sb_print_preacher_image($sermon) +``` + +Print preacher image/photo. + +**Parameters:** +- `$sermon` (object): Sermon object + +**Returns:** void - Outputs HTML img tag + +**Example:** +```php + 5], false); +$series_url = sb_build_url(['series' => 10, 'service' => 3], true); + +// Display tag links +foreach ($sermon->tags as $tag) { + $link = sb_get_tag_link($tag); + echo '' . esc_html($tag->tag_name) . ''; +} + +// Display edit link (only if user has permission) +sb_edit_link($sermon->id); + +// Display preacher info +sb_print_preacher_image($sermon); +sb_print_preacher_description($sermon); +?> +``` + +### Navigation Functions + +Functions for navigating between sermons and displaying related sermon links. + +```php { .api } +function sb_print_next_sermon_link($sermon) +``` + +Display link to next sermon (chronologically). + +**Parameters:** +- `$sermon` (object): Current sermon object + +**Returns:** void - Outputs HTML link or nothing if no next sermon + +```php { .api } +function sb_print_prev_sermon_link($sermon) +``` + +Display link to previous sermon (chronologically). + +**Parameters:** +- `$sermon` (object): Current sermon object + +**Returns:** void - Outputs HTML link or nothing if no previous sermon + +```php { .api } +function sb_print_sameday_sermon_link($sermon) +``` + +Display links to sermons preached on the same day. + +**Parameters:** +- `$sermon` (object): Sermon object + +**Returns:** void - Outputs HTML links + +**Example:** +```php + + +
+ + +
+ + + +``` + +### Filter Display Functions + +Functions for displaying filter interface controls (dropdowns, one-click filters). + +```php { .api } +function sb_print_filters($filter) +``` + +Display complete filter interface (dropdown or oneclick style). + +**Parameters:** +- `$filter` (string): Filter type - "dropdown" for select menus, "oneclick" for clickable links + +**Returns:** void - Outputs HTML filter interface + +```php { .api } +function sb_print_filter_line($id, $results, $filter, $display, $max_num) +``` + +Print individual filter line for a specific filter type. + +**Parameters:** +- `$id` (int): Current filter item ID (if filtered) +- `$results` (array): Array of filter items +- `$filter` (string): Filter type ("preacher", "series", "service", "book", "tag") +- `$display` (string): Display format ("dropdown" or "oneclick") +- `$max_num` (int): Maximum items to show before truncating + +**Returns:** void - Outputs HTML + +```php { .api } +function sb_print_date_filter_line($dates) +``` + +Print date filter dropdown control. + +**Parameters:** +- `$dates` (array): Array of available dates + +**Returns:** void - Outputs HTML select element + +```php { .api } +function sb_url_minus_parameter($param1, $param2) +``` + +Get current URL with specified parameters removed. + +**Parameters:** +- `$param1` (string): First parameter to remove +- `$param2` (string|null): Second parameter to remove (optional) + +**Returns:** string - URL without specified parameters + +**Example:** +```php + + + + + + + +Clear Filters'; +?> +``` + +### Options Functions + +Functions for accessing and updating plugin options stored in WordPress database. + +```php { .api } +function sb_get_option($type) +``` + +Get plugin option value from database. + +**Parameters:** +- `$type` (string): Option key (e.g., "page_id", "preacher_label", "series_label", "service_label", "use_css", "template_search", "template_single") + +**Returns:** mixed - Option value (type varies by option) + +```php { .api } +function sb_update_option($type, $val) +``` + +Update plugin option in database. + +**Parameters:** +- `$type` (string): Option key +- `$val` (mixed): Option value to save + +**Returns:** void + +```php { .api } +function sb_get_default($default_type) +``` + +Get default value for an option. + +**Parameters:** +- `$default_type` (string): Default type key + +**Returns:** mixed - Default value + +**Example:** +```php + +``` + +### Page/URL Functions + +Functions for retrieving sermon page URLs and checking display context. + +```php { .api } +function sb_display_url() +``` + +Get main sermons page URL. + +**Returns:** string - URL to sermons page + +```php { .api } +function sb_get_page_id() +``` + +Get sermons page ID from plugin options. + +**Returns:** int - WordPress page ID for sermons + +```php { .api } +function sb_display_front_end() +``` + +Check if currently displaying sermons page on frontend. + +**Returns:** bool - True if on sermons page, false otherwise + +```php { .api } +function sb_query_char($return_entity) +``` + +Get appropriate query string separator character (? or &). + +**Parameters:** +- `$return_entity` (bool): Return HTML entity (&) if true, & if false + +**Returns:** string - "?" or "&" (or "&") + +```php { .api } +function sb_podcast_url() +``` + +Get podcast feed URL. + +**Returns:** string - URL to podcast RSS feed + +**Example:** +```php +View All Sermons'; + +// Get page ID +$page_id = sb_get_page_id(); +$page = get_post($page_id); + +// Check if on sermons page +if (sb_display_front_end()) { + // Add custom CSS or scripts +} + +// Build query URL +$base_url = sb_display_url(); +$separator = sb_query_char(true); +$filter_url = $base_url . $separator . 'preacher=5&series=10'; + +// Get podcast URL +$podcast = sb_podcast_url(); +echo 'Subscribe to Podcast'; +?> +``` + +### Podcast Functions + +Functions for generating podcast feed data including dates, file sizes, durations, and MIME types. + +```php { .api } +function sb_print_iso_date($sermon) +``` + +Format date in ISO format for podcast RSS feed. + +**Parameters:** +- `$sermon` (object): Sermon object + +**Returns:** void - Outputs ISO date string (RFC 2822 format) + +```php { .api } +function sb_media_size($media_name, $media_type) +``` + +Get file size in bytes for podcast enclosure. + +**Parameters:** +- `$media_name` (string): File path or URL +- `$media_type` (string): Media type ("file", "url", "code") + +**Returns:** int - File size in bytes + +```php { .api } +function sb_mp3_duration($media_name, $media_type) +``` + +Get MP3 file duration for podcast feed. + +**Parameters:** +- `$media_name` (string): File path or URL +- `$media_type` (string): Media type ("file", "url") + +**Returns:** string - Duration in HH:MM:SS format + +```php { .api } +function sb_xml_entity_encode($string) +``` + +Encode string for XML/RSS feed to prevent parsing errors. + +**Parameters:** +- `$string` (string): String to encode + +**Returns:** string - XML-safe encoded string + +```php { .api } +function sb_podcast_file_url($media_name, $media_type) +``` + +Get full URL for podcast file enclosure. + +**Parameters:** +- `$media_name` (string): File path or URL +- `$media_type` (string): Media type ("file", "url") + +**Returns:** string - Full file URL + +```php { .api } +function sb_mime_type($media_name) +``` + +Get MIME type for file based on extension. + +**Parameters:** +- `$media_name` (string): File path or URL + +**Returns:** string - MIME type (e.g., "audio/mpeg", "video/mp4") + +**Example:** +```php +'; + + echo '' . esc_html($duration) . ''; +} + +// Format date for RSS +sb_print_iso_date($sermon); // Outputs: "Wed, 15 Jan 2024 10:30:00 +0000" + +// Encode strings for XML +$title = sb_xml_entity_encode($sermon->title); +$description = sb_xml_entity_encode($sermon->description); +?> +``` + +### Utility Functions + +Low-level utility functions for file handling, path sanitization, and download tracking. + +```php { .api } +function sb_default_time($service) +``` + +Get default time for a service. + +**Parameters:** +- `$service` (int): Service ID + +**Returns:** string - Time in HH:MM format (e.g., "10:30") + +```php { .api } +function sb_increase_download_count($stuff_name) +``` + +Increment download counter for a file. + +**Parameters:** +- `$stuff_name` (string): File name or path + +**Returns:** void + +```php { .api } +function sb_mkdir($pathname, $mode) +``` + +Create directory recursively with proper permissions. + +**Parameters:** +- `$pathname` (string): Directory path to create +- `$mode` (int): Unix permissions mode (e.g., 0755) + +**Returns:** bool - True on success, false on failure + +```php { .api } +function sb_sanitise_path($path) +``` + +Sanitize file paths for Windows compatibility. + +**Parameters:** +- `$path` (string): Path to sanitize + +**Returns:** string - Sanitized path with forward slashes + +```php { .api } +function sb_output_file($filename) +``` + +Output file contents in chunks for download with proper headers. + +**Parameters:** +- `$filename` (string): Full file path + +**Returns:** void - Outputs file data directly to browser + +**Example:** +```php + +``` + +## Integration Examples + +### Custom Theme Template + +```php + + +
+

Our Sermons

+ + + + + + +
+

+ + title); ?> + +

+ +
+ + + Preached by: + + series_name): ?> + + Series: + + +
+ + bible_passage): ?> +
+ bible_passage, $sermon->bible_passage_end); ?> +
+ + + description): ?> +
+ description); ?> +
+ + +
+ stuff_type === 'file') { + sb_print_url($file->stuff); + } + } + ?> +
+
+ + + + +
+ + +``` + +### Single Sermon Template + +```php +Sermon not found.

'; + get_footer(); + exit; +} +?> + +
+
+

title); ?>

+ +
+
+ Date: +
+ +
+ Preacher: +
+ + series_name): ?> +
+ Series: +
+ + + service_name): ?> +
+ Service: +
+ + + bible_passage): ?> +
+ Bible Passage: + bible_passage, $sermon->bible_passage_end); ?> +
+ +
+ + id); ?> +
+ + + preacher_image || $sermon->preacher_description): ?> + + + + + description): ?> +
+ description); ?> +
+ + + + +
+

Listen

+ +
+ + + +
+

Downloads

+ stuff_type === 'file') { + sb_print_url($file->stuff); + } elseif ($file->stuff_type === 'url') { + sb_print_url_link($file->stuff); + } elseif ($file->stuff_type === 'code') { + sb_print_code($file->stuff); + } + } + } + ?> +
+ + + bible_passage): ?> +
+

Scripture Reading

+ bible_passage, $sermon->bible_passage_end); ?> +
+ + + + tags)): ?> +
+ Tags: + tags); ?> +
+ + + + + + + +
+ + +``` + +### Widget Template + +```php + +
+

Recent Sermons

+ + '; + foreach ($sermons as $sermon) { + ?> +
  • + + title); ?> + + + + + + preacher_name); ?> + +
  • + '; + } + ?> + +

    + View All Sermons → +

    +
    + + +
    +

    Sermon Topics

    + +
    + + + +``` + +## Related Documentation + +- [Shortcodes](./shortcodes.md) - High-level shortcode API +- [REST API](./rest-api.md) - HTTP JSON API endpoints +- [Widgets](./widgets.md) - WordPress widget API +- [Gutenberg Blocks](./gutenberg-blocks.md) - Block editor components diff --git a/tile/docs/widgets.md b/tile/docs/widgets.md new file mode 100644 index 0000000..61860e7 --- /dev/null +++ b/tile/docs/widgets.md @@ -0,0 +1,253 @@ +# WordPress Widgets + +Sermon Browser provides three WordPress widgets for displaying sermon content in sidebars and widget areas: Sermons Widget for recent sermon lists, Tag Cloud Widget for topic visualization, and Popular Widget for most-accessed content. + +## Capabilities + +### Sermons Widget + +Display a list of recent sermons in widget areas with filtering options and customizable display fields. + +```php { .api } +/** + * Recent sermons widget + * + * Class: \SermonBrowser\Widgets\SermonsWidget + * Extends: WP_Widget + * Widget ID: sb_sermons + */ +class SermonsWidget extends WP_Widget { + /** + * Widget options: + * - title (string): Widget title + * - limit (int): Number of sermons to display (default: 5) + * - preacher (int): Filter by preacher ID (0 = all) + * - series (int): Filter by series ID (0 = all) + * - service (int): Filter by service ID (0 = all) + * - show_preacher (bool): Display preacher name (default: true) + * - show_book (bool): Display Bible book (default: true) + * - show_date (bool): Display sermon date (default: true) + */ + public function widget($args, $instance): void; + public function form($instance): void; + public function update($new_instance, $old_instance): array; +} +``` + +**Usage Example (Programmatic):** + +```php +// Register widget area in theme +register_sidebar([ + 'name' => 'Sermon Sidebar', + 'id' => 'sermon-sidebar', +]); + +// Widget can be added via Appearance > Widgets in admin +// Or programmatically: +the_widget('SermonBrowser\\Widgets\\SermonsWidget', [ + 'title' => 'Recent Sermons', + 'limit' => 10, + 'show_preacher' => true, + 'show_date' => true, +]); +``` + +**Widget Configuration (Admin UI):** +- Title: Custom widget title +- Number to show: Integer (1-100) +- Filter by preacher: Dropdown of preachers +- Filter by series: Dropdown of series +- Filter by service: Dropdown of services +- Show preacher: Checkbox +- Show Bible book: Checkbox +- Show date: Checkbox + +### Tag Cloud Widget + +Display a tag cloud of sermon topics with font sizes based on usage frequency. + +```php { .api } +/** + * Sermon tag cloud widget + * + * Class: \SermonBrowser\Widgets\TagCloudWidget + * Extends: WP_Widget + * Widget ID: sb_tag_cloud + */ +class TagCloudWidget extends WP_Widget { + /** + * Widget options: + * - title (string): Widget title + */ + public function widget($args, $instance): void; + public function form($instance): void; + public function update($new_instance, $old_instance): array; +} +``` + +**Usage Example (Programmatic):** + +```php +// Add tag cloud to sidebar +the_widget('SermonBrowser\\Widgets\\TagCloudWidget', [ + 'title' => 'Sermon Topics', +]); +``` + +**Widget Configuration (Admin UI):** +- Title: Custom widget title + +**Display Format:** +- Tags displayed with varying font sizes (12-24pt) based on frequency +- Tags are clickable links that filter sermons by tag +- More frequently used tags appear larger + +### Popular Widget + +Display most popular sermons, series, and/or preachers based on download counts and view statistics. + +```php { .api } +/** + * Most popular content widget + * + * Class: \SermonBrowser\Widgets\PopularWidget + * Extends: WP_Widget + * Widget ID: sb_popular + */ +class PopularWidget extends WP_Widget { + /** + * Widget options: + * - title (string): Widget title + * - limit (int): Number of items to display (default: 5) + * - display_sermons (bool): Show popular sermons (default: true) + * - display_series (bool): Show popular series (default: false) + * - display_preachers (bool): Show popular preachers (default: false) + */ + public function widget($args, $instance): void; + public function form($instance): void; + public function update($new_instance, $old_instance): array; +} +``` + +**Usage Example (Programmatic):** + +```php +// Display popular sermons +the_widget('SermonBrowser\\Widgets\\PopularWidget', [ + 'title' => 'Most Popular', + 'limit' => 10, + 'display_sermons' => true, + 'display_series' => true, + 'display_preachers' => false, +]); +``` + +**Widget Configuration (Admin UI):** +- Title: Custom widget title +- Number to show: Integer (1-100) +- Display popular sermons: Checkbox +- Display popular series: Checkbox +- Display popular preachers: Checkbox + +**Popularity Calculation:** +- Sermons: Based on total download counts of attached files +- Series: Based on total download counts of all sermons in series +- Preachers: Based on total download counts of all sermons by preacher + +## Widget Registration + +All widgets are automatically registered on the `widgets_init` hook: + +```php { .api } +/** + * Register all Sermon Browser widgets + * + * Hooked to: widgets_init + */ +add_action('widgets_init', function() { + register_widget('SermonBrowser\\Widgets\\SermonsWidget'); + register_widget('SermonBrowser\\Widgets\\TagCloudWidget'); + register_widget('SermonBrowser\\Widgets\\PopularWidget'); +}); +``` + +## Widget Areas + +Widgets can be added to any widget area (sidebar, footer, etc.) registered by the active theme via: +- **Appearance > Widgets** in WordPress admin +- **Customizer > Widgets** section +- **Block-based widget editor** (WordPress 5.8+) + +## Widget Template Functions + +Widgets can also be displayed programmatically in theme templates using helper functions: + +```php { .api } +/** + * Display tag cloud widget content + * + * @param int $minfont Minimum font size in pixels (default: 12) + * @param int $maxfont Maximum font size in pixels (default: 24) + */ +function sb_print_tag_clouds($minfont, $maxfont): void; + +/** + * Display most popular content + * Uses default settings from PopularWidget + */ +function sb_print_most_popular(): void; +``` + +**Usage:** + +```php +// In theme template + +``` + +## Widget Styling + +Widgets output standard WordPress widget markup: + +```html +
    +

    Widget Title

    +
    + +
    +
    +``` + +Custom CSS can target these classes for styling: + +```css +.sermon-browser-widget { + /* Widget container styles */ +} + +.sermon-browser-widget .widget-title { + /* Widget title styles */ +} + +.sermon-browser-widget .widget-content { + /* Widget content styles */ +} +``` + +## Widget Caching + +Widget output is not cached by default. Each widget queries the database on every page load. For high-traffic sites, consider using a page caching plugin or implementing custom widget caching. + +## Legacy Widget Support + +The widgets are compatible with both the classic widget interface and the block-based widget editor introduced in WordPress 5.8. They appear in the widget picker as: +- "Sermon Browser: Sermons" +- "Sermon Browser: Tag Cloud" +- "Sermon Browser: Most Popular" diff --git a/tile/docs/wordpress-hooks.md b/tile/docs/wordpress-hooks.md new file mode 100644 index 0000000..736984a --- /dev/null +++ b/tile/docs/wordpress-hooks.md @@ -0,0 +1,411 @@ +# WordPress Hooks + +Sermon Browser provides numerous WordPress actions and filters for extending and customizing plugin functionality. These hooks enable theme developers and plugin authors to modify behavior, add features, and integrate with other WordPress systems. + +## Actions + +WordPress actions allow you to execute code at specific points during plugin execution. + +## Capabilities + +### Core Initialization Actions + +```php { .api } +/** + * Main plugin initialization + * + * Hook: init + * Priority: 10 + * Function: sb_sermon_init() + * + * Fires on WordPress 'init' action + * Registers shortcodes, post types, taxonomies, and AJAX handlers + */ +add_action('init', 'sb_sermon_init'); + +/** + * Early request interception + * + * Hook: plugins_loaded + * Priority: 10 + * Function: sb_hijack() + * + * Handles downloads, AJAX requests, and CSS delivery + * Runs before WordPress fully loads + */ +add_action('plugins_loaded', 'sb_hijack'); + +/** + * Widget registration + * + * Hook: widgets_init + * Priority: 10 + * + * Registers all Sermon Browser widgets + */ +add_action('widgets_init', function() { + register_widget('SermonBrowser\\Widgets\\SermonsWidget'); + register_widget('SermonBrowser\\Widgets\\TagCloudWidget'); + register_widget('SermonBrowser\\Widgets\\PopularWidget'); +}); + +/** + * REST API initialization + * + * Hook: rest_api_init + * Priority: 10 + * + * Registers REST API routes and controllers + */ +add_action('rest_api_init', [\SermonBrowser\REST\RestAPI::class, 'registerRoutes']); + +/** + * Block registration + * + * Hook: init + * Priority: 10 + * + * Registers Gutenberg blocks + */ +add_action('init', [\SermonBrowser\Blocks\BlockRegistry::class, 'registerAll']); +``` + +### Frontend Hooks + +```php { .api } +/** + * Add headers and styles + * + * Hook: wp_head + * Priority: 10 + * + * Outputs custom CSS, meta tags, and other head content + */ +add_action('wp_head', function() { + // Custom CSS + // OpenGraph tags for podcasts + // RSS feed links +}); + +/** + * Add admin bar menu + * + * Hook: admin_bar_menu + * Priority: 100 + * + * Adds "Sermons" link to WordPress admin bar + */ +add_action('admin_bar_menu', function($wp_admin_bar) { + if (current_user_can('edit_posts')) { + $wp_admin_bar->add_menu([ + 'id' => 'sermon-browser', + 'title' => 'Sermons', + 'href' => admin_url('admin.php?page=sermon-browser') + ]); + } +}, 100); + +/** + * Footer statistics + * + * Hook: wp_footer + * Priority: 999 + * + * Outputs query statistics if SAVEQUERIES is enabled + */ +add_action('wp_footer', function() { + if (defined('SAVEQUERIES') && SAVEQUERIES) { + // Output query count and time + } +}, 999); +``` + +### Admin Hooks + +```php { .api } +/** + * Add admin menu pages + * + * Hook: admin_menu + * Priority: 10 + * + * Registers admin menu items for sermon management + */ +add_action('admin_menu', function() { + add_menu_page( + 'Sermons', // Page title + 'Sermons', // Menu title + 'edit_posts', // Capability + 'sermon-browser', // Menu slug + 'sb_render_admin_page', // Callback + 'dashicons-microphone', // Icon + 25 // Position + ); + // Add submenu pages... +}); + +/** + * Enqueue admin assets + * + * Hook: admin_enqueue_scripts + * Priority: 10 + * + * Loads admin JavaScript and CSS + */ +add_action('admin_enqueue_scripts', function($hook) { + if (strpos($hook, 'sermon-browser') !== false) { + wp_enqueue_style('sermon-browser-admin', plugins_url('assets/css/admin.css', __FILE__)); + wp_enqueue_script('sermon-browser-admin', plugins_url('assets/js/admin.js', __FILE__)); + } +}); + +/** + * Add help tabs + * + * Hook: current_screen + * Priority: 10 + * + * Adds contextual help tabs to admin pages + */ +add_action('current_screen', function($screen) { + if (strpos($screen->id, 'sermon-browser') !== false) { + $screen->add_help_tab([ + 'id' => 'sb-help', + 'title' => 'Help', + 'content' => '

    Help content...

    ' + ]); + } +}); + +/** + * Admin footer statistics + * + * Hook: admin_footer + * Priority: 10 + * + * Outputs query statistics in admin if SAVEQUERIES enabled + */ +add_action('admin_footer', function() { + if (defined('SAVEQUERIES') && SAVEQUERIES) { + // Output query stats + } +}); + +/** + * Display admin notices + * + * Hook: admin_notices + * Priority: 10 + * + * Shows template migration results and other notices + */ +add_action('admin_notices', function() { + // Show migration results + // Show update notices + // Show error messages +}); + +/** + * Initialize admin settings + * + * Hook: admin_init + * Priority: 10 + * + * Registers settings, migrates widget settings + */ +add_action('admin_init', function() { + // Register settings + // Migrate legacy data +}); +``` + +### Content Hooks + +```php { .api } +/** + * Update podcast URL on sermon save + * + * Hook: save_post + * Priority: 10 + * Parameters: $post_id, $post, $update + * + * Updates cached podcast URL when sermons change + */ +add_action('save_post', function($post_id, $post, $update) { + // Clear podcast feed cache + delete_transient('sb_podcast_url'); +}, 10, 3); +``` + +### Activation Hook + +```php { .api } +/** + * Plugin activation + * + * Hook: register_activation_hook + * + * Runs template migration and initial setup + */ +register_activation_hook(__FILE__, function() { + // Migrate templates from legacy format + $manager = new \SermonBrowser\Templates\TemplateManager(); + $manager->migrateFromLegacy(); + + // Create default options + // Set up database tables if needed +}); +``` + +## Filters + +WordPress filters allow you to modify data before it's used or displayed. + +### Core Filters + +```php { .api } +/** + * Modify page title + * + * Filter: wp_title + * Priority: 10 + * Function: sb_page_title() + * Parameters: $title, $sep + * + * Modifies page title for sermon pages + */ +add_filter('wp_title', 'sb_page_title', 10, 2); + +// Usage example +function sb_page_title($title, $sep) { + if (sb_display_front_end()) { + $sermon = sb_get_single_sermon($_GET['id'] ?? 0); + if ($sermon) { + return $sermon->title . " $sep " . get_bloginfo('name'); + } + } + return $title; +} + +/** + * Add sermon count to dashboard + * + * Filter: dashboard_glance_items + * Priority: 10 + * Parameters: $items + * + * Adds sermon count to "At a Glance" dashboard widget + */ +add_filter('dashboard_glance_items', function($items) { + global $wpdb; + $count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}sb_sermons"); + $items[] = sprintf( + '%d %s', + admin_url('admin.php?page=sermon-browser'), + $count, + _n('Sermon', 'Sermons', $count, 'sermon-browser') + ); + return $items; +}); + +/** + * Filter widget titles + * + * Filter: widget_title + * Priority: 10 + * Parameters: $title, $instance, $id_base + * + * Allows modification of widget titles + */ +add_filter('widget_title', function($title, $instance, $id_base) { + // Modify widget titles if needed + return $title; +}, 10, 3); +``` + +## Extension Points + +The plugin primarily uses WordPress's built-in hooks for extension. Custom plugin-specific action/filter hooks were not identified in the codebase analysis. Developers should use the standard WordPress hooks documented above for extending plugin functionality. + +## Extension Examples + +### Modify Page Title for Sermons + +```php +// Customize sermon page titles +add_filter('wp_title', function($title, $sep) { + if (sb_display_front_end() && isset($_GET['id'])) { + $sermon = sb_get_single_sermon($_GET['id']); + if ($sermon) { + return $sermon->title . " $sep " . get_bloginfo('name'); + } + } + return $title; +}, 10, 2); +``` + +### Add Sermons to Dashboard At-a-Glance + +```php +// Add sermon statistics to dashboard +add_filter('dashboard_glance_items', function($items) { + global $wpdb; + $count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}sb_sermons"); + $items[] = sprintf( + '%d %s', + admin_url('admin.php?page=sermon-browser'), + $count, + _n('Sermon', 'Sermons', $count, 'sermon-browser') + ); + return $items; +}); +``` + +### Custom Widget Title Formatting + +```php +// Modify sermon widget titles +add_filter('widget_title', function($title, $instance, $id_base) { + if (strpos($id_base, 'sb_') === 0) { + // Add icon to sermon widget titles + return ' ' . $title; + } + return $title; +}, 10, 3); +``` + +## Hook Priority + +Use priority values to control execution order: + +```php +// Early execution (priority 1-9) +add_action('init', 'my_early_function', 5); + +// Normal execution (priority 10, default) +add_action('init', 'my_normal_function'); + +// Late execution (priority 11+) +add_action('init', 'my_late_function', 100); +``` + +## Removing Hooks + +Remove plugin hooks if needed: + +```php +// Remove action +remove_action('wp_head', 'sb_add_head_content', 10); + +// Remove filter +remove_filter('wp_title', 'sb_page_title', 10); +``` + +## Best Practices + +1. **Always check exists**: Use `has_action()` and `has_filter()` before adding +2. **Use appropriate priority**: Default 10 works for most cases +3. **Clean up**: Remove hooks when no longer needed +4. **Document custom hooks**: Add PHPDoc comments +5. **Test thoroughly**: Hooks can have side effects +6. **Performance**: Avoid heavy operations in frequently-called hooks diff --git a/tile/evals/scenario-1/criteria.json b/tile/evals/scenario-1/criteria.json new file mode 100644 index 0000000..2d11063 --- /dev/null +++ b/tile/evals/scenario-1/criteria.json @@ -0,0 +1,36 @@ +{ + "context": "This criteria evaluates how effectively the engineer uses the sermon-browser plugin's API to build a WordPress widget that filters and displays sermons with Bible text integration. The focus is entirely on correct usage of the plugin's functions, classes, and facades.", + "type": "weighted_checklist", + "checklist": [ + { + "name": "Sermon Retrieval", + "description": "Uses the correct function or facade to retrieve sermons. Should use either sb_get_sermons() global function or SermonBrowser\\Facades\\Sermon::findForFrontendListing() with appropriate filter array, order array, page, and limit parameters.", + "max_score": 25 + }, + { + "name": "Filtering Implementation", + "description": "Correctly implements sermon filtering by constructing the filter array with appropriate keys. Should use 'preacher_id' key for preacher filtering, 'series_id' key for series filtering, and pass hideEmpty parameter correctly for audio file filtering.", + "max_score": 20 + }, + { + "name": "Sorting Configuration", + "description": "Properly configures sermon ordering using the order array parameter. Should use array with 'column' key (e.g., 'date') and 'direction' key ('ASC' or 'DESC') to control newest/oldest first sorting.", + "max_score": 15 + }, + { + "name": "Bible Text Retrieval", + "description": "Uses the correct function to retrieve Bible text. Should use sb_add_bible_text() global function or BibleText::getBibleText() method with start reference, end reference, and version ('kjv') parameters.", + "max_score": 20 + }, + { + "name": "URL Generation", + "description": "Uses sermon-browser's URL building functions to generate proper sermon and preacher links. Should use sb_print_sermon_link() or sb_build_url() for sermon URLs, and sb_print_preacher_link() or similar for preacher URLs.", + "max_score": 10 + }, + { + "name": "Audio File Access", + "description": "Accesses audio files using the correct method. Should use sb_first_mp3() global function or access the 'files' property from sermon data and filter for audio files.", + "max_score": 10 + } + ] +} diff --git a/tile/evals/scenario-1/task.md b/tile/evals/scenario-1/task.md new file mode 100644 index 0000000..ec54ace --- /dev/null +++ b/tile/evals/scenario-1/task.md @@ -0,0 +1,120 @@ +# Custom Sermon Display Widget + +Build a WordPress widget that displays filtered sermons using a custom template with Bible text integration. + +## Requirements + +You need to create a custom WordPress widget class that: + +1. **Displays a configurable list of sermons** with the following widget settings: + - Maximum number of sermons to display (default: 5) + - Filter by preacher ID (optional) + - Filter by series ID (optional) + - Whether to hide sermons without audio files + - Sort order: newest first or oldest first + +2. **Renders sermons using a custom template** that displays: + - Sermon title with hyperlink + - Preacher name with hyperlink + - Sermon date + - First Bible passage reference + - Bible text excerpt from the first passage (using KJV version, limited to first 100 characters) + - Link to the first audio file if available + +3. **Handles edge cases gracefully**: + - Display a message when no sermons match the filter criteria + - Handle sermons without Bible passages + - Handle sermons without audio files + +## Technical Specifications + +- The widget must extend WordPress's `WP_Widget` class +- Use proper WordPress escaping functions for all output +- Register the widget in the appropriate WordPress hook +- The implementation should be in a single PHP file + +## Implementation + +[@generates](./src/sermon_widget.php) + +## API + +```php { #api } +