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)->where('status', 'publish')->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 EchoScoop.'; // 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::addMeta('article:section', $post->category->name, '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_image_cdn); $jsonld_multi = JsonLdMulti::newJsonLd(); $jsonld_multi->setTitle($post->title) ->setDescription($post_description) ->setType('Article') ->addValue('headline', $post->title) ->addImage($post->featured_image_cdn) ->addValue('author', [ 'type' => 'Person', 'name' => $post->author->name, 'url' => config('app.url'), ]) ->addValue('publisher', [ 'type' => 'Organization', 'name' => 'EchoScoop', 'logo' => [ 'type' => 'ImageObject', 'url' => asset('echoscoop-logo-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').' display-6 fw-bolder mt-3 mb-2')); }); $crawler->filter('h2')->each(function (Crawler $node) { $node->getNode(0)->setAttribute('class', trim($node->attr('class').' h4 mb-3')); }); $crawler->filter('h3')->each(function (Crawler $node) { $node->getNode(0)->setAttribute('class', trim($node->attr('class').' h6 mb-3')); }); // 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 = $post->author->name; // Create the HTML structure for publish date and author $publishInfo = "