774 lines
28 KiB
PHP
774 lines
28 KiB
PHP
<?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',
|
|
];
|
|
}
|
|
} |