diff --git a/config/app.php b/config/app.php index d783633ab..c564ada25 100644 --- a/config/app.php +++ b/config/app.php @@ -42,7 +42,7 @@ return [ */ 'url' => env('APP_URL', 'http://localhost'), - + 'cdn' => env('CDN_URL','http://cdn.orient.tm'), /* |-------------------------------------------------------------------------- | Application Timezone @@ -81,7 +81,7 @@ return [ | */ - 'locale' => 'en', + 'locale' => 'ru', /* |-------------------------------------------------------------------------- diff --git a/config/cms.php b/config/cms.php index afcde7d79..32395167e 100644 --- a/config/cms.php +++ b/config/cms.php @@ -80,7 +80,7 @@ return [ | */ - 'backendTimezone' => 'UTC', + 'backendTimezone' => 'Asia/Ashgabat', /* |-------------------------------------------------------------------------- @@ -396,7 +396,7 @@ return [ | */ - 'enableCsrfProtection' => env('ENABLE_CSRF', false), + 'enableCsrfProtection' => env('ENABLE_CSRF', true), /* |-------------------------------------------------------------------------- diff --git a/config/logging.php b/config/logging.php index 900d48123..041e80a31 100644 --- a/config/logging.php +++ b/config/logging.php @@ -13,7 +13,7 @@ return [ | */ - 'default' => env('LOG_CHANNEL', 'single'), + 'default' => env('LOG_CHANNEL', 'daily'), /* |-------------------------------------------------------------------------- diff --git a/config/session.php b/config/session.php index 2b48b18d4..b0d0d63a4 100644 --- a/config/session.php +++ b/config/session.php @@ -109,7 +109,7 @@ return [ | */ - 'cookie' => 'october_session', + 'cookie' => 'orient_session', /* |-------------------------------------------------------------------------- diff --git a/modules/system/classes/UpdateManager.php b/modules/system/classes/UpdateManager.php index 7e4779de1..8673a5a7b 100644 --- a/modules/system/classes/UpdateManager.php +++ b/modules/system/classes/UpdateManager.php @@ -234,8 +234,8 @@ class UpdateManager $params = [ 'core' => $this->getHash(), - 'plugins' => serialize($versions), - 'themes' => serialize($themes), + 'plugins' => base64_encode(json_encode($versions)), + 'themes' => base64_encode(json_encode($themes)), 'build' => $build, 'force' => $force ]; @@ -590,8 +590,9 @@ class UpdateManager { $fileCode = $name . $hash; $filePath = $this->getFilePath($fileCode); + $innerPath = str_replace('.', '/', strtolower($name)); - if (!Zip::extract($filePath, plugins_path())) { + if (!Zip::extract($filePath, plugins_path($innerPath))) { throw new ApplicationException(Lang::get('system::lang.zip.extract_failed', ['file' => $filePath])); } @@ -632,8 +633,9 @@ class UpdateManager { $fileCode = $name . $hash; $filePath = $this->getFilePath($fileCode); + $innerPath = str_replace('.', '-', strtolower($name)); - if (!Zip::extract($filePath, themes_path())) { + if (!Zip::extract($filePath, themes_path($innerPath))) { throw new ApplicationException(Lang::get('system::lang.zip.extract_failed', ['file' => $filePath])); } @@ -903,13 +905,16 @@ class UpdateManager $http->toFile($filePath); }); - if ($result->code != 200) { - throw new ApplicationException(File::get($filePath)); + if (in_array($result->code, [301, 302])) { + if ($redirectUrl = array_get($result->info, 'redirect_url')) { + $result = Http::get($redirectUrl, function ($http) use ($postData, $filePath) { + $http->toFile($filePath); + }); + } } - if (md5_file($filePath) != $expectedHash) { - @unlink($filePath); - throw new ApplicationException(Lang::get('system::lang.server.file_corrupt')); + if ($result->code != 200) { + throw new ApplicationException(File::get($filePath)); } } @@ -942,7 +947,7 @@ class UpdateManager */ protected function createServerUrl($uri) { - $gateway = Config::get('cms.updateServer', 'http://gateway.octobercms.com/api'); + $gateway = Config::get('cms.updateServer', 'https://gateway.octobercms.com/api'); if (substr($gateway, -1) != '/') { $gateway .= '/'; } @@ -958,10 +963,10 @@ class UpdateManager */ protected function applyHttpAttributes($http, $postData) { - $postData['protocol_version'] = '1.1'; + $postData['protocol_version'] = '1.2'; $postData['client'] = 'october'; - $postData['server'] = base64_encode(serialize([ + $postData['server'] = base64_encode(json_encode([ 'php' => PHP_VERSION, 'url' => Url::to('/'), 'since' => PluginVersion::orderBy('created_at')->value('created_at') diff --git a/modules/system/classes/VersionManager.php b/modules/system/classes/VersionManager.php index 77127cf12..1cff13a46 100644 --- a/modules/system/classes/VersionManager.php +++ b/modules/system/classes/VersionManager.php @@ -126,6 +126,8 @@ class VersionManager */ protected function applyPluginUpdate($code, $version, $details) { + $version = $this->normalizeVersion($version); + list($comments, $scripts) = $this->extractScriptsAndComments($details); /* @@ -291,13 +293,18 @@ class VersionManager $versionInfo = []; } - if ($versionInfo) { - uksort($versionInfo, function ($a, $b) { - return version_compare($a, $b); - }); + // Sort result + uksort($versionInfo, function ($a, $b) { + return version_compare($a, $b); + }); + + $result = []; + + foreach ($versionInfo as $version => $info) { + $result[$this->normalizeVersion($version)] = $info; } - return $this->fileVersions[$code] = $versionInfo; + return $this->fileVersions[$code] = $result; } /** @@ -549,6 +556,11 @@ class VersionManager return $this; } + protected function normalizeVersion($version) + { + return ltrim((string) $version, 'v'); + } + /** * @param $details * diff --git a/modules/system/twig/SecurityPolicy.php b/modules/system/twig/SecurityPolicy.php index aacd39ba6..b06601e8f 100644 --- a/modules/system/twig/SecurityPolicy.php +++ b/modules/system/twig/SecurityPolicy.php @@ -18,10 +18,18 @@ final class SecurityPolicy implements SecurityPolicyInterface * @var array List of forbidden methods. */ protected $blockedMethods = [ + // \October\Rain\Extension\ExtendableTrait 'addDynamicMethod', 'addDynamicProperty', + + // \October\Rain\Support\Traits\Emitter 'bindEvent', 'bindEventOnce', + + // Eloquent & Halcyon data modification + 'insert', + 'update', + 'delete', ]; /** diff --git a/plugins/bedard/blogtags/Plugin.php b/plugins/bedard/blogtags/Plugin.php index 7ac9f4420..ce670f77c 100644 --- a/plugins/bedard/blogtags/Plugin.php +++ b/plugins/bedard/blogtags/Plugin.php @@ -65,13 +65,21 @@ class Plugin extends PluginBase // extend the post model PostModel::extend(function($model) { - $model->belongsToMany['tags'] = [ + $model->belongsToMany['tags_ru'] = [ 'Bedard\BlogTags\Models\Tag', 'table' => 'bedard_blogtags_post_tag', - 'order' => 'name' + 'order' => 'name', + 'conditions' => 'bedard_blogtags_tags.locale = "ru"' + ]; + $model->belongsToMany['tags_en'] = [ + 'Bedard\BlogTags\Models\Tag', + 'table' => 'bedard_blogtags_post_tag', + 'order' => 'name', + 'conditions' => 'bedard_blogtags_tags.locale = "en"' ]; }); + // extend the post form PostsController::extendFormFields(function($form, $model, $context) { if (!$model instanceof PostModel) { @@ -79,9 +87,17 @@ class Plugin extends PluginBase } $form->addSecondaryTabFields([ - 'tags' => [ - 'label' => 'bedard.blogtags::lang.form.label', + 'tags_ru' => [ + 'label' => 'Tags ru', 'mode' => 'relation', + 'dependsOn' => 'locale', + 'tab' => 'rainlab.blog::lang.post.tab_categories', + 'type' => 'taglist' + ], + 'tags_en' => [ + 'label' => 'Tags en', + 'mode' => 'relation', + 'dependsOn' => 'locale', 'tab' => 'rainlab.blog::lang.post.tab_categories', 'type' => 'taglist' ] diff --git a/plugins/bedard/blogtags/models/Tag.php b/plugins/bedard/blogtags/models/Tag.php index 1fc539daa..944fe0429 100644 --- a/plugins/bedard/blogtags/models/Tag.php +++ b/plugins/bedard/blogtags/models/Tag.php @@ -1,6 +1,7 @@ slug = str_slug($this->name); + $this->locale = input('Post')['locale']; } /** @@ -91,4 +93,6 @@ class Tag extends Model return $this->url = $controller->pageUrl($pageName, $params); } + + } diff --git a/plugins/indikator/devtools/LICENCE.md b/plugins/indikator/devtools/LICENCE.md new file mode 100644 index 000000000..38cee799e --- /dev/null +++ b/plugins/indikator/devtools/LICENCE.md @@ -0,0 +1,19 @@ +# MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/indikator/devtools/Plugin.php b/plugins/indikator/devtools/Plugin.php new file mode 100644 index 000000000..ae2275a7f --- /dev/null +++ b/plugins/indikator/devtools/Plugin.php @@ -0,0 +1,169 @@ + 'indikator.devtools::lang.plugin.name', + 'description' => 'indikator.devtools::lang.plugin.description', + 'author' => 'indikator.devtools::lang.plugin.author', + 'icon' => 'icon-wrench', + 'homepage' => 'https://github.com/gergo85/oc-devtools' + ]; + } + + public function registerSettings() + { + return [ + 'devtool' => [ + 'label' => 'indikator.devtools::lang.help.menu_label', + 'description' => 'indikator.devtools::lang.help.menu_description', + 'icon' => 'icon-wrench', + 'class' => 'Indikator\DevTools\Models\Settings', + 'category' => SettingsManager::CATEGORY_SYSTEM, + 'permissions' => ['indikator.devtools.settings'] + ] + ]; + } + + public function registerFormWidgets() + { + return [ + 'Indikator\DevTools\FormWidgets\Help' => [ + 'label' => 'Help', + 'code' => 'help' + ] + ]; + } + + public function registerPermissions() + { + return [ + 'indikator.devtools.editor' => [ + 'tab' => 'indikator.devtools::lang.plugin.name', + 'label' => 'indikator.devtools::lang.editor.permission', + 'order' => 100, + 'roles' => ['developer'] + ], + 'indikator.devtools.settings' => [ + 'tab' => 'indikator.devtools::lang.plugin.name', + 'label' => 'indikator.devtools::lang.help.permission', + 'order' => 200, + 'roles' => ['developer'] + ] + ]; + } + + public function boot() + { + // Add new menu + BackendMenu::registerCallback(function($manager) { + $manager->registerMenuItems('Indikator.DevTools', [ + 'editor' => [ + 'label' => 'indikator.devtools::lang.editor.menu_label', + 'url' => Backend::url('indikator/devtools/editor'), + 'icon' => 'icon-file-code-o', + 'iconSvg' => 'plugins/indikator/devtools/assets/images/devtools-icon.svg', + 'permissions' => ['indikator.devtools.editor'], + 'order' => 390, + + 'sideMenu' => [ + 'assets' => [ + 'label' => 'indikator.devtools::lang.editor.plugins', + 'icon' => 'icon-cubes', + 'url' => 'javascript:;', + 'attributes' => ['data-menu-item' => 'assets'], + 'counterLabel' => 'cms::lang.asset.unsaved_label', + 'order' => 100 + ] + ] + ] + ]); + }); + + // Add new features + Event::listen('backend.form.extendFields', function($form) + { + // Security check + if (!BackendAuth::check()) { + return; + } + + // Help docs + if ($this->tools_enabled('help') && (get_class($form->config->model) == 'Cms\Classes\Page' || get_class($form->config->model) == 'Cms\Classes\Partial' || get_class($form->config->model) == 'Cms\Classes\Layout') || get_class($form->config->model) == 'Indikator\DevTools\Classes\Asset') { + if (get_class($form->config->model) == 'Indikator\DevTools\Classes\Asset') { + $content = 'php'; + } + else { + $content = 'cms'; + } + + $form->addSecondaryTabFields([ + 'help' => [ + 'label' => '', + 'tab' => 'indikator.devtools::lang.help.tab', + 'type' => 'help', + 'content' => $content + ] + ]); + + return; + } + + // Wysiwyg editor + if ($this->tools_enabled('wysiwyg') && get_class($form->config->model) == 'Cms\Classes\Content') { + foreach ($form->getFields() as $field) { + if (!empty($field->config['type']) && $field->config['type'] == 'codeeditor') { + $field->config['type'] = $field->config['widget'] = 'richeditor'; + } + } + } + }); + } + + public function tools_enabled($name) + { + // Security check + if ($name != 'help' && $name != 'wysiwyg') { + return false; + } + + // Is enabled + if (!Tools::get($name.'_enabled', false)) { + return false; + } + + // My account + $admin = BackendAuth::getUser(); + + // Is superuser + if (Tools::get($name.'_superuser', false) && $admin->is_superuser == 1) { + return true; + } + + // Is admin group + if (Tools::get($name.'_admingroup', false) > 0 && Db::table('backend_users_groups')->where(['user_id' => $admin->id, 'user_group_id' => Tools::get($name.'_admingroup', false)])->count() == 1) { + return true; + } + + // Is current user + if (Tools::get($name.'_adminid', false) > 0 && $admin->id == Tools::get($name.'_adminid', false)) { + return true; + } + + // Finish + return false; + } +} diff --git a/plugins/indikator/devtools/README.md b/plugins/indikator/devtools/README.md new file mode 100644 index 000000000..1e8472938 --- /dev/null +++ b/plugins/indikator/devtools/README.md @@ -0,0 +1,24 @@ +# Developer Tools Plugin +It is a must-have plugin for you, if you use the October's Docs a lot or you want to use the build-in wysiwyg editor on the Content page. + +## Main features +* __Edit directly plugins with the online code editor!__ +* Add a Help tab to the Pages, Partials or Layouts pages. +* Replace the code editor to wysiwyg editor on the Content page. +* Show the PHP's configuration if you logged as Superuser. +* Set a different permissions for the above features. + +## Available languages +* en - English +* hu - Magyar + +## Location of plugin +You can find it in the back-end: __Settings > System > Developer Tools__ + +## PHP's configuration +If you like to get the PHP's configuration of your server, use the following URL: www.yourwebsite.com/phpinfo + +## Installation +1. Go to the __Settings > Updates & Plugins__ page in the Backend. +1. Click on the __Install plugins__ button. +1. Type the __Developer Tools__ text in the search field. diff --git a/plugins/indikator/devtools/assets/images/devtools-icon.svg b/plugins/indikator/devtools/assets/images/devtools-icon.svg new file mode 100644 index 000000000..fe0b1cf34 --- /dev/null +++ b/plugins/indikator/devtools/assets/images/devtools-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/indikator/devtools/assets/october.cmspage.js b/plugins/indikator/devtools/assets/october.cmspage.js new file mode 100644 index 000000000..85cd8e7c6 --- /dev/null +++ b/plugins/indikator/devtools/assets/october.cmspage.js @@ -0,0 +1,706 @@ +/* + * Scripts for the CMS page. + */ ++function ($) { "use strict"; + + var Base = $.oc.foundation.base, + BaseProto = Base.prototype + + var CmsPage = function() { + + Base.call(this) + + // + // Initialization + // + + this.init() + } + + CmsPage.prototype = Object.create(BaseProto) + CmsPage.prototype.constructor = CmsPage + + CmsPage.prototype.init = function() { + $(document).ready(this.proxy(this.registerHandlers)) + } + + CmsPage.prototype.updateTemplateList = function(type) { + var $form = $('#cms-side-panel form[data-template-type='+type+']'), + templateList = type + 'List' + + $form.request(templateList + '::onUpdate', { + complete: function() { + $('button[data-control=delete-template]', $form).trigger('oc.triggerOn.update') + } + }) + } + + CmsPage.prototype.registerHandlers = function() { + var $document = $(document), + $masterTabs = $('#cms-master-tabs') + + $masterTabs.on('closed.oc.tab', this.proxy(this.onTabClosed)) + $masterTabs.on('beforeClose.oc.tab', this.proxy(this.onBeforeTabClose)) + $masterTabs.on('oc.beforeRequest', this.proxy(this.onBeforeRequest)) + $masterTabs.on('shown.bs.tab', this.proxy(this.onTabShown)) + $masterTabs.on('initTab.oc.tab', this.proxy(this.onInitTab)) + $masterTabs.on('afterAllClosed.oc.tab', this.proxy(this.onAfterAllTabsClosed)) + + $(window).on('ajaxInvalidField', this.proxy(this.ajaxInvalidField)) + $document.on('open.oc.list', '#cms-side-panel', this.proxy(this.onOpenDocument)) + $document.on('ajaxUpdate', '[data-control=filelist], [data-control=assetlist]', this.proxy(this.onAjaxUpdate)) + $document.on('ajaxError', '#cms-master-tabs form', this.proxy(this.onAjaxError)) + $document.on('ajaxSuccess', '#cms-master-tabs form', this.proxy(this.onAjaxSuccess)) + $document.on('click', '#cms-side-panel form button[data-control=create-template], #cms-side-panel form li a[data-control=create-template]', this.proxy(this.onCreateTemplateClick)) + $document.on('click', '#cms-side-panel form button[data-control=delete-template]', this.proxy(this.onDeleteTemplateClick)) + $document.on('showing.oc.inspector', '[data-inspectable]', this.proxy(this.onInspectorShowing)) + $document.on('hidden.oc.inspector', '[data-inspectable]', this.proxy(this.onInspectorHidden)) + $document.on('hiding.oc.inspector', '[data-inspectable]', this.proxy(this.onInspectorHiding)) + $document.on('click', '#cms-master-tabs > div.tab-content > .tab-pane.active .control-componentlist a.remove', this.proxy(this.onComponentRemove)) + $document.on('click', '#cms-component-list [data-component]', this.proxy(this.onComponentClick)) + } + + // EVENT HANDLERS + // ============================ + + CmsPage.prototype.onOpenDocument = function(event) { + /* + * Open a document when it's clicked in the sidebar + */ + + var $item = $(event.relatedTarget), + $form = $item.closest('[data-template-type]'), + data = { + type: $form.data('template-type'), + theme: $item.data('item-theme'), + path: $item.data('item-path') + }, + tabId = data.type + '-' + data.theme + '-' + data.path + + if (data.type == 'asset' && $item.data('editable') === undefined) + return true + + if ($form.length == 0) + return false + + /* + * Find if the tab is already opened + */ + if ($('#cms-master-tabs').data('oc.tab').goTo(tabId)) + return false + + /* + * Open a new tab + */ + $.oc.stripeLoadIndicator.show() + + $form.request('onOpenTemplate', { + data: data + }).done(function(data) { + $.oc.stripeLoadIndicator.hide() + var fileType = data.tabTitle.split('.').pop() + if (fileType == 'php' || fileType == 'js' || fileType == 'css' || fileType == 'html' || fileType == 'htm') fileType = 'icon-file-code-o' + else fileType = 'icon-file-text-o' + $('#cms-master-tabs').ocTab('addTab', data.tabTitle, data.tab, tabId, fileType) + }).always(function() { + $.oc.stripeLoadIndicator.hide() + }).fail(function(jqXHR, textStatus, errorThrown) { + alert(jqXHR.responseText.length ? jqXHR.responseText : jqXHR.statusText) + $.oc.stripeLoadIndicator.hide() + }) + + return false + } + + CmsPage.prototype.ajaxInvalidField = function(ev, element, name, messages, isFirst) { + /* + * Detect invalid fields, uncollapse the panel + */ + if (!isFirst) + return + + ev.preventDefault() + + var $el = $(element), + $panel = $el.closest('.form-tabless-fields.collapsed'), + $primaryPanel = $el.closest('.control-tabs.primary-tabs.collapsed') + + if ($panel.length > 0) + $panel.removeClass('collapsed') + + if ($primaryPanel.length > 0) { + $primaryPanel.removeClass('collapsed') + + var pane = $primaryPanel.closest('.tab-pane'), + $secondaryPanel = $('.control-tabs.secondary-tabs', pane) + + $secondaryPanel.removeClass('primary-collapsed') + } + + $el.focus() + } + + CmsPage.prototype.onTabClosed = function(ev) { + this.updateModifiedCounter() + + if ($('> div.tab-content > div.tab-pane', '#cms-master-tabs').length == 0) + this.setPageTitle('') + } + + CmsPage.prototype.onBeforeTabClose = function(ev) { + if ($.fn.table !== undefined) + $('[data-control=table]', ev.relatedTarget).table('dispose') + + $.oc.foundation.controlUtils.disposeControls(ev.relatedTarget.get(0)) + } + + CmsPage.prototype.onBeforeRequest = function(ev) { + var $form = $(ev.target) + + if ($('.components .layout-cell.error-component', $form).length > 0) { + if (!confirm('The form contains unknown components. Their properties will be lost on save. Do you want to save the form?')) + ev.preventDefault() + } + } + + CmsPage.prototype.onTabShown = function(ev) { + /* + * Listen for the tabs "shown" event to track the current template in the list + */ + + var $target = $(ev.target) + + if ($target.closest('[data-control=tab]').attr('id') != 'cms-master-tabs') + return + + var dataId = $target.closest('li').attr('data-tab-id'), + title = $target.attr('title'), + $sidePanel = $('#cms-side-panel') + + if (title) + this.setPageTitle(title) + + $sidePanel.find('[data-control=filelist]').fileList('markActive', dataId) + $sidePanel.find('form').trigger('oc.list.setActiveItem', [dataId]) + } + + CmsPage.prototype.onInitTab = function(ev, data) { + /* + * Listen for the tabs "initTab" event to inject extra controls to the tab + */ + + if ($(ev.target).attr('id') != 'cms-master-tabs') + return + + var $collapseIcon = $(''), + $panel = $('.form-tabless-fields', data.pane) + + $panel.append($collapseIcon); + + $collapseIcon.click(function(){ + $panel.toggleClass('collapsed') + + if (typeof(localStorage) !== 'undefined') + localStorage.ocCmsTablessCollapsed = $panel.hasClass('collapsed') ? 1 : 0 + + window.setTimeout(function(){ + $(window).trigger('oc.updateUi') + }, 500) + + return false + }) + + var $primaryCollapseIcon = $(''), + $primaryPanel = $('.control-tabs.primary-tabs', data.pane), + $secondaryPanel = $('.control-tabs.secondary-tabs', data.pane) + + if ($primaryPanel.length > 0) { + $secondaryPanel.append($primaryCollapseIcon); + + $primaryCollapseIcon.click(function(){ + $primaryPanel.toggleClass('collapsed') + $secondaryPanel.toggleClass('primary-collapsed') + $(window).trigger('oc.updateUi') + if (typeof(localStorage) !== 'undefined') + localStorage.ocCmsPrimaryCollapsed = $primaryPanel.hasClass('collapsed') ? 1 : 0 + return false + }) + } + + if (typeof(localStorage) !== 'undefined') { + if (!$('a', data.tab).hasClass('new-template') && localStorage.ocCmsTablessCollapsed == 1) + $panel.addClass('collapsed') + + if (localStorage.ocCmsPrimaryCollapsed == 1) { + $primaryPanel.addClass('collapsed') + $secondaryPanel.addClass('primary-collapsed') + } + } + + var $componentListFormGroup = $('.control-componentlist', data.pane).closest('.form-group') + if ($primaryPanel.length > 0) + $primaryPanel.before($componentListFormGroup) + else + $secondaryPanel.parent().before($componentListFormGroup) + + $componentListFormGroup.removeClass() + $componentListFormGroup.addClass('layout-row min-size') + this.updateComponentListClass(data.pane) + this.updateFormEditorMode(data.pane, true) + + var $form = $('form', data.pane), + self = this + + $form.on('changed.oc.changeMonitor', function() { + $panel.trigger('modified.oc.tab') + self.updateModifiedCounter() + }) + + $form.on('unchanged.oc.changeMonitor', function() { + $panel.trigger('unmodified.oc.tab') + self.updateModifiedCounter() + }) + + this.addTokenExpanderToEditor(data.pane, $form) + } + + CmsPage.prototype.onAfterAllTabsClosed = function(ev) { + var $sidePanel = $('#cms-side-panel') + + $sidePanel.find('[data-control=filelist]').fileList('markActive', null) + $sidePanel.find('form').trigger('oc.list.setActiveItem', [null]) + } + + CmsPage.prototype.onAjaxUpdate = function(ev) { + var dataId = $('#cms-master-tabs .nav-tabs li.active').attr('data-tab-id'), + $sidePanel = $('#cms-side-panel') + + $sidePanel.find('[data-control=filelist]').fileList('markActive', dataId) + $sidePanel.find('form').trigger('oc.list.setActiveItem', [dataId]) + } + + CmsPage.prototype.onAjaxSuccess = function(ev, context, data) { + var element = ev.target + + if (data.templatePath !== undefined) { + $('input[name=templatePath]', element).val(data.templatePath) + $('input[name=templateMtime]', element).val(data.templateMtime) + $('[data-control=delete-button]', element).removeClass('hide') + $('[data-control=preview-button]', element).removeClass('hide') + + if (data.pageUrl !== undefined) + $('[data-control=preview-button]', element).attr('href', data.pageUrl) + } + + if (data.tabTitle !== undefined) { + $('#cms-master-tabs').ocTab('updateTitle', $(element).closest('.tab-pane'), data.tabTitle) + this.setPageTitle(data.tabTitle) + } + + var tabId = $('input[name=templateType]', element).val() + '-' + + $('input[name=theme]', element).val() + '-' + + $('input[name=templatePath]', element).val(); + + $('#cms-master-tabs').ocTab('updateIdentifier', $(element).closest('.tab-pane'), tabId) + + var templateType = $('input[name=templateType]', element).val() + if (templateType.length > 0) { + $.oc.cmsPage.updateTemplateList(templateType) + + if (templateType == 'layout') + this.updateLayouts(element) + } + + this.updateFormEditorMode($(element).closest('.tab-pane'), false) + + if (context.handler == 'onSave' && (!data['X_OCTOBER_ERROR_FIELDS'] && !data['X_OCTOBER_ERROR_MESSAGE'])) { + $(element).trigger('unchange.oc.changeMonitor') + } + } + + CmsPage.prototype.onAjaxError = function(ev, context, message, data, jqXHR) { + if (context.handler == 'onSave') { + if (jqXHR.responseText == 'mtime-mismatch') { + ev.preventDefault() + this.handleMtimeMismatch(ev.target) + } + } + } + + CmsPage.prototype.onCreateTemplateClick = function(ev) { + var $form = $(ev.target).closest('[data-template-type]'), + type = $form.data('template-type'), + tabId = type + Math.random(), + self = this + + $.oc.stripeLoadIndicator.show() + + $form.request('onCreateTemplate', { + data: {type: type} + }).done(function(data) { + $('#cms-master-tabs').ocTab('addTab', data.tabTitle, data.tab, tabId, $form.data('type-icon') + ' new-template') + $('#layout-side-panel').trigger('close.oc.sidePanel') + self.setPageTitle(data.tabTitle) + }).always(function(){ + $.oc.stripeLoadIndicator.hide() + }) + } + + CmsPage.prototype.onDeleteTemplateClick = function(ev) { + var $el = $(ev.currentTarget), + $form = $el.closest('form'), + templateType = $form.data('template-type'), + self = this + + if (!confirm($el.data('confirmation'))) + return + + $.oc.stripeLoadIndicator.show() + + $form.request('onDeleteTemplates', { + data: {type: templateType} + }).done(function(data) { + var tabs = $('#cms-master-tabs').data('oc.tab'); + $.each(data.deleted, function(index, path){ + var + tabId = templateType + '-' + data.theme + '-' + path, + tab = tabs.findByIdentifier(tabId) + + $('#cms-master-tabs').ocTab('closeTab', tab, true) + }) + + if (data.error !== undefined && $.type(data.error) === 'string' && data.error.length) + $.oc.flashMsg({text: data.error, 'class': 'error'}) + }).always(function(){ + self.updateTemplateList(templateType) + $.oc.stripeLoadIndicator.hide() + }) + } + + CmsPage.prototype.onInspectorShowing = function(ev, data) { + $(ev.currentTarget).closest('[data-control="toolbar"]').data('oc.dragScroll').goToElement(ev.currentTarget, data.callback) + + ev.stopPropagation() + } + + CmsPage.prototype.onInspectorHidden = function(ev) { + var element = ev.target, + values = $.parseJSON($('[data-inspector-values]', element).val()) + + $('[name="component_aliases[]"]', element).val(values['oc.alias']) + $('span.alias', element).text(values['oc.alias']) + } + + CmsPage.prototype.onInspectorHiding = function(ev, values) { + var element = ev.target, + values = $.parseJSON($('[data-inspector-values]', element).val()), + alias = values['oc.alias'], + $componentList = $('#cms-master-tabs > div.tab-content > .tab-pane.active .control-componentlist .layout'), + $cell = $(ev.target).parent() + + $('div.layout-cell', $componentList).each(function(){ + if ($cell.get(0) == this) + return true + + var $input = $('input[name="component_aliases[]"]', this) + + if ($input.val() == alias) { + ev.preventDefault() + alert('The component alias "'+alias+'" is already used.') + return false + } + }) + } + + CmsPage.prototype.onComponentRemove = function(ev) { + var element = ev.currentTarget + + $(element).trigger('change') + var pane = $(element).closest('.tab-pane'), + component = $(element).closest('div.layout-cell') + + /* + * Remove any {% component %} tags in the editor for this component + */ + var editor = $('[data-control=codeeditor]', pane) + if (editor.length) { + var alias = $('input[name="component_aliases[]"]', component).val(), + codeEditor = editor.codeEditor('getEditorObject') + + codeEditor.replace('', { + needle: "{% component '" + alias + "' %}" + }) + } + + component.remove() + $(window).trigger('oc.updateUi') + + this.updateComponentListClass(pane) + return false + } + + CmsPage.prototype.onComponentClick = function(ev) { + /* + * Determine if a page or layout is open in the master tabs + */ + + var $componentList = $('#cms-master-tabs > div.tab-content > .tab-pane.active .control-componentlist .layout') + if ($componentList.length == 0) { + alert('Components can be added only to pages, partials and layouts.') + return; + } + + var $component = $(ev.currentTarget).clone(), + $iconInput = $component.find('[data-component-icon]'), + $componentContainer = $('.layout-relative', $component), + $configInput = $component.find('[data-inspector-config]'), + $aliasInput = $component.find('[data-component-default-alias]'), + $valuesInput = $component.find('[data-inspector-values]'), + $nameInput = $component.find('[data-component-name]'), + $classInput = $component.find('[data-inspector-class]'), + alias = $aliasInput.val(), + originalAlias = alias, + counter = 2, + existingAliases = [] + + $('div.layout-cell input[name="component_aliases[]"]', $componentList).each(function(){ + existingAliases.push($(this).val()) + }) + + while($.inArray(alias, existingAliases) !== -1) { + alias = originalAlias + counter + counter++ + } + + // Set the last alias used so dragComponents can use it + $('input[name="component_aliases[]"]', $(ev.currentTarget)).val(alias) + + $component.attr('data-component-attached', true) + $componentContainer.addClass($iconInput.val()) + $iconInput.remove() + + $componentContainer.attr({ + 'data-inspectable': '', + 'data-inspector-title': $component.find('span.name').text(), + 'data-inspector-description': $component.find('span.description').text(), + 'data-inspector-config': $configInput.val(), + 'data-inspector-class': $classInput.val() + }) + + $configInput.remove() + $('input[name="component_names[]"]', $component).val($nameInput.val()) + $nameInput.remove() + $('input[name="component_aliases[]"]', $component).val(alias) + $component.find('span.alias').text(alias) + $valuesInput.val($valuesInput.val().replace('--alias--', alias)) + $aliasInput.remove() + + $component.addClass('adding') + $componentList.append($component) + $componentList.closest('[data-control="toolbar"]').data('oc.dragScroll').goToElement($component) + $component.removeClass('adding') + $component.trigger('change') + + this.updateComponentListClass($component.closest('.tab-pane')) + + $(window).trigger('oc.updateUi') + } + + // INTERNAL METHODS + // ============================ + + CmsPage.prototype.updateComponentListClass = function(pane) { + var $componentList = $('.control-componentlist', pane), + $primaryPanel = $('.control-tabs.primary-tabs', pane), + $primaryTabContainer = $('.nav-tabs', $primaryPanel), + hasComponents = $('.layout', $componentList).children(':not(.hidden)').length > 0 + + $primaryTabContainer.toggleClass('component-area', hasComponents) + $componentList.toggleClass('has-components', hasComponents) + } + + CmsPage.prototype.updateFormEditorMode = function(pane, initialization) { + var $contentTypeElement = $('[data-toolbar-type]', pane) + if ($contentTypeElement.length == 0) + return + + if ($contentTypeElement.data('toolbar-type') != 'content') + return + + var fileName = $('input[name=fileName]', pane).val(), + parts = fileName.split('.'), + extension = 'txt', + mode = 'plain_text', + modes = { css: "css", htm: "html", html: "html", js: "javascript", less: "less", md: "markdown", sass: "sass", scss: "scss", txt: "plain_text", yaml: "yaml", php: "php" }, + editor = $('[data-control=codeeditor]', pane) + + if (parts.length >= 2) + extension = parts.pop().toLowerCase() + + if (modes[extension] !== undefined) + mode = modes[extension]; + + var setEditorMode = function() { + window.setTimeout(function(){ + editor.data('oc.codeEditor').editor.getSession().setMode({path: 'ace/mode/'+mode}) + }, 200) + } + + if (initialization) + editor.on('oc.codeEditorReady', setEditorMode) + else + setEditorMode() + } + + CmsPage.prototype.updateModifiedCounter = function() { + var counters = { + page: { menu: 'pages', count: 0 }, + partial: { menu: 'partials', count: 0 }, + layout: { menu: 'layouts', count: 0 }, + content: { menu: 'content', count: 0 }, + asset: { menu: 'assets', count: 0} + } + + $('> div.tab-content > div.tab-pane[data-modified]', '#cms-master-tabs').each(function(){ + var inputType = $('> form > input[name=templateType]', this).val() + counters[inputType].count++ + }) + + $.each(counters, function(type, data){ + $.oc.sideNav.setCounter('cms/' + data.menu, data.count); + }) + } + + CmsPage.prototype.addTokenExpanderToEditor = function(pane, $form) { + var group = $('[data-field-name=markup]', pane), + editor = $('[data-control=codeeditor]', group), + canExpand = false, + self = this + + if (!editor.length || editor.data('oc.tokenexpander')) + return + + var toolbar = editor.codeEditor('getToolbar') + + editor.tokenExpander() + + var breakButton = $('
').prop({ 'class': 'tokenexpander-button' }).append( + $('').prop({ 'href': 'javascript:; '}).append( + $('').prop({ 'class': 'icon-code-fork' }) + ) + ) + + breakButton.hide().on('click', function(){ + self.handleExpandToken(editor, $form) + return false + }) + + $('ul:first', toolbar).prepend(breakButton) + + editor + .on('show.oc.tokenexpander', function(){ + canExpand = true + breakButton.show() + }) + .on('hide.oc.tokenexpander', function(){ + canExpand = false + breakButton.hide() + }) + .on('dblclick', function(ev){ + if ((ev.metaKey || ev.ctrlKey) && canExpand) { + self.handleExpandToken(editor, $form) + } + }) + } + + CmsPage.prototype.handleExpandToken = function(editor, $form) { + editor.tokenExpander('expandToken', function(token, value){ + return $form.request('onExpandMarkupToken', { + data: { tokenType: token, tokenName: value } + }) + }) + } + + CmsPage.prototype.handleMtimeMismatch = function(form) { + var $form = $(form) + $form.popup({ handler: 'onOpenConcurrencyResolveForm' }) + + var popup = $form.data('oc.popup'), + self = this + + $(popup.$content).on('click', 'button[data-action=reload]', function(){ + popup.hide() + self.reloadForm(form) + }) + + $(popup.$content).on('click', 'button[data-action=save]', function(){ + popup.hide() + + $('input[name=templateForceSave]', $form).val(1) + $('a[data-request=onSave]', $form).trigger('click') + $('input[name=templateForceSave]', $form).val(0) + }) + } + + CmsPage.prototype.reloadForm = function(form) { + var + $form = $(form), + data = { + type: $('[name=templateType]', $form).val(), + theme: $('[name=theme]', $form).val(), + path: $('[name=templatePath]', $form).val(), + }, + tabId = data.type + '-' + data.theme + '-' + data.path, + tabs = $('#cms-master-tabs').data('oc.tab'), + tab = tabs.findByIdentifier(tabId), + self = this + + /* + * Update tab + */ + + $.oc.stripeLoadIndicator.show() + + $form.request('onOpenTemplate', { + data: data + }).done(function(data) { + $('#cms-master-tabs').ocTab('updateTab', tab, data.tabTitle, data.tab) + $('#cms-master-tabs').ocTab('unmodifyTab', tab) + self.updateModifiedCounter() + }).always(function() { + $.oc.stripeLoadIndicator.hide() + }).fail(function(jqXHR, textStatus, errorThrown) { + alert(jqXHR.responseText.length ? jqXHR.responseText : jqXHR.statusText) + }) + } + + CmsPage.prototype.setPageTitle = function(title) { + if (title.length) + $.oc.layout.setPageTitle(title + ' | ') + else + $.oc.layout.setPageTitle(title) + } + + CmsPage.prototype.updateLayouts = function(form) { + $(form).request('onGetTemplateList', { + success: function(data) { + $('#cms-master-tabs > .tab-content select[name="settings[layout]"]').each(function(){ + var + $select = $(this), + value = $select.val() + + $select.find('option').remove() + $.each(data.layouts, function(layoutFile, layoutName){ + $select.append($('