Laravel 13 + Filament 5 admin panel and REST API for a non-profit developer collective, backed by the Pireon OAuth identity server. The public site is a separate, server-rendered application consuming /api/public/v1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
77 lines
2.2 KiB
PHP
77 lines
2.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
/**
|
|
* Builds the public-site URLs and hreflang alternates for a translatable
|
|
* record.
|
|
*
|
|
* This lives in the API rather than the frontend for one reason: only the API
|
|
* knows that `/ca/projectes/el-meu-projecte` and `/proyectos/mi-proyecto` are
|
|
* the same record. Without it the frontend cannot emit correct `hreflang`
|
|
* tags, and search engines treat the three languages as duplicates.
|
|
*/
|
|
final class SiteUrls
|
|
{
|
|
/**
|
|
* Every locale in which a record has a slug, mapped to its public path.
|
|
*
|
|
* @return array<string, string>
|
|
*/
|
|
public static function alternates(Model $record, string $type, string $slugField = 'slug'): array
|
|
{
|
|
$alternates = [];
|
|
|
|
foreach (array_keys(config('locales.supported')) as $locale) {
|
|
$slug = $record->getTranslation($slugField, $locale, useFallbackLocale: false);
|
|
|
|
// A locale without its own slug has no URL of its own — emitting
|
|
// one would advertise a page that does not exist.
|
|
if (blank($slug)) {
|
|
continue;
|
|
}
|
|
|
|
$alternates[$locale] = self::path($type, $locale, (string) $slug);
|
|
}
|
|
|
|
return $alternates;
|
|
}
|
|
|
|
/**
|
|
* Absolute URLs for the same set, ready to drop into `<link hreflang>`.
|
|
*
|
|
* @return array<string, string>
|
|
*/
|
|
public static function absoluteAlternates(Model $record, string $type, string $slugField = 'slug'): array
|
|
{
|
|
return array_map(
|
|
fn (string $path) => rtrim(config('site.url'), '/').$path,
|
|
self::alternates($record, $type, $slugField),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* The path a record occupies on the public site, e.g. "/ca/projectes/foo".
|
|
*/
|
|
public static function path(string $type, string $locale, string $slug): string
|
|
{
|
|
$segments = [];
|
|
|
|
if ($locale !== config('locales.default') || config('site.prefix_default_locale')) {
|
|
$segments[] = $locale;
|
|
}
|
|
|
|
if ($prefix = config("site.paths.{$type}.{$locale}")) {
|
|
$segments[] = $prefix;
|
|
}
|
|
|
|
$segments[] = $slug;
|
|
|
|
return '/'.implode('/', array_filter($segments, fn ($s) => $s !== ''));
|
|
}
|
|
}
|