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();
}
}