diff --git a/CHANGELOG.md b/CHANGELOG.md index 15ae281d8..1f463a17f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ -* **Build 125** (2014-07-xx) +* **Build 125** (2014-07-24) + - Theme support added. - Added new Theme picker to the backend via Settings > Front-end theme - New shorthand method for `$this->getClassExtension('Backend.Behaviors.FormController')` becomes `$this->asExtension('FormController')`. - Buttons inside a popup support new `data-popup-load-indicator` attribute. + - Added a new config item to disable core updates completely (see config cms.disableCoreUpdates). + - Added a unique alternate favicon to the Back-end area. * **Build 124** (2014-07-17) - Improvements to Twig functions and filters. diff --git a/app/config/cms.php b/app/config/cms.php index 239b96556..a0d940450 100644 --- a/app/config/cms.php +++ b/app/config/cms.php @@ -33,6 +33,20 @@ return array( */ 'disablePlugins' => [], + /* + |-------------------------------------------------------------------------- + | Prevents application updates + |-------------------------------------------------------------------------- + | + | If using composer or git to download updates to the core files, set this + | value to 'true' to prevent the update gateway from trying to download + | these files again as part of the application update process. Plugins + | and themes will still be downloaded. + | + */ + + 'disableCoreUpdates' => false, + /* |-------------------------------------------------------------------------- | Back-end URI prefix diff --git a/app/config/database.php b/app/config/database.php index d79cb0441..fa82fbbf3 100644 --- a/app/config/database.php +++ b/app/config/database.php @@ -55,6 +55,7 @@ return array( 'mysql' => array( 'driver' => 'mysql', 'host' => 'localhost', + 'port' => '', 'database' => 'database', 'username' => 'root', 'password' => '', @@ -66,6 +67,7 @@ return array( 'pgsql' => array( 'driver' => 'pgsql', 'host' => 'localhost', + 'port' => '', 'database' => 'database', 'username' => 'root', 'password' => '', @@ -77,6 +79,7 @@ return array( 'sqlsrv' => array( 'driver' => 'sqlsrv', 'host' => 'localhost', + 'port' => '', 'database' => 'database', 'username' => 'root', 'password' => '', diff --git a/app/start/global.php b/app/start/global.php index a5b2c6aff..1241255d8 100644 --- a/app/start/global.php +++ b/app/start/global.php @@ -48,7 +48,10 @@ Log::useFiles(storage_path().'/logs/system.log'); App::error(function(Exception $exception, $code) { - Log::error($exception); + /* + * October uses a custom error handler, see + * System\Classes\ErrorHandler::handleException + */ }); /* diff --git a/modules/backend/assets/images/favicon.png b/modules/backend/assets/images/favicon.png new file mode 100644 index 000000000..037163135 Binary files /dev/null and b/modules/backend/assets/images/favicon.png differ diff --git a/modules/backend/assets/js/october.triggerapi.js b/modules/backend/assets/js/october.triggerapi.js index 629de2372..aa5b811da 100644 --- a/modules/backend/assets/js/october.triggerapi.js +++ b/modules/backend/assets/js/october.triggerapi.js @@ -8,11 +8,14 @@ * Supported data attributes: * - data-trigger-type, values: display, hide, enable, disable * - data-trigger: a CSS selector for elements that trigger the action (checkboxes) - * - data-trigger-condition, values: checked (more conditions to add later) - determines the condition the elements - * specified in the data-trigger should satisfy in order the condition to be considered as "true". + * - data-trigger-condition, values: + * - checked: determines the condition the elements specified in the data-trigger + * should satisfy in order the condition to be considered as "true". + * - value[somevalue]: determines if the value of data-trigger equals the specified value (somevalue) + * the condition is considered "true". * * Example: * @@ -28,7 +31,7 @@ var TriggerOn = function (element, options) { var $el = this.$el = $(element); - + this.options = options || {}; if (this.options.triggerCondition === false) @@ -40,10 +43,20 @@ if (this.options.triggerType === false) throw new Error('Trigger type is not specified.') - if (this.options.triggerCondition == 'checked') + this.triggerCondition = this.options.triggerCondition + + if (this.options.triggerCondition.indexOf('value') == 0) { + var match = this.options.triggerCondition.match(/[^[\]]+(?=])/g) + if (match) { + this.triggerConditionValue = match + this.triggerCondition = 'value' + } + } + + if (this.triggerCondition == 'checked' || this.triggerCondition == 'value') $(document).on('change', this.options.trigger, $.proxy(this.onConditionChanged, this)) - var self = this; + var self = this $el.on('oc.triggerOn.update', function(e){ e.stopPropagation() self.onConditionChanged() @@ -53,8 +66,12 @@ } TriggerOn.prototype.onConditionChanged = function() { - if (this.options.triggerCondition == 'checked') - this.updateTarget($(this.options.trigger + ':checked').length > 0); + if (this.triggerCondition == 'checked') { + this.updateTarget($(this.options.trigger + ':checked').length > 0) + } + else if (this.triggerCondition == 'value') { + this.updateTarget($(this.options.trigger).val() == this.triggerConditionValue) + } } TriggerOn.prototype.updateTarget = function(status) { diff --git a/modules/backend/layouts/_head.htm b/modules/backend/layouts/_head.htm index 2d33dc454..8dceb3af1 100644 --- a/modules/backend/layouts/_head.htm +++ b/modules/backend/layouts/_head.htm @@ -1,5 +1,6 @@ + <?= $this->pageTitle ?> | October CMS diff --git a/modules/cms/classes/Controller.php b/modules/cms/classes/Controller.php index 94017b350..414fa6fae 100644 --- a/modules/cms/classes/Controller.php +++ b/modules/cms/classes/Controller.php @@ -400,7 +400,14 @@ class Controller extends BaseController return Response::make($responseContents, 406); } catch (Exception $ex) { - return Response::make($ex->getMessage(), 500); + /* + * Display a "dumbed down" error if custom page is activated + * otherwise display a more detailed error. + */ + if (Config::get('cms.customErrorPage', false)) + return Response::make($ex->getMessage(), 500); + + return Response::make(sprintf('"%s" on line %s of %s', $ex->getMessage(), $ex->getLine(), $ex->getFile()), 500); } } diff --git a/modules/cms/classes/Theme.php b/modules/cms/classes/Theme.php index d53d5ad9a..ca7b92715 100644 --- a/modules/cms/classes/Theme.php +++ b/modules/cms/classes/Theme.php @@ -175,7 +175,7 @@ class Theme $result = []; foreach ($it as $fileinfo) { - if ($fileinfo->isDot() || (substr($fileinfo->getFilename(), 0, 1) == '.')) + if (!$fileinfo->isDir() || $fileinfo->isDot()) continue; $theme = new static; diff --git a/modules/system/assets/css/updates.css b/modules/system/assets/css/updates.css index 61f9d8f20..03ea787f4 100644 --- a/modules/system/assets/css/updates.css +++ b/modules/system/assets/css/updates.css @@ -14,6 +14,10 @@ .control-updatelist h5:first-of-type { border-top: none; } +.control-updatelist h5 i { + margin-right: 7px; + color: #405261; +} .control-updatelist h5 small { text-transform: none; float: right; diff --git a/modules/system/assets/less/updates.less b/modules/system/assets/less/updates.less index acb05939c..064effe5b 100644 --- a/modules/system/assets/less/updates.less +++ b/modules/system/assets/less/updates.less @@ -18,6 +18,11 @@ border-top: none; } + i { + margin-right: 7px; + color: @color-text-title; + } + small { text-transform: none; float: right; diff --git a/modules/system/classes/UpdateManager.php b/modules/system/classes/UpdateManager.php index f6b0d54c7..b1425d14a 100644 --- a/modules/system/classes/UpdateManager.php +++ b/modules/system/classes/UpdateManager.php @@ -206,6 +206,16 @@ class UpdateManager } $result['plugins'] = $plugins; + /* + * Strip out themes that have been installed before + */ + $themes = []; + foreach (array_get($result, 'themes', []) as $code => $info) { + if (!$this->isThemeInstalled($code)) + $themes[$code] = $info; + } + $result['themes'] = $themes; + Parameters::set('system::update.count', array_get($result, 'update', 0)); return $result; @@ -433,6 +443,58 @@ class UpdateManager @unlink($filePath); } + // + // Themes + // + + /** + * Downloads a theme from the update server. + * @param string $name Theme name. + * @param string $hash Expected file hash. + * @return self + */ + public function downloadTheme($name, $hash) + { + $fileCode = $name . $hash; + $this->requestServerFile('theme/get', $fileCode, $hash, ['name' => $name]); + } + + /** + * Extracts a theme after it has been downloaded. + */ + public function extractTheme($name, $hash) + { + $fileCode = $name . $hash; + $filePath = $this->getFilePath($fileCode); + + if (!Zip::extract($filePath, $this->baseDirectory . '/themes/')) + throw new ApplicationException(Lang::get('system::lang.zip.extract_failed', ['file' => $filePath])); + + $this->setThemeInstalled($name); + @unlink($filePath); + } + + /** + * Checks if a theme has ever been installed before. + * @param string $name Theme code + * @return boolean + */ + public function isThemeInstalled($name) + { + return array_key_exists($name, Parameters::get('system::theme.history', [])); + } + + /** + * Flags a theme as being installed, so it is not downloaded twice. + * @param string $name Theme code + */ + public function setThemeInstalled($name) + { + $history = Parameters::get('system::theme.history', []); + $history[$name] = Carbon::now()->timestamp; + Parameters::set('system::theme.history', $history); + } + // // Notes // diff --git a/modules/system/console/OctoberUpdate.php b/modules/system/console/OctoberUpdate.php index 8e733615e..8d95e71ef 100644 --- a/modules/system/console/OctoberUpdate.php +++ b/modules/system/console/OctoberUpdate.php @@ -1,6 +1,7 @@ output->writeln('Updating October...'); $manager = UpdateManager::instance()->resetNotes(); $forceUpdate = $this->option('force'); - $pluginsOnly = $this->option('plugins'); - $coreOnly = $this->option('core'); + /* + * Check for disabilities + */ + $disableCore = $disablePlugins = $disableThemes = false; + + if ($this->option('plugins')) { + $disableCore = true; + $disableThemes = true; + } + + if ($this->option('core')) { + $disablePlugins = true; + $disableThemes = true; + } + + if (Config::get('cms.disableCoreUpdates', false)) { + $disableCore = true; + } + + /* + * Perform update + */ $updateList = $manager->requestUpdateList($forceUpdate); $updates = (int)array_get($updateList, 'update', 0); @@ -49,7 +70,7 @@ class OctoberUpdate extends Command $this->output->writeln(sprintf('Found %s new %s!', $updates, Str::plural('update', $updates))); } - $coreHash = $pluginsOnly ? null : array_get($updateList, 'core.hash'); + $coreHash = $disableCore ? null : array_get($updateList, 'core.hash'); $coreBuild = array_get($updateList, 'core.build'); if ($coreHash) { @@ -57,7 +78,7 @@ class OctoberUpdate extends Command $manager->downloadCore($coreHash); } - $plugins = $coreOnly ? [] : array_get($updateList, 'plugins'); + $plugins = $disablePlugins ? [] : array_get($updateList, 'plugins'); foreach ($plugins as $code => $plugin) { $pluginName = array_get($plugin, 'name'); $pluginHash = array_get($plugin, 'hash'); diff --git a/modules/system/controllers/Updates.php b/modules/system/controllers/Updates.php index 6b607b5b7..15039312b 100644 --- a/modules/system/controllers/Updates.php +++ b/modules/system/controllers/Updates.php @@ -4,6 +4,7 @@ use Str; use Lang; use File; use Flash; +use Config; use Backend; use Redirect; use BackendMenu; @@ -31,6 +32,11 @@ class Updates extends Controller public $listConfig = ['list' => 'config_list.yaml', 'manage' => 'config_manage_list.yaml']; + /** + * @var boolean If set to true, core updates will not be downloaded or extracted. + */ + protected $disableCoreUpdates = false; + public function __construct() { parent::__construct(); @@ -38,6 +44,8 @@ class Updates extends Controller $this->addCss('/modules/system/assets/css/updates.css', 'core'); BackendMenu::setContext('October.System', 'system', 'updates'); + + $this->disableCoreUpdates = Config::get('cms.disableCoreUpdates', false); } /** @@ -98,10 +106,12 @@ class Updates extends Controller switch ($stepCode) { case 'downloadCore': + if ($this->disableCoreUpdates) return; $manager->downloadCore(post('hash')); break; case 'extractCore': + if ($this->disableCoreUpdates) return; $manager->extractCore(post('hash'), post('build')); break; @@ -109,10 +119,18 @@ class Updates extends Controller $manager->downloadPlugin(post('name'), post('hash')); break; + case 'downloadTheme': + $manager->downloadTheme(post('name'), post('hash')); + break; + case 'extractPlugin': $manager->extractPlugin(post('name'), post('hash')); break; + case 'extractTheme': + $manager->extractTheme(post('name'), post('hash')); + break; + case 'completeUpdate': $manager->update(); Flash::success(Lang::get('system::lang.updates.update_success')); @@ -148,7 +166,8 @@ class Updates extends Controller $this->vars['core'] = array_get($result, 'core', false); $this->vars['hasUpdates'] = array_get($result, 'update', false); - $this->vars['updateList'] = array_get($result, 'plugins', []); + $this->vars['pluginList'] = array_get($result, 'plugins', []); + $this->vars['themeList'] = array_get($result, 'themes', []); } catch (Exception $ex) { $this->handleError($ex); @@ -176,10 +195,16 @@ class Updates extends Controller $plugins[$code] = array_get($plugin, 'hash', null); } + $themes = []; + $themeList = array_get($result, 'themes', []); + foreach ($themeList as $code => $theme) { + $themes[$code] = array_get($theme, 'hash', null); + } + /* * Update steps */ - $updateSteps = $this->buildUpdateSteps($core, $plugins); + $updateSteps = $this->buildUpdateSteps($core, $plugins, $themes); /* * Finish up @@ -204,18 +229,20 @@ class Updates extends Controller public function onApplyUpdates() { try { - $plugins = post('plugins', []); - if (!is_array($plugins)) - $plugins = []; - $coreHash = post('hash'); $coreBuild = post('build'); $core = [$coreHash, $coreBuild]; + $plugins = post('plugins', []); + if (!is_array($plugins)) $plugins = []; + + $themes = post('themes', []); + if (!is_array($themes)) $themes = []; + /* * Update steps */ - $updateSteps = $this->buildUpdateSteps($core, $plugins); + $updateSteps = $this->buildUpdateSteps($core, $plugins, $themes); /* * Finish up @@ -234,13 +261,16 @@ class Updates extends Controller return $this->makePartial('execute'); } - private function buildUpdateSteps($core, $plugins) + private function buildUpdateSteps($core, $plugins, $themes) { + if (!is_array($core)) + $core = [null, null]; + if (!is_array($plugins)) $plugins = []; - if (!is_array($core)) - $core = [null, null]; + if (!is_array($themes)) + $themes = []; $updateSteps = []; list($coreHash, $coreBuild) = $core; @@ -265,6 +295,15 @@ class Updates extends Controller ]; } + foreach ($themes as $name => $hash) { + $updateSteps[] = [ + 'code' => 'downloadTheme', + 'label' => Lang::get('system::lang.updates.theme_downloading', compact('name')), + 'name' => $name, + 'hash' => $hash + ]; + } + /* * Extract */ @@ -286,6 +325,15 @@ class Updates extends Controller ]; } + foreach ($themes as $name => $hash) { + $updateSteps[] = [ + 'code' => 'extractTheme', + 'label' => Lang::get('system::lang.updates.theme_extracting', compact('name')), + 'name' => $name, + 'hash' => $hash + ]; + } + return $updateSteps; } diff --git a/modules/system/controllers/updates/_update_list.htm b/modules/system/controllers/updates/_update_list.htm index 8b0cf7e52..9949ebaaa 100644 --- a/modules/system/controllers/updates/_update_list.htm +++ b/modules/system/controllers/updates/_update_list.htm @@ -13,9 +13,10 @@
+ - $core['old_build']])) ?> + $core['old_build']])) ?>
@@ -26,8 +27,23 @@ - $plugin): ?> + $theme): ?>
+ + + +
+
+
+
+
+ + + + + $plugin): ?> +
+ diff --git a/modules/system/lang/en/lang.php b/modules/system/lang/en/lang.php index 4ac2c3a5b..122912ad8 100644 --- a/modules/system/lang/en/lang.php +++ b/modules/system/lang/en/lang.php @@ -138,6 +138,10 @@ return [ 'plugin_version_none' => 'New plugin', 'plugin_version_old' => 'Current v:version', 'plugin_version_new' => 'v:version', + 'theme_label' => 'Theme', + 'theme_new_install' => 'New theme installation.', + 'theme_downloading' => 'Downloading theme: :name', + 'theme_extracting' => 'Unpacking theme: :name', 'update_label' => 'Update software', 'update_completing' => 'Finishing update process', 'update_loading' => 'Loading available updates...',