From a2443e4d86b660bcb24bac5792086ab29a16a86b Mon Sep 17 00:00:00 2001 From: Andrew Topping Date: Wed, 12 Aug 2026 16:26:19 +0100 Subject: [PATCH] Run pint and add a CI Workflow. --- .github/workflows/ci.yml | 29 ++ .gitignore | 1 + CHANGELOG.md | 38 +++ StarterKitPostInstall.php | 10 +- app/Caching/StaticWarmExtras.php | 2 +- app/Console/Commands/PanelUsage.php | 21 +- app/Console/Commands/ScrapeGoogleUrls.php | 247 +++++++++--------- app/Console/Commands/TestNotification.php | 3 +- app/Console/Kernel.php | 1 - .../Controllers/PanelScreenshotController.php | 37 +-- app/Listeners/EntryListener.php | 2 - app/Listeners/FormListener.php | 15 +- app/Listeners/GlobalListener.php | 16 +- app/Listeners/TreeListener.php | 2 - bootstrap/app.php | 9 +- bootstrap/providers.php | 7 +- composer.json | 13 +- composer.lock | 89 +++++++ config/statamic/assets.php | 2 +- config/statamic/cp.php | 2 +- config/statamic/static_caching.php | 27 +- routes/web.php | 4 +- 22 files changed, 375 insertions(+), 202 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 CHANGELOG.md create mode 100644 composer.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1d807ae --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: none + + - name: Validate composer.json + run: composer validate --strict + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Check code style (Pint) + run: vendor/bin/pint --test diff --git a/.gitignore b/.gitignore index 090a1f0..d66edda 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea .DS_Store +vendor diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ebdac90 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +All notable changes to this starter kit will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). + +## [Unreleased] + +### Added + +- GitHub Actions CI workflow that runs `composer validate` and Laravel Pint (style check) on every push/PR. +- `app/Providers/HorizonServiceProvider.php` now ships with the Thought Collective `viewHorizon` gate pre-configured (restricts non-local Horizon access to `@thoughtcollective.com` emails), so it no longer needs to be added by hand after every install. +- `description` and `license` fields to `composer.json` so `composer validate` passes cleanly. + +### Changed + +- `config/horizon.php` is no longer exported by the starter kit (`starter-kit.yaml`); Horizon's own defaults are used instead, avoiding a stale config file being copied into every new project. +- `resources/blueprints/collections/news/news.yaml`: bumped `earliest_date` to a more current date. +- `resources/blueprints/globals/social_media.yaml`: renamed the `Pinterest` replicator set key to `pinterest` for consistency with the other lowercase set keys. +- `app/Console/Commands/PanelUsage.php`: fixed a check that skipped pages with zero panels (`< 1` didn't correctly handle non-numeric/empty values); now uses `empty()`. +- `resources/js/components/maps.js`: fixed a marker `infoWindow` scoping bug where the info window instance wasn't available inside the click listener; fixed marker scale precedence. +- `app/Console/Commands/ScrapeGoogleUrls.php`: explicitly passed the `$escape` parameter to `fputcsv()` to avoid a PHP 8.4 deprecation warning. +- `README.md`: reworded the Slider Settings section to point to swiper.js's own API docs instead of an outdated description. +- `.gitignore`: trimmed down to just `.idea` and `.DS_Store`. + +### Fixed + +- `app/Listeners/GlobalListener.php`: geocoding lookups that fail (bad address, API key issue, etc.) no longer throw uncaught exceptions — they're now logged and surfaced to the editor as a Statamic CP toast notification instead. +- `app/Console/Commands/TestNotification.php`: added a `class_exists()` guard so passing a non-existent `--class` fails with a clear error instead of a fatal error. + +### Removed + +- `app/Console/Commands/ImportRedirects.php` and its references (superseded / unused). +- `app/Listeners/RecacheUrl.php` and its `UrlInvalidated` event mapping in `EventServiceProvider.php` (unused). +- Static Cache Manager references (`duncanmcclean/static-cache-manager`) from the README and the `access static-cache-manager utility` permission in `resources/users/roles.yaml` (package not used by this kit). +- Stale `image:` front-matter reference on `content/collections/pages/home.md` pointing at an asset that no longer applies. + +[Unreleased]: https://github.com/thoughtco/statamic-starter-kit/compare/main...HEAD diff --git a/StarterKitPostInstall.php b/StarterKitPostInstall.php index 9a00582..165ecb0 100644 --- a/StarterKitPostInstall.php +++ b/StarterKitPostInstall.php @@ -1,7 +1,6 @@ ask('What queue connection do you want?', 'redis'); $eyrisToken = $console->ask('Enter the token for Eyris?', null, function ($value) { if (empty($value)) { - throw new \RuntimeException('Eyris token is required.'); + throw new RuntimeException('Eyris token is required.'); } + return $value; }); $env = app('files')->get(base_path('.env.thoughtco')); - $env = str_replace("APP_NAME=", "APP_NAME=\"{$appName}\"", $env); + $env = str_replace('APP_NAME=', "APP_NAME=\"{$appName}\"", $env); $env = str_replace('APP_URL=', "APP_URL=\"{$appURL}\"", $env); $env = str_replace('APP_KEY=', "APP_KEY=\"{$originalAppKey}\"", $env); $env = str_replace('STATAMIC_REVISIONS_PATH=', "STATAMIC_REVISIONS_PATH=\"{$revisionPath}\"", $env); @@ -57,7 +57,7 @@ public function handle($console) $this->runProcess(['php', 'artisan', 'horizon:install'], $console, 'horizon assets published'); $this->runProcess(['composer', 'require', 'thoughtco/statamic-eyris'], $console, 'eyris installed'); - $this->runProcess(['php', 'artisan', 'vendor:publish', '--tag=statamic-eyris'], $console, 'eyris assets published'); + $this->runProcess(['php', 'artisan', 'vendor:publish', '--tag=statamic-eyris'], $console, 'eyris assets published'); $this->runProcess(['php', 'artisan', 'queue:restart'], $console, 'queues restarted'); diff --git a/app/Caching/StaticWarmExtras.php b/app/Caching/StaticWarmExtras.php index acf5477..47eaa9f 100644 --- a/app/Caching/StaticWarmExtras.php +++ b/app/Caching/StaticWarmExtras.php @@ -8,4 +8,4 @@ public function handle() { return collect(); } -} \ No newline at end of file +} diff --git a/app/Console/Commands/PanelUsage.php b/app/Console/Commands/PanelUsage.php index 3142b5f..0556527 100644 --- a/app/Console/Commands/PanelUsage.php +++ b/app/Console/Commands/PanelUsage.php @@ -7,7 +7,7 @@ class PanelUsage extends Command { - /** + /** * The name and signature of the console command. * * @var string @@ -38,18 +38,18 @@ public function __construct() */ public function handle() { - //get all panels + // get all panels $pages = Entry::query() ->where('collection', 'pages') ->where('published', true) ->get(['panels']); - + $panels = []; $checkMode = $this->option('panel') ? 'panel' : 'count'; $panelWeWant = $this->option('panel') ?? false; - foreach ($pages AS $page) { + foreach ($pages as $page) { if (empty($page->panels)) { continue; @@ -69,13 +69,13 @@ public function handle() $panels[] = ['title' => $page->title, 'url' => url($page->url())]; } - // we are counting panels + // we are counting panels } else { - + foreach ($page->panels as $panel) { $type = $panel['type']; - - if (!isset($panels[$type])) { + + if (! isset($panels[$type])) { $panels[$type] = ['type' => $type, 'count' => 0]; } @@ -83,7 +83,7 @@ public function handle() } } - + } if ($checkMode == 'count') { @@ -91,10 +91,11 @@ public function handle() $panels = collect($panels)->sortByDesc('count')->values(); $this->table(['Type', 'Count'], $panels); + return; } $panels = collect($panels)->sortBy('url')->values(); $this->table(['Title', 'URL'], $panels); } -} \ No newline at end of file +} diff --git a/app/Console/Commands/ScrapeGoogleUrls.php b/app/Console/Commands/ScrapeGoogleUrls.php index cec9bba..6a30c8d 100644 --- a/app/Console/Commands/ScrapeGoogleUrls.php +++ b/app/Console/Commands/ScrapeGoogleUrls.php @@ -8,132 +8,135 @@ class ScrapeGoogleUrls extends Command { - protected $signature = 'site:google-urls + protected $signature = 'site:google-urls {domain : The domain to search (e.g. example.com)} {--output= : Output CSV file path (defaults to domain-urls.csv)} {--limit=100 : Max number of URLs to retrieve} {--delay=1 : Delay in seconds between API requests}'; - protected $description = 'Fetch all Google-indexed URLs for a domain via SerpApi and export to CSV'; + protected $description = 'Fetch all Google-indexed URLs for a domain via SerpApi and export to CSV'; - private string $apiKey; - private string $baseUrl = 'https://serpapi.com/search'; + private string $apiKey; - public function handle(): int - { - $this->apiKey = config('services.serpapi.api_key'); - - if (empty($this->apiKey)) { - $this->error('Missing SERPAPI_API_KEY in your .env file.'); - return self::FAILURE; - } - - $domain = $this->argument('domain'); - $limit = (int) $this->option('limit'); - $delay = (int) $this->option('delay'); - $output = $this->option('output') ?? Str::slug($domain) . '-urls.csv'; - - $this->info("Searching Google for indexed URLs on: {$domain}"); - $this->info("Output file: {$output}"); - $this->newLine(); - - $urls = []; - $start = 0; - $perPage = 10; - - $progress = $this->output->createProgressBar($limit); - $progress->start(); - - while (count($urls) < $limit) { - $response = Http::get($this->baseUrl, [ - 'api_key' => $this->apiKey, - 'engine' => 'google', - 'q' => "site:{$domain}", - 'start' => $start, - 'num' => $perPage, - ]); - - if ($response->failed()) { - $this->newLine(); - $error = $response->json('error') ?? $response->status(); - $this->error("API request failed: {$error}"); - break; - } - - $data = $response->json(); - - // SerpApi returns an error key in the JSON for soft errors - if (!empty($data['error'])) { - $this->newLine(); - $this->error("SerpApi error: {$data['error']}"); - break; - } - - $items = $data['organic_results'] ?? []; - - if (empty($items)) { - $this->newLine(); - $this->info('No more results found.'); - break; - } - - foreach ($items as $item) { - $url = rtrim($item['link'], '/'); - - if (!in_array($url, $urls)) { - $urls[] = $url; - $progress->advance(); - } - - if (count($urls) >= $limit) { - break; - } - } - - $start += $perPage; - - if (count($items) < $perPage) { - // Fewer results than requested — end of results - break; - } - - sleep($delay); - } - - $progress->finish(); - $this->newLine(2); - - if (empty($urls)) { - $this->error('No URLs were found. Check your API key or try a different domain.'); - return self::FAILURE; - } - - $this->writeCsv($urls, $output); - - $this->info('✓ Exported ' . count($urls) . " URLs to {$output}"); - - return self::SUCCESS; - } - - private function writeCsv(array $urls, string $path): void - { - $dir = dirname($path); - - if (!is_dir($dir)) { - mkdir($dir, 0755, true); - } - - $handle = fopen($path, 'w'); - - // UTF-8 BOM for Excel compatibility - fwrite($handle, "\xEF\xBB\xBF"); - - fputcsv($handle, ['Old URLs']); - - foreach ($urls as $url) { - fputcsv($handle, [$url]); - } - - fclose($handle); - } -} \ No newline at end of file + private string $baseUrl = 'https://serpapi.com/search'; + + public function handle(): int + { + $this->apiKey = config('services.serpapi.api_key'); + + if (empty($this->apiKey)) { + $this->error('Missing SERPAPI_API_KEY in your .env file.'); + + return self::FAILURE; + } + + $domain = $this->argument('domain'); + $limit = (int) $this->option('limit'); + $delay = (int) $this->option('delay'); + $output = $this->option('output') ?? Str::slug($domain).'-urls.csv'; + + $this->info("Searching Google for indexed URLs on: {$domain}"); + $this->info("Output file: {$output}"); + $this->newLine(); + + $urls = []; + $start = 0; + $perPage = 10; + + $progress = $this->output->createProgressBar($limit); + $progress->start(); + + while (count($urls) < $limit) { + $response = Http::get($this->baseUrl, [ + 'api_key' => $this->apiKey, + 'engine' => 'google', + 'q' => "site:{$domain}", + 'start' => $start, + 'num' => $perPage, + ]); + + if ($response->failed()) { + $this->newLine(); + $error = $response->json('error') ?? $response->status(); + $this->error("API request failed: {$error}"); + break; + } + + $data = $response->json(); + + // SerpApi returns an error key in the JSON for soft errors + if (! empty($data['error'])) { + $this->newLine(); + $this->error("SerpApi error: {$data['error']}"); + break; + } + + $items = $data['organic_results'] ?? []; + + if (empty($items)) { + $this->newLine(); + $this->info('No more results found.'); + break; + } + + foreach ($items as $item) { + $url = rtrim($item['link'], '/'); + + if (! in_array($url, $urls)) { + $urls[] = $url; + $progress->advance(); + } + + if (count($urls) >= $limit) { + break; + } + } + + $start += $perPage; + + if (count($items) < $perPage) { + // Fewer results than requested — end of results + break; + } + + sleep($delay); + } + + $progress->finish(); + $this->newLine(2); + + if (empty($urls)) { + $this->error('No URLs were found. Check your API key or try a different domain.'); + + return self::FAILURE; + } + + $this->writeCsv($urls, $output); + + $this->info('✓ Exported '.count($urls)." URLs to {$output}"); + + return self::SUCCESS; + } + + private function writeCsv(array $urls, string $path): void + { + $dir = dirname($path); + + if (! is_dir($dir)) { + mkdir($dir, 0755, true); + } + + $handle = fopen($path, 'w'); + + // UTF-8 BOM for Excel compatibility + fwrite($handle, "\xEF\xBB\xBF"); + + fputcsv($handle, ['Old URLs'], ',', '"', '\\'); + + foreach ($urls as $url) { + fputcsv($handle, [$url], ',', '"', '\\'); + } + + fclose($handle); + } +} diff --git a/app/Console/Commands/TestNotification.php b/app/Console/Commands/TestNotification.php index 825ccd5..1c10fd8 100644 --- a/app/Console/Commands/TestNotification.php +++ b/app/Console/Commands/TestNotification.php @@ -42,11 +42,12 @@ public function handle() if (! class_exists($klass)) { $this->error("Notification class not found: {$klass}"); + return 1; } Notification::route('mail', $this->option('recipient')) - ->notify(new $klass()); + ->notify(new $klass); return 0; } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index b50980f..a288243 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -10,7 +10,6 @@ class Kernel extends ConsoleKernel /** * Define the application's command schedule. * - * @param \Illuminate\Console\Scheduling\Schedule $schedule * @return void */ protected function schedule(Schedule $schedule) diff --git a/app/Http/Controllers/PanelScreenshotController.php b/app/Http/Controllers/PanelScreenshotController.php index 9d4bb8a..fceafb2 100644 --- a/app/Http/Controllers/PanelScreenshotController.php +++ b/app/Http/Controllers/PanelScreenshotController.php @@ -14,7 +14,7 @@ public function show(string $handle) abort_if(! file_exists($fieldsetPath), 404, "No fieldset found for panel: {$handle}"); $fieldset = YAML::parse(file_get_contents($fieldsetPath)); - $dummy = $this->buildDummyData($fieldset['fields'] ?? []); + $dummy = $this->buildDummyData($fieldset['fields'] ?? []); $dummy['type'] = $handle; return View::make('__panel-screenshot') @@ -39,13 +39,16 @@ private function buildDummyData(array $fields): array $imported = YAML::parse(file_get_contents($importPath)); $data = array_merge($data, $this->buildDummyData($imported['fields'] ?? [])); } + continue; } $handle = $field['handle'] ?? null; - if (! $handle) continue; + if (! $handle) { + continue; + } - $def = $field['field'] ?? []; + $def = $field['field'] ?? []; $type = $def['type'] ?? null; // Field-level import — handle: foo, field.import: fieldset-name @@ -58,31 +61,35 @@ private function buildDummyData(array $fields): array } else { $data[$handle] = []; } + continue; } // UI-only field types — skip - if ($type === 'section') continue; + if ($type === 'section') { + continue; + } // Use the field's configured default value when available if (array_key_exists('default', $def)) { $data[$handle] = $def['default']; + continue; } $data[$handle] = match ($type) { - 'text' => 'Lorem ipsum dolor sit amet', - 'textarea' => "Lorem ipsum dolor sit amet\nconsectetur adipiscing elit", - 'bard' => '

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

', + 'text' => 'Lorem ipsum dolor sit amet', + 'textarea' => "Lorem ipsum dolor sit amet\nconsectetur adipiscing elit", + 'bard' => '

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

', 'button_group' => array_key_first($def['options'] ?? []), - 'select' => array_key_first($def['options'] ?? []), - 'toggle' => false, - 'integer' => 1, - 'assets' => null, - 'video' => null, - 'entries' => null, - 'replicator' => [], - default => null, + 'select' => array_key_first($def['options'] ?? []), + 'toggle' => false, + 'integer' => 1, + 'assets' => null, + 'video' => null, + 'entries' => null, + 'replicator' => [], + default => null, }; } diff --git a/app/Listeners/EntryListener.php b/app/Listeners/EntryListener.php index c27b3ae..3e391e4 100644 --- a/app/Listeners/EntryListener.php +++ b/app/Listeners/EntryListener.php @@ -59,5 +59,3 @@ public function scheduleReached(Events\EntryScheduleReached $event) $event->entry->save(); // we save the entry to kick the stache and run any invalidation } } - -?> diff --git a/app/Listeners/FormListener.php b/app/Listeners/FormListener.php index 0d53e0a..268c38f 100644 --- a/app/Listeners/FormListener.php +++ b/app/Listeners/FormListener.php @@ -5,9 +5,6 @@ // use \DrewM\MailChimp\MailChimp; use Statamic\Events\FormSubmitted; -use Statamic\Facades\Entry; -use Statamic\Facades\GlobalSet; -use Statamic\Support\Str; /* @@ -23,24 +20,18 @@ class FormListener public function handle(FormSubmitted $event) { - switch ($event->submission->form()->handle()) - { + switch ($event->submission->form()->handle()) { default: $this->processSomething(); - break; + break; } } // process function - public function processSomething(){ - - } - + public function processSomething() {} } - -?> diff --git a/app/Listeners/GlobalListener.php b/app/Listeners/GlobalListener.php index 647419a..592512b 100644 --- a/app/Listeners/GlobalListener.php +++ b/app/Listeners/GlobalListener.php @@ -11,11 +11,10 @@ class GlobalListener { public function saved(Events\GlobalSetSaved $event) { - switch ($event->globals->handle()) - { + switch ($event->globals->handle()) { case 'contact': $this->processContact($event->globals->inDefaultSite()); - break; + break; } } @@ -30,21 +29,20 @@ private function processContact($global) } catch (\Throwable $e) { Log::warning('Failed to geocode contact address: '.$e->getMessage()); Toast::error('Could not look up co-ordinates for the address — check the address and the geocoding API key.'); + return; } // if we have results - if ($location['accuracy'] != 'result_not_found'){ + if ($location['accuracy'] != 'result_not_found') { $global->merge([ - 'latitude' => $location['lat'], - 'longitude' => $location['lng'], + 'latitude' => $location['lat'], + 'longitude' => $location['lng'], ]); - + $global->save(); } } } } - -?> diff --git a/app/Listeners/TreeListener.php b/app/Listeners/TreeListener.php index c7fb38f..ffd2a71 100644 --- a/app/Listeners/TreeListener.php +++ b/app/Listeners/TreeListener.php @@ -26,5 +26,3 @@ public function saved(Events\CollectionTreeSaved|Events\NavTreeSaved $event) // InvalidateTags::dispatch(['partials:_partials/layout/nav']); } } - -?> diff --git a/bootstrap/app.php b/bootstrap/app.php index 4b067d4..b247a76 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -3,7 +3,10 @@ use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; +use Illuminate\Http\Request; use Illuminate\Http\Response; +use Statamic\View\View; +use Symfony\Component\HttpKernel\Exception\HttpException; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( @@ -15,11 +18,11 @@ // }) ->withExceptions(function (Exceptions $exceptions) { - $exceptions->render(function (\Symfony\Component\HttpKernel\Exception\HttpException $e, \Illuminate\Http\Request $request) { + $exceptions->render(function (HttpException $e, Request $request) { if ($e->getStatusCode() != 403) { return; } - - return new Response(\Statamic\View\View::make('errors.403')->layout(config('statamic.system.layout', 'layout')), 403, []); + + return new Response(View::make('errors.403')->layout(config('statamic.system.layout', 'layout')), 403, []); }); })->create(); diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 86f5297..78c06f7 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -1,6 +1,9 @@ 'collection', 'collection' => 'pages', - 'width' => 100 + 'width' => 100, ], ], diff --git a/config/statamic/static_caching.php b/config/statamic/static_caching.php index b1fd10e..1f251da 100644 --- a/config/statamic/static_caching.php +++ b/config/statamic/static_caching.php @@ -1,5 +1,8 @@ [ - \Statamic\StaticCaching\Replacers\CsrfTokenReplacer::class, - \Statamic\StaticCaching\Replacers\NoCacheReplacer::class, + CsrfTokenReplacer::class, + NoCacheReplacer::class, ], /* |-------------------------------------------------------------------------- @@ -155,13 +158,13 @@ | */ - 'warm_queue' => env('STATAMIC_STATIC_WARM_QUEUE'), + 'warm_queue' => env('STATAMIC_STATIC_WARM_QUEUE'), - 'warm_queue_connection' => env('STATAMIC_STATIC_WARM_QUEUE_CONNECTION'), + 'warm_queue_connection' => env('STATAMIC_STATIC_WARM_QUEUE_CONNECTION'), - 'warm_insecure' => env('STATAMIC_STATIC_WARM_INSECURE', false), + 'warm_insecure' => env('STATAMIC_STATIC_WARM_INSECURE', false), - /* + /* |-------------------------------------------------------------------------- | Background Re-cache |-------------------------------------------------------------------------- @@ -171,13 +174,13 @@ | */ - 'background_recache' => env('STATAMIC_BACKGROUND_RECACHE', false), + 'background_recache' => env('STATAMIC_BACKGROUND_RECACHE', false), - 'recache_token' => env('STATAMIC_RECACHE_TOKEN'), + 'recache_token' => env('STATAMIC_RECACHE_TOKEN'), - 'recache_token_parameter' => '__recache', + 'recache_token_parameter' => '__recache', - /* + /* |-------------------------------------------------------------------------- | Shared Error Pages |-------------------------------------------------------------------------- @@ -190,6 +193,6 @@ | */ - 'share_errors' => false, + 'share_errors' => false, - ]; +]; diff --git a/routes/web.php b/routes/web.php index 4aa0cc6..44b96be 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,8 +6,8 @@ // if we're in staging // we can access the cutup -if (app()->environment() == 'staging'){ - Route::get('cutup/{page}', function($page) { +if (app()->environment() == 'staging') { + Route::get('cutup/{page}', function ($page) { return View::make("cutup.{$page}") ->layout('layout'); });