diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4f6c31a04..24356341b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
-* **Build 22x** (2015-03-xx)
+* **Build 226** (2015-03-16)
- Form Tabs now support specifying a default tab using the `defaultTab` option (see Backend > Forms docs).
- - Improved the Theme management features: Edit properties, download, duplicate and delete.
+ - Improved the Theme management features: Edit properties, import, export, duplicate and delete.
* **Build 222** (2015-03-11)
- Form fields can now use a simpler interface for using the Input preset converter (see Backend > Forms docs).
diff --git a/modules/backend/assets/js/october-min.js b/modules/backend/assets/js/october-min.js
index 17b58ce59..d960a8189 100644
--- a/modules/backend/assets/js/october-min.js
+++ b/modules/backend/assets/js/october-min.js
@@ -997,7 +997,7 @@ this.hide()
var indicator=$('
')
indicator.append($('
').text(this.options.text))
indicator.append($(' '))
-if(this.options.opaque!==undefined&&this.options.opaque){indicator.addClass('is-opaque')}
+if(this.options.opaque!==undefined){indicator.addClass('is-opaque')}
this.$el.prepend(indicator)
this.$el.addClass('in-progress')
this.tally++}
diff --git a/modules/backend/assets/js/october.loadindicator.js b/modules/backend/assets/js/october.loadindicator.js
index 3d45d8c11..9a87d2575 100644
--- a/modules/backend/assets/js/october.loadindicator.js
+++ b/modules/backend/assets/js/october.loadindicator.js
@@ -44,7 +44,7 @@
var indicator = $('
')
indicator.append($('
').text(this.options.text))
indicator.append($(' '))
- if (this.options.opaque !== undefined && this.options.opaque) {
+ if (this.options.opaque !== undefined) {
indicator.addClass('is-opaque')
}
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/backend/classes/NavigationManager.php b/modules/backend/classes/NavigationManager.php
index 1e9dc3769..17f1cd620 100644
--- a/modules/backend/classes/NavigationManager.php
+++ b/modules/backend/classes/NavigationManager.php
@@ -226,7 +226,7 @@ class NavigationManager
$this->items[$itemKey] = $item;
if ($sideMenu !== null) {
- $this->addSideMenuItems($sideMenu);
+ $this->addSideMenuItems($owner, $code, $sideMenu);
}
}
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..09d6df74f 100644
--- a/modules/cms/classes/theme/fields.yaml
+++ b/modules/cms/classes/theme/fields.yaml
@@ -7,33 +7,41 @@ tabs:
fields:
name:
- label: Name
- placeholder: New theme name
+ label: cms::lang.theme.name_label
+ placeholder: cms::lang.theme.name_create_placeholder
span: auto
+ required: true
attributes:
default-focus: 1
- directory_name:
- label: Directory name
+ dir_name@create:
+ label: cms::lang.theme.dir_name_label
+ placeholder: cms::lang.theme.dir_name_create_label
+ span: auto
+ preset: name
+ required: true
+
+ dir_name@update:
+ label: cms::lang.theme.dir_name_label
disabled: true
span: auto
description:
- label: Description
- placeholder: Theme description
+ label: cms::lang.theme.description_label
+ placeholder: cms::lang.theme.description_placeholder
type: textarea
size: tiny
author:
- label: Author
- placeholder: Person or company name
+ label: cms::lang.theme.author_label
+ placeholder: cms::lang.theme.author_placeholder
span: auto
homepage:
- label: Homepage
- placeholder: Website URL
+ label: cms::lang.theme.homepage_label
+ placeholder: cms::lang.theme.homepage_placeholder
span: auto
code:
- label: Code
- placeholder: A unique code for this theme used for distribution
+ label: cms::lang.theme.code_label
+ placeholder: cms::lang.theme.code_placeholder
diff --git a/modules/cms/controllers/Themes.php b/modules/cms/controllers/Themes.php
index 7baf9f946..4815dc37d 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(trans('cms::lang.theme.delete_active_theme_failed'));
+ }
+
+ $themePath = $theme->getPath();
+ if (File::isDirectory($themePath)) {
+ File::deleteDirectory($themePath);
+ }
+
+ Flash::success(trans('cms::lang.theme.delete_theme_success'));
+ 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' => trans('cms::lang.theme.create_theme_required_name')]);
+ }
+
+ if (!preg_match('/^[a-z0-9\_\-]+$/i', $newDirName)) {
+ throw new ValidationException(['dir_name' => trans('cms::lang.theme.dir_name_invalid')]);
+ }
+
+ if (File::isDirectory($destinationPath)) {
+ throw new ValidationException(['dir_name' => trans('cms::lang.theme.dir_name_taken')]);
+ }
+
+ 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(trans('cms::lang.theme.create_theme_success'));
+ 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' => trans('cms::lang.theme.dir_name_invalid')]);
+ }
+
+ if (File::isDirectory($destinationPath)) {
+ throw new ValidationException(['new_dir_name' => trans('cms::lang.theme.dir_name_taken')]);
+ }
+
+ File::copyDirectory($sourcePath, $destinationPath);
+ $newTheme = CmsTheme::load($newDirName);
+ $newName = $newTheme->getConfigValue('name') . ' - Copy';
+ $newTheme->writeConfig(['name' => $newName]);
+
+ Flash::success(trans('cms::lang.theme.duplicate_theme_success'));
+ 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(trans('cms::lang.theme.import_theme_success'));
+ 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..5ec6184b7
--- /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
+]) ?>
+
+
+
+ fatalError): ?>
+
+
+ = $widget->render() ?>
+
+
+
+
+
+
+
= e(trans($this->fatalError)) ?>
+
+
+
+
+
+
+
+= Form::close() ?>
diff --git a/modules/cms/controllers/themes/_theme_duplicate_form.htm b/modules/cms/controllers/themes/_theme_duplicate_form.htm
new file mode 100644
index 000000000..bcaa7512a
--- /dev/null
+++ b/modules/cms/controllers/themes/_theme_duplicate_form.htm
@@ -0,0 +1,75 @@
+= Form::ajax('onDuplicateTheme', [
+ 'id' => 'themeDuplicateForm',
+ 'data-popup-load-indicator' => true,
+]) ?>
+
+
+
+
+
+ fatalError): ?>
+
+
+
+
+
+ = e(trans('cms::lang.theme.new_directory_name_label')) ?>
+
+
+
+
+ = e(trans('cms::lang.theme.new_directory_name_comment')) ?>
+
+
+
+
+
+
+
+
+
+
= e(trans($this->fatalError)) ?>
+
+
+
+
+
+
+
+= Form::close() ?>
diff --git a/modules/cms/controllers/themes/_theme_export_form.htm b/modules/cms/controllers/themes/_theme_export_form.htm
new file mode 100644
index 000000000..8d3e77873
--- /dev/null
+++ b/modules/cms/controllers/themes/_theme_export_form.htm
@@ -0,0 +1,63 @@
+= Form::ajax('onExport', [
+ 'id' => 'themeExportForm',
+ 'data-popup-load-indicator' => true,
+ 'data-request-success' => 'closeExportThemePopup()'
+]) ?>
+
+
+
+
+
+ fatalError): ?>
+
+
+ = $widget->render() ?>
+
+
+
+
+
+
+
= e(trans($this->fatalError)) ?>
+
+
+
+
+
+
+
+= Form::close() ?>
diff --git a/modules/cms/controllers/themes/_theme_fields_form.htm b/modules/cms/controllers/themes/_theme_fields_form.htm
index 39586b2a9..eb8ea1e56 100644
--- a/modules/cms/controllers/themes/_theme_fields_form.htm
+++ b/modules/cms/controllers/themes/_theme_fields_form.htm
@@ -1,4 +1,4 @@
-= Form::ajax('onSaveThemeFields', [
+= Form::ajax('onSaveFields', [
'id' => 'themeFieldsForm',
'data-popup-load-indicator' => true
]) ?>
@@ -7,7 +7,7 @@
fatalError): ?>
@@ -19,7 +19,7 @@
- Save properties
+ = e(trans('cms::lang.theme.save_properties')) ?>
'themeImportForm',
+ 'data-popup-load-indicator' => true,
+]) ?>
+
+
+
+
+
+
+ fatalError): ?>
+
+
+ = $widget->render() ?>
+
+
+
+
+
+
+
= e(trans($this->fatalError)) ?>
+
+
+
+
+
+
+
+= Form::close() ?>
diff --git a/modules/cms/controllers/themes/_theme_list.htm b/modules/cms/controllers/themes/_theme_list.htm
index a221e4b92..f28a3f298 100644
--- a/modules/cms/controllers/themes/_theme_list.htm
+++ b/modules/cms/controllers/themes/_theme_list.htm
@@ -14,6 +14,15 @@
diff --git a/modules/cms/controllers/themes/download.htm b/modules/cms/controllers/themes/download.htm
new file mode 100644
index 000000000..e67bf3bd4
--- /dev/null
+++ b/modules/cms/controllers/themes/download.htm
@@ -0,0 +1,13 @@
+
+
+ Themes
+ = e(trans($this->pageTitle)) ?>
+
+
+
+fatalError): ?>
+
+ = e($this->fatalError) ?>
+ = e(trans('cms::lang.theme.return')) ?>
+
+
\ No newline at end of file
diff --git a/modules/cms/controllers/themes/update.htm b/modules/cms/controllers/themes/update.htm
index 104ad9e44..ac53f7f41 100644
--- a/modules/cms/controllers/themes/update.htm
+++ b/modules/cms/controllers/themes/update.htm
@@ -54,6 +54,6 @@
= e($this->fatalError) ?>
- Return to themes list
+ = e(trans('cms::lang.theme.return')) ?>
\ No newline at end of file
diff --git a/modules/cms/lang/en/lang.php b/modules/cms/lang/en/lang.php
index f046c6f8d..3fe002fe2 100644
--- a/modules/cms/lang/en/lang.php
+++ b/modules/cms/lang/en/lang.php
@@ -25,10 +25,57 @@ return [
],
'settings_menu' => 'Front-end theme',
'settings_menu_description' => 'Preview the list of installed themes and select an active theme.',
- 'find_more_themes' => 'Find more themes on OctoberCMS Theme Marketplace.',
+ 'name_label' => 'Name',
+ 'name_create_placeholder' => 'New theme name',
+ 'author_label' => 'Author',
+ 'author_placeholder' => 'Person or company name',
+ 'description_label' => 'Description',
+ 'description_placeholder' => 'Theme description',
+ 'homepage_label' => 'Homepage',
+ 'homepage_placeholder' => 'Website URL',
+ 'code_label' => 'Code',
+ 'code_placeholder' => 'A unique code for this theme used for distribution',
+ 'dir_name_label' => 'Directory name',
+ 'dir_name_create_label' => 'The destination theme directory',
+ 'theme_label' => 'Theme',
'activate_button' => 'Activate',
'active_button' => 'Activate',
- 'customize_button' => 'Customize'
+ 'customize_button' => 'Customize',
+ 'duplicate_button' => 'Duplicate',
+ 'duplicate_title' => 'Duplicate theme',
+ 'duplicate_theme_success' => 'Duplicated theme successfully!',
+ 'manage_button' => 'Manage',
+ 'manage_title' => 'Manage theme',
+ 'edit_properties_title' => 'Theme',
+ 'edit_properties_button' => 'Edit properties',
+ 'save_properties' => 'Save properties',
+ 'import_button' => 'Import',
+ 'import_title' => 'Import theme',
+ 'import_theme_success' => 'Imported theme successfully!',
+ 'import_uploaded_file' => 'Theme archive file',
+ 'import_overwrite_label' => 'Overwrite existing files',
+ 'import_overwrite_comment' => 'Untick this box to only import new files',
+ 'import_folders_label' => 'Folders',
+ 'import_folders_comment' => 'Please select the theme folders you would like to import',
+ 'export_button' => 'Export',
+ 'export_title' => 'Export theme',
+ 'export_folders_label' => 'Folders',
+ 'export_folders_comment' => 'Please select the theme folders you would like to import',
+ 'delete_button' => 'Delete',
+ 'delete_confirm' => 'Are you sure you want to delete this theme? It cannot be undone!',
+ 'delete_active_theme_failed' => 'Cannot delete the active theme, try making another theme active first.',
+ 'delete_theme_success' => 'Deleted theme successfully!',
+ 'create_title' => 'Create theme',
+ 'create_button' => 'Create',
+ 'create_new_blank_theme' => 'Create a new blank theme',
+ 'create_theme_success' => 'Created theme successfully!',
+ 'create_theme_required_name' => 'Please specify a name for the theme.',
+ 'new_directory_name_label' => 'Theme directory',
+ 'new_directory_name_comment' => 'Provide a new directory name for the duplicated theme.',
+ 'dir_name_invalid' => 'Name can contain only digits, Latin letters and the following symbols: _-',
+ 'dir_name_taken' => 'Desired theme directory already exists.',
+ 'find_more_themes' => 'Find more themes on OctoberCMS Theme Marketplace',
+ 'return' => 'Return to themes list',
],
'maintenance' => [
'settings_menu' => 'Maintenance mode',
diff --git a/modules/cms/models/ThemeExport.php b/modules/cms/models/ThemeExport.php
new file mode 100644
index 000000000..dc9247f25
--- /dev/null
+++ b/modules/cms/models/ThemeExport.php
@@ -0,0 +1,141 @@
+ null,
+ 'themeName' => null,
+ 'dirName' => null,
+ 'folders' => [
+ 'assets' => true,
+ 'pages' => true,
+ 'layouts' => true,
+ 'partials' => true,
+ 'content' => true,
+ ]
+ ];
+
+ public function getFoldersOptions()
+ {
+ return [
+ 'assets' => 'Assets',
+ 'pages' => 'Pages',
+ 'layouts' => 'Layouts',
+ 'partials' => 'Partials',
+ 'content' => 'Content',
+ ];
+ }
+
+ public function setThemeAttribute($theme)
+ {
+ if (!$theme instanceof CmsTheme) return;
+
+ $this->attributes['themeName'] = $theme->getConfigValue('name', $theme->getDirName());
+ $this->attributes['dirName'] = $theme->getDirName();
+ $this->attributes['theme'] = $theme;
+ }
+
+ public function export($theme, $data = [])
+ {
+ $this->theme = $theme;
+ $this->fill($data);
+
+ try {
+ $themePath = $this->theme->getPath();
+ $tempPath = temp_path() . '/'.uniqid('oc');
+ $zipName = uniqid('oc');
+ $zipPath = temp_path().'/'.$zipName;
+
+ if (!@mkdir($tempPath))
+ throw new ApplicationException('Unable to create directory '.$tempPath);
+
+ if (!@mkdir($metaPath = $tempPath . '/meta'))
+ throw new ApplicationException('Unable to create directory '.$metaPath);
+
+ File::copy($themePath.'/theme.yaml', $tempPath.'/theme.yaml');
+ File::copyDirectory($themePath.'/meta', $metaPath);
+
+ foreach ($this->folders as $folder) {
+ if (!array_key_exists($folder, $this->getFoldersOptions())) continue;
+ File::copyDirectory($themePath.'/'.$folder, $tempPath.'/'.$folder);
+ }
+
+ Zip::make($zipPath, $tempPath);
+ File::deleteDirectory($tempPath);
+ }
+ catch (Exception $ex) {
+
+ if (strlen($tempPath) && File::isDirectory($tempPath)) {
+ File::deleteDirectory($tempPath);
+ }
+
+ if (strlen($zipPath) && File::isFile($zipPath)) {
+ File::delete($zipPath);
+ }
+
+ throw $ex;
+ }
+
+ return $zipName;
+ }
+
+ public static function download($name, $outputName = null)
+ {
+ if (!preg_match('/^oc[0-9a-z]*$/i', $name)) {
+ throw new ApplicationException('File not found');
+ }
+
+ $zipPath = temp_path() . '/' . $name;
+ if (!file_exists($zipPath)) {
+ throw new ApplicationException('File not found');
+ }
+
+ $headers = Response::download($zipPath, $outputName)->headers->all();
+ $result = Response::make(File::get($zipPath), 200, $headers);
+
+ @unlink($zipPath);
+
+ return $result;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/cms/models/ThemeImport.php b/modules/cms/models/ThemeImport.php
new file mode 100644
index 000000000..5e9fa034f
--- /dev/null
+++ b/modules/cms/models/ThemeImport.php
@@ -0,0 +1,184 @@
+ ['System\Models\File']
+ ];
+
+ /**
+ * @var array Make the model's attributes public so behaviors can modify them.
+ */
+ public $attributes = [
+ 'theme' => null,
+ 'themeName' => null,
+ 'dirName' => null,
+ 'overwrite' => true,
+ 'folders' => [
+ 'assets' => true,
+ 'pages' => true,
+ 'layouts' => true,
+ 'partials' => true,
+ 'content' => true,
+ ]
+ ];
+
+ public function getFoldersOptions()
+ {
+ return [
+ 'assets' => 'Assets',
+ 'pages' => 'Pages',
+ 'layouts' => 'Layouts',
+ 'partials' => 'Partials',
+ 'content' => 'Content',
+ ];
+ }
+
+ public function setThemeAttribute($theme)
+ {
+ if (!$theme instanceof CmsTheme) return;
+
+ $this->attributes['themeName'] = $theme->getConfigValue('name', $theme->getDirName());
+ $this->attributes['dirName'] = $theme->getDirName();
+ $this->attributes['theme'] = $theme;
+ }
+
+ public function import($theme, $data = [], $sessionKey = null)
+ {
+ @set_time_limit(3600);
+
+ $this->theme = $theme;
+ $this->fill($data);
+
+ try
+ {
+ $file = $this->uploaded_file()->withDeferred($sessionKey)->first();
+ if (!$file) {
+ throw new ApplicationException('There is no file attached to import!');
+ }
+
+ $themePath = $this->theme->getPath();
+ $tempPath = temp_path() . '/'.uniqid('oc');
+ $zipName = uniqid('oc');
+ $zipPath = temp_path().'/'.$zipName;
+
+ File::put($zipPath, $file->getContents());
+
+ if (!@mkdir($tempPath))
+ throw new ApplicationException('Unable to create directory '.$tempPath);
+
+ Zip::extract($zipPath, $tempPath);
+
+ // if (File::isFile($tempPath.'/theme.yaml')) {
+ // File::copy($tempPath.'/theme.yaml', $themePath.'/theme.yaml');
+ // }
+
+ if (File::isDirectory($tempPath.'/meta')) {
+ $this->copyDirectory($tempPath.'/meta', $themePath.'/meta');
+ }
+
+ foreach ($this->folders as $folder) {
+ if (!array_key_exists($folder, $this->getFoldersOptions())) continue;
+ $this->copyDirectory($tempPath.'/'.$folder, $themePath.'/'.$folder);
+ }
+
+ File::deleteDirectory($tempPath);
+ File::delete($zipPath);
+ $file->delete();
+ }
+ catch (Exception $ex) {
+
+ if (!empty($tempPath) && File::isDirectory($tempPath)) {
+ File::deleteDirectory($tempPath);
+ }
+
+ if (!empty($zipPath) && File::isFile($zipPath)) {
+ File::delete($zipPath);
+ }
+
+ throw $ex;
+ }
+ }
+
+ /**
+ * Helper for copying directories that supports the ability
+ * to not overwrite existing files. Inherited from File::copyDirectory
+ *
+ * @param string $directory
+ * @param string $destination
+ * @return bool
+ */
+ protected function copyDirectory($directory, $destination)
+ {
+ // Preference is to overwrite existing files
+ if ($this->overwrite) {
+ return File::copyDirectory($directory, $destination);
+ }
+
+ if (!File::isDirectory($directory)) return false;
+
+ $options = FilesystemIterator::SKIP_DOTS;
+
+ if (!File::isDirectory($destination)) {
+ File::makeDirectory($destination, 0777, true);
+ }
+
+ $items = new FilesystemIterator($directory, $options);
+
+ foreach ($items as $item) {
+ $target = $destination.'/'.$item->getBasename();
+
+ if ($item->isDir()) {
+ $path = $item->getPathname();
+
+ if (!$this->copyDirectory($path, $target)) return false;
+ }
+ else {
+ // Do not overwrite existing files
+ if (File::isFile($target)) continue;
+
+ if (!File::copy($item->getPathname(), $target)) return false;
+ }
+ }
+
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/modules/cms/models/themeexport/fields.yaml b/modules/cms/models/themeexport/fields.yaml
new file mode 100644
index 000000000..5a9db2ced
--- /dev/null
+++ b/modules/cms/models/themeexport/fields.yaml
@@ -0,0 +1,14 @@
+# ===================================
+# Field Definitions
+# ===================================
+
+fields:
+
+ themeName:
+ label: cms::lang.theme.theme_label
+ disabled: true
+
+ folders:
+ label: cms::lang.theme.export_folders_label
+ commentAbove: cms::lang.theme.export_folders_comment
+ type: checkboxlist
diff --git a/modules/cms/models/themeimport/fields.yaml b/modules/cms/models/themeimport/fields.yaml
new file mode 100644
index 000000000..bc996eb94
--- /dev/null
+++ b/modules/cms/models/themeimport/fields.yaml
@@ -0,0 +1,25 @@
+# ===================================
+# Field Definitions
+# ===================================
+
+fields:
+
+ themeName:
+ label: cms::lang.theme.theme_label
+ disabled: true
+
+ uploaded_file:
+ label: cms::lang.theme.import_uploaded_file
+ type: fileupload
+ mode: file
+ fileTypes: zip
+
+ overwrite:
+ label: cms::lang.theme.import_overwrite_label
+ comment: cms::lang.theme.import_overwrite_comment
+ type: checkbox
+
+ folders:
+ label: cms::lang.theme.import_folders_label
+ commentAbove: cms::lang.theme.import_folders_comment
+ type: checkboxlist
diff --git a/modules/system/lang/hu/lang.php b/modules/system/lang/hu/lang.php
index 1e240e7df..4d016d31b 100644
--- a/modules/system/lang/hu/lang.php
+++ b/modules/system/lang/hu/lang.php
@@ -13,6 +13,7 @@ return [
'fa' => 'Perzsa',
'fr' => 'Francia',
'hu' => 'Magyar',
+ 'id' => 'Indonéz',
'it' => 'Olasz',
'ja' => 'Japán',
'nl' => 'Holland',
@@ -160,7 +161,7 @@ return [
],
'updates' => [
'title' => 'Frissítések kezelése',
- 'name' => 'Szoftverfrissítés',
+ 'name' => 'Szoftver frissítése',
'menu_label' => 'Frissítések',
'menu_description' => 'A rendszer és a bővítmények frissítése, valamint új kiegészítők telepítése.',
'check_label' => 'Frissítések keresése',
@@ -173,9 +174,10 @@ return [
'core_build_old' => 'Jelenlegi verzió: :build',
'core_build_new' => 'Verzió: :build',
'core_build_new_help' => 'Elérhető a legújabb hivatalos kiadás.',
- 'core_downloading' => 'Alkalmazásfájlok letöltése...',
- 'core_extracting' => 'Alkalmazásfájlok kicsomagolása...',
+ 'core_downloading' => 'Alkalmazás fájlok letöltése...',
+ 'core_extracting' => 'Alkalmazás fájlok kicsomagolása...',
'plugins' => 'Bővítmények',
+ 'disabled' => 'Letiltva',
'plugin_downloading' => 'Bővítmény letöltése: :name',
'plugin_extracting' => 'Bővítmény kicsomagolása: :name',
'plugin_version_none' => 'Új bővítmény',