Files
ABSDEV/Modules/Content/Console/InstallSaaslandContent.php
2026-07-27 14:17:30 +01:00

245 lines
16 KiB
PHP

<?php
namespace Modules\Content\Console;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Modules\Content\Models\ContentDocument;
use Modules\Content\Models\ContentType;
use Modules\Content\Models\PageContent;
use Modules\Menus\Models\Menu;
use Modules\Settings\Services\SettingsRepository;
final class InstallSaaslandContent extends Command
{
protected $signature = 'content:install-saasland {--force : Replace the existing home composition}';
protected $description = 'Install editable Saasland header, body and footer content documents.';
public function handle(): int
{
$requiredTables = ['content_types', 'content_documents', 'page_content'];
$missingTables = array_values(array_filter($requiredTables, static fn (string $table): bool => ! Schema::hasTable($table)));
if ($missingTables !== []) {
$this->components->error('Content composition tables are missing: '.implode(', ', $missingTables).'.');
$this->components->info('Run php artisan migrate before installing the Saasland composition.');
return self::FAILURE;
}
DB::transaction(function (): void {
$this->installSharedSettings();
$this->installMenus();
if ($this->option('force')) {
PageContent::query()->where('page_key', 'home')->delete();
}
$order = [
'site-header' => ['header', 10], 'hero' => ['body', 10], 'feature-grid' => ['body', 20],
'product-feed' => ['body', 30], 'testimonial-feed' => ['body', 40], 'team-feed' => ['body', 50],
'blog-feed' => ['body', 60], 'news-feed' => ['body', 70], 'faq-feed' => ['body', 80],
'newsletter-form' => ['body', 90], 'call-to-action' => ['body', 100], 'site-footer' => ['footer', 10],
];
foreach ($this->definitions() as $key => $definition) {
$type = ContentType::query()->updateOrCreate(['key' => $key], $definition['type']);
$document = ContentDocument::query()->firstOrCreate(
['slug' => 'saasland-'.$key],
[
'content_type_id' => $type->id,
'title' => $definition['title'],
'data' => $definition['data'],
'status' => 'published',
'published_at' => now(),
]
);
$currentData = is_array($document->data) ? $document->data : [];
$mergedData = $this->mergeMissingDefaults($currentData, $definition['data']);
$document->forceFill([
'content_type_id' => $type->id,
'data' => $mergedData,
])->save();
[$region, $sort] = $order[$key];
PageContent::query()->updateOrCreate(
['page_key' => 'home', 'region' => $region, 'sort_order' => $sort],
['content_document_id' => $document->id, 'is_enabled' => true]
);
}
});
$this->components->info('Saasland content composition installed with editable schemas.');
return self::SUCCESS;
}
private function installSharedSettings(): void
{
$settings = app(SettingsRepository::class);
$legacy = [];
if (Schema::hasTable('stylist_theme_settings')) {
$stored = DB::table('stylist_theme_settings')->where('theme_key', 'saasland')->value('settings');
$legacy = is_string($stored) ? (json_decode($stored, true) ?: []) : [];
}
$defaults = [
'site_name' => $legacy['site_title'] ?? config('app.name'),
'site_description' => $legacy['site_description'] ?? 'Modern software for modern companies.',
'company_name' => $legacy['site_title'] ?? config('app.name'),
'site_url' => url('/'),
'logo_url' => $legacy['logo_url'] ?? '',
'facebook_url' => $legacy['facebook_url'] ?? '',
'x_url' => $legacy['x_url'] ?? '',
'linkedin_url' => $legacy['linkedin_url'] ?? '',
'dashboard_label' => 'Dashboard',
'login_label' => 'Log in',
'registration_label' => 'Get started',
];
foreach ($defaults as $key => $value) {
if ($settings->get($key, null, 'site') === null) {
$settings->put($key, $value, 'site', 'string', true);
}
}
}
private function installMenus(): void
{
if (! Schema::hasTable('menus')) {
return;
}
$menus = [
'primary' => ['Primary navigation', [['enabled' => true, 'label' => 'Home', 'url' => '/'], ['enabled' => true, 'label' => 'Blog', 'url' => '/blog'], ['enabled' => true, 'label' => 'News', 'url' => '/news']]],
'footer' => ['Footer — Explore', [['enabled' => true, 'label' => 'Home', 'url' => '/'], ['enabled' => true, 'label' => 'Blog', 'url' => '/blog'], ['enabled' => true, 'label' => 'News', 'url' => '/news']]],
'footer_account' => ['Footer — Account', [['enabled' => true, 'label' => 'Dashboard', 'url' => '/dashboard'], ['enabled' => true, 'label' => 'Profile', 'url' => '/settings/profile']]],
'footer_legal' => ['Footer — Legal', []],
];
foreach ($menus as $location => [$name, $items]) {
Menu::query()->firstOrCreate(['location' => $location], ['name' => $name, 'items' => $items, 'enabled' => true]);
}
}
private function mergeMissingDefaults(array $current, array $defaults): array
{
foreach ($defaults as $key => $defaultValue) {
if (! array_key_exists($key, $current)) {
$current[$key] = $defaultValue;
continue;
}
if (
is_array($defaultValue)
&& is_array($current[$key])
&& ! array_is_list($defaultValue)
&& ! array_is_list($current[$key])
) {
$current[$key] = $this->mergeMissingDefaults(
$current[$key],
$defaultValue
);
}
}
return $current;
}
private function definitions(): array
{
$field = static fn (string $key, string $label, string $type = 'text', array $extra = []): array => array_merge(compact('key', 'label', 'type'), $extra);
$type = static fn (string $name, string $renderer, array $regions, array $schema): array => [
'name' => $name, 'renderer' => $renderer, 'allowed_regions' => $regions,
'is_system' => true, 'is_active' => true, 'schema' => $schema,
];
$section = [$field('enabled', 'Show this section', 'boolean', ['default' => true])];
$feed = array_merge($section, [
$field('eyebrow', 'Eyebrow'), $field('heading', 'Heading'), $field('description', 'Description', 'textarea'),
$field('limit', 'Maximum items', 'number', ['min' => 1, 'max' => 50]),
$field('include_ids', 'Only show record IDs', 'csv', ['help' => 'Optional comma-separated IDs. Leave blank to use the latest records.']),
$field('exclude_ids', 'Exclude record IDs', 'csv', ['help' => 'Optional comma-separated IDs to hide from this section.']),
]);
return [
'site-header' => [
'title' => 'Main website header',
'type' => $type('Site header', 'site-header', ['header'], [
$field('enabled', 'Show header', 'boolean', ['default' => true]),
$field('menu_location', 'Navigation menu location', 'text', ['help' => 'Usually primary. Edit links from Administration → Menus.']),
$field('show_navigation', 'Show navigation', 'boolean', ['default' => true]),
$field('show_dashboard', 'Show dashboard link', 'boolean', ['default' => true]),
$field('show_user_name', 'Show signed-in user', 'boolean', ['default' => true]),
$field('show_login', 'Show login', 'boolean', ['default' => true]),
$field('show_registration', 'Show registration', 'boolean', ['default' => true]),
$field('show_mobile_menu', 'Show mobile menu', 'boolean', ['default' => true]),
]),
'data' => ['enabled' => true, 'menu_location' => 'primary', 'show_navigation' => true, 'show_dashboard' => true, 'show_user_name' => true, 'show_login' => true, 'show_registration' => true, 'show_mobile_menu' => true],
],
'hero' => [
'title' => 'Homepage hero',
'type' => $type('Hero', 'hero', ['body'], [
...$section, $field('eyebrow', 'Eyebrow'), $field('heading', 'Heading'), $field('description', 'Description', 'textarea'),
$field('primary_label', 'Primary button label'), $field('primary_url', 'Primary button URL', 'url'),
$field('secondary_label', 'Secondary button label'), $field('secondary_url', 'Secondary button URL', 'url'),
$field('show_metrics', 'Show metrics panel', 'boolean', ['default' => true]),
$field('metrics', 'Metrics', 'repeater', ['fields' => [
$field('enabled', 'Show item', 'boolean', ['default' => true]), $field('label', 'Label'), $field('value', 'Value'), $field('change', 'Change'), $field('height', 'Chart height (%)', 'number', ['min' => 0, 'max' => 100]),
]]),
]),
'data' => ['enabled' => true, 'eyebrow' => 'A better way to run your business', 'heading' => 'Transform Your Business With Our SaaS Solution', 'description' => 'Streamline operations, boost productivity, and improve customer satisfaction with one connected platform built for modern teams.', 'primary_label' => 'Get started free', 'primary_url' => '/register', 'secondary_label' => 'Book demo', 'secondary_url' => '#contact', 'show_metrics' => true, 'metrics' => [
['enabled' => true, 'label' => 'Monthly revenue', 'value' => '£48,240', 'change' => '+18.4%', 'height' => 62],
['enabled' => true, 'label' => 'Active users', 'value' => '12,842', 'change' => '+9.2%', 'height' => 78],
['enabled' => true, 'label' => 'Conversion', 'value' => '7.8%', 'change' => '+2.1%', 'height' => 92],
]],
],
'feature-grid' => [
'title' => 'Core features',
'type' => $type('Feature grid', 'feature-grid', ['body'], [
...$section, $field('eyebrow', 'Eyebrow'), $field('heading', 'Heading'), $field('description', 'Description', 'textarea'),
$field('items', 'Features', 'repeater', ['fields' => [$field('enabled', 'Show item', 'boolean', ['default' => true]), $field('title', 'Title'), $field('description', 'Description', 'textarea')]]),
]),
'data' => ['enabled' => true, 'eyebrow' => 'Powerful features', 'heading' => 'Everything your team needs to grow', 'description' => 'Planning, collaboration, reporting, publishing, commerce and customer insight in one extensible platform.', 'items' => [
['enabled' => true, 'title' => 'Smart automation', 'description' => 'Remove repetitive work and delays.'],
['enabled' => true, 'title' => 'Real-time analytics', 'description' => 'Turn activity into useful business insight.'],
['enabled' => true, 'title' => 'Content operations', 'description' => 'Create, review and publish confidently.'],
['enabled' => true, 'title' => 'Secure by design', 'description' => 'Protect users and data with dependable controls.'],
['enabled' => true, 'title' => 'Flexible integrations', 'description' => 'Connect the tools your organisation already uses.'],
['enabled' => true, 'title' => 'Modular architecture', 'description' => 'Activate only the capabilities each site needs.'],
]],
],
'product-feed' => ['title' => 'Pricing plans', 'type' => $type('Product feed', 'product-feed', ['body'], $feed), 'data' => ['enabled' => true, 'eyebrow' => 'Simple pricing', 'heading' => 'Choose a plan that fits', 'description' => 'Plans are managed from Products & Pricing in the CMS.', 'limit' => 6, 'include_ids' => '', 'exclude_ids' => '']],
'testimonial-feed' => ['title' => 'Customer stories', 'type' => $type('Testimonial feed', 'testimonial-feed', ['body'], $feed), 'data' => ['enabled' => true, 'eyebrow' => 'Customer stories', 'heading' => 'Trusted by ambitious teams', 'description' => '', 'limit' => 6, 'include_ids' => '', 'exclude_ids' => '']],
'team-feed' => ['title' => 'Team', 'type' => $type('Team feed', 'team-feed', ['body'], $feed), 'data' => ['enabled' => true, 'eyebrow' => 'Our team', 'heading' => 'People behind the platform', 'description' => '', 'limit' => 8, 'include_ids' => '', 'exclude_ids' => '']],
'blog-feed' => ['title' => 'Latest blog posts', 'type' => $type('Blog feed', 'blog-feed', ['body'], $feed), 'data' => ['enabled' => true, 'eyebrow' => 'Insights', 'heading' => 'Latest from the blog', 'description' => '', 'limit' => 3, 'include_ids' => '', 'exclude_ids' => '']],
'news-feed' => ['title' => 'Latest news', 'type' => $type('News feed', 'news-feed', ['body'], $feed), 'data' => ['enabled' => true, 'eyebrow' => 'Newsroom', 'heading' => 'Latest news', 'description' => '', 'limit' => 3, 'include_ids' => '', 'exclude_ids' => '']],
'faq-feed' => ['title' => 'Frequently asked questions', 'type' => $type('FAQ feed', 'faq-feed', ['body'], $feed), 'data' => ['enabled' => true, 'eyebrow' => 'FAQ', 'heading' => 'Answers before you begin', 'description' => '', 'limit' => 12, 'include_ids' => '', 'exclude_ids' => '']],
'newsletter-form' => [
'title' => 'Newsletter signup',
'type' => $type('Newsletter form', 'newsletter-form', ['body'], [...$section, $field('eyebrow', 'Eyebrow'), $field('heading', 'Heading'), $field('description', 'Description', 'textarea'), $field('placeholder', 'Email placeholder'), $field('button_label', 'Button label')]),
'data' => ['enabled' => true, 'eyebrow' => 'Newsletter', 'heading' => 'Useful updates, no noise', 'description' => 'Receive product news, publishing guidance, and practical platform updates.', 'placeholder' => 'you@example.com', 'button_label' => 'Subscribe'],
],
'call-to-action' => [
'title' => 'Homepage final call to action',
'type' => $type('Call to action', 'call-to-action', ['body'], [...$section, $field('anchor', 'Section anchor'), $field('heading', 'Heading'), $field('description', 'Description', 'textarea'), $field('button_label', 'Button label'), $field('button_url', 'Button URL', 'url')]),
'data' => ['enabled' => true, 'anchor' => 'contact', 'heading' => 'Ready to transform your business?', 'description' => 'Start building a faster, more connected operation today.', 'button_label' => 'Get started free', 'button_url' => '/register'],
],
'site-footer' => [
'title' => 'Main website footer',
'type' => $type('Site footer', 'site-footer', ['footer'], [
$field('enabled', 'Show footer', 'boolean', ['default' => true]),
$field('explore_heading', 'Explore column heading'),
$field('explore_menu_location', 'Explore menu location'),
$field('account_heading', 'Account column heading'),
$field('account_menu_location', 'Account menu location'),
$field('legal_heading', 'Legal column heading'),
$field('legal_menu_location', 'Legal menu location'),
$field('show_back_to_top', 'Show back-to-top link', 'boolean', ['default' => true]),
$field('back_to_top_label', 'Back-to-top label'),
]),
'data' => ['enabled' => true, 'explore_heading' => 'Explore', 'explore_menu_location' => 'footer', 'account_heading' => 'Account', 'account_menu_location' => 'footer_account', 'legal_heading' => 'Legal', 'legal_menu_location' => 'footer_legal', 'show_back_to_top' => true, 'back_to_top_label' => 'Back to top ↑'],
],
];
}
}