diff --git a/composer.json b/composer.json index 0ce6af0..59e9f82 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "seiger/smultisite", "license": "GPL-3.0", - "type": "EvolutionCMS-Plugin", + "type": "evolutioncms-plugin", "description": "Evocms package Multisite Tools for Evolution CMS.", "keywords": ["evocms", "evolution-cms", "multisite"], "authors": [ @@ -11,7 +11,7 @@ } ], "require": { - "php": "^8" + "php": "^8.1" }, "autoload": { "psr-4": { @@ -31,4 +31,4 @@ "scripts": { "post-autoload-dump": [] } -} \ No newline at end of file +} diff --git a/lang/en/global.php b/lang/en/global.php index 48578cd..acbec86 100644 --- a/lang/en/global.php +++ b/lang/en/global.php @@ -6,11 +6,11 @@ 'configure_icon' => 'fa fa-sliders', 'default_domain' => 'Default domain:', 'default_domain_help' => 'The domain name is configured in the Site System Configuration, Site tab. This domain will be used if the incoming request does not match any of the following.', + 'description' => 'Setting up additional site domains.', 'domain' => 'Domain host:', 'domain_help' => 'Enter a domain host without protocol.', 'domain_on' => 'Enable domain:', 'domain_on_help' => 'If the domain is disabled, access to the pages of this domain will be prohibited.', - 'description' => 'Setting up additional site domains.', 'empty_field' => 'the field must not be empty.', 'hide_from_tree' => 'Hide in the resource tree:', 'hide_from_tree_help' => 'If this box is checked, domain resources will not be displayed in the resource tree.', @@ -18,4 +18,4 @@ 'must_be_dis-active' => 'to hide, the domain must be disabled.', 'not_saved' => 'Not saved.', 'title' => 'Multisite', -]; \ No newline at end of file +]; diff --git a/lang/ru/global.php b/lang/ru/global.php index 5808997..62a0ea5 100644 --- a/lang/ru/global.php +++ b/lang/ru/global.php @@ -6,11 +6,11 @@ 'configure_icon' => 'fa fa-sliders', 'default_domain' => 'Домен по умолчанию:', 'default_domain_help' => 'Название домена настраивается в Системной конфигурации сайта, таб Сайт. Этот домен будет задействован, если входной запрос не отвечает ни одному из тех, что приведены ниже.', + 'description' => 'Настройка дополнительных доменов сайта.', 'domain' => 'Доменное имя:', 'domain_help' => 'Укажите доменное имя без протокола.', 'domain_on' => 'Включить домен:', 'domain_on_help' => 'Если домен выключен, то доступ к страницам этого домена будет запрещен.', - 'description' => 'Настройка дополнительных доменов сайта.', 'empty_field' => 'поле не должно быть пустым.', 'hide_from_tree' => 'Спрятать в дереве ресурсов:', 'hide_from_tree_help' => 'Если это поле будет отмечено, то ресурсы домена не будут отражены в дереве ресурсов.', @@ -18,4 +18,4 @@ 'must_be_dis-active' => 'чтобы спрятать, домен домен должен быть выключен.', 'not_saved' => 'Не сохранено.', 'title' => 'Мультисайт', -]; \ No newline at end of file +]; diff --git a/lang/uk/global.php b/lang/uk/global.php index c2bd182..91328fb 100644 --- a/lang/uk/global.php +++ b/lang/uk/global.php @@ -6,11 +6,11 @@ 'configure_icon' => 'fa fa-sliders', 'default_domain' => 'Домен типово:', 'default_domain_help' => 'Назва домену налаштовується в Системній кофігурації сайту, таб Сайт. Цей домен буде задіяно, якщо вхідний запит не відповідає жодному з тих, що наведено нижче.', + 'description' => 'Налаштування додаткових доменів сайту.', 'domain' => 'Доменне ім`я:', 'domain_help' => 'Вкажіть доменне ім`я без протоколу.', 'domain_on' => 'Ввімкнути домен:', 'domain_on_help' => 'Якщо домен вимкнено, то доступ до сторінок цього домену буде заборонено.', - 'description' => 'Налаштування додаткових доменів сайту.', 'empty_field' => 'поле не має бути порожнім.', 'hide_from_tree' => 'Приховати в дереві ресурсів:', 'hide_from_tree_help' => 'Якщо це поле буде відмічено, то ресурси домену не буде відображено в дереві ресурсів.', diff --git a/src/Controllers/sMultisiteController.php b/src/Controllers/sMultisiteController.php index 73545ad..cd65554 100644 --- a/src/Controllers/sMultisiteController.php +++ b/src/Controllers/sMultisiteController.php @@ -17,110 +17,189 @@ public function index() return $this->view('index'); } + /** + * Update domain settings + * + * @return \Illuminate\Http\RedirectResponse + */ public function update() { - $refresh = false; + $newDomains = request()->input('new-domains', []); + $existingDomains = request()->input('domains', []); + $this->processNewDomains($newDomains); + $this->updateExistingDomains($existingDomains); + $_SESSION['sMultisite.refresh'] = true; + return header("Location: " . \Seiger\sMultisite\Facades\sMultisite::route('sMultisite.index')); + } - if (request()->has('new-domains') && is_array(request()->input('new-domains', [])) && count(request()->input('new-domains', []))) { - foreach (request()->input('new-domains', []) as $item) { - $item = is_array($item) ? implode('-', $item) : $item; - $item = trim($item, 'https://'); - $item = trim($item, 'http://'); - $item = trim($item, '/'); - $find = sMultisite::whereDomain($item)->first(); - if (!$find) { - // Base resource - $resource = new SiteContent(); - $resource->pagetitle = $item; - $resource->content = 'https://'.$item; - $resource->type = 'reference'; - $resource->parent = 0; - $resource->alias_visible = 0; - $resource->richtext = 0; - $resource->published = 1; - $resource->isfolder = 1; - $resource->hidemenu = 1; - $resource->save(); - - // Home resource - $homepage = new SiteContent(); - $homepage->pagetitle = 'Homepage'; - $homepage->published = 1; - $homepage->parent = $resource->id; - $homepage->save(); - - // Domain - $domain = new sMultisite(); - $domain->domain = $item; - $domain->key = $this->validateKey($item); - $domain->resource = $resource->id; - $domain->site_start = $homepage->id; - $domain->error_page = $homepage->id; - $domain->unauthorized_page = $homepage->id; - $domain->save(); - } + /** + * Process new domains and save them + * + * @param array $domains + * @return bool + */ + private function processNewDomains(array $domains): bool + { + $refresh = false; + foreach ($domains as $item) { + $item = $this->sanitizeDomain($item); + if (!$this->domainExists($item)) { + $this->createDomainResources($item); } } + return $refresh; + } - if (request()->has('domains') && is_array(request()->input('domains', [])) && count(request()->input('domains', []))) { - foreach (request()->input('domains', []) as $id => $item) { - if (is_array($item) && count($item)) { - $domain = sMultisite::find($id); - if ($domain) { - foreach ($item as $name => $value) { - $domain->{$name} = $value; - } - $domain->update(); - - if (is_array($domain->getChanges()) && isset($domain->getChanges()['hide_from_tree'])) { - $refresh = true; - } - } + /** + * Update existing domain settings + * + * @param array $domains + * @return bool + */ + private function updateExistingDomains(array $domains): bool + { + $refresh = false; + foreach ($domains as $id => $item) { + if (is_array($item) && $domain = sMultisite::find($id)) { + if ($item['key'] != 'default') { + $item['active'] = intval($item['active'] ?? 0); + $item['hide_from_tree'] = intval($item['hide_from_tree'] ?? 0); + } + $domain->update($item); + if (isset($domain->getChanges()['hide_from_tree'])) { + $refresh = true; } } } + return $refresh; + } - if ($refresh) { - return header("Location: /".MGR_DIR."/index.php?a=7&r=10"); - } else { - return back(); - } + /** + * Create resources for a new domain + * + * @param string $item + */ + private function createDomainResources(string $item): void + { + $resource = new SiteContent(); + $resource->fill([ + 'pagetitle' => $item, + 'content' => 'https://' . $item, + 'type' => 'reference', + 'parent' => 0, + 'alias_visible' => 0, + 'richtext' => 0, + 'published' => 1, + 'isfolder' => 1, + 'hidemenu' => 1, + ]); + $resource->save(); + + $homepage = new SiteContent(); + $homepage->fill([ + 'pagetitle' => 'Homepage', + 'published' => 1, + 'parent' => $resource->id, + ]); + $homepage->save(); + + $domain = new sMultisite(); + $domain->fill([ + 'domain' => $item, + 'key' => $this->validateKey($item), + 'resource' => $resource->id, + 'site_name' => 'Evolution CMS website', + 'site_start' => $homepage->id, + 'error_page' => $homepage->id, + 'unauthorized_page' => $homepage->id, + ]); + $domain->save(); + } + + /** + * Check if a domain already exists + * + * @param string $item + * @return bool + */ + private function domainExists(string $item): bool + { + return sMultisite::whereDomain($item)->exists(); + } + + /** + * Sanitize domain input + * + * @param mixed $item + * @return string + */ + private function sanitizeDomain($item): string + { + // Convert array to string if needed + $item = is_array($item) ? implode('-', $item) : $item; + + // Remove protocols and trailing slashes + $item = preg_replace(['/^(https?:\/\/)/'], '', $item); + + return trim($item, '/'); + } + + /** + * Validate and generate a unique key for a domain + * + * @param string $string + * @param int $id + * @return string + */ + private function validateKey(string $string = '', int $id = 0): string + { + $alias = $this->generateAlias($string); + $existingAliases = sMultisite::where('s_multisites.id', '<>', $id) + ->pluck('key') + ->toArray(); + + return $this->resolveAliasConflict($alias, $existingAliases); } /** - * Alias validation + * Generate a slug-like alias from the domain string * - * @param $data - * @param string $table + * @param string $string * @return string */ - private function validateKey($string = '', $id = 0): string + private function generateAlias(string $string): string { if (trim($string)) { $alias = explode('.', $string); - // array_pop($string); - array_pop($alias); // Here we are passing the $alias array, not the $string - $alias = Str::slug(trim(implode('', $alias))); - } else { - $alias = $id; + array_pop($alias); + return Str::slug(trim(implode('', $alias))); } + return ''; + } - $aliases = sMultisite::where('s_multisites.id', '<>', $id)->get('key')->pluck('key')->toArray(); - - if (in_array($alias, $aliases)) { + /** + * Resolve alias conflicts by appending a numeric suffix + * + * @param string $alias + * @param array $existingAliases + * @return string + */ + private function resolveAliasConflict(string $alias, array $existingAliases): string + { + if (in_array($alias, $existingAliases)) { $cnt = 1; $tempAlias = $alias; - while (in_array($tempAlias, $aliases)) { + while (in_array($tempAlias, $existingAliases)) { $tempAlias = $alias . $cnt; $cnt++; } - $alias = $tempAlias; + return $tempAlias; } return $alias; } /** - * Display render + * Render a view with optional data * * @param string $tpl * @param array $data @@ -128,6 +207,6 @@ private function validateKey($string = '', $id = 0): string */ public function view(string $tpl, array $data = []) { - return View::make('sMultisite::'.$tpl, $data); + return View::make('sMultisite::' . $tpl, $data); } } diff --git a/src/Facades/sMultisite.php b/src/Facades/sMultisite.php index c104bfe..d77c706 100644 --- a/src/Facades/sMultisite.php +++ b/src/Facades/sMultisite.php @@ -5,6 +5,8 @@ /** * Class sMultisite * + * Facade for accessing the sMultisite service. + * * @package Seiger\sMultisite * @mixin \Seiger\sMultisite\sMultisite */ @@ -13,10 +15,12 @@ class sMultisite extends Facade /** * Get the registered name of the component. * + * This method should return the name of the component being accessed by the facade. + * * @return string */ protected static function getFacadeAccessor() { return 'sMultisite'; } -} \ No newline at end of file +} diff --git a/src/Models/sMultisite.php b/src/Models/sMultisite.php index 555ee24..4be7e56 100644 --- a/src/Models/sMultisite.php +++ b/src/Models/sMultisite.php @@ -2,7 +2,62 @@ use Illuminate\Database\Eloquent\Model; +/** + * Class sMultisite + * + * Represents a multisite instance in the Evolution CMS. + * + * @package Seiger\sMultisite\Models + * @property int $id + * @property int $active + * @property int $hide_from_tree + * @property int $resource + * @property string $key + * @property string $domain + * @property string $site_name + * @property int $site_start + * @property int $error_page + * @property int $unauthorized_page + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + */ class sMultisite extends Model { + /** + * The table associated with the model. + * + * @var string + */ + protected $table = 's_multisites'; + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'active', + 'hide_from_tree', + 'resource', + 'key', + 'domain', + 'site_name', + 'site_start', + 'error_page', + 'unauthorized_page' + ]; + + /** + * The attributes that should be cast to native types. + * + * @var array + */ + protected $casts = [ + 'active' => 'integer', + 'hide_from_tree' => 'integer', + 'resource' => 'integer', + 'site_start' => 'integer', + 'error_page' => 'integer', + 'unauthorized_page' => 'integer' + ]; } diff --git a/src/sMultisite.php b/src/sMultisite.php index c5c64eb..1db999b 100644 --- a/src/sMultisite.php +++ b/src/sMultisite.php @@ -1,93 +1,136 @@ 'default', - 'link' => evo()->getConfig('server_protocol', 'https') . '://' . $_SERVER['HTTP_HOST'], + 'link' => $this->scheme(evo()->getConfig('server_protocol', 'https') . '://' . $_SERVER['HTTP_HOST']), 'site_name' => evo()->getConfig('site_name', 'Evolution CMS'), 'is_current' => true, ]; - $items = \Seiger\sMultisite\Models\sMultisite::whereActive(1)->get(); - if ($items) { - foreach ($items as $item) { - $domains[$item->key] = [ - 'key' => $item->key, - 'link' => evo()->getConfig('server_protocol', 'https') . '://' . $item->domain, - 'site_name' => $item->site_name, - 'is_current' => ($_SERVER['HTTP_HOST'] == $item->domain), - ]; - } + // Retrieve all active multisite records + $items = SMultisiteModel::whereActive(1)->get(); + + // Process each active multisite record + foreach ($items as $item) { + $domains[$item->key] = [ + 'key' => $item->key, + 'link' => $this->scheme(evo()->getConfig('server_protocol', 'https') . '://' . $item->domain), + 'site_name' => $item->site_name, + 'is_current' => ($_SERVER['HTTP_HOST'] === $item->domain), + ]; } return $domains; } /** - * Get url from route name with action id + * Generate a URL from the route name with an action ID appended. * * @param string $name Route name * @return string */ public function route(string $name): string { + // Generate the base route URL and remove trailing slashes $route = rtrim(route($name), '/'); - if (evo()->getConfig('friendly_url_suffix', '') != '/') { - $route = str_ireplace(evo()->getConfig('friendly_url_suffix', ''), '', route($name)); - } + $friendlyUrlSuffix = evo()->getConfig('friendly_url_suffix', ''); - $a = 0; - $arr = str_split($name, 1); - foreach ($arr as $n) { - $a += ord($n); + // Remove friendly URL suffix if it's not a slash + if ($friendlyUrlSuffix !== '/') { + $route = str_ireplace($friendlyUrlSuffix, '', $route); } + + // Generate a unique action ID based on the route name + $a = array_sum(array_map('ord', str_split($name))) + 999; $a = $a < 999 ? $a + 999 : $a; - return $route.'?a='.$a; + // Return the route URL with the action ID appended + return $this->scheme($route) . '?a=' . $a; } /** - * Update resiurces tree with domains + * Determine the URL scheme (HTTP or HTTPS) based on server variables. + * + * @param string $url URL to modify + * @return string + */ + public function scheme(string $url): string + { + // Determine the current scheme from various sources + $scheme = 'http'; // Default to HTTP + + // Check the server variables for HTTPS + if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { + $scheme = 'https'; + } + // Check the forward headers if present + elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') { + $scheme = 'https'; + } + // Check the HTTP_HOST for known HTTPS setups + elseif (isset($_SERVER['HTTP_HOST']) && preg_match('/^https:/i', $_SERVER['HTTP_HOST'])) { + $scheme = 'https'; + } + // Check if the server is using HTTPS by default + elseif (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) { + $scheme = 'https'; + } + + // Replace the scheme in the URL if necessary + return preg_replace('/^http/', $scheme, $url); + } + + /** + * Update resources tree with domains and cache the results. * * @return void */ - public function domainsTree() + public function domainsTree(): void { $domainIds = []; $domainBaseIds = evo()->getChildIds(0, 1); - $domains = \Seiger\sMultisite\Models\sMultisite::all(); + $domains = SMultisiteModel::all(); $multisiteResources = $domains->pluck('resource')->toArray(); + // Exclude existing multisite resources from the base IDs if (count($multisiteResources)) { $domainBaseIds = array_diff($domainBaseIds, $multisiteResources); } + + // Collect all resource IDs for the domains foreach ($domainBaseIds as $domainBaseId) { $domainIds = array_merge($domainIds, evo()->getChildIds($domainBaseId)); } + + // Cache default resources Cache::forget('sMultisite-default-resources'); - Cache::rememberForever('sMultisite-default-resources', function () use ($domainIds) { - return $domainIds; - }); - - if ($domains) { - foreach ($domains as $domain) { - $domainIds = evo()->getChildIds($domain->resource); - Cache::forget('sMultisite-' . $domain->key . '-resources'); - Cache::rememberForever('sMultisite-' . $domain->key . '-resources', function () use ($domainIds) { - return $domainIds; - }); - } + Cache::rememberForever('sMultisite-default-resources', fn() => $domainIds); + + // Cache resources for each domain + foreach ($domains as $domain) { + $domainIds = evo()->getChildIds($domain->resource); + Cache::forget('sMultisite-' . $domain->key . '-resources'); + Cache::rememberForever('sMultisite-' . $domain->key . '-resources', fn() => $domainIds); } } -} \ No newline at end of file +} diff --git a/src/sMultisiteServiceProvider.php b/src/sMultisiteServiceProvider.php index f51b970..8b00e3f 100644 --- a/src/sMultisiteServiceProvider.php +++ b/src/sMultisiteServiceProvider.php @@ -1,54 +1,65 @@ app->singleton('sMultisite', fn($app) => new sMultisite()); + + // Create an alias for the sMultisite facade + class_alias(SMultisiteFacade::class, 'sMultisite'); + + // Add plugins to Evolution CMS + $this->loadPluginsFrom(dirname(__DIR__) . '/plugins/'); + } + + /** + * Perform post-registration booting of services. + * + * This method is used to perform any tasks required after the services are registered. * * @return void */ public function boot() { - // Only Manager + // Check if in Manager mode if (IN_MANAGER_MODE) { - // Add custom routes for package - include(__DIR__.'/Http/routes.php'); + // Load the package routes + $this->loadRoutesFrom(__DIR__.'/Http/routes.php'); - // Migration for create tables - $this->loadMigrationsFrom(dirname(__DIR__) . '/database/migrations'); - - // Views + // Load the package views $this->loadViewsFrom(dirname(__DIR__) . '/views', 'sMultisite'); - // MultiLang + // Load the package translations $this->loadTranslationsFrom(dirname(__DIR__) . '/lang', 'sMultisite'); - // Files + // Publish configuration files $this->publishes([ - dirname(__DIR__) . '/config/sMultisiteAlias.php' => config_path('app/aliases/sMultisite.php', true), dirname(__DIR__) . '/config/sMultisiteSettings.php' => config_path('seiger/settings/sMultisite.php', true), dirname(__DIR__) . '/images/seigerit-blue.svg' => public_path('assets/site/seigerit-blue.svg'), ]); + + // Load migration files + $this->loadMigrationsFrom(dirname(__DIR__) . '/database/migrations'); } - // Check sMultisite + // Merge sMultisite configuration $this->mergeConfigFrom(dirname(__DIR__) . '/config/sMultisiteCheck.php', 'cms.settings'); - - // Class alias - $this->app->singleton(\Seiger\sMultisite\sMultisite::class); - $this->app->alias(\Seiger\sMultisite\sMultisite::class, 'sMultisite'); - } - - /** - * Register the service provider. - * - * @return void - */ - public function register() - { - // Add plugins to Evo - $this->loadPluginsFrom(dirname(__DIR__) . '/plugins/'); } -} \ No newline at end of file +} diff --git a/views/configureTab.blade.php b/views/configureTab.blade.php index a805a44..f0dce0b 100644 --- a/views/configureTab.blade.php +++ b/views/configureTab.blade.php @@ -1,16 +1,20 @@
@foreach(Seiger\sMultisite\Models\sMultisite::all() as $domain)
+
+
+
+
+
+ @if($domain->key != 'default')
- active == 1) checked @endif> - - + active) checked @endif> +
+
- hide_from_tree == 1) checked @endif> - - + hide_from_tree) checked @endif> +
@endif +
@endforeach @@ -91,7 +99,8 @@ - + @endpush diff --git a/views/index.blade.php b/views/index.blade.php index 9d43af6..2ad16e7 100644 --- a/views/index.blade.php +++ b/views/index.blade.php @@ -1,13 +1,20 @@ @extends('manager::template.page') + @section('content') -

@lang('sMultisite::global.title')

+

+ + @lang('sMultisite::global.title') +

- @lang('sMultisite::global.configure') + + + @lang('sMultisite::global.configure') +

@@ -18,25 +25,33 @@
@endsection + @push('scripts.top') @include('sMultisite::partials.style') @endpush + @push('scripts.bot') - + + @if(isset($_SESSION['sMultisite.refresh']) && $_SESSION['sMultisite.refresh']) + @unset($_SESSION['sMultisite.refresh']) + + @endif @endpush diff --git a/views/partials/style.blade.php b/views/partials/style.blade.php index 2843524..dfbdd5e 100644 --- a/views/partials/style.blade.php +++ b/views/partials/style.blade.php @@ -1,5 +1,5 @@