259 lines
9.7 KiB
PHP
259 lines
9.7 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Front;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Post;
|
|
use Artesaos\SEOTools\Facades\JsonLdMulti;
|
|
use Artesaos\SEOTools\Facades\OpenGraph;
|
|
use Artesaos\SEOTools\Facades\SEOMeta;
|
|
use GrahamCampbell\Markdown\Facades\Markdown;
|
|
use Illuminate\Http\Request;
|
|
use JsonLd\Context;
|
|
use Symfony\Component\DomCrawler\Crawler;
|
|
|
|
class FrontPostController extends Controller
|
|
{
|
|
public function redirect(Request $request, $slug)
|
|
{
|
|
$post = Post::where('slug', $slug)->where('status', 'publish')->first();
|
|
|
|
if (is_null($post)) {
|
|
return abort(404);
|
|
}
|
|
|
|
return redirect()->route('front.post', ['category_slug' => $post->category->slug, 'slug' => $post->slug]);
|
|
|
|
}
|
|
|
|
public function index(Request $request, $category_slug, $slug)
|
|
{
|
|
$post = Post::where('slug', $slug)->whereIn('status', ['publish', 'future'])->first();
|
|
|
|
if (is_null($post)) {
|
|
return abort(404);
|
|
}
|
|
|
|
$content = Markdown::convert($post->body)->getContent();
|
|
|
|
//dd($content);
|
|
//$content = $this->injectTableOfContents($content);
|
|
$content = $this->injectBootstrapClasses($content);
|
|
//$content = $this->injectFeaturedImage($post, $content);
|
|
//$content = $this->injectPublishDateAndAuthor($post, $content);
|
|
|
|
$post_description = $post->excerpt.' '.$post->title.' by FutureWalker.';
|
|
|
|
// Generate breadcrumb data
|
|
$breadcrumbs = collect([
|
|
['name' => 'Home', 'url' => route('front.home')],
|
|
]);
|
|
|
|
if ($post->category) {
|
|
foreach ($post->category->ancestors as $ancestor) {
|
|
$breadcrumbs->push([
|
|
'name' => $ancestor->name,
|
|
'url' => route('front.category', $ancestor->slug),
|
|
]);
|
|
}
|
|
|
|
$breadcrumbs->push([
|
|
'name' => $post->category->name,
|
|
'url' => route('front.category', $post->category->slug),
|
|
]);
|
|
}
|
|
|
|
$breadcrumbs->push([
|
|
'name' => $post->title,
|
|
'url' => url()->current(), // The current page URL; the breadcrumb is not clickable
|
|
]);
|
|
|
|
SEOMeta::setTitle($post->title, false);
|
|
SEOMeta::setDescription($post_description);
|
|
SEOMeta::addMeta('article:published_time', $post->published_at->format('Y-m-d'), 'property');
|
|
SEOMeta::setRobots('INDEX, FOLLOW, MAX-IMAGE-PREVIEW:LARGE, MAX-SNIPPET:-1, MAX-VIDEO-PREVIEW:-1');
|
|
|
|
OpenGraph::setDescription($post_description);
|
|
OpenGraph::setTitle($post->title);
|
|
OpenGraph::setUrl(url()->current());
|
|
OpenGraph::addProperty('type', 'article');
|
|
OpenGraph::addProperty('locale', 'en_US');
|
|
OpenGraph::addImage($post->featured_og_image);
|
|
|
|
$jsonld_multi = JsonLdMulti::newJsonLd();
|
|
$jsonld_multi->setTitle($post->title)
|
|
->setDescription($post_description)
|
|
->setType('Article')
|
|
->addValue('headline', $post->title)
|
|
->addImage($post->featured_og_image)
|
|
->addValue('author', [
|
|
'type' => 'Person',
|
|
'name' => 'FutureWalker',
|
|
'url' => config('app.url'),
|
|
])
|
|
->addValue('publisher', [
|
|
'type' => 'Organization',
|
|
'name' => 'FutureWalker',
|
|
'logo' => [
|
|
'type' => 'ImageObject',
|
|
'url' => asset('images/icons/icon-512x512.png'),
|
|
],
|
|
])
|
|
->addValue('datePublished', $post->published_at->format('Y-m-d'))
|
|
->addValue('dateCreated', $post->created_at->format('Y-m-d'))
|
|
->addValue('dateModified', $post->updated_at->format('Y-m-d'))
|
|
->addValue('description', $post_description)
|
|
->addValue('articleBody', plain_text($content));
|
|
|
|
// breadcrumb json ld
|
|
$listItems = [];
|
|
|
|
foreach ($breadcrumbs as $index => $breadcrumb) {
|
|
$listItems[] = [
|
|
'name' => $breadcrumb['name'],
|
|
'url' => $breadcrumb['url'],
|
|
];
|
|
}
|
|
|
|
$breadcrumb_context = Context::create('breadcrumb_list', [
|
|
'itemListElement' => $listItems,
|
|
]);
|
|
|
|
return view('front.single_post', compact('post', 'content', 'breadcrumbs', 'breadcrumb_context'));
|
|
}
|
|
|
|
private function injectBootstrapClasses($content)
|
|
{
|
|
$crawler = new Crawler($content);
|
|
|
|
// Handle Headings
|
|
$crawler->filter('h1')->each(function (Crawler $node) {
|
|
$node->getNode(0)->setAttribute('class', trim($node->attr('class').' h4 fw-bolder mt-3 mb-2'));
|
|
});
|
|
|
|
$crawler->filter('h2')->each(function (Crawler $node) {
|
|
$node->getNode(0)->setAttribute('class', trim($node->attr('class').' h5 mb-2 fw-bolder font-family-roboto-condensed'));
|
|
});
|
|
|
|
$crawler->filter('h3')->each(function (Crawler $node) {
|
|
$node->getNode(0)->setAttribute('class', trim($node->attr('class').' h6 mb-2 fw-bolder font-family-roboto-condensed'));
|
|
});
|
|
|
|
// Handle Paragraphs
|
|
$crawler->filter('p')->each(function (Crawler $pNode) {
|
|
$precedingHeaders = $pNode->previousAll()->filter('h2');
|
|
|
|
if (! $precedingHeaders->count()) {
|
|
$existingClasses = $pNode->attr('class');
|
|
$newClasses = trim($existingClasses.' mt-2 mb-4');
|
|
$pNode->getNode(0)->setAttribute('class', $newClasses);
|
|
|
|
return;
|
|
}
|
|
|
|
$precedingHeader = $precedingHeaders->first();
|
|
if (trim($precedingHeader->text()) !== 'FAQs') {
|
|
$existingClasses = $pNode->attr('class');
|
|
$newClasses = trim($existingClasses.' mt-2 mb-4');
|
|
$pNode->getNode(0)->setAttribute('class', $newClasses);
|
|
}
|
|
|
|
if (strpos($pNode->text(), 'Q:') === 0) {
|
|
$currentClasses = $pNode->attr('class');
|
|
$newClasses = trim($currentClasses.' ');
|
|
$pNode->getNode(0)->setAttribute('class', $newClasses);
|
|
}
|
|
});
|
|
|
|
// Handle Lists
|
|
$crawler->filter('ul')->each(function (Crawler $node) {
|
|
$node->getNode(0)->setAttribute('class', trim($node->attr('class').' py-2'));
|
|
});
|
|
|
|
$crawler->filter('ol')->each(function (Crawler $node) {
|
|
$node->getNode(0)->setAttribute('class', trim($node->attr('class').' py-2'));
|
|
});
|
|
|
|
// Handle Tables
|
|
$crawler->filter('table')->each(function (Crawler $node) {
|
|
$node->getNode(0)->setAttribute('class', trim($node->attr('class').' table table-bordered mb-4'));
|
|
});
|
|
|
|
// Convert the modified DOM back to string
|
|
$modifiedContent = '';
|
|
foreach ($crawler as $domElement) {
|
|
$modifiedContent .= $domElement->ownerDocument->saveHTML($domElement);
|
|
}
|
|
|
|
return $modifiedContent;
|
|
}
|
|
|
|
private function injectTableOfContents($html)
|
|
{
|
|
$crawler = new Crawler($html);
|
|
|
|
$h2Elements = $crawler->filter('h2');
|
|
|
|
if ($h2Elements->count() < 3) {
|
|
// Return the original HTML if there are fewer than 3 h2 tags
|
|
return $html;
|
|
}
|
|
|
|
// Create the Table of Contents
|
|
$domDocument = $crawler->getNode(0)->ownerDocument;
|
|
$tocDiv = $domDocument->createElement('div');
|
|
$tocDiv->setAttribute('class', 'p-3 rounded-3 bg-light mb-3');
|
|
$ol = $domDocument->createElement('ol');
|
|
$tocDiv->appendChild($ol);
|
|
|
|
$h2Elements->each(function (Crawler $node, $i) use ($ol) {
|
|
$content = htmlspecialchars($node->text(), ENT_XML1 | ENT_COMPAT, 'UTF-8');
|
|
$id = 'link-'.$i;
|
|
$node->getNode(0)->setAttribute('id', $id);
|
|
$li = $ol->appendChild(new \DOMElement('li'));
|
|
$li->setAttribute('class', 'py-1');
|
|
$a = $li->appendChild(new \DOMElement('a', $content));
|
|
$a->setAttribute('class', 'text-decoration-none hover-text-decoration-underline');
|
|
$a->setAttribute('href', '#'.$id);
|
|
});
|
|
|
|
// Insert TOC after h1
|
|
$h1Node = $crawler->filter('h1')->getNode(0);
|
|
$h1Node->parentNode->insertBefore($tocDiv, $h1Node->nextSibling);
|
|
|
|
// Get the updated HTML
|
|
$updatedHtml = $crawler->filter('body')->html();
|
|
|
|
return $updatedHtml;
|
|
}
|
|
|
|
private function injectPublishDateAndAuthor($post, $content)
|
|
{
|
|
$publishedAtFormatted = $post->published_at->format('F j, Y');
|
|
$authorName = 'FutureWalker Team';
|
|
|
|
// Create the HTML structure for publish date and author
|
|
$publishInfo = "<div class=\"mb-4\"><span class=\"text-secondary small\">Published on {$publishedAtFormatted} by {$authorName}<i class=\"bi bi-dot\"></i>".markdown_min_read($post->body).'</span></div>';
|
|
|
|
// Inject the publish date and author information after the `h1` tag
|
|
$content = preg_replace('/(<\/h1>)/', '$1'.$publishInfo, $content, 1);
|
|
|
|
return $content;
|
|
}
|
|
|
|
private function injectFeaturedImage($post, $content)
|
|
{
|
|
if (! empty($post->featured_image)) {
|
|
$featured_image_alt = strtolower($post->short_title);
|
|
$featured_image_alt_caps = strtoupper($post->short_title);
|
|
|
|
// Update the HTML structure
|
|
$featured_image = "<div class=\"w-100 d-flex justify-content-center\"><figure class=\"text-center\"><img decoding=\"async\" class=\"img-fluid rounded mb-2 shadow lqip-blur\" src=\"{$post->featured_image_lqip_cdn}\" data-src=\"{$post->featured_image_cdn}\" alt=\"{$featured_image_alt}\"><figcaption class=\"text-secondary small\">{$featured_image_alt_caps}</figcaption></figure></div>";
|
|
|
|
$content = preg_replace('/(<\/h1>)/', '$1'.$featured_image, $content, 1);
|
|
}
|
|
|
|
return $content;
|
|
}
|
|
}
|