From 902f959b30a10839684f093eb86edebc5d826a0b Mon Sep 17 00:00:00 2001 From: Yassine Doghri Date: Fri, 12 Nov 2021 16:31:35 +0000 Subject: [PATCH] feat: add schema.org json-ld objects to podcasts, episodes, posts and comments pages - refactor meta-tags by generating them in the controller and injecting them into the views - use `melbahja/seo` library to build opengraph and twitter meta-tags + schema.org objects --- app/Config/App.php | 2 + app/Config/Embed.php | 19 ++ app/Config/Routes.php | 4 +- app/Controllers/ActorController.php | 4 +- app/Controllers/BaseController.php | 2 +- app/Controllers/CreditsController.php | 3 +- app/Controllers/EpisodeCommentController.php | 1 + app/Controllers/EpisodeController.php | 15 +- app/Controllers/HomeController.php | 1 + ...MarkerController.php => MapController.php} | 4 +- app/Controllers/PageController.php | 3 +- app/Controllers/PodcastController.php | 3 + app/Controllers/PostController.php | 6 +- app/Helpers/page_helper.php | 2 +- app/Helpers/seo_helper.php | 275 ++++++++++++++++++ app/Language/en/Page.php | 19 +- app/Language/en/Podcast.php | 3 + app/Language/fr/Page.php | 19 +- composer.json | 3 +- composer.lock | 60 +++- themes/cp_admin/_layout.php | 2 + themes/cp_admin/episode/_sidebar.php | 2 +- themes/cp_admin/episode/publish.php | 2 +- themes/cp_admin/episode/publish_edit.php | 2 +- themes/cp_app/embed.php | 1 + themes/cp_app/episode/_layout.php | 5 +- .../cp_app/episode/_partials/preview_card.php | 2 +- themes/cp_app/episode/activity.php | 31 -- themes/cp_app/episode/comment.php | 23 +- themes/cp_app/episode/comments.php | 33 --- themes/cp_app/home.php | 7 +- themes/cp_app/page.php | 48 --- themes/cp_app/{ => pages}/_layout.php | 14 +- themes/cp_app/{ => pages}/credits.php | 2 +- themes/cp_app/{ => pages}/map.php | 10 +- themes/cp_app/pages/page.php | 11 + themes/cp_app/podcast/_layout.php | 5 +- themes/cp_app/podcast/about.php | 40 +-- themes/cp_app/podcast/activity.php | 27 +- themes/cp_app/podcast/episodes.php | 26 -- themes/cp_app/podcast/follow.php | 15 +- themes/cp_app/post/post.php | 23 +- themes/cp_app/post/remote_action.php | 18 +- themes/cp_auth/_layout.php | 2 + themes/cp_install/_layout.php | 4 +- 45 files changed, 449 insertions(+), 354 deletions(-) create mode 100644 app/Config/Embed.php rename app/Controllers/{MapMarkerController.php => MapController.php} (95%) create mode 100644 app/Helpers/seo_helper.php delete mode 100644 themes/cp_app/page.php rename themes/cp_app/{ => pages}/_layout.php (76%) rename themes/cp_app/{ => pages}/credits.php (98%) rename themes/cp_app/{ => pages}/map.php (85%) create mode 100644 themes/cp_app/pages/page.php diff --git a/app/Config/App.php b/app/Config/App.php index 2c5e100f..d5473947 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -435,6 +435,8 @@ class App extends BaseConfig */ public string $siteName = 'Castopod'; + public string $siteTitleSeparator = ' | '; + public string $siteDescription = 'Castopod Host is an open-source hosting platform made for podcasters who want engage and interact with their audience.'; /** diff --git a/app/Config/Embed.php b/app/Config/Embed.php new file mode 100644 index 00000000..0c97f86f --- /dev/null +++ b/app/Config/Embed.php @@ -0,0 +1,19 @@ +group('@(:podcastHandle)', function ($routes): void { $routes->get('/credits', 'CreditsController', [ 'as' => 'credits', ]); -$routes->get('/map', 'MapMarkerController', [ +$routes->get('/map', 'MapController', [ 'as' => 'map', ]); -$routes->get('/episodes-markers', 'MapMarkerController::getEpisodesMarkers', [ +$routes->get('/episodes-markers', 'MapController::getEpisodesMarkers', [ 'as' => 'episodes-markers', ]); $routes->get('/pages/(:slug)', 'PageController/$1', [ diff --git a/app/Controllers/ActorController.php b/app/Controllers/ActorController.php index ff1dbae5..b7233df4 100644 --- a/app/Controllers/ActorController.php +++ b/app/Controllers/ActorController.php @@ -20,7 +20,7 @@ class ActorController extends FediverseActorController /** * @var string[] */ - protected $helpers = ['auth', 'svg', 'components', 'misc']; + protected $helpers = ['auth', 'svg', 'components', 'misc', 'seo']; public function follow(): string { @@ -34,6 +34,8 @@ class ActorController extends FediverseActorController if (! ($cachedView = cache($cacheName))) { helper(['form', 'components', 'svg']); $data = [ + // @phpstan-ignore-next-line + 'metatags' => get_follow_metatags($this->actor), 'actor' => $this->actor, ]; diff --git a/app/Controllers/BaseController.php b/app/Controllers/BaseController.php index 2d30eb3c..312f1ec2 100644 --- a/app/Controllers/BaseController.php +++ b/app/Controllers/BaseController.php @@ -28,7 +28,7 @@ class BaseController extends Controller ResponseInterface $response, LoggerInterface $logger ): void { - $this->helpers = array_merge($this->helpers, ['auth', 'svg', 'components', 'misc']); + $this->helpers = array_merge($this->helpers, ['auth', 'svg', 'components', 'misc', 'seo']); // Do Not Edit This Line parent::initController($request, $response, $logger); diff --git a/app/Controllers/CreditsController.php b/app/Controllers/CreditsController.php index 816653dd..3531b3e2 100644 --- a/app/Controllers/CreditsController.php +++ b/app/Controllers/CreditsController.php @@ -165,11 +165,12 @@ class CreditsController extends BaseController } $data = [ + 'metatags' => get_page_metatags($page), 'page' => $page, 'credits' => $credits, ]; - $found = view('credits', $data); + $found = view('pages/credits', $data); cache() ->save($cacheName, $found, DECADE); diff --git a/app/Controllers/EpisodeCommentController.php b/app/Controllers/EpisodeCommentController.php index f64c1db3..6bb1ef0e 100644 --- a/app/Controllers/EpisodeCommentController.php +++ b/app/Controllers/EpisodeCommentController.php @@ -95,6 +95,7 @@ class EpisodeCommentController extends BaseController if (! ($cachedView = cache($cacheName))) { $data = [ + 'metatags' => get_episode_comment_metatags($this->comment), 'podcast' => $this->podcast, 'actor' => $this->actor, 'episode' => $this->episode, diff --git a/app/Controllers/EpisodeController.php b/app/Controllers/EpisodeController.php index 3a59f91a..e79b9be5 100644 --- a/app/Controllers/EpisodeController.php +++ b/app/Controllers/EpisodeController.php @@ -77,6 +77,7 @@ class EpisodeController extends BaseController if (! ($cachedView = cache($cacheName))) { $data = [ + 'metatags' => get_episode_metatags($this->episode), 'podcast' => $this->podcast, 'episode' => $this->episode, ]; @@ -115,6 +116,7 @@ class EpisodeController extends BaseController if (! ($cachedView = cache($cacheName))) { $data = [ + 'metatags' => get_episode_metatags($this->episode), 'podcast' => $this->podcast, 'episode' => $this->episode, ]; @@ -220,20 +222,21 @@ class EpisodeController extends BaseController $oembed->addChild('author_name', $this->podcast->title); $oembed->addChild('author_url', $this->podcast->link); $oembed->addChild('thumbnail', $this->episode->cover->large_url); - $oembed->addChild('thumbnail_width', config('Images')->podcastCoverSizes['large'][0]); - $oembed->addChild('thumbnail_height', config('Images')->podcastCoverSizes['large'][1]); + $oembed->addChild('thumbnail_width', (string) config('Images')->podcastCoverSizes['large'][0]); + $oembed->addChild('thumbnail_height', (string) config('Images')->podcastCoverSizes['large'][1]); $oembed->addChild( 'html', htmlentities( '', + '" width="100%" height="' . config('Embed')->height . '" frameborder="0" scrolling="no">', ), ); - $oembed->addChild('width', '600'); - $oembed->addChild('height', '144'); + $oembed->addChild('width', (string) config('Embed')->width); + $oembed->addChild('height', (string) config('Embed')->height); - return $this->response->setXML((string) $oembed); + // @phpstan-ignore-next-line + return $this->response->setXML($oembed); } /** diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php index f19eb74d..aeaaff0a 100644 --- a/app/Controllers/HomeController.php +++ b/app/Controllers/HomeController.php @@ -36,6 +36,7 @@ class HomeController extends BaseController // default behavior: list all podcasts on home page $data = [ + 'metatags' => get_home_metatags(), 'podcasts' => $allPodcasts, ]; diff --git a/app/Controllers/MapMarkerController.php b/app/Controllers/MapController.php similarity index 95% rename from app/Controllers/MapMarkerController.php rename to app/Controllers/MapController.php index 5ead9597..c4a6cd4b 100644 --- a/app/Controllers/MapMarkerController.php +++ b/app/Controllers/MapController.php @@ -13,7 +13,7 @@ namespace App\Controllers; use App\Models\EpisodeModel; use CodeIgniter\HTTP\ResponseInterface; -class MapMarkerController extends BaseController +class MapController extends BaseController { public function index(): string { @@ -21,7 +21,7 @@ class MapMarkerController extends BaseController ->getLocale(); $cacheName = "page_map_{$locale}"; if (! ($found = cache($cacheName))) { - $found = view('map', [], [ + $found = view('pages/map', [], [ 'cache' => DECADE, 'cache_name' => $cacheName, ]); diff --git a/app/Controllers/PageController.php b/app/Controllers/PageController.php index f1e1bf2a..3a251a7d 100644 --- a/app/Controllers/PageController.php +++ b/app/Controllers/PageController.php @@ -40,10 +40,11 @@ class PageController extends BaseController $cacheName = "page-{$this->page->slug}"; if (! ($found = cache($cacheName))) { $data = [ + 'metatags' => get_page_metatags($this->page), 'page' => $this->page, ]; - $found = view('page', $data); + $found = view('pages/page', $data); // The page cache is set to a decade so it is deleted manually upon page update cache() diff --git a/app/Controllers/PodcastController.php b/app/Controllers/PodcastController.php index f330064d..6d0b6c69 100644 --- a/app/Controllers/PodcastController.php +++ b/app/Controllers/PodcastController.php @@ -80,6 +80,7 @@ class PodcastController extends BaseController if (! ($cachedView = cache($cacheName))) { $data = [ + 'metatags' => get_podcast_metatags($this->podcast, 'activity'), 'podcast' => $this->podcast, 'posts' => (new PostModel())->getActorPublishedPosts($this->podcast->actor_id), ]; @@ -125,6 +126,7 @@ class PodcastController extends BaseController if (! ($cachedView = cache($cacheName))) { $data = [ + 'metatags' => get_podcast_metatags($this->podcast, 'about'), 'podcast' => $this->podcast, ]; @@ -240,6 +242,7 @@ class PodcastController extends BaseController } $data = [ + 'metatags' => get_podcast_metatags($this->podcast, 'episodes'), 'podcast' => $this->podcast, 'episodesNav' => $episodesNavigation, 'activeQuery' => $activeQuery, diff --git a/app/Controllers/PostController.php b/app/Controllers/PostController.php index 69837feb..0edb02dc 100644 --- a/app/Controllers/PostController.php +++ b/app/Controllers/PostController.php @@ -35,7 +35,7 @@ class PostController extends FediversePostController /** * @var string[] */ - protected $helpers = ['auth', 'fediverse', 'svg', 'components', 'misc']; + protected $helpers = ['auth', 'fediverse', 'svg', 'components', 'misc', 'seo']; public function _remap(string $method, string ...$params): mixed { @@ -81,6 +81,8 @@ class PostController extends FediversePostController if (! ($cachedView = cache($cacheName))) { $data = [ + // @phpstan-ignore-next-line + 'metatags' => get_post_metatags($this->post), 'post' => $this->post, 'podcast' => $this->podcast, ]; @@ -233,6 +235,8 @@ class PostController extends FediversePostController if (! ($cachedView = cache($cacheName))) { $data = [ + // @phpstan-ignore-next-line + 'metatags' => get_remote_actions_metatags($this->post, $action), 'podcast' => $this->podcast, 'actor' => $this->actor, 'post' => $this->post, diff --git a/app/Helpers/page_helper.php b/app/Helpers/page_helper.php index beb1eb27..7817a4d9 100644 --- a/app/Helpers/page_helper.php +++ b/app/Helpers/page_helper.php @@ -25,7 +25,7 @@ if (! function_exists('render_page_links')) { $links .= anchor(route_to('credits'), lang('Person.credits'), [ 'class' => 'px-2 py-1 underline hover:no-underline focus:ring-accent', ]); - $links .= anchor(route_to('map'), lang('Page.map'), [ + $links .= anchor(route_to('map'), lang('Page.map.title'), [ 'class' => 'px-2 py-1 underline hover:no-underline focus:ring-accent', ]); foreach ($pages as $page) { diff --git a/app/Helpers/seo_helper.php b/app/Helpers/seo_helper.php new file mode 100644 index 00000000..36edf1cb --- /dev/null +++ b/app/Helpers/seo_helper.php @@ -0,0 +1,275 @@ + $podcast->title, + 'url' => url_to('podcast-activity', $podcast->handle), + 'image' => $podcast->cover->feed_url, + 'description' => $podcast->description, + 'webFeed' => $podcast->feed_url, + 'author' => new Thing('Person', [ + 'name' => $podcast->publisher, + ]), + ]) + ); + + $metatags = new MetaTags(); + + $metatags + ->title(' ' . $podcast->title . " (@{$podcast->handle})" . ' • ' . lang('Podcast.' . $page)) + ->description(htmlspecialchars($podcast->description)) + ->image((string) $podcast->cover->large_url) + ->canonical((string) current_url()) + ->og('image:width', (string) config('Images')->podcastCoverSizes['large'][0]) + ->og('image:height', (string) config('Images')->podcastCoverSizes['large'][1]) + ->og('locale', $podcast->language_code) + ->og('site_name', service('settings')->get('App.siteName')); + + if ($podcast->payment_pointer) { + $metatags->meta('monetization', $podcast->payment_pointer); + } + + return '' . PHP_EOL . $metatags->__toString() . PHP_EOL . $schema->__toString(); + } +} + +if (! function_exists('get_episode_metatags')) { + function get_episode_metatags(Episode $episode): string + { + $schema = new Schema( + new Thing('PodcastEpisode', [ + 'url' => url_to('episode', $episode->podcast->handle, $episode->slug), + 'name' => $episode->title, + 'image' => $episode->cover->feed_url, + 'description' => $episode->description, + 'datePublished' => $episode->published_at->format(DATE_ISO8601), + 'timeRequired' => iso8601_duration($episode->audio_file_duration), + 'associatedMedia' => new Thing('MediaObject', [ + 'contentUrl' => $episode->audio_file_url, + ]), + 'partOfSeries' => new Thing('PodcastSeries', [ + 'name' => $episode->podcast->title, + 'url' => url_to('podcast-activity', $episode->podcast->handle), + ]), + ]) + ); + + $metatags = new MetaTags(); + + $metatags + ->title($episode->title) + ->description(htmlspecialchars($episode->description)) + ->image((string) $episode->cover->large_url, 'player') + ->canonical($episode->link) + ->og('site_name', service('settings')->get('App.siteName')) + ->og('image:width', (string) config('Images')->podcastCoverSizes['large'][0]) + ->og('image:height', (string) config('Images')->podcastCoverSizes['large'][1]) + ->og('locale', $episode->podcast->language_code) + ->og('audio', $episode->audio_file_opengraph_url) + ->og('audio:type', $episode->audio_file_mimetype) + ->meta('article:published_time', $episode->published_at->format(DATE_ISO8601)) + ->meta('article:modified_time', $episode->updated_at->format(DATE_ISO8601)) + ->twitter('audio:partner', $episode->podcast->publisher ?? '') + ->twitter('audio:artist_name', $episode->podcast->owner_name) + ->twitter('player', $episode->getEmbedUrl('light')) + ->twitter('player:width', (string) config('Embed')->width) + ->twitter('player:height', (string) config('Embed')->height); + + if ($episode->podcast->payment_pointer) { + $metatags->meta('monetization', $episode->podcast->payment_pointer); + } + + return $metatags->__toString() . PHP_EOL . '' . PHP_EOL . '' . PHP_EOL . $schema->__toString(); + } +} + +if (! function_exists('get_post_metatags')) { + function get_post_metatags(Post $post): string + { + $socialMediaPosting = new Thing('SocialMediaPosting', [ + '@id' => url_to('post', $post->actor->username, $post->id), + 'datePublished' => $post->published_at->format(DATE_ISO8601), + 'author' => new Thing('Person', [ + 'name' => $post->actor->display_name, + 'url' => $post->actor->uri, + ]), + 'text' => $post->message, + ]); + + if ($post->episode_id !== null) { + $socialMediaPosting->__set('sharedContent', new Thing('Audio', [ + 'headline' => $post->episode->title, + 'url' => $post->episode->link, + 'author' => new Thing('Person', [ + 'name' => $post->episode->podcast->owner_name, + ]), + ])); + } elseif ($post->preview_card !== null) { + $socialMediaPosting->__set('sharedContent', new Thing('WebPage', [ + 'headline' => $post->preview_card->title, + 'url' => $post->preview_card->url, + 'author' => new Thing('Person', [ + 'name' => $post->preview_card->author_name, + ]), + ])); + } + + $schema = new Schema($socialMediaPosting); + + $metatags = new MetaTags(); + $metatags + ->title(lang('Post.title', [ + 'actorDisplayName' => $post->actor->display_name, + ])) + ->description($post->message) + ->image($post->actor->avatar_image_url) + ->canonical((string) current_url()) + ->og('site_name', service('settings')->get('App.siteName')); + + return $metatags->__toString() . PHP_EOL . $schema->__toString(); + } +} + +if (! function_exists('get_episode_comment_metatags')) { + function get_episode_comment_metatags(EpisodeComment $episodeComment): string + { + $schema = new Schema(new Thing('SocialMediaPosting', [ + '@id' => url_to( + 'episode-comment', + $episodeComment->actor->username, + $episodeComment->episode->slug, + $episodeComment->id + ), + 'datePublished' => $episodeComment->created_at->format(DATE_ISO8601), + 'author' => new Thing('Person', [ + 'name' => $episodeComment->actor->display_name, + 'url' => $episodeComment->actor->uri, + ]), + 'text' => $episodeComment->message, + 'upvoteCount' => $episodeComment->likes_count, + ])); + + $metatags = new MetaTags(); + $metatags + ->title(lang('Comment.title', [ + 'actorDisplayName' => $episodeComment->actor->display_name, + 'episodeTitle' => $episodeComment->episode->title, + ])) + ->description($episodeComment->message) + ->image($episodeComment->actor->avatar_image_url) + ->canonical((string) current_url()) + ->og('site_name', service('settings')->get('App.siteName')); + + return $metatags->__toString() . PHP_EOL . $schema->__toString(); + } +} + +if (! function_exists('get_follow_metatags')) { + function get_follow_metatags(Actor $actor): string + { + $metatags = new MetaTags(); + $metatags + ->title(lang('Podcast.followTitle', [ + 'actorDisplayName' => $actor->display_name, + ])) + ->description($actor->summary) + ->image($actor->avatar_image_url) + ->canonical((string) current_url()) + ->og('site_name', service('settings')->get('App.siteName')); + + return $metatags->__toString(); + } +} + +if (! function_exists('get_remote_actions_metatags')) { + function get_remote_actions_metatags(Post $post, string $action): string + { + $metatags = new MetaTags(); + $metatags + ->title(lang('Fediverse.' . $action . '.title', [ + 'actorDisplayName' => $post->actor->display_name, + ],)) + ->description($post->message) + ->image($post->actor->avatar_image_url) + ->canonical((string) current_url()) + ->og('site_name', service('settings')->get('App.siteName')); + + return $metatags->__toString(); + } +} + +if (! function_exists('get_home_metatags')) { + function get_home_metatags(): string + { + $metatags = new MetaTags(); + $metatags + ->title(service('settings')->get('App.siteName')) + ->description(service('settings')->get('App.siteDescription')) + ->image(service('settings')->get('App.siteIcon')['512']) + ->canonical((string) current_url()) + ->og('site_name', service('settings')->get('App.siteName')); + + return $metatags->__toString(); + } +} + +if (! function_exists('get_page_metatags')) { + function get_page_metatags(Page $page): string + { + $metatags = new MetaTags(); + $metatags + ->title( + $page->title . service('settings')->get('App.siteTitleSeparator') . service( + 'settings' + )->get('App.siteName') + ) + ->description(service('settings')->get('App.siteDescription')) + ->image(service('settings')->get('App.siteIcon')['512']) + ->canonical((string) current_url()) + ->og('site_name', service('settings')->get('App.siteName')); + + return $metatags->__toString(); + } +} + +if (! function_exists('iso8601_duration')) { + // From https://stackoverflow.com/a/40761380 + function iso8601_duration(float $seconds): string + { + $days = floor($seconds / 86400); + $seconds %= 86400; + + $hours = floor($seconds / 3600); + $seconds %= 3600; + + $minutes = floor($seconds / 60); + $seconds %= 60; + + return sprintf('P%dDT%dH%dM%dS', $days, $hours, $minutes, $seconds); + } +} diff --git a/app/Language/en/Page.php b/app/Language/en/Page.php index 592b8784..9a255bda 100644 --- a/app/Language/en/Page.php +++ b/app/Language/en/Page.php @@ -10,21 +10,8 @@ declare(strict_types=1); return [ 'back_to_home' => 'Back to home', - 'page' => 'Page', - 'all_pages' => 'All pages', - 'create' => 'New page', - 'go_to_page' => 'Go to page', - 'edit' => 'Edit page', - 'delete' => 'Delete page', - 'form' => [ - 'title' => 'Title', - 'permalink' => 'Permalink', - 'content' => 'Content', - 'submit_create' => 'Create page', - 'submit_edit' => 'Save', + 'map' => [ + 'title' => 'Map', + 'description' => 'Discover podcast episodes on {siteName} that are placed on a map! Travel through the map and listen to episodes that talk about specific locations.', ], - 'messages' => [ - 'createSuccess' => 'The page “{pageTitle}” was created successfully!', - ], - 'map' => 'Map', ]; diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php index d8c0a20e..7528a9a3 100644 --- a/app/Language/en/Podcast.php +++ b/app/Language/en/Podcast.php @@ -28,8 +28,11 @@ return [ other {# posts} }', 'activity' => 'Activity', + 'activity_title' => '{podcastTitle} news & activity', 'episodes' => 'Episodes', + 'episodes_title' => 'Episodes of {podcastTitle}', 'about' => 'About', + 'about_title' => 'About {podcastTitle}', 'sponsor_title' => 'Enjoying the show?', 'sponsor' => 'Sponsor', 'funding_links' => 'Funding links for {podcastTitle}', diff --git a/app/Language/fr/Page.php b/app/Language/fr/Page.php index 220b1a73..8ad3f5d1 100644 --- a/app/Language/fr/Page.php +++ b/app/Language/fr/Page.php @@ -10,21 +10,8 @@ declare(strict_types=1); return [ 'back_to_home' => 'Retour à l’accueil', - 'page' => 'Page', - 'all_pages' => 'Toutes les pages', - 'create' => 'Créer une page', - 'go_to_page' => 'Aller à la page', - 'edit' => 'Modifier la page', - 'delete' => 'Supprimer la page', - 'form' => [ - 'title' => 'Titre', - 'permalink' => 'Lien permanent', - 'content' => 'Contenu', - 'submit_create' => 'Créer la page', - 'submit_edit' => 'Enregistrer', + 'map' => [ + 'title' => 'Cartographie', + 'description' => 'Découvrez des épisodes de podcast placés sur une carte avec {siteName} ! Voyagez sur une carte du monde et écoutez des épisodes mentionnant des lieux spécifiques.', ], - 'messages' => [ - 'createSuccess' => 'La page {pageTitle} a été créée avec succès !', - ], - 'map' => 'Cartographie', ]; diff --git a/composer.json b/composer.json index b7dffcaf..a2f50588 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "michalsn/codeigniter4-uuid": "dev-develop", "essence/essence": "^3.5.4", "codeigniter4/settings": "dev-develop", - "chrisjean/php-ico": "^1.0" + "chrisjean/php-ico": "^1.0", + "melbahja/seo": "^2.0" }, "require-dev": { "mikey179/vfsstream": "^v1.6.8", diff --git a/composer.lock b/composer.lock index 82b1287a..fabdde83 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f35a050323bdc632cd550f9d13f0679c", + "content-hash": "c0a25c3d11c806b4bc62eafb22902bc8", "packages": [ { "name": "brick/math", @@ -1120,6 +1120,64 @@ }, "time": "2020-11-02T17:00:53+00:00" }, + { + "name": "melbahja/seo", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/melbahja/seo.git", + "reference": "a42500223cb532d4069e85097cc5b5e6ee402de1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/melbahja/seo/zipball/a42500223cb532d4069e85097cc5b5e6ee402de1", + "reference": "a42500223cb532d4069e85097cc5b5e6ee402de1", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-xml": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Melbahja\\Seo\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["MIT"], + "authors": [ + { + "name": "Mohamed ELbahja", + "email": "mohamed@elbahja.me", + "homepage": "https://elbahja.me", + "role": "Developer" + } + ], + "description": "Simple PHP library to help developers 🍻 do better on-page SEO optimization", + "keywords": [ + "PHP7", + "meta tags", + "open graph", + "php7.1", + "schema.org", + "search engine optimization", + "seo", + "sitemap index", + "sitemap.xml", + "sitemaps", + "twitter tags" + ], + "support": { + "issues": "https://github.com/melbahja/seo/issues", + "source": "https://github.com/melbahja/seo/tree/v2.0.0" + }, + "time": "2021-10-26T00:36:49+00:00" + }, { "name": "michalsn/codeigniter4-uuid", "version": "dev-develop", diff --git a/themes/cp_admin/_layout.php b/themes/cp_admin/_layout.php index 11e60213..9cc6cdc0 100644 --- a/themes/cp_admin/_layout.php +++ b/themes/cp_admin/_layout.php @@ -4,6 +4,8 @@ + + <?= $this->renderSection('title') ?> | Castopod Admin diff --git a/themes/cp_admin/episode/_sidebar.php b/themes/cp_admin/episode/_sidebar.php index 96d21e34..af4c2e3b 100644 --- a/themes/cp_admin/episode/_sidebar.php +++ b/themes/cp_admin/episode/_sidebar.php @@ -16,7 +16,7 @@ $podcastNavigation = [ /> title ?> -
+
<?= $episode->title ?>
-
+
<?= $episode->title ?>
diff --git a/themes/cp_admin/episode/publish_edit.php b/themes/cp_admin/episode/publish_edit.php index 17f0162c..6e38016e 100644 --- a/themes/cp_admin/episode/publish_edit.php +++ b/themes/cp_admin/episode/publish_edit.php @@ -41,7 +41,7 @@
-
+
<?= $episode->title ?>
diff --git a/themes/cp_app/embed.php b/themes/cp_app/embed.php index 0f4b3464..e81ac9e9 100644 --- a/themes/cp_app/embed.php +++ b/themes/cp_app/embed.php @@ -4,6 +4,7 @@ + <?= $episode->title ?> - renderSection('meta-tags') ?> - payment_pointer): ?> - - + asset('styles/index.css', 'css') ?> diff --git a/themes/cp_app/episode/_partials/preview_card.php b/themes/cp_app/episode/_partials/preview_card.php index c0966e56..d429d938 100644 --- a/themes/cp_app/episode/_partials/preview_card.php +++ b/themes/cp_app/episode/_partials/preview_card.php @@ -1,4 +1,4 @@ -
+