245 lines
16 KiB
PHP
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 ↑'],
|
|
],
|
|
];
|
|
}
|
|
}
|