{{-- resources/views/partials/seo.blade.php --}} @php use Illuminate\Support\Str; use Illuminate\Support\Carbon; /** * Inputs (all optional): * - $seoMeta (array) : manual overrides (meta_title, meta_description, meta_keywords, robots, canonical) * - $pageModel(Model) : Product / Article / Category ... or null for static pages * - $config (array) : site config (site_title, site_description, site_keywords, site_name, currency, default_og_image) * - $canonical (string): explicit canonical URL */ $conf = is_array($config ?? null) ? $config : []; $model = $pageModel ?? null; $manual = is_array($seoMeta ?? null) ? $seoMeta : []; // Helpers $isObj = is_object($model); $clean = function ($v) { return trim(preg_replace('/\s+/u', ' ', strip_tags((string)$v))); }; $limit = function ($v, $len = 160) use ($clean) { $t = $clean($v); return Str::limit($t, $len, '…'); }; // نوع مدل (ایمن) $table = ($isObj && method_exists($model, 'getTable')) ? $model->getTable() : null; $isProduct = $table === 'products'; $isArticle = $table === 'articles'; $isCategory = $table === 'categories'; // صفحهٔ اصلی؟ // اگر route name داری (مثلاً 'home') می‌تونی از routeIs استفاده کنی $isHome = url()->current() === url('/'); // seo_metas مرتبط با مدل (ایمن) $attachedSeo = null; if ($isObj) { if (isset($model->seoMeta)) { $attachedSeo = $model->seoMeta; } elseif (method_exists($model, 'seoMeta')) { try { $attachedSeo = $model->seoMeta; } catch (\Throwable $e) {} } elseif (method_exists($model, 'seoMetas')) { try { $attachedSeo = $model->seoMetas()->latest()->first(); } catch (\Throwable $e) {} } } // انتخاب مقدار از: دستی ← seo_metas ← مدل ← fallback $pick = function ($keys, $fallback = null) use ($manual, $attachedSeo, $model, $isObj) { foreach ((array)$keys as $k) { if (array_key_exists($k, $manual) && filled($manual[$k])) return $manual[$k]; if ($attachedSeo && isset($attachedSeo->$k) && filled($attachedSeo->$k)) return $attachedSeo->$k; if ($isObj && isset($model->$k) && filled($model->$k)) return $model->$k; } return $fallback; }; // ----- Title با برند ----- $siteTitle = $conf['site_title'] ?? config('app.name', 'Website'); $pageTitle = $pick(['meta_title', 'title'], null); if ($isHome) { $title = $siteTitle; // فقط نام سایت برای صفحهٔ اصلی } elseif ($pageTitle) { $title = $pageTitle . ' - ' . $siteTitle; // سایر صفحات: عنوان + نام سایت } else { $title = $siteTitle; } // ----- Description ----- $desc = $pick(['meta_description', 'short_description', 'description'], $conf['site_description'] ?? ''); $desc = $limit($desc, 160); // ----- Keywords (merge + unique) ----- $kwCandidates = [ $manual['meta_keywords'] ?? null, $attachedSeo->meta_keywords ?? null, $isObj ? ($model->keywords ?? null) : null, $isObj ? ($model->tags ?? null) : null, $conf['site_keywords'] ?? null, ]; $kwList = []; foreach ($kwCandidates as $kwStr) { if (!filled($kwStr)) continue; $parts = preg_split('/[,،]/u', $kwStr); foreach ($parts as $p) { $p = trim($p); if ($p !== '' && !in_array($p, $kwList, true)) $kwList[] = $p; } } $keywords = implode(', ', $kwList); // ----- Canonical ----- // manual['canonical'] اگر URL باشد → همان // اگر manual/attachedSeo canonical == 0 → اصلاً خروجی نده // وگرنه → $canonical یا آدرس فعلی $emitCanonical = true; $canonicalUrl = null; if (array_key_exists('canonical', $manual)) { if (is_string($manual['canonical']) && preg_match('#^https?://#i', $manual['canonical'])) { $canonicalUrl = $manual['canonical']; $emitCanonical = true; } elseif ((string)$manual['canonical'] === '0') { $emitCanonical = false; } } if (is_null($canonicalUrl)) { $canonicalUrl = $canonical ?? request()->url(); } if ($attachedSeo && isset($attachedSeo->canonical) && (string)$attachedSeo->canonical === '0') { $emitCanonical = false; } // ----- Robots ----- if (array_key_exists('robots', $manual) && is_string($manual['robots'])) { $robots = $manual['robots']; // استفاده از رشتهٔ دستی } else { // پیش‌فرض: اگر index/follow در seo_metas نبود → true $seoIndex = null; $seoFollow = null; if ($attachedSeo) { if (isset($attachedSeo->index)) $seoIndex = (int)$attachedSeo->index; if (isset($attachedSeo->follow)) $seoFollow = (int)$attachedSeo->follow; } $seoIndex = is_null($seoIndex) ? 1 : (int)$seoIndex; $seoFollow = is_null($seoFollow) ? 1 : (int)$seoFollow; // Auto-index بر اساس وضعیت انتشار $autoIndex = true; if ($isProduct && $isObj && isset($model->status)) { $autoIndex = ((int)$model->status === 1); } if ($isArticle && $isObj && isset($model->publish_date) && filled($model->publish_date)) { try { $publishAt = Carbon::parse($model->publish_date); if ($publishAt->isFuture()) $autoIndex = false; } catch (\Throwable $e) {} } if ($isObj && method_exists($model, 'getAttribute') && $model->getAttribute('deleted_at')) { $autoIndex = false; } $finalIndex = ($seoIndex === 1) && $autoIndex; $finalFollow = ($seoFollow === 1); $robots = ($finalIndex ? 'index' : 'noindex') . ', ' . ($finalFollow ? 'follow' : 'nofollow'); } // ----- Open Graph / Twitter ----- $siteName = $conf['site_name'] ?? $siteTitle; $ogType = $isProduct ? 'product' : ($isArticle ? 'article' : 'website'); $ogImage = null; if ($isObj) { foreach (['primary_image_url','main_image_url','image_url','thumbnail_url','image'] as $imgField) { if ($ogImage) break; if (isset($model->$imgField) && filled($model->$imgField)) { $ogImage = $model->$imgField; } elseif (method_exists($model, $imgField)) { try { $ogImage = $model->$imgField(); } catch (\Throwable $e) {} } } } if (!$ogImage && !empty($conf['default_og_image'])) { $ogImage = $conf['default_og_image']; } // ----- JSON-LD ----- $prune = function ($arr) use (&$prune) { if (!is_array($arr)) return $arr; $out = []; foreach ($arr as $k=>$v) { if (is_array($v)) { $v = $prune($v); if ($v === []) continue; } if ($v !== null && $v !== '') $out[$k] = $v; } return $out; }; $jsonLd = null; if ($isProduct) { $currency = $conf['currency'] ?? 'IRR'; $offers = []; if ($isObj && isset($model->product_price) && (int)$model->product_price > 0) { $offers = [ '@type' => 'Offer', 'priceCurrency' => $currency, 'price' => (string) $model->product_price, 'availability' => 'https://schema.org/InStock', 'url' => $canonicalUrl, ]; } $brandName = null; if ($isObj && (isset($model->brand) || isset($model->brand_id))) { try { $brandName = optional($model->brand)->title; } catch (\Throwable $e) {} } $categoryName = null; if ($isObj && (isset($model->category) || isset($model->category_id))) { try { $categoryName = optional($model->category)->title; } catch (\Throwable $e) {} } $jsonLd = [ '@context' => 'https://schema.org', '@type' => 'Product', 'name' => $clean($pageTitle ?: $siteTitle), 'description' => $clean($desc), 'sku' => $isObj ? ($model->vendor_code ?? null) : null, 'brand' => $brandName ? ['@type'=>'Brand','name'=>$clean($brandName)] : null, 'category' => $categoryName ? $clean($categoryName) : null, 'image' => $ogImage ? [$ogImage] : null, 'offers' => $offers ?: null, 'url' => $canonicalUrl, ]; } elseif ($isArticle) { $jsonLd = [ '@context' => 'https://schema.org', '@type' => 'Article', 'headline' => $clean($pageTitle ?: $siteTitle), 'description' => $clean($desc), 'datePublished' => ($isObj && isset($model->publish_date)) ? (string)$model->publish_date : null, 'image' => $ogImage ? [$ogImage] : null, 'mainEntityOfPage'=> $canonicalUrl, ]; } elseif ($isCategory) { $jsonLd = [ '@context' => 'https://schema.org', '@type' => 'CollectionPage', 'name' => $clean($pageTitle ?: $siteTitle), 'description' => $clean($desc), 'url' => $canonicalUrl, ]; } else { // صفحات استاتیک بدون مدل $jsonLd = [ '@context' => 'https://schema.org', '@type' => 'WebPage', 'name' => $clean($pageTitle ?: $siteTitle), 'description' => $clean($desc), 'url' => $canonicalUrl, ]; } if ($jsonLd) $jsonLd = $prune($jsonLd); @endphp {{ $title }} @if(!empty($keywords)) @endif @if($emitCanonical) @endif @if($ogImage) @endif @if($ogImage) @endif @if($jsonLd) @endif