diff --git a/library/Tiger/Theme.php b/library/Tiger/Theme.php index b0e50a6..33a076d 100644 --- a/library/Tiger/Theme.php +++ b/library/Tiger/Theme.php @@ -231,14 +231,19 @@ public static function active() * @return array{slug:string,key:string,asset_base:string} * @throws RuntimeException when no theme with that slug is on disk */ - public static function activate($slug) + public static function activate($slug, $makeDefault = true) { $d = self::_discovered($slug); $key = (string) ($d['key'] ?? preg_replace('/^theme-/', '', $slug)); $base = ((string) ($d['asset_base'] ?? '')) !== '' ? (string) $d['asset_base'] : '/_' . $key; - (new Tiger_Model_Config())->set(Tiger_Model_Config::SCOPE_GLOBAL, '', 'tiger.theme', $key); + // Multiple themes can be active at once (their assets published, their layouts available); only + // ONE is the DEFAULT site theme (`tiger.theme`). Setting the default is opt-in, so activating a + // theme never hijacks the site — the Module manager's "make default" checkbox drives $makeDefault. + if ($makeDefault) { + (new Tiger_Model_Config())->set(Tiger_Model_Config::SCOPE_GLOBAL, '', 'tiger.theme', $key); + } self::_linkAssets($slug, $base, (string) ($d['area'] ?? 'app')); - return ['slug' => $slug, 'key' => $key, 'asset_base' => $base]; + return ['slug' => $slug, 'key' => $key, 'asset_base' => $base, 'default' => (bool) $makeDefault]; } /** diff --git a/modules/system/controllers/ModulesController.php b/modules/system/controllers/ModulesController.php index f78a0ad..7a1daa6 100644 --- a/modules/system/controllers/ModulesController.php +++ b/modules/system/controllers/ModulesController.php @@ -35,8 +35,10 @@ public function indexAction() foreach (Tiger_Module_Discovery::all() as $slug => $m) { $row = $installed[$slug] ?? null; $isTheme = ($m['type'] ?? 'module') === 'theme'; - // A theme's active state is the tiger.theme config (its KEY, one per scope); a module's is its flag. - $active = $isTheme ? ($activeTheme === ($m['key'] ?? $slug)) : ($row ? ((int) $row->active === 1) : true); + // Active is the module FLAG for everything now, themes included — multiple themes can be + // active at once. WHICH theme is the DEFAULT site theme is the separate `tiger.theme` config. + $active = $row ? ((int) $row->active === 1) : true; + $isDefault = $isTheme && $activeTheme !== '' && $activeTheme === (string) ($m['key'] ?? $slug); $source = $row ? $row->source : ($m['area'] === 'core' ? 'bundled' : 'custom'); // Taxonomy resolution (AUTHORING.md): the value STORED at install (retained from the source // listing/manifest) wins; else the live manifest that Discovery read; else its default. Read @@ -49,6 +51,7 @@ public function indexAction() $m['protected'] = !empty($m['protected']) || in_array($slug, System_Service_Modules::PROTECTED, true); $modules[] = $m + [ 'active' => $active, + 'is_default' => $isDefault, 'source' => $source, // Advisory: tested-version compat notice (never blocks) + who requires this module // (drives the "required by X, Y — deactivate anyway?" confirm; empty for most). diff --git a/modules/system/languages/en/system.php b/modules/system/languages/en/system.php index 122cfbf..c493cc9 100644 --- a/modules/system/languages/en/system.php +++ b/modules/system/languages/en/system.php @@ -150,6 +150,8 @@ 'system.modules.source_addon' => 'addon', 'system.modules.status_active' => 'Active', 'system.modules.status_inactive' => 'Inactive', + 'system.modules.default_theme' => 'Default', + 'system.modules.default_theme_title' => 'The default site theme — the chrome for the home page and any page that does not pick its own layout.', 'system.modules.always_on' => 'always on', 'system.modules.deactivate' => 'Deactivate', 'system.modules.activate' => 'Activate', @@ -390,8 +392,9 @@ 'system.js.deactivate_ok' => 'Deactivate', 'system.js.deactivate_required' => 'This module is required by %1$s to function. Deactivate anyway?', 'system.js.deactivate_theme' => 'Deactivate the "%1$s" theme? The public site reverts to the default theme.', - 'system.js.activate_theme_multi_title' => 'Multiple active themes', - 'system.js.activate_theme_multi' => 'Another theme is already active. Activating more than one theme at a time can have unpredictable results — unless the theme is designed to work alongside others. Activate anyway?', + 'system.js.activate_theme_title' => 'Activate theme', + 'system.js.activate_theme_body' => 'Activate the "%s" theme? Its layouts become available to your pages, and any other active themes stay active.', + 'system.js.activate_theme_make_default' => 'Make "%s" the default site theme', 'system.js.deactivate_confirm' => 'Deactivate "%1$s"? Its routes stop working until you reactivate it. Nothing is deleted.', 'system.js.confirm_install_title' => 'Install module', 'system.js.confirm_install_body' => 'This module’s code will run in your app once active.', diff --git a/modules/system/services/Modules.php b/modules/system/services/Modules.php index 0f1afee..ce606b1 100644 --- a/modules/system/services/Modules.php +++ b/modules/system/services/Modules.php @@ -104,13 +104,9 @@ public function delete(array $params): void } } - /** Whether a module is currently active — a theme by its `tiger.theme` config, else its registry flag. */ + /** Whether a module is currently active — the registry flag, themes included (multiple can be active). */ private function _isModuleActive(string $slug, array $d): bool { - if (($d['type'] ?? '') === 'theme') { - $active = (string) (new Tiger_Model_Config())->get(Tiger_Model_Config::SCOPE_GLOBAL, '', 'tiger.theme'); - return $active === (string) ($d['key'] ?? $slug); - } $row = (new Tiger_Model_Module())->bySlug($slug); return $row ? ((int) $row->active === 1) : true; } @@ -172,7 +168,7 @@ protected function _toggle(array $params, $on): void // Themes activate differently (THEMES.md §5a): not the module.active flag, but the // `tiger.theme` config (one active per scope) + the asset-base symlink. No build/deploy. if (($d['type'] ?? 'module') === 'theme') { - $this->_toggleTheme($slug, $d, $on); + $this->_toggleTheme($slug, $d, $on, $params); return; } @@ -198,19 +194,30 @@ protected function _toggle(array $params, $on): void } /** - * Activate/deactivate a THEME (THEMES.md §5a) — through the one authority, Tiger_Theme::activate() - * / deactivate(), which the headless installer calls too. Activation writes `tiger.theme` and links - * the theme's assets; deactivation clears the config back to the platform base theme. + * Activate/deactivate a THEME (THEMES.md §5a). Multiple themes can be active at once — activation is + * the module active FLAG plus publishing the theme's assets. Making it the DEFAULT site theme + * (`tiger.theme`) is opt-in: only when `make_default` is set (the Module manager's checkbox), so + * activating a theme never silently hijacks the site. Deactivation clears the flag and, if this was + * the default, `Tiger_Theme::deactivate` clears `tiger.theme` (home falls back to blank). * - * @param string $slug the theme slug - * @param array $d its discovery row (unused here; kept for the caller's signature) - * @param bool $on activate (true) or deactivate (false) + * @param string $slug the theme slug + * @param array $d its discovery row (name/version for the module row) + * @param bool $on activate (true) or deactivate (false) + * @param array $params the /api payload (reads `make_default`) * @return void */ - protected function _toggleTheme($slug, array $d, $on): void - { - if ($on) { Tiger_Theme::activate($slug); } - else { Tiger_Theme::deactivate($slug); } + protected function _toggleTheme($slug, array $d, $on, array $params = []): void + { + $model = new Tiger_Model_Module(); + $meta = ['name' => $d['name'] ?? $slug, 'version' => $d['version'] ?? null]; + if ($on) { + $makeDefault = ((string) ($params['make_default'] ?? '')) === '1'; + $model->setActive($slug, true, $meta); + Tiger_Theme::activate($slug, $makeDefault); + } else { + $model->setActive($slug, false, $meta); + Tiger_Theme::deactivate($slug); + } $this->_success( ['slug' => $slug, 'theme' => true, 'active' => (bool) $on], $on ? 'system.theme.activated' : 'system.theme.deactivated', diff --git a/modules/system/views/scripts/modules/index.phtml b/modules/system/views/scripts/modules/index.phtml index 522e849..e1de4a6 100644 --- a/modules/system/views/scripts/modules/index.phtml +++ b/modules/system/views/scripts/modules/index.phtml @@ -105,6 +105,7 @@ $typeIcon = static function ($t) {