Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions app/Http/Controllers/Zunscan/AboutController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Zunscan;

use Illuminate\View\View;

class AboutController extends ZunscanController
{
public function __invoke(): View
{
return view('zunscan.about.index', [
'title' => __('zunscan.seo.about.title'),
'description' => __('zunscan.seo.about.description'),
'image' => self::OG_IMAGE,
]);
}
}
19 changes: 19 additions & 0 deletions app/Http/Controllers/Zunscan/ContactController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Zunscan;

use Illuminate\View\View;

class ContactController extends ZunscanController
{
public function __invoke(): View
{
return view('zunscan.contact.index', [
'title' => __('zunscan.seo.contact.title'),
'description' => __('zunscan.seo.contact.description'),
'image' => self::OG_IMAGE,
]);
}
}
19 changes: 19 additions & 0 deletions app/Http/Controllers/Zunscan/MediaController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Zunscan;

use Illuminate\View\View;

class MediaController extends ZunscanController
{
public function __invoke(): View
{
return view('zunscan.media.index', [
'title' => __('zunscan.seo.media.title'),
'description' => __('zunscan.seo.media.description'),
'image' => self::OG_IMAGE,
]);
}
}
22 changes: 22 additions & 0 deletions app/Http/Controllers/Zunscan/PrivacyController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Zunscan;

use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use Illuminate\View\View;

class PrivacyController extends ZunscanController
{
public function __invoke(): View
{
return view('zunscan.legal.privacy', [
'title' => __('zunscan.seo.privacy.title'),
'description' => __('zunscan.seo.privacy.description'),
'image' => self::OG_IMAGE,
'body' => Str::markdown(File::get(resource_path('content/zunscan/'.app()->getLocale().'/datenschutz.md'))),
]);
}
}
41 changes: 41 additions & 0 deletions app/Http/Controllers/Zunscan/RobotsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Zunscan;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class RobotsController extends Controller
{
public function __invoke(Request $request): Response
{
// Deliberately not config('app.url'): AppServiceProvider forces every
// absolute URL Laravel generates to the main site's host, so this domain's
// own sitemap has to be built from the request instead.
$sitemapUrl = $request->getSchemeAndHttpHost().'/sitemap.xml';

return response(content: $this->content($sitemapUrl), headers: [
'Content-Type' => 'text/plain',
]);
}

private function content(string $sitemapUrl): string
{
if (! app()->isProduction()) {
return implode("\n", [
'User-agent: *',
'Disallow: /',
]);
}

return implode("\n", [
'User-agent: *',
'Disallow:',
'',
'Sitemap: '.$sitemapUrl,
]);
}
}
19 changes: 19 additions & 0 deletions app/Http/Controllers/Zunscan/ServicesScanningController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Zunscan;

use Illuminate\View\View;

class ServicesScanningController extends ZunscanController
{
public function __invoke(): View
{
return view('zunscan.services.show.scanning', [
'title' => __('zunscan.seo.scanning.title'),
'description' => __('zunscan.seo.scanning.description'),
'image' => self::OG_IMAGE,
]);
}
}
46 changes: 46 additions & 0 deletions app/Http/Controllers/Zunscan/SitemapController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Zunscan;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class SitemapController extends Controller
{
private const array PAGES = [
'start.index',
'about.index',
'services.scanning.show',
'contact.index',
'media.index',
'terms.index',
'privacy.index',
];

private const array LOCALES = ['de-ch', 'en-ch'];

public function __invoke(Request $request): Response
{
$host = $request->getSchemeAndHttpHost();

$urls = collect(self::LOCALES)
->crossJoin(self::PAGES)
->map(fn (array $pair): string => sprintf(
'<url><loc>%s</loc></url>',
e($host.route('zunscan.'.$pair[0].'.'.$pair[1], absolute: false))
))
->implode('');

$xml = '<?xml version="1.0" encoding="UTF-8"?>'
.'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
.$urls
.'</urlset>';

return response(content: $xml)
->header('Content-Type', 'application/xml')
->header('Cache-Control', 'public, max-age=3600');
}
}
19 changes: 19 additions & 0 deletions app/Http/Controllers/Zunscan/StartController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Zunscan;

use Illuminate\View\View;

class StartController extends ZunscanController
{
public function __invoke(): View
{
return view('zunscan.start.index', [
'title' => __('zunscan.seo.start.title'),
'description' => __('zunscan.seo.start.description'),
'image' => self::OG_IMAGE,
]);
}
}
22 changes: 22 additions & 0 deletions app/Http/Controllers/Zunscan/TermsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Zunscan;

use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use Illuminate\View\View;

class TermsController extends ZunscanController
{
public function __invoke(): View
{
return view('zunscan.legal.terms', [
'title' => __('zunscan.seo.imprint.title'),
'description' => __('zunscan.seo.imprint.description'),
'image' => self::OG_IMAGE,
'body' => Str::markdown(File::get(resource_path('content/zunscan/'.app()->getLocale().'/impressum.md'))),
]);
}
}
19 changes: 19 additions & 0 deletions app/Http/Controllers/Zunscan/ZunscanController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Zunscan;

use App\Http\Controllers\Controller;

abstract class ZunscanController extends Controller
{
/**
* The share image, pinned to the 1200×630 the layout declares in
* og:image:width/height — the previous c_scale,dpr_2.0 transform produced
* whatever size the source happened to be, so the declared dimensions
* would not have matched the file. f_jpg rather than f_auto: social
* crawlers are not browsers and several still do not accept webp.
*/
protected const string OG_IMAGE = 'https://res.cloudinary.com/codebar/image/upload/c_fill,g_auto,w_1200,h_630,f_jpg,q_auto/www-paperflakes-ch/pages/x7img596iuglmttin6ro.jpg';
}
48 changes: 48 additions & 0 deletions app/Http/Middleware/Zunscan/SetZunscanLanguage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace App\Http\Middleware\Zunscan;

use App\Enums\LocaleEnum;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class SetZunscanLanguage
{
/**
* The URL is the single source of truth for the language here too (see the
* main site's SetLanguage), but Zunscan is a single-language client site
* with no session-backed preference — every route name already carries its
* locale ("zunscan.de-ch." / "zunscan.en-ch."), so there is nothing to fall
* back to and nothing worth persisting.
*
* Also un-poisons every absolute URL for the rest of this request:
* AppServiceProvider calls URL::forceRootUrl(config('app.url')) for the
* main site's own sake, but that forces @vite()'s asset() calls (and any
* route(..., absolute: true)) to the main site's host too — which is how
* Zunscan's own CSS/JS ended up being requested from web.codebar.test
* instead of its own domain. Safe to override here: this only ever runs
* for Zunscan requests, and PHP-FPM starts each request fresh.
*
* @param Closure(Request): (Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$routeName = (string) $request->route()?->getName();

foreach (LocaleEnum::cases() as $locale) {
if (Str::startsWith($routeName, 'zunscan.'.Str::slug($locale->value).'.')) {
app()->setLocale($locale->value);
break;
}
}

URL::forceRootUrl($request->getSchemeAndHttpHost());

return $next($request);
}
}
50 changes: 50 additions & 0 deletions app/Support/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,53 @@ function locale_switch_url(string $locale): string
}
}
}

if (! function_exists('zunscan_route')) {
/**
* Zunscan's own version of localized_route(): its route names are
* "zunscan.{locale}.{rest}" rather than "{locale}.{rest}", so the
* locale slug has to be inserted after the "zunscan." prefix instead
* of standing in front of it.
*
* Defaults to a relative path — deliberately, not just for convenience.
* AppServiceProvider forces every *absolute* route()/url() call app-wide
* to config('app.url') (www.codebar.ch), because that is correct for the
* main site. Zunscan lives on a different host, so an absolute call here
* would silently point at the wrong domain; a relative path sidesteps
* that forced root entirely and still resolves correctly in the browser.
*/
function zunscan_route(string $name, mixed $parameters = [], bool $absolute = false): string
{
$locale = Str::slug(app()->getLocale());

return route('zunscan.'.$locale.'.'.$name, $parameters, $absolute);
}
}

if (! function_exists('zunscan_locale_switch_url')) {
/**
* The current Zunscan page's URL in another locale, as a real absolute URL
* (hreflang tags and the switcher both need one). Built from the request's
* own host rather than route()'s absolute mode, for the same reason as
* zunscan_route() above.
*/
function zunscan_locale_switch_url(string $locale): string
{
$localeSlug = Str::slug($locale);
$routeName = (string) request()->route()?->getName();
$prefix = 'zunscan.'.Str::slug(app()->getLocale()).'.';
$host = request()->getSchemeAndHttpHost();

if (! Str::startsWith($routeName, $prefix)) {
return $host.route('zunscan.'.$localeSlug.'.start.index', absolute: false);
}

$routeKey = Str::after($routeName, $prefix);

try {
return $host.route('zunscan.'.$localeSlug.'.'.$routeKey, absolute: false);
} catch (Throwable) {
return $host.route('zunscan.'.$localeSlug.'.start.index', absolute: false);
}
}
}
Loading