published(50); $this->seo->title('Guides & Tutorials – Media How-Tos') ->description('Practical guides for converting, compressing and editing media: video, audio, images, GIFs, PDFs and YouTube workflows.') ->canonical('/blog') ->breadcrumbs([['label' => 'Blog']]); return $this->render('guides_index', ['guides' => $guides]); } public function show(string $slug): string { $guide = model(\App\Models\GuideModel::class)->findBySlug($slug); if ($guide === null) { return (new Errors())->notFound(); } model(\App\Models\GuideModel::class)->incrementViews((int) $guide['id']); $linkedTools = []; foreach (json_decode((string) ($guide['tool_slugs'] ?? '[]'), true) ?: [] as $toolSlug) { if ($tool = service('finder')->tool((string) $toolSlug)) { $linkedTools[] = $tool; } } $related = array_values(array_filter( model(\App\Models\GuideModel::class)->published(20), static fn (array $g): bool => $g['slug'] !== $guide['slug'] )); $this->seo->title($guide['seo_title'] ?: $guide['title']) ->description($guide['seo_description'] ?: mb_substr($guide['excerpt'], 0, 300)) ->canonical('/blog/' . $guide['slug']) ->breadcrumbs([['label' => 'Blog', 'url' => '/blog'], ['label' => $guide['title']]]) ->article($guide['title'], (string) $guide['body_md'], (string) $guide['published_at'], config('Site')->name) ->ogImage('/og/blog-' . $guide['slug'] . '.png'); return $this->render('guide', [ 'guide' => $guide, 'html' => self::markdown((string) $guide['body_md']), 'tools' => $linkedTools, 'related'=> array_slice($related, 0, 4), ]); } public function rss(): \CodeIgniter\HTTP\Response { $guides = model(\App\Models\GuideModel::class)->published(30); $site = config('Site'); $items = ''; foreach ($guides as $g) { $items .= "\n" . htmlspecialchars((string) $g['title'], ENT_XML1) . "" . base_url('/blog/' . $g['slug']) . "" . "" . htmlspecialchars((string) $g['excerpt'], ENT_XML1) . "" . "" . date(DATE_RSS, strtotime((string) $g['published_at'])) . "" . "" . base_url('/blog/' . $g['slug']) . ""; } $xml = '' . htmlspecialchars($site->name . ' Guides', ENT_XML1) . '' . base_url('/blog') . '' . htmlspecialchars($site->tagline, ENT_XML1) . '' . $items . ''; return response()->setContentType('application/rss+xml; charset=utf-8')->setBody($xml); } private static function markdown(string $text): string { $env = new Environment(['html_input' => 'strip', 'allow_unsafe_links' => false]); $env->addExtension(new CommonMarkCoreExtension()); $env->addExtension(new AutolinkExtension()); $env->addExtension(new TableExtension()); return (new MarkdownConverter($env))->convert($text)->getContent(); } }