diff --git a/modules/backend/classes/Controller.php b/modules/backend/classes/Controller.php index 3fba8270a..7638d61a8 100644 --- a/modules/backend/classes/Controller.php +++ b/modules/backend/classes/Controller.php @@ -317,7 +317,8 @@ class Controller extends Extendable // Execute the action $result = call_user_func_array([$this, $actionName], $parameters); - if ($result instanceof RedirectResponse) { + // Expecting \Response and \RedirectResponse + if ($result instanceof \Symfony\Component\HttpFoundation\Response) { return $result; } @@ -402,10 +403,10 @@ class Controller extends Extendable */ if ($result instanceof RedirectResponse) { $responseContents['X_OCTOBER_REDIRECT'] = $result->getTargetUrl(); + } /* * No redirect is used, look for any flash messages */ - } elseif (Flash::check()) { $responseContents['#layout-flash-messages'] = $this->makeLayoutPartial('flash_messages'); } diff --git a/modules/cms/assets/css/october.theme-selector.css b/modules/cms/assets/css/october.theme-selector.css index 6cf246c36..0b2f99cbb 100644 --- a/modules/cms/assets/css/october.theme-selector.css +++ b/modules/cms/assets/css/october.theme-selector.css @@ -95,6 +95,10 @@ .theme-selector-layout .layout-row.links .theme-description { border-bottom: 1px solid #f2f3f4; } +.theme-selector-layout .create-new-theme { + margin-bottom: 10px; +} +.theme-selector-layout .create-new-theme, .theme-selector-layout .find-more-themes { background: #ecf0f1; color: #2b3e50; @@ -105,6 +109,7 @@ -moz-border-radius: 4px; border-radius: 4px; } +.theme-selector-layout .create-new-theme:hover, .theme-selector-layout .find-more-themes:hover { background: #1795f1; color: white; diff --git a/modules/cms/assets/less/october.theme-selector.less b/modules/cms/assets/less/october.theme-selector.less index ee4bc042b..7e0a6cb23 100644 --- a/modules/cms/assets/less/october.theme-selector.less +++ b/modules/cms/assets/less/october.theme-selector.less @@ -112,6 +112,11 @@ } } + .create-new-theme { + margin-bottom: 10px; + } + + .create-new-theme, .find-more-themes { background: #ecf0f1; color: #2b3e50; diff --git a/modules/cms/classes/Theme.php b/modules/cms/classes/Theme.php index b66c65cc5..01bdc3780 100644 --- a/modules/cms/classes/Theme.php +++ b/modules/cms/classes/Theme.php @@ -287,7 +287,7 @@ class Theme public function writeConfig($values = [], $overwrite = false) { if (!$overwrite) { - $values = $values + $this->getConfig(); + $values = $values + (array) $this->getConfig(); } $path = $this->getPath().'/theme.yaml'; diff --git a/modules/cms/classes/theme/fields.yaml b/modules/cms/classes/theme/fields.yaml index 408ee75fd..cd79ba0a2 100644 --- a/modules/cms/classes/theme/fields.yaml +++ b/modules/cms/classes/theme/fields.yaml @@ -10,10 +10,18 @@ tabs: label: Name placeholder: New theme name span: auto + required: true attributes: default-focus: 1 - directory_name: + dir_name@create: + label: Directory name + span: auto + placeholder: The destination theme directory + preset: name + required: true + + dir_name@update: label: Directory name disabled: true span: auto diff --git a/modules/cms/controllers/Themes.php b/modules/cms/controllers/Themes.php index 7baf9f946..92978349e 100644 --- a/modules/cms/controllers/Themes.php +++ b/modules/cms/controllers/Themes.php @@ -1,15 +1,20 @@ pageTitle = 'cms::lang.theme.settings_menu'; BackendMenu::setContext('October.System', 'system', 'settings'); SettingsManager::setContext('October.Cms', 'theme'); + + /* + * Enable AJAX for Form widgets + */ + if (post('mode') == 'import') { + $this->makeImportFormWidget($this->findThemeObject())->bindToController(); + } } public function index() @@ -57,41 +69,153 @@ class Themes extends Controller ]; } + public function index_onDelete() + { + $theme = $this->findThemeObject(); + + if ($theme->isActiveTheme()) { + throw new ApplicationException('Cannot delete the active theme, try making another theme active first.'); + } + + $themePath = $theme->getPath(); + if (File::isDirectory($themePath)) { + File::deleteDirectory($themePath); + } + + Flash::success('Deleted theme successfully!'); + return Redirect::refresh(); + } + // // Theme properties // - public function index_onLoadThemeFieldsForm() + public function index_onLoadFieldsForm() { $theme = $this->findThemeObject(); - $this->vars['widget'] = $this->makeThemeFieldsFormWidget($theme); + $this->vars['widget'] = $this->makeFieldsFormWidget($theme); $this->vars['themeDir'] = $theme->getDirName(); return $this->makePartial('theme_fields_form'); } - public function index_onSaveThemeFields() + public function index_onSaveFields() { $theme = $this->findThemeObject(); - $widget = $this->makeThemeFieldsFormWidget($theme); + $widget = $this->makeFieldsFormWidget($theme); $theme->writeConfig($widget->getSaveData()); return ['#themeListItem-'.$theme->getId() => $this->makePartial('theme_list_item', ['theme' => $theme])]; } - protected function makeThemeFieldsFormWidget($theme) + protected function makeFieldsFormWidget($theme) { $widgetConfig = $this->makeConfig('~/modules/cms/classes/theme/fields.yaml'); $widgetConfig->alias = 'form'.studly_case($theme->getDirName()); $widgetConfig->model = $theme; $widgetConfig->data = $theme->getConfig(); - $widgetConfig->data['directory_name'] = $theme->getDirName(); + $widgetConfig->data['dir_name'] = $theme->getDirName(); $widgetConfig->arrayName = 'Theme'; + $widgetConfig->context = 'update'; $widget = $this->makeWidget('Backend\Widgets\Form', $widgetConfig); return $widget; } + // + // Create theme + // + + public function index_onLoadCreateForm() + { + $this->vars['widget'] = $this->makeCreateFormWidget(); + return $this->makePartial('theme_create_form'); + } + + public function index_onCreate() + { + $widget = $this->makeCreateFormWidget(); + $data = $widget->getSaveData(); + $newDirName = trim(array_get($data, 'dir_name')); + $destinationPath = themes_path().'/'.$newDirName; + + $data = array_except($data, 'dir_name'); + + if (!strlen(trim(array_get($data, 'name')))) { + throw new ValidationException(['name' => 'Please specify a name for the theme.']); + } + + if (!preg_match('/^[a-z0-9\_\-]+$/i', $newDirName)) { + throw new ValidationException(['dir_name' => 'Name can contain only digits, Latin letters and the following symbols: _-']); + } + + if (File::isDirectory($destinationPath)) { + throw new ValidationException(['dir_name' => 'Desired theme directory already exists.']); + } + + File::makeDirectory($destinationPath); + File::makeDirectory($destinationPath.'/assets'); + File::makeDirectory($destinationPath.'/content'); + File::makeDirectory($destinationPath.'/layouts'); + File::makeDirectory($destinationPath.'/pages'); + File::makeDirectory($destinationPath.'/partials'); + File::put($destinationPath.'/theme.yaml', ''); + + $theme = CmsTheme::load($newDirName); + $theme->writeConfig($data); + + Flash::success('Created theme successfully!'); + return Redirect::refresh(); + } + + protected function makeCreateFormWidget() + { + $widgetConfig = $this->makeConfig('~/modules/cms/classes/theme/fields.yaml'); + $widgetConfig->alias = 'formCreateTheme'; + $widgetConfig->model = new CmsTheme; + $widgetConfig->arrayName = 'Theme'; + $widgetConfig->context = 'create'; + + $widget = $this->makeWidget('Backend\Widgets\Form', $widgetConfig); + return $widget; + } + + // + // Duplicate + // + + public function index_onLoadDuplicateForm() + { + $theme = $this->findThemeObject(); + $this->vars['themeDir'] = $theme->getDirName(); + + return $this->makePartial('theme_duplicate_form'); + } + + public function index_onDuplicateTheme() + { + $theme = $this->findThemeObject(); + $newDirName = trim(post('new_dir_name')); + $sourcePath = $theme->getPath(); + $destinationPath = themes_path().'/'.$newDirName; + + if (!preg_match('/^[a-z0-9\_\-]+$/i', $newDirName)) { + throw new ValidationException(['new_dir_name' => 'Name can contain only digits, Latin letters and the following symbols: _-']); + } + + if (File::isDirectory($destinationPath)) { + throw new ValidationException(['new_dir_name' => 'Duplicate theme directory already exists.']); + } + + File::copyDirectory($sourcePath, $destinationPath); + $newTheme = CmsTheme::load($newDirName); + $newName = $newTheme->getConfigValue('name') . ' - Copy'; + $newTheme->writeConfig(['name' => $newName]); + + Flash::success('Duplicated theme successfully!'); + return Redirect::refresh(); + } + // // Theme customization // @@ -149,6 +273,90 @@ class Themes extends Controller } } + // + // Theme export + // + + public function index_onLoadExportForm() + { + $theme = $this->findThemeObject(); + $this->vars['widget'] = $this->makeExportFormWidget($theme); + $this->vars['themeDir'] = $theme->getDirName(); + + return $this->makePartial('theme_export_form'); + } + + public function index_onExport() + { + $theme = $this->findThemeObject(); + $widget = $this->makeExportFormWidget($theme); + + $model = new ThemeExport; + $file = $model->export($theme, $widget->getSaveData()); + + return Backend::redirect('cms/themes/download/'.$file.'/'.$theme->getDirName().'.zip'); + } + + public function download($name, $outputName = null) + { + try { + $this->pageTitle = 'Download theme export archive'; + return ThemeExport::download($name, $outputName); + } + catch (Exception $ex) { + $this->handleError($ex); + } + } + + protected function makeExportFormWidget($theme) + { + $widgetConfig = $this->makeConfig('~/modules/cms/models/themeexport/fields.yaml'); + $widgetConfig->alias = 'form'.studly_case($theme->getDirName()); + $widgetConfig->model = new ThemeExport; + $widgetConfig->model->theme = $theme; + $widgetConfig->arrayName = 'ThemeExport'; + + $widget = $this->makeWidget('Backend\Widgets\Form', $widgetConfig); + return $widget; + } + + // + // Theme import + // + + public function index_onLoadImportForm() + { + $theme = $this->findThemeObject(); + $this->vars['widget'] = $this->makeImportFormWidget($theme); + $this->vars['themeDir'] = $theme->getDirName(); + + return $this->makePartial('theme_import_form'); + } + + public function index_onImport() + { + $theme = $this->findThemeObject(); + $widget = $this->makeImportFormWidget($theme); + + $model = new ThemeImport; + $model->import($theme, $widget->getSaveData(), $widget->getSessionKey()); + + Flash::success('Imported theme successfully!'); + return Redirect::refresh(); + } + + protected function makeImportFormWidget($theme) + { + $widgetConfig = $this->makeConfig('~/modules/cms/models/themeimport/fields.yaml'); + $widgetConfig->alias = 'form'.studly_case($theme->getDirName()); + $widgetConfig->model = new ThemeImport; + $widgetConfig->model->theme = $theme; + $widgetConfig->arrayName = 'ThemeImport'; + + $widget = $this->makeWidget('Backend\Widgets\Form', $widgetConfig); + return $widget; + } + // // Helpers // diff --git a/modules/cms/controllers/themes/_theme_create_form.htm b/modules/cms/controllers/themes/_theme_create_form.htm new file mode 100644 index 000000000..ab0972bc5 --- /dev/null +++ b/modules/cms/controllers/themes/_theme_create_form.htm @@ -0,0 +1,54 @@ += Form::ajax('onCreate', [ + 'id' => 'themeCreateForm', + 'data-popup-load-indicator' => true +]) ?> + +
+ Provide a new directory name for the duplicated theme. +
+