Update
This commit is contained in:
774
app/Http/Controllers/PageController.php
Normal file
774
app/Http/Controllers/PageController.php
Normal file
@@ -0,0 +1,774 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\StoreContactMessageRequest;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
use Klevze\ControlPanel\Facades\ActiveLanguage;
|
||||
use cPad\Plugins\Pages\Services\Page as CmsPage;
|
||||
|
||||
class PageController extends Controller
|
||||
{
|
||||
public function home(): View
|
||||
{
|
||||
$frontpageId = DB::table('contents')
|
||||
->where('page_type', 'frontpage')
|
||||
->value('content_id');
|
||||
|
||||
$frontpage = $frontpageId
|
||||
? app(CmsPage::class)->load((int) $frontpageId, app()->getLocale())
|
||||
: null;
|
||||
|
||||
$seo = $this->pageSeo($frontpage, 'Home');
|
||||
|
||||
return $this->render('pages.home', $seo['title'], 'home', [
|
||||
'seo' => $seo,
|
||||
]);
|
||||
}
|
||||
|
||||
public function about(): View
|
||||
{
|
||||
return $this->render('pages.about', 'About Us', 'about');
|
||||
}
|
||||
|
||||
public function work(): View
|
||||
{
|
||||
$categoryMap = $this->projectCategoryMap();
|
||||
|
||||
return $this->render('pages.work', 'Works', 'work', [
|
||||
'artworks' => $this->portfolioProjects($categoryMap),
|
||||
'categories' => array_values($categoryMap),
|
||||
]);
|
||||
}
|
||||
|
||||
public function project(string $locale, ?int $projectId = null, ?string $slug = null): View
|
||||
{
|
||||
$project = $this->findPublicProject($projectId);
|
||||
|
||||
abort_if(!$project, 404);
|
||||
|
||||
$payload = $this->projectRendererPayload($project);
|
||||
|
||||
$headline = (string) ($project->headline ?: 'Single Project');
|
||||
$metaTitle = (string) ($project->meta_title ?: $project->page_title ?: $headline);
|
||||
|
||||
return $this->render('pages.project', $metaTitle, 'work', [
|
||||
'project' => $this->normalizeProjectForBlade($payload),
|
||||
'nextProject' => $this->nextPublicProject((int) $project->project_id),
|
||||
'seo' => [
|
||||
'title' => $metaTitle,
|
||||
'meta_description' => (string) ($project->meta_description ?? ''),
|
||||
'og_title' => (string) ($project->og_title ?: $metaTitle),
|
||||
'og_description' => (string) ($project->og_description ?: $project->meta_description ?? ''),
|
||||
'og_image' => $this->projectCoverUrl($project->picture_catalog ?? null)
|
||||
?? $this->projectCoverUrl($project->picture_cover ?? null)
|
||||
?? '',
|
||||
'og_url' => request()->url(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function contact(): View
|
||||
{
|
||||
return $this->render('pages.contact', 'Contact Us', 'contact');
|
||||
}
|
||||
|
||||
public function terms(): View
|
||||
{
|
||||
return $this->render('pages.terms', 'Terms', 'terms');
|
||||
}
|
||||
|
||||
public function thankyou(): View
|
||||
{
|
||||
return $this->render('pages.thankyou', 'Thank you!', 'contact');
|
||||
}
|
||||
|
||||
public function submitContact(StoreContactMessageRequest $request): RedirectResponse
|
||||
{
|
||||
Log::info('Website contact form submission', $request->validated());
|
||||
|
||||
return redirect()->route('thankyou');
|
||||
}
|
||||
|
||||
private function render(string $view, string $title, string $page, array $data = []): View
|
||||
{
|
||||
return view($view, array_merge($data, [
|
||||
'activeLanguages' => ActiveLanguage::getList('fp'),
|
||||
'assetBase' => asset('assets'),
|
||||
'page' => $page,
|
||||
'title' => $title,
|
||||
]));
|
||||
}
|
||||
|
||||
private function pageSeo(?object $page, ?string $fallbackTitle = null): array
|
||||
{
|
||||
$meta = data_get($page, 'meta') ?? [];
|
||||
$og = data_get($page, 'og') ?? [];
|
||||
$translation = data_get($page, 'translation');
|
||||
$content = data_get($page, 'content');
|
||||
|
||||
$title = (string) (data_get($meta, 'title')
|
||||
?: data_get($translation, 'page_title')
|
||||
?: data_get($content, 'headline')
|
||||
?: $fallbackTitle
|
||||
?: 'Home');
|
||||
|
||||
$description = (string) (data_get($meta, 'description')
|
||||
?: data_get($translation, 'meta_description')
|
||||
?: '');
|
||||
|
||||
return [
|
||||
'title' => $title,
|
||||
'meta_description' => $description,
|
||||
'meta_keywords' => (string) (data_get($meta, 'keywords') ?: ''),
|
||||
'meta_author' => (string) (data_get($meta, 'author') ?: ''),
|
||||
'meta_publisher' => (string) (data_get($meta, 'publisher') ?: ''),
|
||||
'meta_copyright' => (string) (data_get($meta, 'copyright') ?: ''),
|
||||
'meta_refresh' => data_get($meta, 'refresh'),
|
||||
'og_title' => (string) (data_get($og, 'title') ?: $title),
|
||||
'og_description' => (string) (data_get($og, 'description') ?: $description),
|
||||
'og_image' => (string) (data_get($og, 'image') ?: ''),
|
||||
'og_type' => (string) (data_get($og, 'type') ?: 'website'),
|
||||
];
|
||||
}
|
||||
|
||||
private function portfolioProjects(array $categoryMap): array
|
||||
{
|
||||
return $this->applyPublicProjectOrder($this->publicProjectsQuery())
|
||||
->get()
|
||||
->values()
|
||||
->map(function ($project, int $index) use ($categoryMap) {
|
||||
$categoryIds = collect($this->decodeCategoryIds($project->categories ?? null))
|
||||
->filter(fn ($id) => is_numeric($id) && (int) $id > 0)
|
||||
->map(fn ($id) => (int) $id)
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
$filters = $categoryIds
|
||||
->map(fn (int $id) => $categoryMap[$id]['filter'] ?? null)
|
||||
->filter()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$headline = (string) ($project->headline ?: $project->name ?: 'Untitled project');
|
||||
|
||||
return [
|
||||
'id' => (int) $project->project_id,
|
||||
'sort_order' => (int) ($project->num ?? 0),
|
||||
'title' => $headline,
|
||||
'subline' => (string) ($project->subline ?: ''),
|
||||
'subtitle' => '',
|
||||
'thumbnail' => $this->projectThumbnailForGrid($project, $index),
|
||||
'image' => $this->projectCoverForGrid($project, $index),
|
||||
'filters' => $filters,
|
||||
'group' => $this->defaultProjectGroup(),
|
||||
'url' => route('project', [
|
||||
'locale' => app()->getLocale(),
|
||||
'projectId' => $project->project_id,
|
||||
'slug' => Str::slug($headline),
|
||||
]),
|
||||
];
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
private function projectCategoryMap(): array
|
||||
{
|
||||
$locale = app()->getLocale();
|
||||
$fallbackLocale = ActiveLanguage::getDefaultLanguage('fp') ?: config('app.fallback_locale');
|
||||
|
||||
return DB::table('projects_categories as c')
|
||||
->leftJoin('projects_categories_description as cd', function ($join) use ($locale) {
|
||||
$join->on('c.category_id', '=', 'cd.category_id')
|
||||
->where('cd.iso', '=', $locale);
|
||||
})
|
||||
->leftJoin('projects_categories_description as fallback_cd', function ($join) use ($fallbackLocale) {
|
||||
$join->on('c.category_id', '=', 'fallback_cd.category_id')
|
||||
->where('fallback_cd.iso', '=', $fallbackLocale);
|
||||
})
|
||||
->where('c.parent_id', 0)
|
||||
->where('c.active', 1)
|
||||
->orderBy('c.num')
|
||||
->select('c.category_id', DB::raw('COALESCE(cd.title, fallback_cd.title) as title'))
|
||||
->get()
|
||||
->mapWithKeys(function ($category) {
|
||||
$title = (string) ($category->title ?? '');
|
||||
|
||||
if ($title === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [(int) $category->category_id => [
|
||||
'id' => (int) $category->category_id,
|
||||
'title' => $title,
|
||||
'filter' => (string) Str::of($title)->lower()->replaceMatches('/[^a-z0-9]+/', ''),
|
||||
]];
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
private function findPublicProject(?int $projectId = null): ?object
|
||||
{
|
||||
$query = $this->applyPublicProjectOrder($this->publicProjectsQuery());
|
||||
|
||||
if ($projectId !== null) {
|
||||
$query->where('p.project_id', $projectId);
|
||||
}
|
||||
|
||||
return $query->first();
|
||||
}
|
||||
|
||||
private function nextPublicProject(int $currentProjectId): ?array
|
||||
{
|
||||
$current = $this->findPublicProject($currentProjectId);
|
||||
|
||||
if (!$current) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$next = $this->applyPublicProjectOrder(
|
||||
$this->publicProjectsQuery()
|
||||
->where(function ($query) use ($current, $currentProjectId) {
|
||||
$query->where('p.num', '>', (int) ($current->num ?? 0))
|
||||
->orWhere(function ($nestedQuery) use ($current, $currentProjectId) {
|
||||
$nestedQuery->where('p.num', '=', (int) ($current->num ?? 0))
|
||||
->where('p.project_id', '<', $currentProjectId);
|
||||
});
|
||||
})
|
||||
)->first();
|
||||
|
||||
if (!$next) {
|
||||
$next = $this->applyPublicProjectOrder(
|
||||
$this->publicProjectsQuery()
|
||||
->where('p.project_id', '<>', $currentProjectId)
|
||||
)->first();
|
||||
}
|
||||
|
||||
if (!$next) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$headline = (string) ($next->headline ?: $next->name ?: 'Next project');
|
||||
|
||||
return [
|
||||
'title' => $headline,
|
||||
'url' => route('project', [
|
||||
'locale' => app()->getLocale(),
|
||||
'projectId' => $next->project_id,
|
||||
'slug' => Str::slug($headline),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
private function publicProjectsQuery()
|
||||
{
|
||||
$locale = app()->getLocale();
|
||||
$fallbackLocale = ActiveLanguage::getDefaultLanguage('fp') ?: config('app.fallback_locale');
|
||||
|
||||
return DB::table('projects as p')
|
||||
->leftJoin('projects_description as pd', function ($join) use ($locale) {
|
||||
$join->on('p.project_id', '=', 'pd.project_id')
|
||||
->where('pd.iso', '=', $locale);
|
||||
})
|
||||
->leftJoin('projects_description as fallback_pd', function ($join) use ($fallbackLocale) {
|
||||
$join->on('p.project_id', '=', 'fallback_pd.project_id')
|
||||
->where('fallback_pd.iso', '=', $fallbackLocale);
|
||||
})
|
||||
->where('p.active', 1)
|
||||
->select([
|
||||
'p.project_id',
|
||||
'p.num',
|
||||
'p.categories',
|
||||
'p.picture_cover',
|
||||
'p.picture_catalog',
|
||||
'p.picture_catalog_full',
|
||||
'p.video',
|
||||
'p.structure',
|
||||
DB::raw('COALESCE(pd.headline, fallback_pd.headline) as headline'),
|
||||
DB::raw('COALESCE(pd.subline, fallback_pd.subline) as subline'),
|
||||
DB::raw('COALESCE(pd.name, fallback_pd.name) as name'),
|
||||
DB::raw('COALESCE(pd.year, fallback_pd.year) as year'),
|
||||
DB::raw('COALESCE(pd.preview, fallback_pd.preview) as preview'),
|
||||
DB::raw('COALESCE(pd.content, fallback_pd.content) as content'),
|
||||
DB::raw('COALESCE(pd.tag1, fallback_pd.tag1) as tag1'),
|
||||
DB::raw('COALESCE(pd.tag2, fallback_pd.tag2) as tag2'),
|
||||
DB::raw('COALESCE(pd.tag3, fallback_pd.tag3) as tag3'),
|
||||
DB::raw('COALESCE(pd.youtube, fallback_pd.youtube) as youtube'),
|
||||
DB::raw('COALESCE(pd.page_title, fallback_pd.page_title) as page_title'),
|
||||
DB::raw('COALESCE(pd.meta_title, fallback_pd.meta_title) as meta_title'),
|
||||
DB::raw('COALESCE(pd.meta_description, fallback_pd.meta_description) as meta_description'),
|
||||
DB::raw('COALESCE(pd.og_title, fallback_pd.og_title) as og_title'),
|
||||
DB::raw('COALESCE(pd.og_description, fallback_pd.og_description) as og_description'),
|
||||
]);
|
||||
}
|
||||
|
||||
private function applyPublicProjectOrder($query)
|
||||
{
|
||||
return $query
|
||||
->orderBy('p.num')
|
||||
->orderByDesc('p.project_id');
|
||||
}
|
||||
|
||||
private function normalizeProjectForBlade(array $payload): array
|
||||
{
|
||||
$structure = $payload['structure'] ?? [];
|
||||
$legacy = $payload['legacy'] ?? [];
|
||||
$lang = $payload['defaultLanguage'] ?? app()->getLocale();
|
||||
|
||||
$hasModernStructure = !empty($structure) && (
|
||||
array_key_exists('header', $structure) ||
|
||||
array_key_exists('heroMedia', $structure) ||
|
||||
array_key_exists('contentBlocks', $structure) ||
|
||||
array_key_exists('blocks', $structure)
|
||||
);
|
||||
|
||||
if ($hasModernStructure) {
|
||||
$legacyAwards = $this->bladeNormalizeHtmlList((array) ($legacy['awarded'] ?? []));
|
||||
$structureAwards = $this->bladeNormalizeHtmlList((array) ($structure['metadata']['awarded'] ?? []));
|
||||
|
||||
return [
|
||||
'headline' => $this->bladeFirstFilledHtml($structure['header']['headline'] ?? null, $legacy['headline'] ?? null),
|
||||
'subline' => $this->bladeFirstFilledHtml($structure['header']['subline'] ?? null, $legacy['subline'] ?? null),
|
||||
'hero' => $this->bladeHeroSlot($structure['heroMedia'] ?? null),
|
||||
'year' => $this->bladeFirstFilledText($structure['metadata']['year'] ?? null, $legacy['year'] ?? null),
|
||||
'clientName' => $this->bladeFirstFilledHtml($structure['metadata']['clientName'] ?? null, $legacy['clientName'] ?? null),
|
||||
'awarded' => !empty($structureAwards) ? $structureAwards : $legacyAwards,
|
||||
'description'=> $this->bladeFirstFilledHtml($legacy['description'] ?? null, $structure['metadata']['description'] ?? null),
|
||||
'blocks' => $this->bladeNormalizeBlocks(
|
||||
$structure['contentBlocks'] ?? $structure['blocks'] ?? [],
|
||||
$lang
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
// Fall back to legacy flat fields
|
||||
$heroMedia = null;
|
||||
if (!empty($legacy['youtube'])) {
|
||||
$heroMedia = ['type' => 'youtube', 'url' => $legacy['youtube'], 'poster' => ''];
|
||||
} elseif (!empty($legacy['video'])) {
|
||||
$heroMedia = ['type' => 'video', 'url' => $legacy['video'], 'poster' => ''];
|
||||
} elseif (!empty($legacy['pictureCatalogFull'])) {
|
||||
$heroMedia = ['type' => 'image', 'url' => $legacy['pictureCatalogFull'], 'poster' => ''];
|
||||
} elseif (!empty($legacy['pictureCover'])) {
|
||||
$heroMedia = ['type' => 'image', 'url' => $legacy['pictureCover'], 'poster' => ''];
|
||||
}
|
||||
|
||||
return [
|
||||
'headline' => $this->bladeNormalizeHtml((string) ($legacy['headline'] ?? '')),
|
||||
'subline' => $this->bladeNormalizeHtml((string) ($legacy['subline'] ?? '')),
|
||||
'hero' => $this->bladeHeroSlot($heroMedia),
|
||||
'year' => $this->bladeNormalizeText((string) ($legacy['year'] ?? '')),
|
||||
'clientName' => $this->bladeNormalizeHtml((string) ($legacy['clientName'] ?? '')),
|
||||
'awarded' => array_map($this->bladeNormalizeHtml(...), (array) ($legacy['awarded'] ?? [])),
|
||||
'description' => $this->bladeNormalizeHtml((string) ($legacy['description'] ?? '')),
|
||||
'blocks' => [],
|
||||
];
|
||||
}
|
||||
|
||||
private function bladeHeroSlot(?array $heroMedia): ?array
|
||||
{
|
||||
if (!$heroMedia) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $heroMedia['type'] ?? 'image';
|
||||
$url = $heroMedia['url'] ?? '';
|
||||
|
||||
if (!$url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($type === 'image') {
|
||||
return [
|
||||
'type' => 'image',
|
||||
'text' => '',
|
||||
'image' => ['url' => $url, 'alt' => ''],
|
||||
'media' => null,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'video',
|
||||
'text' => '',
|
||||
'image' => ['url' => $heroMedia['poster'] ?? '', 'alt' => ''],
|
||||
'media' => $this->bladeNormalizeMedia($heroMedia),
|
||||
];
|
||||
}
|
||||
|
||||
private function bladeNormalizeBlocks(array $blocks, string $lang): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($blocks as $block) {
|
||||
$type = $block['type'] ?? '';
|
||||
|
||||
// Legacy type migrations
|
||||
if ($type === 'FullWidthText') {
|
||||
$result[] = [
|
||||
'type' => 'FullWidth',
|
||||
'hidden' => (bool) ($block['hidden'] ?? false),
|
||||
'slot' => [
|
||||
'type' => 'text',
|
||||
'text' => $this->bladeResolveText($block['content'] ?? [], $lang),
|
||||
'image' => null,
|
||||
'media' => null,
|
||||
],
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === 'FullWidthImage') {
|
||||
$img = $block['image'] ?? [];
|
||||
$result[] = [
|
||||
'type' => 'FullWidth',
|
||||
'hidden' => (bool) ($block['hidden'] ?? false),
|
||||
'slot' => [
|
||||
'type' => 'image',
|
||||
'text' => '',
|
||||
'image' => ['url' => $img['url'] ?? '', 'alt' => $this->bladeResolveText($img['alt'] ?? [], $lang)],
|
||||
'media' => null,
|
||||
],
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === 'Video') {
|
||||
$result[] = [
|
||||
'type' => 'FullWidth',
|
||||
'hidden' => (bool) ($block['hidden'] ?? false),
|
||||
'slot' => [
|
||||
'type' => 'video',
|
||||
'text' => '',
|
||||
'image' => null,
|
||||
'media' => $this->bladeNormalizeMedia($block['media'] ?? null),
|
||||
],
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === 'TwoColumnImages') {
|
||||
$images = $block['images'] ?? [];
|
||||
$result[] = [
|
||||
'type' => 'TwoColumns',
|
||||
'hidden' => (bool) ($block['hidden'] ?? false),
|
||||
'left' => $this->bladeImageSlot($images[0] ?? [], $lang),
|
||||
'right' => $this->bladeImageSlot($images[1] ?? [], $lang),
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === 'TwoColumns') {
|
||||
$result[] = [
|
||||
'type' => 'TwoColumns',
|
||||
'hidden' => (bool) ($block['hidden'] ?? false),
|
||||
'left' => $this->bladeNormalizeSlot($block['left'] ?? [], $lang),
|
||||
'right' => $this->bladeNormalizeSlot($block['right'] ?? [], $lang),
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// FullWidth (default)
|
||||
$result[] = [
|
||||
'type' => 'FullWidth',
|
||||
'hidden' => (bool) ($block['hidden'] ?? false),
|
||||
'slot' => $this->bladeNormalizeSlot($block['slot'] ?? [], $lang),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function bladeNormalizeSlot(array $slot, string $lang): array
|
||||
{
|
||||
return [
|
||||
'type' => $slot['type'] ?? 'text',
|
||||
'text' => $this->bladeResolveText($slot['content'] ?? [], $lang),
|
||||
'image' => [
|
||||
'url' => $slot['image']['url'] ?? '',
|
||||
'alt' => $this->bladeResolveText($slot['image']['alt'] ?? [], $lang),
|
||||
],
|
||||
'media' => $this->bladeNormalizeMedia($slot['media'] ?? null),
|
||||
];
|
||||
}
|
||||
|
||||
private function bladeImageSlot(array $image, string $lang): array
|
||||
{
|
||||
return [
|
||||
'type' => 'image',
|
||||
'text' => '',
|
||||
'image' => ['url' => $image['url'] ?? '', 'alt' => $this->bladeResolveText($image['alt'] ?? [], $lang)],
|
||||
'media' => null,
|
||||
];
|
||||
}
|
||||
|
||||
private function bladeNormalizeMedia(?array $media): ?array
|
||||
{
|
||||
if (!$media) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $media['type'] ?? 'image';
|
||||
$url = $media['url'] ?? '';
|
||||
|
||||
if (!$url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => $type,
|
||||
'url' => $url,
|
||||
'poster' => $media['poster'] ?? '',
|
||||
'autoplay' => ($media['autoplay'] ?? false) === true,
|
||||
'loop' => ($media['loop'] ?? false) === true,
|
||||
'muted' => ($media['muted'] ?? false) === true,
|
||||
'embedUrl' => $this->bladeEmbedUrl($type, $media),
|
||||
];
|
||||
}
|
||||
|
||||
private function bladeResolveText(mixed $content, string $lang): string
|
||||
{
|
||||
if (is_string($content)) {
|
||||
return $this->bladeNormalizeHtml($content);
|
||||
}
|
||||
|
||||
if (!is_array($content) || empty($content)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$values = array_values($content);
|
||||
|
||||
return $this->bladeNormalizeHtml((string) ($content[$lang] ?? $values[0] ?? ''));
|
||||
}
|
||||
|
||||
private function bladeNormalizeHtml(string $value): string
|
||||
{
|
||||
$normalized = $value;
|
||||
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$decoded = html_entity_decode($normalized, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
|
||||
if ($decoded === $normalized) {
|
||||
break;
|
||||
}
|
||||
|
||||
$normalized = $decoded;
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
private function bladeFirstFilledHtml(mixed ...$values): string
|
||||
{
|
||||
foreach ($values as $value) {
|
||||
$normalized = $this->bladeNormalizeHtml((string) ($value ?? ''));
|
||||
|
||||
if (trim(strip_tags($normalized)) !== '') {
|
||||
return $normalized;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function bladeNormalizeText(string $value): string
|
||||
{
|
||||
return trim(strip_tags($this->bladeNormalizeHtml($value)));
|
||||
}
|
||||
|
||||
private function bladeFirstFilledText(mixed ...$values): string
|
||||
{
|
||||
foreach ($values as $value) {
|
||||
$normalized = $this->bladeNormalizeText((string) ($value ?? ''));
|
||||
|
||||
if ($normalized !== '') {
|
||||
return $normalized;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function bladeNormalizeHtmlList(array $values): array
|
||||
{
|
||||
$normalized = [];
|
||||
|
||||
foreach ($values as $value) {
|
||||
$item = $this->bladeNormalizeHtml((string) ($value ?? ''));
|
||||
|
||||
if (trim(strip_tags($item)) !== '') {
|
||||
$normalized[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
private function bladeEmbedUrl(string $type, array|string $media): string
|
||||
{
|
||||
$mediaObject = is_array($media) ? $media : ['url' => $media];
|
||||
$url = (string) ($mediaObject['url'] ?? '');
|
||||
|
||||
if ($type === 'youtube') {
|
||||
preg_match('/(?:v=|youtu\.be\/)([A-Za-z0-9_-]{11})/', $url, $m);
|
||||
|
||||
return isset($m[1])
|
||||
? 'https://www.youtube.com/embed/' . $m[1]
|
||||
. '?autoplay=' . (($mediaObject['autoplay'] ?? false) ? '1' : '0')
|
||||
. '&mute=' . (($mediaObject['muted'] ?? false) ? '1' : '0')
|
||||
. '&loop=' . (($mediaObject['loop'] ?? false) ? '1' : '0')
|
||||
. ((($mediaObject['loop'] ?? false) ? '&playlist=' . $m[1] : ''))
|
||||
: '';
|
||||
}
|
||||
|
||||
if ($type === 'bunny') {
|
||||
// Normalize /play/ → /embed/ regardless of what was stored
|
||||
$url = preg_replace('#player\.mediadelivery\.net/play/#', 'player.mediadelivery.net/embed/', $url);
|
||||
|
||||
$params = http_build_query([
|
||||
'autoplay' => ($mediaObject['autoplay'] ?? false) ? 'true' : 'false',
|
||||
'loop' => ($mediaObject['loop'] ?? false) ? 'true' : 'false',
|
||||
'muted' => ($mediaObject['muted'] ?? false) ? 'true' : 'false',
|
||||
'preload' => 'true',
|
||||
'responsive' => 'true',
|
||||
]);
|
||||
|
||||
return $url . '?' . $params;
|
||||
}
|
||||
|
||||
// frameio, video — url is used as-is (embed src or file src)
|
||||
return $url;
|
||||
}
|
||||
|
||||
private function projectRendererPayload(object $project): array
|
||||
{
|
||||
return [
|
||||
'structure' => $this->decodeStructure($project->structure ?? null),
|
||||
'defaultLanguage' => app()->getLocale(),
|
||||
'legacy' => [
|
||||
'headline' => (string) ($project->headline ?? ''),
|
||||
'subline' => (string) ($project->subline ?? ''),
|
||||
'year' => (string) ($project->year ?? ''),
|
||||
'clientName' => (string) ($project->name ?? ''),
|
||||
'description' => (string) ($project->preview ?: $project->content ?: ''),
|
||||
'awarded' => array_values(array_filter([
|
||||
$project->tag1 ?? null,
|
||||
$project->tag2 ?? null,
|
||||
$project->tag3 ?? null,
|
||||
])),
|
||||
'youtube' => (string) ($project->youtube ?? ''),
|
||||
'video' => $this->projectVideoUrl($project->video ?? null),
|
||||
'pictureCover' => $this->projectCoverUrl($project->picture_cover ?? null),
|
||||
'pictureCatalog' => $this->projectCoverUrl($project->picture_catalog ?? null),
|
||||
'pictureCatalogFull' => $this->projectCoverUrl($project->picture_catalog_full ?? null),
|
||||
'gallery' => $this->projectGallery((int) $project->project_id),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function projectGallery(int $projectId): array
|
||||
{
|
||||
return DB::table('projects_gallery')
|
||||
->where('project_id', $projectId)
|
||||
->orderBy('num')
|
||||
->get()
|
||||
->map(function ($photo) {
|
||||
return [
|
||||
'url' => asset('files/projects/gallery/' . (int) floor($photo->project_id / 100) . '/' . $photo->project_id . '/' . $photo->diskname),
|
||||
'alt' => (string) ($photo->name ?? ''),
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
private function decodeCategoryIds(?string $categories): array
|
||||
{
|
||||
$decoded = json_decode($categories ?? '[]', true);
|
||||
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
private function decodeStructure(?string $structure): array
|
||||
{
|
||||
$normalized = html_entity_decode((string) ($structure ?? ''), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$decoded = json_decode($normalized, true);
|
||||
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
private function projectCoverForGrid(object $project, int $index): string
|
||||
{
|
||||
return $this->projectCoverUrl($project->picture_cover ?? null)
|
||||
?? $this->projectCoverUrl($project->picture_catalog ?? null)
|
||||
?? asset('assets/img/portfolio-' . (($index % 6) + 1) . '.jpg');
|
||||
}
|
||||
|
||||
private function projectThumbnailForGrid(object $project, int $index): array
|
||||
{
|
||||
$structure = $this->decodeStructure($project->structure ?? null);
|
||||
$rawThumbnailMedia = is_array($structure['thumbnailMedia'] ?? null) ? $structure['thumbnailMedia'] : null;
|
||||
$thumbnailMedia = $this->bladeNormalizeMedia(
|
||||
$rawThumbnailMedia
|
||||
);
|
||||
|
||||
if (($thumbnailMedia['type'] ?? null) === 'bunny' && !empty($thumbnailMedia['embedUrl'])) {
|
||||
return [
|
||||
'type' => 'bunny',
|
||||
'url' => '',
|
||||
'embedUrl' => $thumbnailMedia['embedUrl'],
|
||||
];
|
||||
}
|
||||
|
||||
$heroMedia = $this->bladeNormalizeMedia(
|
||||
is_array($structure['heroMedia'] ?? null) ? $structure['heroMedia'] : null
|
||||
);
|
||||
|
||||
$prefersBunnyThumbnail = ($rawThumbnailMedia['type'] ?? null) === 'bunny';
|
||||
|
||||
if ($prefersBunnyThumbnail && ($heroMedia['type'] ?? null) === 'bunny' && !empty($heroMedia['embedUrl'])) {
|
||||
return [
|
||||
'type' => 'bunny',
|
||||
'url' => '',
|
||||
'embedUrl' => $heroMedia['embedUrl'],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'image',
|
||||
'url' => $this->projectCoverForGrid($project, $index),
|
||||
'embedUrl' => '',
|
||||
];
|
||||
}
|
||||
|
||||
private function projectCoverUrl(?string $filename): ?string
|
||||
{
|
||||
if (!$filename) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return asset('files/projects/cover/' . $filename);
|
||||
}
|
||||
|
||||
private function projectVideoUrl(?string $filename): ?string
|
||||
{
|
||||
if (!$filename) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return asset('files/projects/video/' . $filename);
|
||||
}
|
||||
|
||||
private function defaultProjectGroup(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'Aritmija',
|
||||
'label' => 'Uploaded by',
|
||||
'icon' => 'fas fa-layer-group',
|
||||
'color' => '#4050FF',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user