diff --git a/CHANGELOG.md b/CHANGELOG.md index 624e03bf0..35981416b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +* **Build 236** (2015-03-28) + - Default context of `manage` and `pivot` forms is now *create* and *update* respectively, instead of the old value *relation*. Use the `context` option to set it back to the old value (see Backend > Relations docs). + * **Build 229** (2015-03-19) - Belongs-to-many model relations now support defining a custom pivot model with the `pivotModel` option (see Database > Model docs). - The config definitions for behavior `RelationController` have been refactored. When using `pivot` mode all columns and fields should now reside in a `pivot[]` array (see Backend > Relations docs). diff --git a/README.md b/README.md index 304e6c7c0..5eb0cbe28 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ October's mission is to show the world that web development is not rocket scienc ### Learning October -The best place to learn October is by [reading the documentation](http://octobercms.com/docs). +The best place to learn October is by [reading the documentation](http://octobercms.com/docs) or [the resources page](http://octobercms.com/resources). ### Installing October @@ -50,4 +50,4 @@ The OctoberCMS platform is open-sourced software licensed under the [MIT license ### Contributing -Before sending a Pull Request, be sure to review the [Contributing Guidelines](CONTRIBUTING.md) first. \ No newline at end of file +Before sending a Pull Request, be sure to review the [Contributing Guidelines](CONTRIBUTING.md) first. diff --git a/modules/backend/assets/css/controls.css b/modules/backend/assets/css/controls.css index f5343b3fd..f3889c72b 100644 --- a/modules/backend/assets/css/controls.css +++ b/modules/backend/assets/css/controls.css @@ -791,8 +791,7 @@ ul.status-list li span.status.info{background:#5bc0de} .control-breadcrumb li a:hover{color:#ecf0f1} .control-breadcrumb li:after{font-size:14px;line-height:14px;display:inline-block;margin-left:6px;margin-right:2px;vertical-align:baseline;color:#9da3a7;font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;content:"\f105"} .control-breadcrumb li:last-child:after{content:''} -.control-breadcrumb + .content-tabs,.control-breadcrumb + .padded-container{margin-top:-20px} -.control-breadcrumb.breadcrumb-flush{margin-bottom:0} +body.breadcrumb-flush .control-breadcrumb,.control-breadcrumb.breadcrumb-flush{margin-bottom:0} body.slim-container .control-breadcrumb{margin-left:0;margin-right:0} body.compact-container .control-breadcrumb{margin-top:0;margin-left:0;margin-right:0} div.control-popover{position:absolute;background-clip:content-box;left:0;top:0;z-index:570;visibility:hidden} @@ -1180,4 +1179,4 @@ div.control-scrollpad > .scrollpad-scrollbar:hover{opacity:0.7;-webkit-transitio div.control-scrollpad > .scrollpad-scrollbar[data-visible]{opacity:0.7} div.control-scrollpad > .scrollpad-scrollbar[data-hidden]{display:none} div.control-scrollpad[data-direction=horizontal] > .scrollpad-scrollbar{top:auto;left:0;width:auto;height:11px} -div.control-scrollpad[data-direction=horizontal] > .scrollpad-scrollbar .drag-handle{right:auto;top:2px;height:7px;min-height:0;min-width:10px;width:auto} \ No newline at end of file +div.control-scrollpad[data-direction=horizontal] > .scrollpad-scrollbar .drag-handle{right:auto;top:2px;height:7px;min-height:0;min-width:10px;width:auto} diff --git a/modules/backend/assets/js/october-min.js b/modules/backend/assets/js/october-min.js index ecdcfb29b..adfe0a18a 100644 --- a/modules/backend/assets/js/october-min.js +++ b/modules/backend/assets/js/october-min.js @@ -261,17 +261,18 @@ $el.on('oc.triggerOn.update',function(e){e.stopPropagation() self.onConditionChanged()}) self.onConditionChanged()} TriggerOn.prototype.onConditionChanged=function(){if(this.triggerCondition=='checked'){this.updateTarget($(this.options.trigger+':checked',this.triggerParent).length>0)} -else if(this.triggerCondition=='value'){var trigger=$(this.options.trigger+':checked',this.triggerParent);if(trigger.length){this.updateTarget(trigger.val()==this.triggerConditionValue)}else{this.updateTarget($(this.options.trigger,this.triggerParent).val()==this.triggerConditionValue)}}} +else if(this.triggerCondition=='value'){var trigger=$(this.options.trigger+':checked',this.triggerParent);if(trigger.length){this.updateTarget(trigger.val()==this.triggerConditionValue)} +else{this.updateTarget($(this.options.trigger,this.triggerParent).val()==this.triggerConditionValue)}}} TriggerOn.prototype.updateTarget=function(status){if(this.options.triggerAction=='show') -this.$el.toggleClass('hide',!status).trigger('hide',[!status]) +this.$el.toggleClass('hide',!status).trigger('hide.oc.triggerapi',[!status]) else if(this.options.triggerAction=='hide') -this.$el.toggleClass('hide',status).trigger('hide',[status]) +this.$el.toggleClass('hide',status).trigger('hide.oc.triggerapi',[status]) else if(this.options.triggerAction=='enable') -this.$el.prop('disabled',!status).trigger('disable',[!status]).toggleClass('control-disabled',!status) +this.$el.prop('disabled',!status).trigger('disable.oc.triggerapi',[!status]).toggleClass('control-disabled',!status) else if(this.options.triggerAction=='disable') -this.$el.prop('disabled',status).trigger('disable',[status]).toggleClass('control-disabled',status) +this.$el.prop('disabled',status).trigger('disable.oc.triggerapi',[status]).toggleClass('control-disabled',status) else if(this.options.triggerAction=='empty'&&status) -this.$el.trigger('empty').val('') +this.$el.trigger('empty.oc.triggerapi').val('') if(this.options.triggerAction=='show'||this.options.triggerAction=='hide') this.fixButtonClasses() $(window).trigger('resize')} @@ -583,7 +584,9 @@ this.$el.on('modified.oc.tab',function(ev){ev.preventDefault() self.modifyTab($(ev.target).closest('ul.nav-tabs > li, div.tab-content > div'))}) this.$el.on('unmodified.oc.tab',function(ev){ev.preventDefault() self.unmodifyTab($(ev.target).closest('ul.nav-tabs > li, div.tab-content > div'))}) -this.$tabsContainer.on('shown.bs.tab','li',function(){$(window).trigger('oc.updateUi')}) +this.$tabsContainer.on('shown.bs.tab','li',function(){$(window).trigger('oc.updateUi') +var tabUrl=$('> a',this).data('tabUrl') +if(tabUrl){window.history.replaceState({},'Tab link reference',tabUrl)}}) if(this.options.slidable){this.$pagesContainer.touchwipe({wipeRight:function(){self.prev();},wipeLeft:function(){self.next();},preventDefaultEvents:false,min_move_x:60});} this.$tabsContainer.toolbar({scrollClassContainer:this.$el}) this.updateClasses()} diff --git a/modules/backend/assets/js/october.tab.js b/modules/backend/assets/js/october.tab.js index 7154eb71d..a4d5bf344 100644 --- a/modules/backend/assets/js/october.tab.js +++ b/modules/backend/assets/js/october.tab.js @@ -107,8 +107,13 @@ }) this.$tabsContainer.on('shown.bs.tab', 'li', function(){ - // self.$tabsContainer.dragScroll('fixScrollClasses') - $(window).trigger('oc.updateUi') + // self.$tabsContainer.dragScroll('fixScrollClasses') + $(window).trigger('oc.updateUi') + + var tabUrl = $('> a', this).data('tabUrl') + if (tabUrl) { + window.history.replaceState({}, 'Tab link reference', tabUrl) + } }) if (this.options.slidable) { diff --git a/modules/backend/assets/js/october.triggerapi.js b/modules/backend/assets/js/october.triggerapi.js index c0be46ff8..f9af7a5a9 100644 --- a/modules/backend/assets/js/october.triggerapi.js +++ b/modules/backend/assets/js/october.triggerapi.js @@ -81,7 +81,8 @@ var trigger = $(this.options.trigger + ':checked', this.triggerParent); if (trigger.length) { this.updateTarget(trigger.val() == this.triggerConditionValue) - } else { + } + else { this.updateTarget($(this.options.trigger, this.triggerParent).val() == this.triggerConditionValue) } } @@ -89,15 +90,15 @@ TriggerOn.prototype.updateTarget = function(status) { if (this.options.triggerAction == 'show') - this.$el.toggleClass('hide', !status).trigger('hide', [!status]) + this.$el.toggleClass('hide', !status).trigger('hide.oc.triggerapi', [!status]) else if (this.options.triggerAction == 'hide') - this.$el.toggleClass('hide', status).trigger('hide', [status]) + this.$el.toggleClass('hide', status).trigger('hide.oc.triggerapi', [status]) else if (this.options.triggerAction == 'enable') - this.$el.prop('disabled', !status).trigger('disable', [!status]).toggleClass('control-disabled', !status) + this.$el.prop('disabled', !status).trigger('disable.oc.triggerapi', [!status]).toggleClass('control-disabled', !status) else if (this.options.triggerAction == 'disable') - this.$el.prop('disabled', status).trigger('disable', [status]).toggleClass('control-disabled', status) + this.$el.prop('disabled', status).trigger('disable.oc.triggerapi', [status]).toggleClass('control-disabled', status) else if (this.options.triggerAction == 'empty' && status) - this.$el.trigger('empty').val('') + this.$el.trigger('empty.oc.triggerapi').val('') if (this.options.triggerAction == 'show' || this.options.triggerAction == 'hide') this.fixButtonClasses() diff --git a/modules/backend/assets/less/controls/breadcrumb.less b/modules/backend/assets/less/controls/breadcrumb.less index c52445643..348005d2c 100644 --- a/modules/backend/assets/less/controls/breadcrumb.less +++ b/modules/backend/assets/less/controls/breadcrumb.less @@ -39,15 +39,12 @@ content:''; } } +} - + .content-tabs, + .padded-container { - margin-top: -20px; - } - - // Breadcrumb to sit flush to the element below - &.breadcrumb-flush { - margin-bottom: 0; - } +// Breadcrumb to sit flush to the element below +body.breadcrumb-flush .control-breadcrumb, +.control-breadcrumb.breadcrumb-flush { + margin-bottom: 0; } body.slim-container { diff --git a/modules/backend/assets/less/october.less b/modules/backend/assets/less/october.less index 5ead03bf1..ca2803d1c 100644 --- a/modules/backend/assets/less/october.less +++ b/modules/backend/assets/less/october.less @@ -1,3 +1,15 @@ +// +// Z-Index frequencies: +// +// 0-200 - Base layer (content) +// 200-400 - Base menus / dropdowns +// 400-600 - Secondary layer (full screen) +// 600-800 - Secondary menus / dropdowns +// 800-1000 - Tertiary layer (popups) +// 1000-1200 - Tertiary menus / dropdowns +// 1200-9000 - Reserved for frequency manager +// + // // Combines layout and vendor styles // diff --git a/modules/backend/behaviors/RelationController.php b/modules/backend/behaviors/RelationController.php index ac10a4d98..0c5d97140 100644 --- a/modules/backend/behaviors/RelationController.php +++ b/modules/backend/behaviors/RelationController.php @@ -154,6 +154,11 @@ class RelationController extends ControllerBehavior */ protected $manageId; + /** + * @var int Foeign id of a selected pivot record. + */ + protected $foreignId; + /** * @var string Active session key, used for deferred bindings. */ @@ -187,6 +192,69 @@ class RelationController extends ControllerBehavior $this->config = $this->originalConfig = $this->makeConfig($controller->relationConfig, $this->requiredConfig); } + /** + * Validates the supplied field and initializes the relation manager. + * @param string $field The relationship field. + * @return string The active field name. + */ + protected function validateField($field = null) + { + $field = $field ?: post(self::PARAM_FIELD); + + if ($field && $field != $this->field) { + $this->initRelation($this->model, $field); + } + + if (!$field && !$this->field) { + throw new ApplicationException(Lang::get('backend::lang.relation.missing_definition', compact('field'))); + } + + return $field ?: $this->field; + } + + /** + * Prepares the view data. + * @return void + */ + public function prepareVars() + { + $this->vars['relationManageId'] = $this->manageId; + $this->vars['relationLabel'] = $this->config->label ?: $this->field; + $this->vars['relationField'] = $this->field; + $this->vars['relationType'] = $this->relationType; + $this->vars['relationSearchWidget'] = $this->searchWidget; + $this->vars['relationToolbarWidget'] = $this->toolbarWidget; + $this->vars['relationManageMode'] = $this->manageMode; + $this->vars['relationManageWidget'] = $this->manageWidget; + $this->vars['relationToolbarButtons'] = $this->toolbarButtons; + $this->vars['relationViewMode'] = $this->viewMode; + $this->vars['relationViewWidget'] = $this->viewWidget; + $this->vars['relationViewModel'] = $this->viewModel; + $this->vars['relationPivotWidget'] = $this->pivotWidget; + $this->vars['relationSessionKey'] = $this->relationGetSessionKey(); + } + + /** + * The controller action is responsible for supplying the parent model + * so it's action must be fired. Additionally, each AJAX request must + * supply the relation's field name (_relation_field). + */ + protected function beforeAjax() + { + if ($this->initialized) { + return; + } + + $this->controller->pageAction(); + $this->validateField(); + $this->prepareVars(); + $this->initialized = true; + } + + // + // Interface + // + /** * Prepare the widgets used by this behavior * @param Model $model @@ -242,6 +310,7 @@ class RelationController extends ControllerBehavior $this->viewMode = $this->evalViewMode(); $this->manageMode = $this->evalManageMode(); $this->manageId = post('manage_id'); + $this->foreignId = post('foreign_id'); /* * Toolbar widget @@ -354,65 +423,6 @@ class RelationController extends ControllerBehavior return $this->relationRender($field, ['section' => 'view']); } - /** - * Validates the supplied field and initializes the relation manager. - * @param string $field The relationship field. - * @return string The active field name. - */ - protected function validateField($field = null) - { - $field = $field ?: post(self::PARAM_FIELD); - - if ($field && $field != $this->field) { - $this->initRelation($this->model, $field); - } - - if (!$field && !$this->field) { - throw new ApplicationException(Lang::get('backend::lang.relation.missing_definition', compact('field'))); - } - - return $field ?: $this->field; - } - - /** - * Prepares the view data. - * @return void - */ - public function prepareVars() - { - $this->vars['relationManageId'] = $this->manageId; - $this->vars['relationLabel'] = $this->config->label ?: $this->field; - $this->vars['relationField'] = $this->field; - $this->vars['relationType'] = $this->relationType; - $this->vars['relationSearchWidget'] = $this->searchWidget; - $this->vars['relationToolbarWidget'] = $this->toolbarWidget; - $this->vars['relationManageMode'] = $this->manageMode; - $this->vars['relationManageWidget'] = $this->manageWidget; - $this->vars['relationToolbarButtons'] = $this->toolbarButtons; - $this->vars['relationViewMode'] = $this->viewMode; - $this->vars['relationViewWidget'] = $this->viewWidget; - $this->vars['relationViewModel'] = $this->viewModel; - $this->vars['relationPivotWidget'] = $this->pivotWidget; - $this->vars['relationSessionKey'] = $this->relationGetSessionKey(); - } - - /** - * The controller action is responsible for supplying the parent model - * so it's action must be fired. Additionally, each AJAX request must - * supply the relation's field name (_relation_field). - */ - protected function beforeAjax() - { - if ($this->initialized) { - return; - } - - $this->controller->pageAction(); - $this->validateField(); - $this->prepareVars(); - $this->initialized = true; - } - /** * Controller accessor for making partials within this behavior. * @param string $partial @@ -449,39 +459,313 @@ class RelationController extends ControllerBehavior } /** - * Returns the existing record IDs for the relation. + * Returns the active session key. */ - protected function findExistingRelationIds($checkIds = null) + public function relationGetSessionKey($force = false) { - $foreignKeyName = $this->relationModel->getQualifiedKeyName(); - - $results = $this->relationObject - ->getBaseQuery() - ->select($foreignKeyName); - - if ($checkIds !== null && is_array($checkIds) && count($checkIds)) { - $results = $results->whereIn($foreignKeyName, $checkIds); + if ($this->sessionKey && !$force) { + return $this->sessionKey; } - return $results->lists($foreignKeyName); + if (post('_relation_session_key')) { + return $this->sessionKey = post('_relation_session_key'); + } + + if (post('_session_key')) { + return $this->sessionKey = post('_session_key'); + } + + return $this->sessionKey = FormHelper::getSessionKey(); } // - // Overrides + // Widgets // - /** - * Controller override: Extend the query used for populating the list - * after the default query is processed. - * @param October\Rain\Database\Builder $query - * @param string $field - */ - public function relationExtendQuery($query, $field) + protected function makeSearchWidget() { + $config = $this->makeConfig(); + $config->alias = $this->alias . 'ManageSearch'; + $config->growable = false; + $config->prompt = 'backend::lang.list.search_prompt'; + $widget = $this->makeWidget('Backend\Widgets\Search', $config); + $widget->cssClasses[] = 'recordfinder-search'; + return $widget; } - public function relationExtendRefreshResults($field) + protected function makeToolbarWidget() { + $defaultConfig = []; + + /* + * Add buttons to toolbar + */ + $defaultButtons = null; + + if (!$this->readOnly) { + $defaultButtons = '~/modules/backend/behaviors/relationcontroller/partials/_toolbar.htm'; + } + + $defaultConfig['buttons'] = $this->getConfig('view[toolbarPartial]', $defaultButtons); + + /* + * Make config + */ + $toolbarConfig = $this->makeConfig($this->getConfig('toolbar', $defaultConfig)); + $toolbarConfig->alias = $this->alias . 'Toolbar'; + + /* + * Add search to toolbar + */ + $useSearch = $this->viewMode == 'multi' && $this->getConfig('view[showSearch]'); + + if ($useSearch) { + $toolbarConfig->search = [ + 'prompt' => 'backend::lang.list.search_prompt' + ]; + } + + /* + * No buttons, no search should mean no toolbar + */ + if (empty($toolbarConfig->search) && empty($toolbarConfig->buttons)) + return; + + $toolbarWidget = $this->makeWidget('Backend\Widgets\Toolbar', $toolbarConfig); + $toolbarWidget->cssClasses[] = 'list-header'; + + return $toolbarWidget; + } + + protected function makeViewWidget() + { + /* + * Multiple (has many, belongs to many) + */ + if ($this->viewMode == 'multi') { + $config = $this->makeConfigForMode('view', 'list'); + $config->model = $this->relationModel; + $config->alias = $this->alias . 'ViewList'; + $config->showSorting = $this->getConfig('view[showSorting]', true); + $config->defaultSort = $this->getConfig('view[defaultSort]'); + $config->recordsPerPage = $this->getConfig('view[recordsPerPage]'); + $config->showCheckboxes = $this->getConfig('view[showCheckboxes]', !$this->readOnly); + $config->recordUrl = $this->getConfig('view[recordUrl]', null); + + $defaultOnClick = sprintf( + "$.oc.relationBehavior.clickViewListRecord(':id', '%s', '%s')", + $this->field, + $this->relationGetSessionKey() + ); + + if ($config->recordUrl) { + $defaultOnClick = null; + } + + $config->recordOnClick = $this->getConfig('view[recordOnClick]', $defaultOnClick); + + if ($emptyMessage = $this->getConfig('emptyMessage')) { + $config->noRecordsMessage = $emptyMessage; + } + + /* + * Constrain the query by the relationship and deferred items + */ + $widget = $this->makeWidget('Backend\Widgets\Lists', $config); + $widget->bindEvent('list.extendQuery', function ($query) { + $this->controller->relationExtendQuery($query, $this->field); + + $this->relationObject->setQuery($query); + if ($sessionKey = $this->relationGetSessionKey()) { + $this->relationObject->withDeferred($sessionKey); + } + elseif ($this->model->exists) { + $this->relationObject->addConstraints(); + } + + /* + * Allows pivot data to enter the fray + */ + if ($this->relationType == 'belongsToMany') { + $this->relationObject->setQuery($query->getQuery()); + return $this->relationObject; + } + }); + + /* + * Constrain the list by the search widget, if available + */ + if ($this->toolbarWidget && $this->getConfig('view[showSearch]')) { + if ($searchWidget = $this->toolbarWidget->getSearchWidget()) { + $searchWidget->bindEvent('search.submit', function () use ($widget, $searchWidget) { + $widget->setSearchTerm($searchWidget->getActiveTerm()); + return $widget->onRefresh(); + }); + + $searchWidget->setActiveTerm(null); + } + } + } + /* + * Single (belongs to, has one) + */ + elseif ($this->viewMode == 'single') { + $query = $this->relationObject; + $this->controller->relationExtendQuery($query, $this->field); + $this->viewModel = $query->getResults() ?: $this->relationModel; + + $config = $this->makeConfigForMode('view', 'form'); + $config->model = $this->viewModel; + $config->arrayName = class_basename($this->relationModel); + $config->context = 'relation'; + $config->alias = $this->alias . 'ViewForm'; + + $widget = $this->makeWidget('Backend\Widgets\Form', $config); + $widget->previewMode = true; + } + + return $widget; + } + + protected function makeManageWidget() + { + $widget = null; + + /* + * List / Pivot + */ + if ($this->manageMode == 'list' || $this->manageMode == 'pivot') { + $isPivot = $this->manageMode == 'pivot'; + + $config = $this->makeConfigForMode('manage', 'list'); + $config->model = $this->relationModel; + $config->alias = $this->alias . 'ManageList'; + $config->showSetup = false; + $config->showCheckboxes = $this->getConfig('manage[showCheckboxes]', !$isPivot); + $config->showSorting = $this->getConfig('manage[showSorting]', !$isPivot); + $config->defaultSort = $this->getConfig('manage[defaultSort]'); + $config->recordsPerPage = $this->getConfig('manage[recordsPerPage]'); + + if ($this->viewMode == 'single') { + $config->showCheckboxes = false; + $config->recordOnClick = sprintf( + "$.oc.relationBehavior.clickManageListRecord(:id, '%s', '%s')", + $this->field, + $this->relationGetSessionKey() + ); + } + elseif ($config->showCheckboxes) { + $config->recordOnClick = "$.oc.relationBehavior.toggleListCheckbox(this)"; + } + elseif ($isPivot) { + $config->recordOnClick = sprintf( + "$.oc.relationBehavior.clickManagePivotListRecord(:id, '%s', '%s')", + $this->field, + $this->relationGetSessionKey() + ); + } + + $widget = $this->makeWidget('Backend\Widgets\Lists', $config); + + /* + * Link the Search Widget to the List Widget + */ + if ($this->getConfig('manage[showSearch]')) { + $this->searchWidget = $this->makeSearchWidget(); + $this->searchWidget->bindToController(); + $this->searchWidget->bindEvent('search.submit', function () use ($widget) { + $widget->setSearchTerm($this->searchWidget->getActiveTerm()); + return $widget->onRefresh(); + }); + + $this->searchWidget->setActiveTerm(null); + } + } + /* + * Form + */ + elseif ($this->manageMode == 'form') { + + $config = $this->makeConfigForMode('manage', 'form'); + $config->model = $this->relationModel; + $config->arrayName = class_basename($this->relationModel); + $config->context = $this->evalFormContext('manage', !!$this->manageId); + $config->alias = $this->alias . 'ManageForm'; + + /* + * Existing record + */ + if ($this->manageId) { + $config->model = $config->model->find($this->manageId); + if (!$config->model) { + throw new ApplicationException(Lang::get('backend::lang.model.not_found', [ + 'class' => get_class($config->model), 'id' => $this->manageId + ])); + } + } + + $widget = $this->makeWidget('Backend\Widgets\Form', $config); + } + + if (!$widget) { + return null; + } + + /* + * Exclude existing relationships + */ + if ($this->manageMode == 'pivot' || $this->manageMode == 'list') { + $widget->bindEvent('list.extendQuery', function ($query) { + $this->controller->relationExtendQuery($query, $this->field); + + /* + * Where not in the current list of related records + */ + $existingIds = $this->findExistingRelationIds(); + if (count($existingIds)) { + $query->whereNotIn($this->relationModel->getQualifiedKeyName(), $existingIds); + } + + }); + } + + return $widget; + } + + protected function makePivotWidget() + { + $config = $this->makeConfigForMode('pivot', 'form'); + $config->model = $this->relationModel; + $config->arrayName = class_basename($this->relationModel); + $config->context = $this->evalFormContext('pivot', !!$this->manageId); + $config->alias = $this->alias . 'ManagePivotForm'; + + /* + * Existing record + */ + if ($this->manageId) { + $foreignKeyName = $this->relationModel->getQualifiedKeyName(); + $hydratedModel = $this->relationObject->where($foreignKeyName, $this->manageId)->first(); + + $config->model = $hydratedModel; + if (!$config->model) { + throw new ApplicationException(Lang::get('backend::lang.model.not_found', [ + 'class' => get_class($config->model), 'id' => $this->manageId + ])); + } + } + else { + if ($this->foreignId && ($foreignModel = $this->relationModel->find($this->foreignId))) { + $foreignModel->exists = false; + $config->model = $foreignModel; + } + + $pivotModel = $this->relationObject->newPivot(); + $config->model->setRelation('pivot', $pivotModel); + } + + $widget = $this->makeWidget('Backend\Widgets\Form', $config); + return $widget; } // @@ -775,11 +1059,19 @@ class RelationController extends ControllerBehavior return $this->relationRefresh(); } + /** + * Add multiple items using a single pivot form. + */ + public function onRelationManageAddPivot() + { + return $this->onRelationManagePivotForm(); + } + public function onRelationManagePivotForm() { $this->beforeAjax(); - $this->vars['foreignId'] = post('foreign_id'); + $this->vars['foreignId'] = $this->foreignId ?: post('checked'); return $this->relationMakePartial('pivot_form'); } @@ -787,18 +1079,24 @@ class RelationController extends ControllerBehavior { $this->beforeAjax(); - $foreignId = post('foreign_id'); - $foreignModel = $this->relationModel->find($foreignId); - $saveData = $this->pivotWidget->getSaveData(); + /* + * Add the checked IDs to the pivot table + */ + $foreignIds = (array) $this->foreignId; + $this->relationObject->sync($foreignIds, false); /* - * Check for existing relation + * Save data to models */ $foreignKeyName = $this->relationModel->getQualifiedKeyName(); - $existing = $this->relationObject->where($foreignKeyName, $foreignId)->count(); + $hyrdatedModels = $this->relationObject->whereIn($foreignKeyName, $foreignIds)->get(); + $saveData = $this->pivotWidget->getSaveData(); - if (!$existing) { - $this->relationObject->add($foreignModel, null, $saveData); + foreach ($hyrdatedModels as $hydratedModel) { + $modelsToSave = $this->prepareModelsToSave($hydratedModel, $saveData); + foreach ($modelsToSave as $modelToSave) { + $modelToSave->save(); + } } return ['#'.$this->relationGetId('view') => $this->relationRenderView()]; @@ -821,339 +1119,44 @@ class RelationController extends ControllerBehavior } // - // Widgets + // Overrides // - protected function makeSearchWidget() - { - $config = $this->makeConfig(); - $config->alias = $this->alias . 'ManageSearch'; - $config->growable = false; - $config->prompt = 'backend::lang.list.search_prompt'; - $widget = $this->makeWidget('Backend\Widgets\Search', $config); - $widget->cssClasses[] = 'recordfinder-search'; - return $widget; - } - - protected function makeToolbarWidget() - { - $defaultConfig = []; - - /* - * Add buttons to toolbar - */ - $defaultButtons = null; - - if (!$this->readOnly) { - $defaultButtons = '~/modules/backend/behaviors/relationcontroller/partials/_toolbar.htm'; - } - - $defaultConfig['buttons'] = $this->getConfig('view[toolbarPartial]', $defaultButtons); - - /* - * Make config - */ - $toolbarConfig = $this->makeConfig($this->getConfig('toolbar', $defaultConfig)); - $toolbarConfig->alias = $this->alias . 'Toolbar'; - - /* - * Add search to toolbar - */ - $useSearch = $this->viewMode == 'multi' && $this->getConfig('view[showSearch]'); - - if ($useSearch) { - $toolbarConfig->search = [ - 'prompt' => 'backend::lang.list.search_prompt' - ]; - } - - /* - * No buttons, no search should mean no toolbar - */ - if (empty($toolbarConfig->search) && empty($toolbarConfig->buttons)) - return; - - $toolbarWidget = $this->makeWidget('Backend\Widgets\Toolbar', $toolbarConfig); - $toolbarWidget->cssClasses[] = 'list-header'; - - return $toolbarWidget; - } - - protected function makeViewWidget() - { - /* - * Multiple (has many, belongs to many) - */ - if ($this->viewMode == 'multi') { - $config = $this->makeConfigForMode('view', 'list'); - $config->model = $this->relationModel; - $config->alias = $this->alias . 'ViewList'; - $config->showSorting = $this->getConfig('view[showSorting]', true); - $config->defaultSort = $this->getConfig('view[defaultSort]'); - $config->recordsPerPage = $this->getConfig('view[recordsPerPage]'); - $config->showCheckboxes = $this->getConfig('view[showCheckboxes]', !$this->readOnly); - - $defaultOnClick = sprintf( - "$.oc.relationBehavior.clickViewListRecord(':id', '%s', '%s')", - $this->field, - $this->relationGetSessionKey() - ); - - $config->recordOnClick = $this->getConfig('view[recordOnClick]', $defaultOnClick); - $config->recordUrl = $this->getConfig('view[recordUrl]', null); - - if ($emptyMessage = $this->getConfig('emptyMessage')) { - $config->noRecordsMessage = $emptyMessage; - } - - /* - * Constrain the query by the relationship and deferred items - */ - $widget = $this->makeWidget('Backend\Widgets\Lists', $config); - $widget->bindEvent('list.extendQuery', function ($query) { - $this->controller->relationExtendQuery($query, $this->field); - - $this->relationObject->setQuery($query); - if ($sessionKey = $this->relationGetSessionKey()) { - $this->relationObject->withDeferred($sessionKey); - } - elseif ($this->model->exists) { - $this->relationObject->addConstraints(); - } - - /* - * Allows pivot data to enter the fray - */ - if ($this->relationType == 'belongsToMany') { - $this->relationObject->setQuery($query->getQuery()); - return $this->relationObject; - } - }); - - /* - * Constrain the list by the search widget, if available - */ - if ($this->toolbarWidget && $this->getConfig('view[showSearch]')) { - if ($searchWidget = $this->toolbarWidget->getSearchWidget()) { - $searchWidget->bindEvent('search.submit', function () use ($widget, $searchWidget) { - $widget->setSearchTerm($searchWidget->getActiveTerm()); - return $widget->onRefresh(); - }); - - $searchWidget->setActiveTerm(null); - } - } - } - /* - * Single (belongs to, has one) - */ - elseif ($this->viewMode == 'single') { - $query = $this->relationObject; - $this->controller->relationExtendQuery($query, $this->field); - $this->viewModel = $query->getResults() ?: $this->relationModel; - - $config = $this->makeConfigForMode('view', 'form'); - $config->model = $this->viewModel; - $config->arrayName = class_basename($this->relationModel); - $config->context = 'relation'; - $config->alias = $this->alias . 'ViewForm'; - - $widget = $this->makeWidget('Backend\Widgets\Form', $config); - $widget->previewMode = true; - } - - return $widget; - } - - protected function makeManageWidget() - { - $widget = null; - /* - * Pivot - */ - if ($this->manageMode == 'pivot') { - $config = $this->makeConfigForMode('manage', 'list'); - $config->model = $this->relationModel; - $config->alias = $this->alias . 'ManagePivotList'; - $config->showSetup = false; - $config->showSorting = $this->getConfig('manage[showSorting]', false); - $config->defaultSort = $this->getConfig('manage[defaultSort]'); - $config->recordsPerPage = $this->getConfig('manage[recordsPerPage]'); - $config->recordOnClick = sprintf( - "$.oc.relationBehavior.clickManagePivotListRecord(:id, '%s', '%s')", - $this->field, - $this->relationGetSessionKey() - ); - - $widget = $this->makeWidget('Backend\Widgets\Lists', $config); - - /* - * Link the Search Widget to the List Widget - */ - if ($this->getConfig('pivot[showSearch]')) { - $this->searchWidget = $this->makeSearchWidget(); - $this->searchWidget->bindToController(); - $this->searchWidget->bindEvent('search.submit', function () use ($widget) { - $widget->setSearchTerm($this->searchWidget->getActiveTerm()); - return $widget->onRefresh(); - }); - - $this->searchWidget->setActiveTerm(null); - } - } - /* - * List - */ - elseif ($this->manageMode == 'list') { - $config = $this->makeConfigForMode('manage', 'list'); - $config->model = $this->relationModel; - $config->alias = $this->alias . 'ManageList'; - $config->showSetup = false; - $config->showCheckboxes = true; - $config->showSorting = $this->getConfig('manage[showSorting]', true); - $config->defaultSort = $this->getConfig('manage[defaultSort]'); - $config->recordsPerPage = $this->getConfig('manage[recordsPerPage]'); - - if ($this->viewMode == 'single') { - $config->showCheckboxes = false; - $config->recordOnClick = sprintf( - "$.oc.relationBehavior.clickManageListRecord(:id, '%s', '%s')", - $this->field, - $this->relationGetSessionKey() - ); - } - - $widget = $this->makeWidget('Backend\Widgets\Lists', $config); - - /* - * Link the Search Widget to the List Widget - */ - if ($this->getConfig('manage[showSearch]')) { - $this->searchWidget = $this->makeSearchWidget(); - $this->searchWidget->bindToController(); - $this->searchWidget->bindEvent('search.submit', function () use ($widget) { - $widget->setSearchTerm($this->searchWidget->getActiveTerm()); - return $widget->onRefresh(); - }); - - $this->searchWidget->setActiveTerm(null); - } - } - /* - * Form - */ - elseif ($this->manageMode == 'form') { - - /* - * Determine supplied form context - */ - $manageConfig = isset($this->config->manage) ? $this->config->manage : []; - - if ($context = array_get($manageConfig, 'context')) { - if (is_array($context)) { - $context = $this->manageId - ? array_get($context, 'update') - : array_get($context, 'create'); - } - } - - $config = $this->makeConfigForMode('manage', 'form'); - $config->model = $this->relationModel; - $config->arrayName = class_basename($this->relationModel); - $config->context = $context ?: 'relation'; - $config->alias = $this->alias . 'ManageForm'; - - /* - * Existing record - */ - if ($this->manageId) { - $config->model = $config->model->find($this->manageId); - if (!$config->model) { - throw new ApplicationException(Lang::get('backend::lang.model.not_found', [ - 'class' => get_class($config->model), 'id' => $this->manageId - ])); - } - } - - $widget = $this->makeWidget('Backend\Widgets\Form', $config); - } - - if (!$widget) { - return null; - } - - /* - * Exclude existing relationships - */ - if ($this->manageMode == 'pivot' || $this->manageMode == 'list') { - $widget->bindEvent('list.extendQuery', function ($query) { - $this->controller->relationExtendQuery($query, $this->field); - - /* - * Where not in the current list of related records - */ - $existingIds = $this->findExistingRelationIds(); - if (count($existingIds)) { - $query->whereNotIn($this->relationModel->getQualifiedKeyName(), $existingIds); - } - - }); - } - - return $widget; - } - - protected function makePivotWidget() - { - $config = $this->makeConfigForMode('pivot', 'form'); - $config->model = $this->relationModel; - $config->arrayName = class_basename($this->relationModel); - $config->context = 'relation'; - $config->alias = $this->alias . 'ManagePivotForm'; - - /* - * Existing record - */ - if ($this->manageId) { - $foreignKeyName = $this->relationModel->getQualifiedKeyName(); - $hydratedModel = $this->relationObject->where($foreignKeyName, $this->manageId)->first(); - - $config->model = $hydratedModel; - if (!$config->model) { - throw new ApplicationException(Lang::get('backend::lang.model.not_found', [ - 'class' => get_class($config->model), 'id' => $this->manageId - ])); - } - } - - $widget = $this->makeWidget('Backend\Widgets\Form', $config); - return $widget; - } - /** - * Returns the active session key. + * Controller override: Extend the query used for populating the list + * after the default query is processed. + * @param October\Rain\Database\Builder $query + * @param string $field */ - public function relationGetSessionKey($force = false) + public function relationExtendQuery($query, $field) { - if ($this->sessionKey && !$force) { - return $this->sessionKey; - } + } - if (post('_relation_session_key')) { - return $this->sessionKey = post('_relation_session_key'); - } - - if (post('_session_key')) { - return $this->sessionKey = post('_session_key'); - } - - return $this->sessionKey = FormHelper::getSessionKey(); + public function relationExtendRefreshResults($field) + { } // // Helpers // + /** + * Returns the existing record IDs for the relation. + */ + protected function findExistingRelationIds($checkIds = null) + { + $foreignKeyName = $this->relationModel->getQualifiedKeyName(); + + $results = $this->relationObject + ->getBaseQuery() + ->select($foreignKeyName); + + if ($checkIds !== null && is_array($checkIds) && count($checkIds)) { + $results = $results->whereIn($foreignKeyName, $checkIds); + } + + return $results->lists($foreignKeyName); + } /** * Determine the default buttons based on the model relationship type. @@ -1238,6 +1241,28 @@ class RelationController extends ControllerBehavior } } + /** + * Determine supplied form context. + */ + protected function evalFormContext($mode = 'manage', $exists = false) + { + $config = isset($this->config->{$mode}) ? $this->config->{$mode} : []; + + if ($context = array_get($config, 'context')) { + if (is_array($context)) { + $context = $exists + ? array_get($context, 'update') + : array_get($context, 'create'); + } + } + + if (!$context) { + $context = $exists ? 'update' : 'create'; + } + + return $context; + } + /** * Returns the configuration for a mode (view, manage, pivot) for an * expected type (list, form). Uses fallback configuration. diff --git a/modules/backend/behaviors/relationcontroller/assets/js/october.relation.js b/modules/backend/behaviors/relationcontroller/assets/js/october.relation.js index b541f593d..581feb4ff 100644 --- a/modules/backend/behaviors/relationcontroller/assets/js/october.relation.js +++ b/modules/backend/behaviors/relationcontroller/assets/js/october.relation.js @@ -5,18 +5,8 @@ var RelationBehavior = function() { - this.clickManageListRecord = function(recordId, relationField, sessionKey) { - var oldPopup = $('#relationManagePopup') - - $.request('onRelationClickManageList', { - data: { - 'record_id': recordId, - '_relation_field': relationField, - '_session_key': sessionKey - } - }) - - oldPopup.popup('hide') + this.toggleListCheckbox = function(el) { + $(el).closest('.control-list').listWidget('toggleChecked', [el]) } this.clickViewListRecord = function(recordId, relationField, sessionKey) { @@ -33,6 +23,20 @@ }) } + this.clickManageListRecord = function(recordId, relationField, sessionKey) { + var oldPopup = $('#relationManagePopup') + + $.request('onRelationClickManageList', { + data: { + 'record_id': recordId, + '_relation_field': relationField, + '_session_key': sessionKey + } + }) + + oldPopup.popup('hide') + } + this.clickManagePivotListRecord = function(foreignId, relationField, sessionKey) { var oldPopup = $('#relationManagePivotPopup'), newPopup = $('') diff --git a/modules/backend/behaviors/relationcontroller/partials/_manage_pivot.htm b/modules/backend/behaviors/relationcontroller/partials/_manage_pivot.htm index 02f0ca616..6572dc1be 100644 --- a/modules/backend/behaviors/relationcontroller/partials/_manage_pivot.htm +++ b/modules/backend/behaviors/relationcontroller/partials/_manage_pivot.htm @@ -17,6 +17,18 @@ + + +
+ + +
+

Recommended

+
+
+
+
+ +
+ + + + + + diff --git a/modules/system/controllers/updates/_install_themes.htm b/modules/system/controllers/updates/_install_themes.htm new file mode 100644 index 000000000..9ea744a83 --- /dev/null +++ b/modules/system/controllers/updates/_install_themes.htm @@ -0,0 +1,104 @@ + +
+ + +
+ +
+ +
+ +
+ + +
+ +

+ Installed themes + (0) +

+ + +
+

There are no themes installed from the marketplace.

+
+ +
    + + +
  • +
    + +
    +
    +

    +

    by

    +
    + +
  • + + +
+ + +
+ +
+
+ + +
+

Recommended

+
+
+
+
+ +
+ +
+ +
+ + diff --git a/modules/system/controllers/updates/_list_search.htm b/modules/system/controllers/updates/_list_search.htm deleted file mode 100644 index edddfa2f1..000000000 --- a/modules/system/controllers/updates/_list_search.htm +++ /dev/null @@ -1,24 +0,0 @@ - - -
- - - - - -
- - diff --git a/modules/system/controllers/updates/_list_toolbar.htm b/modules/system/controllers/updates/_list_toolbar.htm index 8639e2672..c680e089d 100644 --- a/modules/system/controllers/updates/_list_toolbar.htm +++ b/modules/system/controllers/updates/_list_toolbar.htm @@ -11,4 +11,9 @@ class="btn btn-default oc-icon-puzzle-piece"> + + Install plugins + \ No newline at end of file diff --git a/modules/system/controllers/updates/_theme_form.htm b/modules/system/controllers/updates/_theme_form.htm new file mode 100644 index 000000000..8729e8974 --- /dev/null +++ b/modules/system/controllers/updates/_theme_form.htm @@ -0,0 +1,47 @@ + 'themeForm']) ?> + + + + + + \ No newline at end of file diff --git a/modules/system/controllers/updates/config_list.yaml b/modules/system/controllers/updates/config_list.yaml index 02b043b32..5bda9f802 100644 --- a/modules/system/controllers/updates/config_list.yaml +++ b/modules/system/controllers/updates/config_list.yaml @@ -9,4 +9,3 @@ noRecordsMessage: backend::lang.list.no_records toolbar: buttons: list_toolbar - search: list_search \ No newline at end of file diff --git a/modules/system/controllers/updates/install.htm b/modules/system/controllers/updates/install.htm new file mode 100644 index 000000000..5043e13da --- /dev/null +++ b/modules/system/controllers/updates/install.htm @@ -0,0 +1,49 @@ + + + + +fatalError): ?> + +
+ +
+
+
+ makePartial('install_plugins') ?> +
+
+
+
+ makePartial('install_themes') ?> +
+
+
+
+ + + +
+

fatalError) ?>

+

+

+
+ + diff --git a/modules/system/lang/en/lang.php b/modules/system/lang/en/lang.php index 440086139..6c5e9e02e 100644 --- a/modules/system/lang/en/lang.php +++ b/modules/system/lang/en/lang.php @@ -52,6 +52,12 @@ return [ 'my_settings' => 'My Settings' ] ], + 'theme' => [ + 'name' => [ + 'label' => 'Theme Name', + 'help' => 'Name the theme by its unique code. For example, RainLab.Vanilla' + ] + ], 'plugin' => [ 'unnamed' => 'Unnamed plugin', 'name' => [ @@ -156,7 +162,9 @@ return [ 'install' => [ 'project_label' => 'Attach to Project', 'plugin_label' => 'Install Plugin', + 'theme_label' => 'Install Theme', 'missing_plugin_name' => 'Please specify a Plugin name to install.', + 'missing_theme_name' => 'Please specify a Theme name to install.', 'install_completing' => 'Finishing installation process', 'install_success' => 'The plugin has been installed successfully.' ], diff --git a/modules/system/lang/ru/lang.php b/modules/system/lang/ru/lang.php index bee58c3f8..1fd8ab3e4 100644 --- a/modules/system/lang/ru/lang.php +++ b/modules/system/lang/ru/lang.php @@ -13,20 +13,23 @@ return [ 'fa' => 'Persian', 'fr' => 'French', 'hu' => 'Hungarian', + 'id' => 'Bahasa Indonesia', 'it' => 'Italian', 'ja' => 'Japanese', 'nl' => 'Dutch', + 'pl' => 'Polish', 'pt-br' => 'Brazilian Portuguese', 'ro' => 'Romanian', 'ru' => 'Russian', 'se' => 'Swedish', - 'tr' => 'Turkish', + 'sk' => 'Slovak (Slovakia)', + 'tr' => 'Turkish' ], 'directory' => [ - 'create_fail' => "Невозможно создать директорию: :name", + 'create_fail' => 'Невозможно создать директорию: :name', ], 'file' => [ - 'create_fail' => "Невозможно создать файл: :name", + 'create_fail' => 'Невозможно создать файл: :name', ], 'combiner' => [ 'not_found' => "Сборщик ресурсов не может найти файл ':name'.", @@ -66,13 +69,13 @@ return [ 'disabled_help' => 'Отключенные плагины будут игнорироваться.', 'selected_amount' => 'Выбрано плагинов: :amount', 'remove_confirm' => 'Вы уверены?', - 'remove_success' => "Выбранные плагины успешно удалены.", + 'remove_success' => 'Выбранные плагины успешно удалены.', 'refresh_confirm' => 'Вы уверены?', - 'refresh_success' => "Выбранные плагины успешно обновлены.", + 'refresh_success' => 'Выбранные плагины успешно обновлены.', 'disable_confirm' => 'Вы уверены?', - 'disable_success' => "Плагин успешно отключен.", - 'enable_success' => "Плагин успешно включен.", - 'unknown_plugin' => "Плагин был удален из файловой системы.", + 'disable_success' => 'Плагин успешно отключен.', + 'enable_success' => 'Плагин успешно включен.', + 'unknown_plugin' => 'Плагин был удален из файловой системы.', ], 'project' => [ 'name' => 'Проект', @@ -114,6 +117,7 @@ return [ 'smtp_password' => 'SMTP пароль', 'smtp_port' => 'SMTP порт', 'smtp_ssl' => 'Использовать SSL', + 'sendmail' => 'Sendmail', 'sendmail_path' => 'Sendmail Путь', 'sendmail_path_comment' => 'Пожалуйста, укажите путь к sendmail.', 'mailgun' => 'Mailgun', @@ -121,6 +125,9 @@ return [ 'mailgun_domain_comment' => 'Пожалуйста, укажите Mailgun домен.', 'mailgun_secret' => 'Секретный API-ключ', 'mailgun_secret_comment' => 'Введите ваш Mailgun API-ключ.', + 'mandrill' => 'Mandrill', + 'mandrill_secret' => 'Секретный ключ Mandrill', + 'mandrill_secret_comment' => 'Введите ваш Mandrill API-ключ.' ], 'mail_templates' => [ 'menu_label' => 'Шаблоны почты', @@ -170,6 +177,8 @@ return [ 'core_build_new_help' => 'Последняя доступная сборка.', 'core_downloading' => 'Загрузка файлов приложения', 'core_extracting' => 'Распаковка файлов приложения', + 'plugins' => 'Плагины', + 'disabled' => 'Отключено', 'plugin_downloading' => 'Загрузка плагина: :name', 'plugin_extracting' => 'Распаковка плагина: :name', 'plugin_version_none' => 'Новый плагин', @@ -207,7 +216,7 @@ return [ ], 'config' => [ 'not_found' => 'Не удалось найти конфигурационный файл :file, ожидаемый для :location.', - 'required' => 'Для конфигурации, используемой в :location не указано свойство $:property.', + 'required' => "Для конфигурации, используемой в :location не указано свойство ':property'.", ], 'zip' => [ 'extract_failed' => "Невозможно извлечь файл ':file'.", @@ -242,9 +251,11 @@ return [ 'status_code' => 'Статус', ], 'permissions' => [ + 'name' => 'Система', 'manage_system_settings' => 'Настройка системных параметров', 'manage_software_updates' => 'Управлять обновлениями', 'manage_mail_templates' => 'Управление почтовыми шаблонами', + 'manage_mail_settings' => 'Управление настройками почты', 'manage_other_administrators' => 'Управление другими администраторами', 'view_the_dashboard' => 'Просмотр приборной панели' ] diff --git a/modules/system/lang/sv/lang.php b/modules/system/lang/sv/lang.php index 8f79658ec..fc33fa230 100644 --- a/modules/system/lang/sv/lang.php +++ b/modules/system/lang/sv/lang.php @@ -7,17 +7,23 @@ return [ ], 'locale' => [ 'en' => 'English', - 'nl' => 'Dutch', - 'ja' => 'Japanese', - 'se' => 'Swedish', - 'tr' => 'Turkish', 'de' => 'German', - 'ru' => 'Russian', - 'fr' => 'French', - 'it' => 'Italian', - 'ro' => 'Romana', - 'pt-br' => 'Brazilian Portuguese', + 'es' => 'Spanish', + 'es-ar' => 'Spanish (Argentina)', 'fa' => 'Persian', + 'fr' => 'French', + 'hu' => 'Hungarian', + 'id' => 'Bahasa Indonesia', + 'it' => 'Italian', + 'ja' => 'Japanese', + 'nl' => 'Dutch', + 'pl' => 'Polish', + 'pt-br' => 'Brazilian Portuguese', + 'ro' => 'Romanian', + 'ru' => 'Russian', + 'se' => 'Swedish', + 'sk' => 'Slovak (Slovakia)', + 'tr' => 'Turkish' ], 'directory' => [ 'create_fail' => "Kunde inte skapa mapp: :name", @@ -31,6 +37,20 @@ return [ 'system' => [ 'name' => 'System', 'menu_label' => 'System', + 'categories' => [ + 'cms' => 'CMS', + 'misc' => 'Övrigt', + 'logs' => 'Loggar', + 'mail' => 'Mail', + 'shop' => 'Affär', + 'team' => 'Lag', + 'users' => 'Användare', + 'system' => 'System', + 'social' => 'Social', + 'events' => 'Händelser', + 'customers' => 'Kunder', + 'my_settings' => 'Mina inställningar' + ] ], 'plugin' => [ 'unnamed' => 'Namnlöst plugin', @@ -39,6 +59,24 @@ return [ 'help' => 'Namnge pluginet efter dess unika kod. Exempelvis RainLab.Blog', ], ], + 'plugins' => [ + 'manage' => 'Hantera pluginerna', + 'enable_or_disable' => 'Aktivera eller inaktivera', + 'enable_or_disable_title' => 'Aktivera eller inaktivera pluginerna', + 'remove' => 'Ta bort', + 'refresh' => 'Uppdatera', + 'disabled_label' => 'Avaktivera', + 'disabled_help' => 'Pluginerna som är avaktiverade är igorerade av appliationen.', + 'selected_amount' => 'Markerade plugins: :amount', + 'remove_confirm' => 'Är du säker?', + 'remove_success' => 'Lyckades bort dessa plugins från systemet.', + 'refresh_confirm' => 'Är du säker?', + 'refresh_success' => 'Lyckades uppdatera dessa plugins från systemet.', + 'disable_confirm' => 'Är du säker?', + 'disable_success' => 'Lyckades avaktivera dessa plugins från systemet.', + 'enable_success' => 'Lyckades aktivera dessa plugins.', + 'unknown_plugin' => 'Pluginen har blivit borttagen från systemet.' + ], 'project' => [ 'name' => 'Projekt', 'owner_label' => 'Ägare', @@ -55,17 +93,22 @@ return [ ], 'settings' => [ 'menu_label' => 'Inställningar', + 'not_found' => 'Det går inte att hitta de angivna inställningarna.', 'missing_model' => 'Inställningssidan saknar en modell-definition', 'update_success' => 'Inställningar för :name har uppdaterats', 'return' => 'Återgå till systeminställningar', + 'search' => 'Sök' ], - 'email' => [ + 'mail' => [ + 'log_file' => 'Logfiler', 'menu_label' => 'Epost-konfiguration', 'menu_description' => 'Hantera epost-inställningar', 'general' => 'Generellt', 'method' => 'Email-metod', 'sender_name' => 'Avsändarnamn', 'sender_email' => 'Avsändarens epost-adress', + 'php_mail' => 'PHP mail', + 'sendmail' => 'Sendmail', 'smtp' => 'SMTP', 'smtp_address' => 'SMTP-adress', 'smtp_authorization' => 'SMTP-autentisering krävs', @@ -77,6 +120,38 @@ return [ 'sendmail' => 'Sendmail', 'sendmail_path' => 'Sendmail-sökväg', 'sendmail_path_comment' => 'Var god ange sökvägen till sendmail', + 'mailgun' => 'Mailgun', + 'mailgun_domain' => 'Mailgun domän', + 'mailgun_domain_comment' => 'Vänligen ange Mailgun domännamnet.', + 'mailgun_secret' => 'Mailgun hemlighet', + 'mailgun_secret_comment' => 'Ange din Mailgun API nyckel.', + 'mandrill' => 'Mandrill', + 'mandrill_secret' => 'Mandrill hemlighet', + 'mandrill_secret_comment' => 'Ange din API nyckel.' + ], + 'mail_templates' => [ + 'menu_label' => 'Epost mall', + 'menu_description' => 'Ändra epost-mallar som skickas till användare och administratörer, hantera epost-utseende.', + 'new_template' => 'Ny mall', + 'new_layout' => 'Ny utseende', + 'template' => 'Mall', + 'templates' => 'Mallar', + 'menu_layouts_label' => 'Epost mallar', + 'layout' => 'Utseende', + 'layouts' => 'Utseenden', + 'name' => 'Namn', + 'name_comment' => 'Unikt namn för att hänvisa till den här mallen', + 'code' => 'Kod', + 'code_comment' => 'Unik kod som används för att hänvisa till den här mallen', + 'subject' => 'Ärende', + 'subject_comment' => 'Ärende till epost meddelandet', + 'description' => 'Beskrivning', + 'content_html' => 'HTML', + 'content_css' => 'CSS', + 'content_text' => 'Klartext', + 'test_send' => 'Skicka ett testmeddelande', + 'test_success' => 'Testmeddelandet har skickats.', + 'return' => 'Återvänd till mall-listan' ], 'install' => [ 'project_label' => 'Länka till Projekt', @@ -89,7 +164,8 @@ return [ 'title' => 'Hantera uppdateringar', 'name' => 'Uppdatera mjukvara', 'menu_label' => 'Uppdateringar', - 'check_label' => 'Kontrollera uppdateringar', + 'menu_description' => 'Uppdatera systemet, hantera och installera plugins och teman.', + 'check_label' => 'Sök efter uppdateringar', 'retry_label' => 'Försök igen', 'plugin_name' => 'Namn', 'plugin_description' => 'Beskrivning', @@ -101,11 +177,17 @@ return [ 'core_build_new_help' => 'Senaste build är tillgänglig.', 'core_downloading' => 'Laddar ner applikationsfiler', 'core_extracting' => 'Packar upp applikationsfiler', + 'plugins' => 'Plugins', + 'disabled' => 'Avaktiverade', 'plugin_downloading' => 'Laddar ner plugin: :name', 'plugin_extracting' => 'Packar upp plugin: :name', 'plugin_version_none' => 'Nytt plugin', 'plugin_version_old' => 'Nuvarande v:version', 'plugin_version_new' => 'v:version', + 'theme_label' => 'Tema', + 'theme_new_install' => 'Installation av nytt tema.', + 'theme_downloading' => 'Ladda ner temat: :name', + 'theme_extracting' => 'Packar upp temat: :name', 'update_label' => 'Updatera mjukvara', 'update_completing' => 'Slutför uppdatering', 'update_loading' => 'Laddar tillgängliga uppdateringar...', @@ -114,12 +196,12 @@ return [ 'force_label' => 'Tvinga uppdatering', 'found' => [ 'label' => 'Hittade nya uppdateringar!', - 'help' => 'Klicka på Uppdatera mjukvara för att påbörja uppdateringen.', + 'help' => 'Klicka på Updatera mjukvara för att påbörja processen.' ], 'none' => [ 'label' => 'Inga uppdateringar', - 'help' => 'Inga nya uppdateringar hittades.', - ], + 'help' => 'Inga nya uppdateringar hittades.' + ] ], 'server' => [ 'connect_error' => 'Ett fel uppstod vid anslutning till servern.', @@ -139,4 +221,44 @@ return [ 'zip' => [ 'extract_failed' => "Kunde inte packa upp core-fil ':file'.", ], + 'event_log' => [ + 'hint' => 'Denna logg visar en lista över potentiella fel som uppstår i applikationen, såsom undantag och felsökningsinformation.', + 'menu_label' => 'Händelselog', + 'menu_description' => 'Visa systemloggmeddelanden med respektive tid och detaljer.', + 'empty_link' => 'Töm händelseloggen', + 'empty_loading' => 'Tömmer händelselogg...', + 'empty_success' => 'Lyckades tömma händelseloggen.', + 'return_link' => 'Återvänd till händelseloggen', + 'id' => 'ID', + 'id_label' => 'Händelse ID', + 'created_at' => 'Datum och tid', + 'message' => 'Meddelande', + 'level' => 'Nivå' + ], + 'request_log' => [ + 'hint' => 'Denna loggen visar en lista med förfrågningar från webbläsare som kan kräva uppmärksamhet. Till exempel, om en besökare öppnar en CMS sida som inte kan hittas så skapas en post med statuskoden 404.', + 'menu_label' => 'Förgrådan-logg', + 'menu_description' => 'Visa otillåtna eller omdirigerade förgrågningar, så som Sidan kunde inte hittas (404).', + 'empty_link' => 'Töm förfrågningan-loggen', + 'empty_loading' => 'Tömmer förfrågningan-loggen...', + 'empty_success' => 'Lyckades tömma förfrågningan-loggen.', + 'return_link' => 'Återvänd till förfrågningan-loggen', + 'id' => 'ID', + 'id_label' => 'Log ID', + 'count' => 'Räknare', + 'referer' => 'Refererare', + 'url' => 'URL', + 'status_code' => 'Status' + ], + 'permissions' => [ + 'name' => 'System', + 'manage_system_settings' => 'Hantera system inställningar', + 'manage_software_updates' => 'Hantera mjukvaruuppdateringar', + 'access_logs' => 'Visa system loggen', + 'manage_mail_templates' => 'Hantera epost-mallar', + 'manage_mail_settings' => 'Hantera epost inställningar', + 'manage_other_administrators' => 'Hantera andra administratörer', + 'view_the_dashboard' => 'Visa the kontrollpanelen', + 'manage_branding' => 'Anpassa back-end' + ] ]; diff --git a/modules/system/models/pluginversion/columns.yaml b/modules/system/models/pluginversion/columns.yaml index 5bd7b41e5..8d49f9085 100644 --- a/modules/system/models/pluginversion/columns.yaml +++ b/modules/system/models/pluginversion/columns.yaml @@ -4,20 +4,20 @@ columns: - name: - label: system::lang.updates.plugin_name - sortable: false + name: + label: system::lang.updates.plugin_name + sortable: false - description: - label: system::lang.updates.plugin_description - sortable: false + description: + label: system::lang.updates.plugin_description + sortable: false - version: - label: system::lang.updates.plugin_version - sortable: false + version: + label: system::lang.updates.plugin_version + sortable: false - author: - label: system::lang.updates.plugin_author - sortable: false - type: partial - path: column_author \ No newline at end of file + author: + label: system::lang.updates.plugin_author + sortable: false + type: partial + path: column_author \ No newline at end of file