Merge branch 'develop' into media-manager
This commit is contained in:
commit
790b464679
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
Before sending a Pull Request, be sure to review the [Contributing Guidelines](CONTRIBUTING.md) first.
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
div.control-scrollpad[data-direction=horizontal] > .scrollpad-scrollbar .drag-handle{right:auto;top:2px;height:7px;min-height:0;min-width:10px;width:auto}
|
||||
|
|
|
|||
|
|
@ -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()}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 = $('<a />')
|
||||
|
|
|
|||
|
|
@ -17,6 +17,18 @@
|
|||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<?php if ($relationManageWidget->showCheckboxes): ?>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
data-control="popup"
|
||||
data-handler="onRelationManageAddPivot"
|
||||
data-size="huge"
|
||||
data-dismiss="popup"
|
||||
data-stripe-load-indicator>
|
||||
<?= e(trans('backend::lang.relation.add_selected')) ?>
|
||||
</button>
|
||||
<?php endif ?>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?php namespace Backend\Classes;
|
||||
|
||||
use Html;
|
||||
use Model;
|
||||
use October\Rain\Database\Model;
|
||||
use October\Rain\Html\Helper as HtmlHelper;
|
||||
|
||||
/**
|
||||
|
|
@ -49,6 +49,16 @@ class FormField
|
|||
*/
|
||||
public $valueFrom;
|
||||
|
||||
/**
|
||||
* @var string Specifies a default value for supported fields.
|
||||
*/
|
||||
public $defaults;
|
||||
|
||||
/**
|
||||
* @var string Model attribute to use for the default value.
|
||||
*/
|
||||
public $defaultFrom;
|
||||
|
||||
/**
|
||||
* @var string Specifies if this field belongs to a tab.
|
||||
*/
|
||||
|
|
@ -114,11 +124,6 @@ class FormField
|
|||
*/
|
||||
public $commentHtml = false;
|
||||
|
||||
/**
|
||||
* @var string Specifies a default value for supported fields.
|
||||
*/
|
||||
public $defaults;
|
||||
|
||||
/**
|
||||
* @var string Specifies a message to display when there is no value supplied (placeholder).
|
||||
*/
|
||||
|
|
@ -296,6 +301,9 @@ class FormField
|
|||
if (isset($config['default'])) {
|
||||
$this->defaults = $config['default'];
|
||||
}
|
||||
if (isset($config['defaultFrom'])) {
|
||||
$this->defaultFrom = $config['defaultFrom'];
|
||||
}
|
||||
if (isset($config['attributes'])) {
|
||||
$this->attributes($config['attributes']);
|
||||
}
|
||||
|
|
@ -518,7 +526,7 @@ class FormField
|
|||
*/
|
||||
public function getValueFromData($data, $default = null)
|
||||
{
|
||||
$fieldName = $this->fieldName;
|
||||
$fieldName = $this->valueFrom ?: $this->fieldName;
|
||||
|
||||
/*
|
||||
* Array field name, eg: field[key][key2][key3]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div class="padded-container list-header">
|
||||
<div class="padded-container container-flush">
|
||||
<?= $this->makeHintPartial('backend_accesslogs_hint', 'hint') ?>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ class DbBackendUsers extends Migration
|
|||
$table->increments('id');
|
||||
$table->string('first_name')->nullable();
|
||||
$table->string('last_name')->nullable();
|
||||
$table->string('login')->unique()->index();
|
||||
$table->string('email')->unique();
|
||||
$table->string('login')->unique('login_unique')->index('login_index');
|
||||
$table->string('email')->unique('email_unique');
|
||||
$table->string('password');
|
||||
$table->string('activation_code')->nullable()->index();
|
||||
$table->string('activation_code')->nullable()->index('act_code_index');
|
||||
$table->string('persist_code')->nullable();
|
||||
$table->string('reset_password_code')->nullable()->index();
|
||||
$table->string('reset_password_code')->nullable()->index('reset_code_index');
|
||||
$table->text('permissions')->nullable();
|
||||
$table->boolean('is_activated')->default(0);
|
||||
$table->timestamp('activated_at')->nullable();
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class DbBackendUserGroups extends Migration
|
|||
Schema::create('backend_user_groups', function ($table) {
|
||||
$table->engine = 'InnoDB';
|
||||
$table->increments('id');
|
||||
$table->string('name')->unique();
|
||||
$table->string('name')->unique('name_unique');
|
||||
$table->text('permissions')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class DbBackendUsersGroups extends Migration
|
|||
$table->engine = 'InnoDB';
|
||||
$table->integer('user_id')->unsigned();
|
||||
$table->integer('user_group_id')->unsigned();
|
||||
$table->primary(array('user_id', 'user_group_id'));
|
||||
$table->primary(['user_id', 'user_group_id'], 'user_group');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class DbBackendAddDescriptionField extends Migration
|
|||
public function up()
|
||||
{
|
||||
Schema::table('backend_user_groups', function (Blueprint $table) {
|
||||
$table->string('code')->nullable()->index();
|
||||
$table->string('code')->nullable()->index('code_index');
|
||||
$table->text('description')->nullable();
|
||||
$table->boolean('is_new_user_default')->default(false);
|
||||
});
|
||||
|
|
@ -16,10 +16,10 @@ class DbBackendAddDescriptionField extends Migration
|
|||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('backend_user_groups', function (Blueprint $table) {
|
||||
$table->dropColumn('code');
|
||||
$table->dropColumn('description');
|
||||
$table->dropColumn('is_new_user_default');
|
||||
});
|
||||
// Schema::table('backend_user_groups', function (Blueprint $table) {
|
||||
// $table->dropColumn('code');
|
||||
// $table->dropColumn('description');
|
||||
// $table->dropColumn('is_new_user_default');
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ class DataTable extends FormWidgetBase
|
|||
|
||||
$result = [];
|
||||
while ($records = $dataSource->readRecords()) {
|
||||
$result += $records;
|
||||
$result = array_merge($result, $records);
|
||||
}
|
||||
|
||||
// We should be dealing with a simple array, so
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use Backend\Classes\FormField;
|
|||
use Backend\Classes\FormWidgetBase;
|
||||
use ValidationException;
|
||||
use Exception;
|
||||
use Lang;
|
||||
|
||||
/**
|
||||
* File upload field
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use Lang;
|
||||
use Backend\Classes\FormWidgetBase;
|
||||
use ApplicationException;
|
||||
use SystemException;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation as RelationBase;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,18 +6,18 @@ return [
|
|||
],
|
||||
'field' => [
|
||||
'invalid_type' => 'Использован неверный тип поля: :type.',
|
||||
'options_method_not_exists' => 'Класс модели :model должен содержать метод :method(), возвращающий опции для поля формы ":field".',
|
||||
'options_method_not_exists' => "Класс модели :model должен содержать метод :method(), возвращающий опции для поля формы ':field'.",
|
||||
],
|
||||
'widget' => [
|
||||
'not_registered' => "Класс виджета ':name' не зарегистрирован.",
|
||||
'not_bound' => "Виджет с именем класса ':name' не связан с контроллером.",
|
||||
],
|
||||
'page' => [
|
||||
'untitled' => "Без названия",
|
||||
'untitled' => 'Без названия',
|
||||
'access_denied' => [
|
||||
'label' => "Доступ запрещен",
|
||||
'label' => 'Доступ запрещен',
|
||||
'help' => "У вас нет необходимых прав для просмотра этой страницы.",
|
||||
'cms_link' => "Перейти к CMS",
|
||||
'cms_link' => 'Перейти к CMS',
|
||||
],
|
||||
],
|
||||
'partial' => [
|
||||
|
|
@ -30,17 +30,17 @@ return [
|
|||
'restore' => 'Восстановить',
|
||||
'login_placeholder' => 'пользователь',
|
||||
'password_placeholder' => 'пароль',
|
||||
'forgot_password' => "Забыли пароль?",
|
||||
'forgot_password' => 'Забыли пароль?',
|
||||
'enter_email' => 'Введите вашу почту',
|
||||
'enter_login' => "Введите ваш Логин",
|
||||
'email_placeholder' => "почта",
|
||||
'enter_login' => 'Введите ваш Логин',
|
||||
'email_placeholder' => 'почта',
|
||||
'enter_new_password' => 'Введите новый пароль',
|
||||
'password_reset' => "Сбросить пароль",
|
||||
'restore_success' => "На вашу электронную почту отправлено сообщение с инструкциями для восстановления пароля.",
|
||||
'password_reset' => 'Сбросить пароль',
|
||||
'restore_success' => 'На вашу электронную почту отправлено сообщение с инструкциями для восстановления пароля.',
|
||||
'restore_error' => "Пользователь с логином ':login' не найден.",
|
||||
'reset_success' => "Ваш пароль был успешно изменен. Теперь вы можете войти на сайт.",
|
||||
'reset_error' => "Недействительные данные для изменения пароля. Пожалуйста, попробуйте еще раз!",
|
||||
'reset_fail' => "Невозможно изменить пароль!",
|
||||
'reset_success' => 'Ваш пароль был успешно изменен. Теперь вы можете войти на сайт.',
|
||||
'reset_error' => 'Недействительные данные для изменения пароля. Пожалуйста, попробуйте еще раз!',
|
||||
'reset_fail' => 'Невозможно изменить пароль!',
|
||||
'apply' => 'Применить',
|
||||
'cancel' => 'Отменить',
|
||||
'delete' => 'Удалить',
|
||||
|
|
@ -75,18 +75,19 @@ return [
|
|||
'menu_description' => 'Управление группой администраторов, создание групп и разрешений.',
|
||||
'list_title' => 'Управление администраторами',
|
||||
'new' => 'Добавить администратора',
|
||||
'login' => "Логин",
|
||||
'first_name' => "Имя",
|
||||
'last_name' => "Фамилия",
|
||||
'full_name' => "Полное имя",
|
||||
'email' => "Почта",
|
||||
'groups' => "Группы",
|
||||
'groups_comment' => "Укажите к какой группе принадлежит этот пользователь.",
|
||||
'avatar' => "Аватар",
|
||||
'password' => "Пароль",
|
||||
'password_confirmation' => "Подтверждение пароля",
|
||||
'superuser' => "Суперпользователь",
|
||||
'superuser_comment' => "Установите этот флажок, чтобы позволить пользователю получать доступ ко всем областям.",
|
||||
'login' => 'Логин',
|
||||
'first_name' => 'Имя',
|
||||
'last_name' => 'Фамилия',
|
||||
'full_name' => 'Полное имя',
|
||||
'email' => 'Почта',
|
||||
'groups' => 'Группы',
|
||||
'groups_comment' => 'Укажите к какой группе принадлежит этот пользователь.',
|
||||
'avatar' => 'Аватар',
|
||||
'password' => 'Пароль',
|
||||
'password_confirmation' => 'Подтверждение пароля',
|
||||
'permissions' => 'Полномочия',
|
||||
'superuser' => 'Суперпользователь',
|
||||
'superuser_comment' => 'Установите этот флажок, чтобы позволить пользователю получать доступ ко всем областям.',
|
||||
'send_invite' => 'Отправить приглашение по электронной почте',
|
||||
'send_invite_comment' => 'Используйте эту опцию, чтобы отправить приглашение пользователю по электронной почте',
|
||||
'delete_confirm' => 'Вы действительно хотите удалить этого администратора?',
|
||||
|
|
@ -97,6 +98,10 @@ return [
|
|||
'group' => [
|
||||
'name' => 'Группы',
|
||||
'name_field' => 'Название',
|
||||
'description_field' => 'Описание',
|
||||
'is_new_user_default_field' => 'Добавлять новых администраторов в эту группу по умолчанию',
|
||||
'code_field' => 'Уникальный код',
|
||||
'code_comment' => 'Введите уникальный код, если вы хотите открыть доступ к нему с помощью API.',
|
||||
'menu_label' => 'Группы',
|
||||
'list_title' => 'Управление группами',
|
||||
'new' => 'Добавить группу',
|
||||
|
|
@ -124,7 +129,11 @@ return [
|
|||
'setup_title' => 'Настройка списка',
|
||||
'setup_help' => 'Используйте флажки для выбора колонок, которые вы хотите видеть в списке. Вы можете изменить положение столбцов, перетаскивая их вверх или вниз.',
|
||||
'records_per_page' => 'Записей на странице',
|
||||
'records_per_page_help' => 'Выберите количество записей на странице для отображения. Обратите внимание, что большое количество записей на одной странице может привести к снижению производительности.'
|
||||
'records_per_page_help' => 'Выберите количество записей на странице для отображения. Обратите внимание, что большое количество записей на одной странице может привести к снижению производительности.',
|
||||
'delete_selected' => 'Удалить выбранное',
|
||||
'delete_selected_empty' => 'Нет выбранных записей для удаления.',
|
||||
'delete_selected_confirm' => 'Удалить выбранные записи?',
|
||||
'delete_selected_success' => 'Выбранные записи успешно удалены.',
|
||||
],
|
||||
'fileupload' => [
|
||||
'attachment' => 'Приложение',
|
||||
|
|
@ -133,17 +142,17 @@ return [
|
|||
'description_label' => 'Описание'
|
||||
],
|
||||
'form' => [
|
||||
'create_title' => "Создание :name",
|
||||
'update_title' => "Редактирование :name",
|
||||
'preview_title' => "Предпросмотр :name",
|
||||
'create_title' => 'Создание :name',
|
||||
'update_title' => 'Редактирование :name',
|
||||
'preview_title' => 'Предпросмотр :name',
|
||||
'create_success' => ':name был успешно создан',
|
||||
'update_success' => ':name был успешно сохранен',
|
||||
'delete_success' => ':name был успешно удален',
|
||||
'missing_id' => "Идентификатор формы записи не указан.",
|
||||
'missing_id' => 'Идентификатор формы записи не указан.',
|
||||
'missing_model' => 'Для формы используемой в :class не определена модель.',
|
||||
'missing_definition' => "Поведение формы не содержит поле для':field'.",
|
||||
'not_found' => 'Форма записи с идентификатором :ID не найдена.',
|
||||
'action_confirm' => "Вы уверены, что хотите сделать это?",
|
||||
'action_confirm' => 'Вы уверены, что хотите сделать это?',
|
||||
'create' => 'Создать',
|
||||
'create_and_close' => 'Создать и закрыть',
|
||||
'creating' => 'Создание...',
|
||||
|
|
@ -178,10 +187,11 @@ return [
|
|||
'select_placeholder' => 'Пожалуйста, выберите',
|
||||
'insert_row' => 'Вставить строку',
|
||||
'delete_row' => 'Удалить строку',
|
||||
'concurrency_file_changed_title' => "Файл был изменен",
|
||||
'concurrency_file_changed_title' => 'Файл был изменен',
|
||||
'concurrency_file_changed_description' => "Файл, который вы редактируете был изменен другим пользователем. Вы можете либо перезагрузить файл и потерять ваши изменения или перезаписать его",
|
||||
],
|
||||
'relation' => [
|
||||
'missing_config' => "Поведение отношения не имеет конфигурации для ':config'.",
|
||||
'missing_definition' => "Поведение отношения не содержит определения для ':field'.",
|
||||
'missing_model' => "Для поведения отношения, используемого в :class не определена модель.",
|
||||
'invalid_action_single' => "Это действие не может быть выполнено для особого отношения.",
|
||||
|
|
@ -191,23 +201,34 @@ return [
|
|||
'add' => "Добавить",
|
||||
'add_selected' => "Добавить выбранные",
|
||||
'add_a_new' => "Добавить новый :name",
|
||||
'link_selected' => "Связать выбранное",
|
||||
'link_a_new' => "Новая ссылка :name",
|
||||
'cancel' => "Отмена",
|
||||
'add_name' => "Добавить :name",
|
||||
'close' => "Закрыть",
|
||||
'add_name' => "Добавление :name",
|
||||
'create' => "Создать",
|
||||
'create_name' => "Создание :name",
|
||||
'update' => "Update",
|
||||
'update_name' => "Update :name",
|
||||
'update' => "Обновить",
|
||||
'update_name' => "Обновление :name",
|
||||
'preview' => "Предпросмотр",
|
||||
'preview_name' => "Предпросмотр :name",
|
||||
'remove' => "Удалить",
|
||||
'remove_name' => "Удаление :name",
|
||||
'delete' => "Удалить",
|
||||
'delete_name' => "Удаление :name",
|
||||
'delete_confirm' => "Вы уверены?",
|
||||
'link' => "Ссылка",
|
||||
'link_name' => "Соединение :name",
|
||||
'unlink' => "Отвязать",
|
||||
'unlink_name' => "Разъединение :name",
|
||||
'unlink_confirm' => "Вы уверены?",
|
||||
],
|
||||
'model' => [
|
||||
'name' => "Модель",
|
||||
'name' => 'Модель',
|
||||
'not_found' => "Модель ':class' с идентификатором :id не найдена",
|
||||
'missing_id' => "Нет идентификатора для поиска модели записи.",
|
||||
'missing_id' => 'Нет идентификатора для поиска модели записи.',
|
||||
'missing_relation' => "Модель ':class' не содержит определения для ':relation'",
|
||||
'missing_method' => "Модель ':class' не содержит метод ':method'.",
|
||||
'invalid_class' => "Модель :model используемая в :class не допустима, она должна наследовать класс \Model.",
|
||||
'mass_assignment_failed' => "Массовое заполнение недоступно для атрибута модели ':attribute'.",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'auth' => [
|
||||
'title' => 'Administrations område'
|
||||
],
|
||||
'field' => [
|
||||
'invalid_type' => 'Felaktig fälttyp använd :type.',
|
||||
'options_method_not_exists' => 'Modelklassen :model måste definera en metod :method() som returnerar villkor för formfältet ":field"',
|
||||
|
|
@ -45,10 +48,31 @@ return [
|
|||
],
|
||||
'dashboard' => [
|
||||
'menu_label' => 'Kontrollpanelen',
|
||||
'widget_label' => 'Widget',
|
||||
'widget_width' => 'Bredd',
|
||||
'full_width' => 'max bredd',
|
||||
'add_widget' => 'Lägg till widget',
|
||||
'widget_inspector_title' => 'Widget inställningar',
|
||||
'widget_inspector_description' => 'Widget informations inställningar',
|
||||
'widget_columns_label' => 'Bredd :columns',
|
||||
'widget_columns_description' => 'Bredden på widgeten ska vara ett nummer mellan 1 och 10.',
|
||||
'widget_columns_error' => 'Vänligen ange widgetens bredden som ett nummer mellan 1 och 10.',
|
||||
'columns' => '{1} column|[2,Inf] kolonner',
|
||||
'widget_new_row_label' => 'Forcera en ny rad',
|
||||
'widget_new_row_description' => 'Lägg widgeten på en ny rad.',
|
||||
'widget_title_label' => 'Widget titel',
|
||||
'widget_title_error' => 'En widgets titel är tvingande.',
|
||||
'status' => [
|
||||
'widget_title_default' => 'System status',
|
||||
'online' => 'online',
|
||||
'maintenance' => 'i underhåll',
|
||||
'update_available' => '{0} uppdateringar tillgängliga!|{1} uppdatering tillgänglig!|[2,Inf] uppdateringar tillgängliga!'
|
||||
]
|
||||
],
|
||||
'user' => [
|
||||
'name' => 'Administratör',
|
||||
'menu_label' => 'Administratörer',
|
||||
'menu_description' => 'Hantera administratörs användare, grupper och behörigheter.',
|
||||
'list_title' => 'Hantera administratörer',
|
||||
'new' => 'Ny Administratör',
|
||||
'login' => "Användarnamn",
|
||||
|
|
@ -61,18 +85,26 @@ return [
|
|||
'avatar' => "Avatar",
|
||||
'password' => "Lösenord",
|
||||
'password_confirmation' => "Bekräfta lösenord",
|
||||
'permissions' => 'Rättigheter',
|
||||
'superuser' => "Superanvändare",
|
||||
'superuser_comment' => "Markera denna checkbox för att ge denna person tillgång till alla områden",
|
||||
'send_invite' => 'Inbjudan är sänd via e-post',
|
||||
'send_invite_comment' => 'Markera denna checkbox för att skicka en inbjudan till användaren via e-post',
|
||||
'delete_confirm' => 'Vill du verkligen radera denna administratör?',
|
||||
'return' => 'Återgå till administratörlistan',
|
||||
'allow' => 'Tillåt',
|
||||
'inherit' => 'Ärv',
|
||||
'deny' => 'Förbjud',
|
||||
'group' => [
|
||||
'name' => 'Grupp',
|
||||
'name_field' => 'Namn',
|
||||
'description_field' => 'Beskriving',
|
||||
'is_new_user_default_field' => 'Lägg till nya administratörer till gruppen som standard',
|
||||
'code_field' => 'Kod',
|
||||
'code_comment' => 'Ange en unik kod om du vill komma åt det med API.',
|
||||
'menu_label' => 'Grupper',
|
||||
'list_title' => 'Hantera grupper',
|
||||
'new' => 'New administratörgrupp',
|
||||
'new' => 'Ny administratörsgrupp',
|
||||
'delete_confirm' => 'Vill du verkligen radera denna administratörgrupp?',
|
||||
'return' => 'Återgå till grupplistan',
|
||||
],
|
||||
|
|
@ -89,7 +121,25 @@ return [
|
|||
'missing_columns' => 'Listan som används i :class har inga listkolumner definerade',
|
||||
'missing_definition' => "Listegenskapen saknar en kolumn för ':field'",
|
||||
'behavior_not_ready' => 'Listegenskapen har inte blivit initierad, kontrollera att du har anropat makeLists() i din controller',
|
||||
'invalid_column_datetime' => "Column value ':column' is not a DateTime object, are you missing a \$dates reference in the Model?",
|
||||
'invalid_column_datetime' => "Kolumns värde ':column' är inte ett DateTime objekt, saknar du en \$dates referens i Model?",
|
||||
'pagination' => 'Visade poster: :from-:to av :total',
|
||||
'prev_page' => 'Föregående sida',
|
||||
'next_page' => 'Nästa sida',
|
||||
'loading' => 'Laddar...',
|
||||
'setup_title' => 'List inställningar',
|
||||
'setup_help' => 'Använd kryssrutorna för att välja kolumner du vill se i listan. Du kan ändra positionen på kolumnerna genom att dra dem upp eller ner.',
|
||||
'records_per_page' => 'Poster per sida',
|
||||
'records_per_page_help' => 'Välj antalet poster som ska visas per sida. Observera att högt antal poster per sida kan påverka prestandan.',
|
||||
'delete_selected' => 'Radera utvald',
|
||||
'delete_selected_empty' => 'Det finns inga markerad post att radera.',
|
||||
'delete_selected_confirm' => 'Ta bort de markerade posterna?',
|
||||
'delete_selected_success' => 'De markerade posterna är raderade.',
|
||||
],
|
||||
'fileupload' => [
|
||||
'attachment' => 'Bilaga',
|
||||
'help' => 'Lägg till en och beskriving för denna bilagan.',
|
||||
'title_label' => 'Titel',
|
||||
'description_label' => 'Beskriving'
|
||||
],
|
||||
'form' => [
|
||||
'create_title' => "Ny :name",
|
||||
|
|
@ -102,66 +152,153 @@ return [
|
|||
'missing_model' => 'Formuläregenskapen som används i :class har ingen modell definierad',
|
||||
'missing_definition' => "Formuläregenskapen saknar ett fält för ':field'",
|
||||
'not_found' => 'Record ID :id för formuläret kunde ej hittas',
|
||||
'action_confirm' => 'Är du säker?',
|
||||
'create' => 'Skapa',
|
||||
'create_and_close' => 'Skapa och stäng',
|
||||
'creating' => 'Skapar...',
|
||||
'creating_name' => 'Skapar :name...',
|
||||
'save' => 'Spara',
|
||||
'save_and_close' => 'Spara och stäng',
|
||||
'saving' => 'Sparar...',
|
||||
'saving_name' => 'Sparar :name...',
|
||||
'delete' => 'Radera',
|
||||
'deleting' => 'Raderar...',
|
||||
'deleting_name' => 'Raderar :name...',
|
||||
'reset_default' => 'Äterställ till utgångsläge',
|
||||
'resetting' => 'Återställer',
|
||||
'resetting_name' => 'Återställer :name',
|
||||
'undefined_tab' => 'Övrigt',
|
||||
'field_off' => 'Av',
|
||||
'field_on' => 'På',
|
||||
'add' => 'Lägg till',
|
||||
'apply' => 'Spara',
|
||||
'cancel' => 'Avbryt',
|
||||
'close' => 'Stäng',
|
||||
'confirm' => 'Bekräfta',
|
||||
'reload' => 'Ladda om',
|
||||
'ok' => 'OK',
|
||||
'or' => 'eller',
|
||||
'confirm_tab_close' => 'Vill du verkligen stänga fliken? Ej sparade ändringar kommer gå förlorade',
|
||||
'behavior_not_ready' => 'Formuläregenskap har ej blivit initierad, kontrollera att du anropat initForm() i din controller',
|
||||
'preview_no_files_message' => 'Filen är inte uppladdad',
|
||||
'select' => 'Välj',
|
||||
'select_all' => 'alla',
|
||||
'select_none' => 'ingen',
|
||||
'select_placeholder' => 'Vänligen välj',
|
||||
'insert_row' => 'Lägg till rad',
|
||||
'delete_row' => 'Radera rad',
|
||||
'concurrency_file_changed_title' => 'Filen var ändrad',
|
||||
'concurrency_file_changed_description' => "Filen du redigerar har ändrats av en annan användare. Du kan antingen ladda om sidan och förlora dina ändringar eller skriva över filen med dina ändringar."
|
||||
],
|
||||
'relation' => [
|
||||
'missing_config' => "Relations beteendet har ingen konfiguration för ': config '.",
|
||||
'missing_definition' => "Relationen saknar en definintion för ':field'",
|
||||
'missing_model' => "Relationen som används i :class har ingen modell definierad",
|
||||
'invalid_action_single' => "Den här åtgärden kan inte appliceras på en enskild relation",
|
||||
'invalid_action_multi' => "Denna åtgärd kan inte appliceras på flera relationer",
|
||||
'help' => "Klicka på en post för att lägga till",
|
||||
'related_data' => "Relaterad :name data",
|
||||
'add' => "Lägg till",
|
||||
'add_selected' => "Lägg till vald",
|
||||
'add_a_new' => "Lägg till en ny :name",
|
||||
'link_selected' => "Länka vald",
|
||||
'link_a_new' => "Länka en ny :name",
|
||||
'cancel' => "Avbryt",
|
||||
'close' => "Stäng",
|
||||
'add_name' => "Lägg till :name",
|
||||
'create' => "Skapa",
|
||||
'create_name' => "Skapa :name",
|
||||
'update' => "Update",
|
||||
'update_name' => "Update :name",
|
||||
'update' => "Uppdatera",
|
||||
'update_name' => "Uppdatera :name",
|
||||
'preview' => "Förhandsgranska",
|
||||
'preview_name' => "Förhandsgranska :name",
|
||||
'remove' => "Ta bort",
|
||||
'remove_name' => "Ta bort :name",
|
||||
'delete' => "Radera",
|
||||
'delete_name' => "Radera :name",
|
||||
'delete_confirm' => "Är du säker?",
|
||||
'link' => "Länka",
|
||||
'link_name' => "Länka :name",
|
||||
'unlink' => "Avlänka",
|
||||
'unlink_name' => "Avlänka :name",
|
||||
'unlink_confirm' => "Är du säker?",
|
||||
],
|
||||
'model' => [
|
||||
'name' => "Modell",
|
||||
'not_found' => "Modellen ':class' med ID :id kunde ej hittas",
|
||||
'missing_id' => "Det finns inget ID anviget för modellen",
|
||||
'missing_relation' => "Modellen ':class' saknar en definition för ':relation'",
|
||||
'missing_method' => "Modellen ':class' innehåller inte metoden ':method'.",
|
||||
'invalid_class' => "Modellen :model i klass :class är ej giltig. Den måste ärva från \Model-klassen",
|
||||
'mass_assignment_failed' => "Mass assignment failed for Model attribute ':attribute'.",
|
||||
'mass_assignment_failed' => "Mass uppdraget misslyckades för Modell-attributet ':attribute'.",
|
||||
],
|
||||
'warnings' => [
|
||||
'tips' => 'System configuration tips',
|
||||
'tips_description' => 'There are issues you need to pay attention to in order to configure the system properly.',
|
||||
'permissions' => 'Directory :name or its subdirectories is not writable for PHP. Please set corresponding permissions for the webserver on this directory.',
|
||||
'extension' => 'The PHP extension :name is not installed. Please install this library and activate the extension.'
|
||||
'tips' => 'Systemkonfigurationstips',
|
||||
'tips_description' => 'Det finns problem som du behöver åtgärda för att konfigurera systemet ordentligt.',
|
||||
'permissions' => 'Katalogen :name eller dess underkataloger är inte skrivbara av PHP. Väligen ändra dess motsvarande behörigheter för web-servern i denna katalogen.',
|
||||
'extension' => 'PHP-tillägget: Namnet är inte installerat. Vänligen installera och aktivera det biblioteket.'
|
||||
],
|
||||
'editor' => [
|
||||
'menu_label' => 'Editor Configuration',
|
||||
'menu_description' => 'Manage editor configuration.',
|
||||
'font_size' => 'Font size',
|
||||
'tab_size' => 'Tab size',
|
||||
'use_hard_tabs' => 'Indent using tabs',
|
||||
'code_folding' => 'Code folding',
|
||||
'word_wrap' => 'Word wrap',
|
||||
'highlight_active_line' => 'Highlight active line',
|
||||
'show_invisibles' => 'Show invisible characters',
|
||||
'show_gutter' => 'Show gutter',
|
||||
'theme' => 'Color scheme',
|
||||
'menu_label' => 'Kodnings preferenser',
|
||||
'menu_description' => 'Anpassa dina preferenser för kodredigering, så som typsnitt och färgschema.',
|
||||
'font_size' => 'Teckenstorlek',
|
||||
'tab_size' => 'Tab längd',
|
||||
'use_hard_tabs' => 'Indentera med tab',
|
||||
'code_folding' => 'Dölj kod',
|
||||
'word_wrap' => 'Radbryting',
|
||||
'highlight_active_line' => 'Markera aktiv rad',
|
||||
'show_invisibles' => 'Visa dolda tecken',
|
||||
'show_gutter' => 'Visa ränna',
|
||||
'theme' => 'Färgschema'
|
||||
],
|
||||
'tooltips' => [
|
||||
'preview_website' => 'Förhandsgranska websidan'
|
||||
],
|
||||
'mysettings' => [
|
||||
'menu_label' => 'Mina inställningar',
|
||||
'menu_description' => 'Inställningar rörande ditt administrationskonto'
|
||||
],
|
||||
'myaccount' => [
|
||||
'menu_label' => 'Mitt konto',
|
||||
'menu_description' => 'Uppdatera dina kontouppgifter såsom namn, e-postadress och lösenord.',
|
||||
'menu_keywords' => 'säkerhets inloggning'
|
||||
],
|
||||
'branding' => [
|
||||
'menu_label' => 'Anpassa back-end',
|
||||
'menu_description' => 'Anpassa administrations området såsom namn, färger och logotyp.',
|
||||
'brand' => 'Varumärke',
|
||||
'logo' => 'Logga',
|
||||
'logo_description' => 'Ladda upp en egen logotyp för att använda i back-end.',
|
||||
'app_name' => 'Applikationsnamn',
|
||||
'app_name_description' => 'Detta namn visas i titelområdet back-end.',
|
||||
'app_tagline' => 'Applikationstaggning',
|
||||
'app_tagline_description' => 'Detta namn visas på inloggningsskärmen för back-end.',
|
||||
'colors' => 'Färger',
|
||||
'primary_light' => 'Primär (Ljus)',
|
||||
'primary_dark' => 'Primär (Mörk)',
|
||||
'secondary_light' => 'Sekundär (Ljus)',
|
||||
'secondary_dark' => 'Sekundär (Mörk)',
|
||||
'styles' => 'Formatmallar',
|
||||
'custom_stylesheet' => 'Anpassad formatmall'
|
||||
],
|
||||
'backend_preferences' => [
|
||||
'menu_label' => 'Back-end preferenser',
|
||||
'menu_description' => 'Hantera dina kontoinställningar såsom önskat språk.',
|
||||
'locale' => 'Språk',
|
||||
'locale_comment' => 'Välj önskat språk.'
|
||||
],
|
||||
'access_log' => [
|
||||
'hint' => 'Denna logg visar en lista över lyckade inloggningsförsök till administratrationen. Registret behålls i :days dagar.',
|
||||
'menu_label' => 'Åtkomst logg',
|
||||
'menu_description' => 'Visa en lista över framgångsrika inloggningar av back-end användare.',
|
||||
'created_at' => 'Dataum och tid',
|
||||
'login' => 'Inlogging',
|
||||
'ip_address' => 'IP adress',
|
||||
'first_name' => 'Förnamn',
|
||||
'last_name' => 'Efternamn',
|
||||
'email' => 'E-post'
|
||||
],
|
||||
'filter' => [
|
||||
'all' => 'alla'
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -26,17 +26,18 @@
|
|||
<?php endif ?>
|
||||
|
||||
<!-- Content Body -->
|
||||
<div class="layout-cell layout-container" id="layout-body" >
|
||||
<div class="layout-cell layout-container" id="layout-body">
|
||||
<div class="layout-relative">
|
||||
|
||||
<div class="layout">
|
||||
<!-- Breadcrumb -->
|
||||
<?php if ($breadcrumbContent = Block::placeholder('breadcrumb')): ?>
|
||||
<!-- Breadcrumb -->
|
||||
<div class="control-breadcrumb">
|
||||
<?= $breadcrumbContent ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="layout-row">
|
||||
<?= Block::placeholder('body') ?>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
<div class="layout-cell">
|
||||
|
||||
<div class="layout">
|
||||
<!-- Breadcrumb -->
|
||||
<?php if ($breadcrumbContent = Block::placeholder('breadcrumb')): ?>
|
||||
<!-- Breadcrumb -->
|
||||
<div class="control-breadcrumb breadcrumb-flush">
|
||||
<?= $breadcrumbContent ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="layout-row">
|
||||
<div class="padded-container layout">
|
||||
<?= Block::placeholder('form-contents') ?>
|
||||
|
|
|
|||
|
|
@ -775,9 +775,17 @@ class Form extends WidgetBase
|
|||
$field = $this->allFields[$field];
|
||||
}
|
||||
|
||||
$defaultValue = (!$this->model->exists && $field->defaults !== '')
|
||||
? $field->defaults
|
||||
: null;
|
||||
$defaultValue = null;
|
||||
|
||||
if (!$this->model->exists) {
|
||||
if ($field->defaultFrom) {
|
||||
list($model, $attribute) = $field->resolveModelAttribute($this->model, $field->defaultFrom);
|
||||
$defaultValue = $model->{$attribute};
|
||||
}
|
||||
elseif ($field->defaults !== '') {
|
||||
$defaultValue = $field->defaults;
|
||||
}
|
||||
}
|
||||
|
||||
return $field->getValueFromData($this->data, $defaultValue);
|
||||
}
|
||||
|
|
@ -863,34 +871,35 @@ class Form extends WidgetBase
|
|||
|
||||
/*
|
||||
* Handle fields that differ by fieldName and valueFrom
|
||||
* @todo @deprecated / Not needed? Remove if year >= 2016
|
||||
*/
|
||||
$remappedFields = [];
|
||||
foreach ($this->allFields as $field) {
|
||||
if ($field->fieldName == $field->valueFrom) {
|
||||
continue;
|
||||
}
|
||||
// $remappedFields = [];
|
||||
// foreach ($this->allFields as $field) {
|
||||
// if ($field->fieldName == $field->valueFrom) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
/*
|
||||
* Get the value, remove it from the data collection
|
||||
*/
|
||||
$parts = HtmlHelper::nameToArray($field->fieldName);
|
||||
$dotted = implode('.', $parts);
|
||||
$value = array_get($data, $dotted);
|
||||
array_forget($data, $dotted);
|
||||
// /*
|
||||
// * Get the value, remove it from the data collection
|
||||
// */
|
||||
// $parts = HtmlHelper::nameToArray($field->fieldName);
|
||||
// $dotted = implode('.', $parts);
|
||||
// $value = array_get($data, $dotted);
|
||||
// array_forget($data, $dotted);
|
||||
|
||||
/*
|
||||
* Set the new value to the data collection
|
||||
*/
|
||||
$parts = HtmlHelper::nameToArray($field->valueFrom);
|
||||
$dotted = implode('.', $parts);
|
||||
array_set($remappedFields, $dotted, $value);
|
||||
}
|
||||
// /*
|
||||
// * Set the new value to the data collection
|
||||
// */
|
||||
// $parts = HtmlHelper::nameToArray($field->valueFrom);
|
||||
// $dotted = implode('.', $parts);
|
||||
// array_set($remappedFields, $dotted, $value);
|
||||
// }
|
||||
|
||||
if (count($remappedFields) > 0) {
|
||||
$data = array_merge($remappedFields, $data);
|
||||
// Could be useful one day for field name collisions
|
||||
// $data['X_OCTOBER_REMAPPED_FIELDS'] = $remappedFields;
|
||||
}
|
||||
// if (count($remappedFields) > 0) {
|
||||
// $data = array_merge($remappedFields, $data);
|
||||
// // Could be useful one day for field name collisions
|
||||
// // $data['X_OCTOBER_REMAPPED_FIELDS'] = $remappedFields;
|
||||
// }
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,21 +71,25 @@
|
|||
}).get();
|
||||
}
|
||||
|
||||
ListWidget.prototype.toggleChecked = function(el) {
|
||||
var $checkbox = $('.list-checkbox input[type="checkbox"]', $(el).closest('tr'))
|
||||
$checkbox.prop('checked', !$checkbox.is(':checked')).trigger('change')
|
||||
}
|
||||
|
||||
// LIST WIDGET PLUGIN DEFINITION
|
||||
// ============================
|
||||
|
||||
var old = $.fn.listWidget
|
||||
|
||||
$.fn.listWidget = function (option) {
|
||||
var args = arguments,
|
||||
result
|
||||
var args = Array.prototype.slice.call(arguments, 1), result
|
||||
|
||||
this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('oc.listwidget')
|
||||
var options = $.extend({}, ListWidget.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||
if (!data) $this.data('oc.listwidget', (data = new ListWidget(this, options)))
|
||||
if (typeof option == 'string') result = data[option].call($this)
|
||||
if (typeof option == 'string') result = data[option].apply(data, args)
|
||||
if (typeof result != 'undefined') return false
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?php namespace Cms\Classes;
|
||||
|
||||
use Illuminate\Support\Collection as CollectionBase;
|
||||
use ApplicationException;
|
||||
use Illuminate\Support\Collection as CollectionBase;
|
||||
|
||||
/**
|
||||
* This class represents a collection of Cms Objects.
|
||||
|
|
@ -11,4 +11,24 @@ use ApplicationException;
|
|||
*/
|
||||
class CmsObjectCollection extends CollectionBase
|
||||
{
|
||||
/**
|
||||
* Returns objects that use the supplied component.
|
||||
* @param string|array $components
|
||||
* @return static
|
||||
*/
|
||||
public function withComponent($components)
|
||||
{
|
||||
return $this->filter(function($object) use ($components) {
|
||||
|
||||
$hasComponent = false;
|
||||
|
||||
foreach ((array) $components as $component) {
|
||||
if ($object->hasComponent($component)) {
|
||||
$hasComponent = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $hasComponent;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,18 @@ use ApplicationException;
|
|||
/**
|
||||
* This class provides helper methods to make the CmsObject behave like a Model
|
||||
*
|
||||
* Some examples:
|
||||
*
|
||||
* Page::find('blog/post');
|
||||
*
|
||||
* Page::all();
|
||||
*
|
||||
* Page::inEditTheme()->useCache()->all();
|
||||
*
|
||||
* Page::withComponent('blogPost')
|
||||
* ->sortBy('baseFileName')
|
||||
* ->lists('baseFileName', 'baseFileName');
|
||||
*
|
||||
* @package october\cms
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
<?php namespace Cms\Classes;
|
||||
|
||||
use File;
|
||||
use ApplicationException;
|
||||
use System\Models\Parameters;
|
||||
use Cms\Classes\Theme as CmsTheme;
|
||||
|
||||
/**
|
||||
* Theme manager
|
||||
*
|
||||
* @package october\cms
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class ThemeManager
|
||||
{
|
||||
use \October\Rain\Support\Traits\Singleton;
|
||||
|
||||
//
|
||||
// Gateway spawned
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns a collection of themes installed via the update gateway
|
||||
* @return array
|
||||
*/
|
||||
public function getInstalled()
|
||||
{
|
||||
return Parameters::get('system::theme.history', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a theme has ever been installed before.
|
||||
* @param string $name Theme code
|
||||
* @return boolean
|
||||
*/
|
||||
public function isInstalled($name)
|
||||
{
|
||||
return array_key_exists($name, Parameters::get('system::theme.history', []));
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags a theme as being installed, so it is not downloaded twice.
|
||||
* @param string $name Theme code
|
||||
*/
|
||||
public function setInstalled($code, $dirName = null)
|
||||
{
|
||||
if (!$dirName) {
|
||||
$dirName = strtolower(str_replace('.', '-', $code));
|
||||
}
|
||||
|
||||
$history = Parameters::get('system::theme.history', []);
|
||||
$history[$code] = $dirName;
|
||||
Parameters::set('system::theme.history', $history);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags a theme as being uninstalled.
|
||||
* @param string $name Theme code
|
||||
*/
|
||||
public function setUninstalled($code)
|
||||
{
|
||||
$history = Parameters::get('system::theme.history', []);
|
||||
if (array_key_exists($code, $history)) {
|
||||
unset($history[$code]);
|
||||
}
|
||||
|
||||
Parameters::set('system::theme.history', $history);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an installed theme's code from it's dirname.
|
||||
* @return string
|
||||
*/
|
||||
public function findByDirName($dirName)
|
||||
{
|
||||
$installed = $this->getInstalled();
|
||||
foreach ($installed as $code => $name) {
|
||||
if ($dirName == $name) {
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//
|
||||
// Management
|
||||
//
|
||||
|
||||
/**
|
||||
* Completely delete a theme from the system.
|
||||
* @param string $id Theme code/namespace
|
||||
* @return void
|
||||
*/
|
||||
public function deleteTheme($theme)
|
||||
{
|
||||
if (!$theme) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_string($theme)) {
|
||||
$theme = CmsTheme::load($theme);
|
||||
}
|
||||
|
||||
if ($theme->isActiveTheme()) {
|
||||
throw new ApplicationException(trans('cms::lang.theme.delete_active_theme_failed'));
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete from file system
|
||||
*/
|
||||
$themePath = $theme->getPath();
|
||||
if (File::isDirectory($themePath)) {
|
||||
File::deleteDirectory($themePath);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set uninstalled
|
||||
*/
|
||||
if ($themeCode = $this->findByDirName($theme->getDirName())) {
|
||||
$this->setUninstalled($themeCode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ use Cms\Models\ThemeData;
|
|||
use Cms\Models\ThemeExport;
|
||||
use Cms\Models\ThemeImport;
|
||||
use Cms\Classes\Theme as CmsTheme;
|
||||
use Cms\Classes\ThemeManager;
|
||||
use System\Classes\SettingsManager;
|
||||
use Backend\Classes\Controller;
|
||||
use Exception;
|
||||
|
|
@ -71,16 +72,7 @@ class Themes extends Controller
|
|||
|
||||
public function index_onDelete()
|
||||
{
|
||||
$theme = $this->findThemeObject();
|
||||
|
||||
if ($theme->isActiveTheme()) {
|
||||
throw new ApplicationException(trans('cms::lang.theme.delete_active_theme_failed'));
|
||||
}
|
||||
|
||||
$themePath = $theme->getPath();
|
||||
if (File::isDirectory($themePath)) {
|
||||
File::deleteDirectory($themePath);
|
||||
}
|
||||
ThemeManager::instance()->deleteTheme(post('theme'));
|
||||
|
||||
Flash::success(trans('cms::lang.theme.delete_theme_success'));
|
||||
return Redirect::refresh();
|
||||
|
|
|
|||
|
|
@ -19,14 +19,12 @@
|
|||
data-control="popup"
|
||||
data-handler="onLoadCreateForm"
|
||||
data-size="huge"
|
||||
href="javascript:;"
|
||||
target="_blank">
|
||||
href="javascript:;">
|
||||
<?= e(trans('cms::lang.theme.create_new_blank_theme')) ?>
|
||||
</a>
|
||||
<a
|
||||
class="find-more-themes"
|
||||
href="http://octobercms.com/themes"
|
||||
target="_blank">
|
||||
href="<?= Backend::url('system/updates/install/themes') ?>">
|
||||
<?= e(trans('cms::lang.theme.find_more_themes')) ?>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ return [
|
|||
'new_directory_name_comment' => 'Provide a new directory name for the duplicated theme.',
|
||||
'dir_name_invalid' => 'Name can contain only digits, Latin letters and the following symbols: _-',
|
||||
'dir_name_taken' => 'Desired theme directory already exists.',
|
||||
'find_more_themes' => 'Find more themes on OctoberCMS Theme Marketplace',
|
||||
'find_more_themes' => 'Find more themes',
|
||||
'return' => 'Return to themes list',
|
||||
],
|
||||
'maintenance' => [
|
||||
|
|
|
|||
|
|
@ -2,32 +2,80 @@
|
|||
|
||||
return [
|
||||
'cms_object' => [
|
||||
'invalid_file' => "Ошибка в имени файла: :name. Имена файлов могут содержать только латинские буквы, цифры, знаки подчеркивания и точки. Пример правильных имен файлов: page.htm, page, subdirectory/page",
|
||||
'invalid_property' => 'Параметр ":name" нельзя изменить.',
|
||||
'file_already_exists' => 'Файл ":name" уже существует.',
|
||||
'error_saving' => 'Ошибка сохранения файла ":name". Пожалуйста, проверьте права на запись.',
|
||||
'invalid_file' => 'Ошибка в имени файла: :name. Имена файлов могут содержать только латинские буквы, цифры, знаки подчеркивания и точки. Пример правильных имен файлов: page.htm, page, subdirectory/page',
|
||||
'invalid_property' => "Параметр ':name' нельзя изменить.",
|
||||
'file_already_exists' => "Файл ':name' уже существует.",
|
||||
'error_saving' => "Ошибка сохранения файла ':name'. Пожалуйста, проверьте права на запись.",
|
||||
'error_creating_directory' => 'Ошибка создания директории :name. Пожалуйста, проверьте права на запись.',
|
||||
'invalid_file_extension'=>'Указано неправильное расширение файла: :invalid. Разрешенные расширения: :allowed.',
|
||||
'error_deleting' => 'Невозможно удалить файл шаблона :name. Пожалуйста, проверьте права на запись.',
|
||||
'error_deleting' => "Невозможно удалить файл шаблона ':name'. Пожалуйста, проверьте права на запись.",
|
||||
'delete_success' => 'Шаблоны были успешно удалены: :count.',
|
||||
'file_name_required' => 'Пожалуйста, укажите имя файла шаблона.'
|
||||
],
|
||||
'theme' => [
|
||||
'not_found_name' => "Тема ':name' не найдена.",
|
||||
'active' => [
|
||||
'not_set' => "Активная тема не установлена.",
|
||||
'not_found' => "Активная тема не найдена.",
|
||||
'not_set' => 'Активная тема не установлена.',
|
||||
'not_found' => 'Активная тема не найдена.',
|
||||
],
|
||||
'edit' => [
|
||||
'not_set' => "Тема для редактирования не установлена.",
|
||||
'not_found' => "Тема для редактирования не найдена.",
|
||||
'not_set' => 'Тема для редактирования не установлена.',
|
||||
'not_found' => 'Тема для редактирования не найдена.',
|
||||
'not_match' => "Объект, который вы пытаетесь октрыть, не пренадлежит редактируемой теме. Пожалуйста, обновите страницу."
|
||||
],
|
||||
'settings_menu' => 'Фронтенд темы',
|
||||
'settings_menu_description' => 'Просмотр списка установленных тем и выбор активной темы.',
|
||||
'find_more_themes' => 'Найти еще темы на OctoberCMS Theme Marketplace',
|
||||
'name_label' => 'Название',
|
||||
'name_create_placeholder' => 'Новое название темы',
|
||||
'author_label' => 'Автор',
|
||||
'author_placeholder' => 'Человек или название компании',
|
||||
'description_label' => 'Описание',
|
||||
'description_placeholder' => 'Описание темы',
|
||||
'homepage_label' => 'Домашняя страница',
|
||||
'homepage_placeholder' => 'Адрес сайта',
|
||||
'code_label' => 'Уникальный код',
|
||||
'code_placeholder' => 'Уникальный код темы, который используются для её распространения',
|
||||
'dir_name_create_label' => 'Директория темы',
|
||||
'dir_name_label' => 'Название директории',
|
||||
'theme_label' => 'Тема',
|
||||
'activate_button' => 'Активировать',
|
||||
'active_button' => 'Активировано',
|
||||
'customize_button' => 'Настроить',
|
||||
'duplicate_button' => 'Дублировать',
|
||||
'duplicate_title' => 'Дублировать тему',
|
||||
'duplicate_theme_success' => 'Дублирование успешно завершено!',
|
||||
'manage_button' => 'Упрвление',
|
||||
'manage_title' => 'Управление темой',
|
||||
'edit_properties_title' => 'Тема',
|
||||
'edit_properties_button' => 'Редактирование свойств',
|
||||
'save_properties' => 'Сохранить свойства',
|
||||
'import_button' => 'Импортировать',
|
||||
'import_title' => 'Импортировать тему',
|
||||
'import_theme_success' => 'Импортирование темы успешно завершено!',
|
||||
'import_uploaded_file' => 'Файл архива темы',
|
||||
'import_overwrite_label' => 'Перезаписывать существующие файлы',
|
||||
'import_overwrite_comment' => 'Отключите эту опцию, чтобы импортировать только новые файлы',
|
||||
'import_folders_label' => 'Директории',
|
||||
'import_folders_comment' => 'Пожалуйста, выберите директории темы, которые вы хотели бы импортировать',
|
||||
'export_button' => 'Экспортировать',
|
||||
'export_title' => 'Экспортировать тему',
|
||||
'export_folders_label' => 'Директории',
|
||||
'export_folders_comment' => 'Пожалуйста, выберите директории темы, которые вы хотели бы экспортировать',
|
||||
'delete_button' => 'Удалить',
|
||||
'delete_confirm' => 'Вы уверены, что хотите удалить эту тему? Это действие необратимо!',
|
||||
'delete_active_theme_failed' => 'Невозможно удалить активный тему, попробуйте сделать другую тему активной.',
|
||||
'delete_theme_success' => 'Удаление темы успешно завершено!',
|
||||
'create_title' => 'Создать тему',
|
||||
'create_button' => 'Создать',
|
||||
'create_new_blank_theme' => 'Создать новый бланк темы',
|
||||
'create_theme_success' => 'Создание темы успешно завершено!',
|
||||
'create_theme_required_name' => 'Пожалуйста, укажите имя для темы.',
|
||||
'new_directory_name_label' => 'Директория темы',
|
||||
'new_directory_name_comment' => 'Укажите новое имя каталога для дубликата темы.',
|
||||
'dir_name_invalid' => 'Имя может содержать только цифры, латинские буквы и следующие символы: _ -',
|
||||
'dir_name_taken' => 'Указанный каталог уже существует.',
|
||||
'find_more_themes' => 'Найти еще темы на OctoberCMS Theme Marketplace',
|
||||
'return' => 'Вернуться к списку тем',
|
||||
],
|
||||
'maintenance' => [
|
||||
'settings_menu' => 'Режим обслуживания',
|
||||
|
|
@ -36,12 +84,13 @@ return [
|
|||
'is_enabled_comment' => 'При активации этого режима посетители сайта увидят страницу выбранную ниже.',
|
||||
],
|
||||
'page' => [
|
||||
'not_found_name' => "Страница ':name' не найдена",
|
||||
'not_found' => [
|
||||
'label' => "Страница не найдена",
|
||||
'help' => "Запрошенная страница не найдена.",
|
||||
'label' => 'Страница не найдена',
|
||||
'help' => 'Запрошенная страница не найдена.',
|
||||
],
|
||||
'custom_error' => [
|
||||
'label' => "Ошибка на странице",
|
||||
'label' => 'Ошибка на странице',
|
||||
'help' => "К сожалению, страница не может быть отображена из-за ошибки.",
|
||||
],
|
||||
'menu_label' => 'Страницы',
|
||||
|
|
@ -64,7 +113,7 @@ return [
|
|||
],
|
||||
'partial' => [
|
||||
'not_found_name' => "Не удалось найти шаблон (partial) с именем :name.",
|
||||
'invalid_name' => "Ошибка в имени шаблона (partial) :name.",
|
||||
'invalid_name' => 'Ошибка в имени шаблона (partial) :name.',
|
||||
'menu_label' => 'Фрагменты',
|
||||
'unsaved_label' => 'Несохранённый(е) фрагмент(ы)',
|
||||
'no_list_records' => 'Фрагменты не найдены',
|
||||
|
|
@ -82,11 +131,11 @@ return [
|
|||
'new' => 'Новый файл содержимого'
|
||||
],
|
||||
'ajax_handler' => [
|
||||
'invalid_name' => "Ошибка в имени обработчика AJAX: :name.",
|
||||
'invalid_name' => 'Ошибка в имени обработчика AJAX: :name.',
|
||||
'not_found' => "Обработчик AJAX не найден: ':name'.",
|
||||
],
|
||||
'cms' => [
|
||||
'menu_label' => "CMS"
|
||||
'menu_label' => 'CMS'
|
||||
],
|
||||
'sidebar' => [
|
||||
'add' => 'Добавить',
|
||||
|
|
@ -113,7 +162,7 @@ return [
|
|||
'exit_fullscreen' => 'Выйти из полноэкранного режима'
|
||||
],
|
||||
'asset' => [
|
||||
'menu_label' => "Ресурсы",
|
||||
'menu_label' => 'Ресурсы',
|
||||
'unsaved_label' => 'Несохранённый(е) файл(ы)',
|
||||
'drop_down_add_title' => 'Добавить...',
|
||||
'drop_down_operation_title' => 'Действие...',
|
||||
|
|
@ -141,7 +190,7 @@ return [
|
|||
'too_large' => 'Загруженный файл слишком велик. Максимальный допустимый размер файла составляет :max_size',
|
||||
'type_not_allowed' => 'Разрешены только файлы следующих типов: :allowed_types',
|
||||
'file_not_valid' => 'Файл не может быть сохранен',
|
||||
'error_uploading_file' => 'Ошибка загрузки файла ":name": :error',
|
||||
'error_uploading_file' => "Ошибка загрузки файла ':name': :error",
|
||||
'move_please_select' => 'пожалуйста, выберите директорию',
|
||||
'move_destination' => 'Новая директория',
|
||||
'move_popup_title' => 'Переместить файлы',
|
||||
|
|
@ -155,23 +204,24 @@ return [
|
|||
'path' => 'Путь'
|
||||
],
|
||||
'component' => [
|
||||
'menu_label' => "Компоненты",
|
||||
'unnamed' => "Безымянный",
|
||||
'no_description' => "Без описания",
|
||||
'alias' => "Псевдоним",
|
||||
'alias_description' => "Псевдоним компонента определяет его имя, под которым он доступен в коде страницы или шаблона.",
|
||||
'validation_message' => "Псевдонимы обязательны и могут содержать только латинские буквы, цифры и знаки подчеркивания. Псевдонимы должны начинаться с латинской буквы.",
|
||||
'invalid_request' => "Шаблон не может быть сохранен, так как запрос содержит поврежденную информацию о компоненентах.",
|
||||
'menu_label' => 'Компоненты',
|
||||
'unnamed' => 'Безымянный',
|
||||
'no_description' => 'Без описания',
|
||||
'alias' => 'Псевдоним',
|
||||
'alias_description' => 'Псевдоним компонента определяет его имя, под которым он доступен в коде страницы или шаблона.',
|
||||
'validation_message' => 'Псевдонимы обязательны и могут содержать только латинские буквы, цифры и знаки подчеркивания. Псевдонимы должны начинаться с латинской буквы.',
|
||||
'invalid_request' => 'Шаблон не может быть сохранен, так как запрос содержит поврежденную информацию о компоненентах.',
|
||||
'no_records' => 'Компоненты не найдены',
|
||||
'not_found' => "Компонент ':name' не найден.",
|
||||
'method_not_found' => "Компонент ':name' не содержит метод ':method'.",
|
||||
],
|
||||
'template' => [
|
||||
'invalid_type' => "Неизвестный тип шаблона.",
|
||||
'not_found' => "Запрошенный шаблон не найден.",
|
||||
'saved'=> "Шаблон был успешно сохранен."
|
||||
'invalid_type' => 'Неизвестный тип шаблона.',
|
||||
'not_found' => 'Запрошенный шаблон не найден.',
|
||||
'saved'=> 'Шаблон был успешно сохранен.'
|
||||
],
|
||||
'permissions' => [
|
||||
'name' => 'Управление CMS',
|
||||
'manage_content' => 'Управление контентом',
|
||||
'manage_assets' => 'Управление файлами',
|
||||
'manage_pages' => 'Управление страницами',
|
||||
|
|
|
|||
|
|
@ -13,16 +13,78 @@ return [
|
|||
'file_name_required' => 'Filnamnsfältet är obligatoriskt.'
|
||||
],
|
||||
'theme' => [
|
||||
'not_found_name' => "Kunde inte hitta temat ':name'.",
|
||||
'active' => [
|
||||
'not_set' => "Ett aktivt tema är ej valt",
|
||||
'not_found' => 'Kunde inte hitta det aktiva temat.'
|
||||
],
|
||||
'edit' => [
|
||||
'not_set' => "Redigeringstemat är ej valt",
|
||||
'not_found' => "Redigeringstemat kunde ej hittas",
|
||||
'not_match' => "Objektet du försöker komma åt tillhör inte det tema som för håller på att redigeras. Var god ladda om sidan",
|
||||
]
|
||||
],
|
||||
'settings_menu' => 'Front-end tema',
|
||||
'settings_menu_description' => 'Förhandsgranska listan av installerade teman och välj ett aktivt tema.',
|
||||
'name_label' => 'Namn',
|
||||
'name_create_placeholder' => 'Nytt tema namn',
|
||||
'author_label' => 'Författare',
|
||||
'author_placeholder' => 'Person eller företagsnamn',
|
||||
'description_label' => 'Beskrivning',
|
||||
'description_placeholder' => 'Tema beskrivning',
|
||||
'homepage_label' => 'Hemsida',
|
||||
'homepage_placeholder' => 'Webbadress',
|
||||
'code_label' => 'Kod',
|
||||
'code_placeholder' => 'En unik kod för detta tema som används för distribution',
|
||||
'dir_name_label' => 'Katalognamn',
|
||||
'dir_name_create_label' => 'Destinationen för temakatalogen',
|
||||
'theme_label' => 'Tema',
|
||||
'activate_button' => 'Aktivera',
|
||||
'active_button' => 'Aktivera',
|
||||
'customize_button' => 'Anpassa',
|
||||
'duplicate_button' => 'Duplicera',
|
||||
'duplicate_title' => 'Duplicera temat',
|
||||
'duplicate_theme_success' => 'Lyckades duplicera temat!',
|
||||
'manage_button' => 'Hantera',
|
||||
'manage_title' => 'Hantera teman',
|
||||
'edit_properties_title' => 'Tema',
|
||||
'edit_properties_button' => 'Redigera egenskaper',
|
||||
'save_properties' => 'Spara egenskaperna',
|
||||
'import_button' => 'Importera',
|
||||
'import_title' => 'Importera tema',
|
||||
'import_theme_success' => 'Lyckades importera temat!',
|
||||
'import_uploaded_file' => 'Tema akrivfil',
|
||||
'import_overwrite_label' => 'Skriv över befintliga filer',
|
||||
'import_overwrite_comment' => 'Avmarkera rutan för att endast importera nya filer',
|
||||
'import_folders_label' => 'Mappar',
|
||||
'import_folders_comment' => 'Vänligen ange temamappen som du vill importera',
|
||||
'export_button' => 'Exportera',
|
||||
'export_title' => 'Exportera tema',
|
||||
'export_folders_label' => 'Mappar',
|
||||
'export_folders_comment' => 'Vänligen välj temamappen du vill importera',
|
||||
'delete_button' => 'Radera',
|
||||
'delete_confirm' => 'Är du säker på att du vill readera detta tema?? Det kan inte bli ogjort!',
|
||||
'delete_active_theme_failed' => 'Du kan inte att readera det akriva temat, försök markera ett annat tema som aktivt först.',
|
||||
'delete_theme_success' => 'Lyckades radera temat!',
|
||||
'create_title' => 'Skapa tema',
|
||||
'create_button' => 'Skapa',
|
||||
'create_new_blank_theme' => 'Skapa ett nytt blankt tema',
|
||||
'create_theme_success' => 'Lyckades skapa temat!',
|
||||
'create_theme_required_name' => 'Vänligen ange ett namn för temat.',
|
||||
'new_directory_name_label' => 'Temamappen',
|
||||
'new_directory_name_comment' => 'Ange ett nytt katalognamn för det duplicerade temat.',
|
||||
'dir_name_invalid' => 'Namn kan bara innehålla siffror, latinska bokstäver och följande symboler: _-',
|
||||
'dir_name_taken' => 'Den önskade temakatalogen finns redan.',
|
||||
'find_more_themes' => 'Hitta fler teman på OctoberCMS Theme Marketplace',
|
||||
'return' => 'Återvänd till temalistan',
|
||||
],
|
||||
'maintenance' => [
|
||||
'settings_menu' => 'Underhållsläge',
|
||||
'settings_menu_description' => 'Konfigurera underhållsläge-sidan och växla inställningen.',
|
||||
'is_enabled' => 'Akrivera underhållsläge-läget',
|
||||
'is_enabled_comment' => 'När den är aktiverad så kommer besökare att se sidan som väljs nedan.'
|
||||
],
|
||||
'page' => [
|
||||
'not_found_name' => "The page ':name' is not found",
|
||||
'not_found' => [
|
||||
'label' => "Sidan kunde ej hittas",
|
||||
'help' => "Den begärda sidan kunde ej hittas",
|
||||
|
|
@ -32,6 +94,7 @@ return [
|
|||
'help' => "Tyvärr kan inte sidan visas",
|
||||
],
|
||||
'menu_label' => 'Sidor',
|
||||
'unsaved_label' => 'Osparade sidor',
|
||||
'no_list_records' => 'Inga sidor funna',
|
||||
'new' => 'Ny sida',
|
||||
'invalid_url' => 'Felaktigt URL-format. URLen skall starta med ett / och kan innehålla siffror, bokstäver och följande tecken: ._-[]:?|/+*^$',
|
||||
|
|
@ -42,6 +105,7 @@ return [
|
|||
'layout' => [
|
||||
'not_found_name' => "Layouten ':name' hittades ej",
|
||||
'menu_label' => 'Layouter',
|
||||
'unsaved_label' => 'Osparade layouter',
|
||||
'no_list_records' => 'Inga layouter funna',
|
||||
'new' => 'Ny layout',
|
||||
'delete_confirm_multiple' => 'Vill du verkligen radera valda layouter?',
|
||||
|
|
@ -51,6 +115,7 @@ return [
|
|||
'not_found_name' => "En partial med namnet ':name' kunde ej hittas",
|
||||
'invalid_name' => "Felaktigt partialnamn: :name",
|
||||
'menu_label' => 'Partials',
|
||||
'unsaved_label' => 'Osparade partials',
|
||||
'no_list_records' => 'Inga partials funna',
|
||||
'delete_confirm_multiple' => 'Vill du verkligen radera markerade partials?',
|
||||
'delete_confirm_single' => 'Vill du verkligen radera denna partial?',
|
||||
|
|
@ -59,6 +124,7 @@ return [
|
|||
'content' => [
|
||||
'not_found_name' => "Innehållet ':name' kunde ej hittas",
|
||||
'menu_label' => 'Innehåll',
|
||||
'unsaved_label' => 'Osparat innehåll',
|
||||
'no_list_records' => 'Inga innehållsfiler funna',
|
||||
'delete_confirm_multiple' => 'Vill du verkligen radera markerade filer eller mappar?',
|
||||
'delete_confirm_single' => 'Vill du verkligen radera detta innehållsfil?',
|
||||
|
|
@ -90,17 +156,25 @@ return [
|
|||
'markup' => 'Markup',
|
||||
'code' => 'Kod',
|
||||
'content' => 'Innehåll',
|
||||
'hidden' => 'Dold',
|
||||
'hidden_comment' => 'Dolda sidor är endast tillgängliga genom inloggade back-end användare.',
|
||||
'enter_fullscreen' => 'Starta helskärmsläge',
|
||||
'exit_fullscreen' => 'Avsluta helskärmsläge'
|
||||
],
|
||||
'asset' => [
|
||||
'menu_label' => "Filsystem",
|
||||
'unsaved_label' => 'Osparade filer',
|
||||
'drop_down_add_title' => 'Lägg till...',
|
||||
'drop_down_operation_title' => 'Åtgärd...',
|
||||
'upload_files' => 'Ladda upp fil(er)',
|
||||
'create_file' => 'Skapa fil',
|
||||
'create_directory' => 'Skapa mapp',
|
||||
'directory_popup_title' => 'Ny mapp',
|
||||
'directory_name' => 'Mappnamn',
|
||||
'rename' => 'Döp om',
|
||||
'delete' => 'Radera',
|
||||
'move' => 'Flytta',
|
||||
'select' => 'Välj',
|
||||
'new' => 'Ny fil',
|
||||
'rename_popup_title' => 'Byt namn',
|
||||
'rename_new_name' => 'Nytt namn',
|
||||
|
|
@ -145,5 +219,14 @@ return [
|
|||
'invalid_type' => "Felaktig malltyp",
|
||||
'not_found' => "Den angivna mallen kunde ej hittas",
|
||||
'saved'=> "Mallen har sparats"
|
||||
],
|
||||
'permissions' => [
|
||||
'name' => 'Cms',
|
||||
'manage_content' => 'Hantera innehåll',
|
||||
'manage_assets' => 'Hantera filer',
|
||||
'manage_pages' => 'Hantera sidor',
|
||||
'manage_layouts' => 'Hantera layouts',
|
||||
'manage_partials' => 'Hantera partials',
|
||||
'manage_themes' => 'Hantera teman'
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,276 @@
|
|||
.product-list-empty {
|
||||
padding: 5px 0;
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
}
|
||||
.product-list {
|
||||
margin: 0;
|
||||
padding: 10px 0;
|
||||
overflow: hidden;
|
||||
/* clearfix */
|
||||
}
|
||||
.product-list li button,
|
||||
.product-list li .image,
|
||||
.product-list li .details {
|
||||
-webkit-transition: opacity .2s linear;
|
||||
-moz-transition: opacity .2s linear;
|
||||
transition: opacity .2s linear;
|
||||
}
|
||||
.product-list li button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
opacity: 0;
|
||||
outline: none;
|
||||
}
|
||||
.product-list li:hover button {
|
||||
opacity: .3;
|
||||
}
|
||||
.product-list li:hover button:hover {
|
||||
opacity: .8;
|
||||
}
|
||||
.plugin-list li {
|
||||
list-style: none;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #E6E9E9;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.plugin-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.plugin-list li .image {
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.plugin-list li .image img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
.plugin-list li .details p {
|
||||
padding: 0;
|
||||
margin: 3px 0 0 0;
|
||||
color: #808C8D;
|
||||
}
|
||||
.plugin-list li h4 {
|
||||
padding: 5px 0 0;
|
||||
margin: 0;
|
||||
color: #C03F31;
|
||||
font-weight: 400;
|
||||
}
|
||||
.theme-list li {
|
||||
float: left;
|
||||
padding: 0;
|
||||
margin: 0 10px 10px 0;
|
||||
list-style: none;
|
||||
border: 1px solid #E6E9E9;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.theme-list li:hover {
|
||||
border-color: transparent;
|
||||
}
|
||||
.theme-list li {
|
||||
-webkit-transition: border .2s linear;
|
||||
-moz-transition: border .2s linear;
|
||||
transition: border .2s linear;
|
||||
}
|
||||
.theme-list li .image {
|
||||
padding: 5px;
|
||||
}
|
||||
.theme-list li .image img {
|
||||
width: 210px;
|
||||
height: 140px;
|
||||
}
|
||||
.theme-list li:hover .image {
|
||||
opacity: 0;
|
||||
}
|
||||
.theme-list li .details {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.theme-list li:hover .details {
|
||||
opacity: 1;
|
||||
}
|
||||
.theme-list li h4 {
|
||||
padding: 15px 0 0;
|
||||
margin: 0;
|
||||
}
|
||||
.theme-list li p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: #999;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
}
|
||||
.suggested-products {
|
||||
padding: 0;
|
||||
}
|
||||
.suggested-products .product {
|
||||
padding: 0;
|
||||
}
|
||||
.suggested-products .image img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.suggested-themes .image img {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
}
|
||||
.suggested-products .image {
|
||||
float: left;
|
||||
position: relative;
|
||||
}
|
||||
.suggested-products .details {
|
||||
margin-left: 50px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.suggested-themes .details {
|
||||
margin-left: 70px;
|
||||
}
|
||||
.suggested-products .details h5 {
|
||||
margin: 0 0 3px;
|
||||
font-size: 14px;
|
||||
color: #C03F31;
|
||||
font-weight: 400;
|
||||
}
|
||||
.suggested-products .details p {
|
||||
font-size: 12px;
|
||||
}
|
||||
.suggested-products a {
|
||||
color: #777;
|
||||
background: #fff;
|
||||
padding: 5px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid #E6E9E9;
|
||||
}
|
||||
.suggested-products a:hover {
|
||||
color: #333;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
.suggested-products a:hover .image:after {
|
||||
content: "+";
|
||||
color: #999;
|
||||
font-size: 32px;
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 0;
|
||||
}
|
||||
.suggested-products a:hover .image img {
|
||||
opacity: .5;
|
||||
}
|
||||
/*!
|
||||
* Typeahead
|
||||
*/
|
||||
.product-search {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0 auto 0 auto;
|
||||
text-align: left;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
.twitter-typeahead {
|
||||
width: 100%;
|
||||
}
|
||||
.typeahead,
|
||||
.tt-hint {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
padding: 8px 12px;
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
border: 1px solid #024e6a;
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
}
|
||||
.typeahead {
|
||||
background-color: #fff;
|
||||
border-color: #e0e0e0;
|
||||
}
|
||||
.tt-input {
|
||||
font-weight: 200;
|
||||
}
|
||||
.tt-input:focus {
|
||||
border-color: #E6E9E9;
|
||||
}
|
||||
.tt-hint {
|
||||
color: #999;
|
||||
font-weight: 200;
|
||||
}
|
||||
.tt-dropdown-menu {
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 3px;
|
||||
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.tt-suggestion {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.tt-suggestion + .tt-suggestion {
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
.tt-suggestions .product-details {
|
||||
padding: 5px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.tt-suggestions .product-image {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.tt-suggestions .product-image img {
|
||||
height: 45px;
|
||||
width: 45px;
|
||||
}
|
||||
.tt-suggestions .product-name {
|
||||
font-size: 20px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
.tt-suggestion.tt-cursor {
|
||||
cursor: pointer;
|
||||
}
|
||||
.tt-suggestion.tt-cursor .product-details {
|
||||
color: #333;
|
||||
background: #f9f9f9;
|
||||
border-color: #f0f0f0;
|
||||
}
|
||||
.tt-suggestion.tt-cursor .product-details .product-image:after {
|
||||
content: "+";
|
||||
color: #999;
|
||||
font-size: 38px;
|
||||
display: block;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
text-align: center;
|
||||
line-height: 45px;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
}
|
||||
.tt-suggestion.tt-cursor .product-details .product-image img {
|
||||
opacity: .5;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
@import "../../../backend/assets/less/core/boot.less";
|
||||
@import "../../../../backend/assets/less/core/boot.less";
|
||||
|
||||
.control-settings {
|
||||
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
@import "../../../../backend/assets/less/core/boot.less";
|
||||
|
||||
.product-list-empty {
|
||||
padding: 5px 0;
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
}
|
||||
.product-list {
|
||||
margin: 0;
|
||||
padding: 10px 0;
|
||||
overflow: hidden; /* clearfix */
|
||||
}
|
||||
.product-list li button,
|
||||
.product-list li .image,
|
||||
.product-list li .details {
|
||||
-webkit-transition: opacity .2s linear;
|
||||
-moz-transition: opacity .2s linear;
|
||||
transition: opacity .2s linear;
|
||||
}
|
||||
.product-list li button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
opacity: 0;
|
||||
outline: none;
|
||||
}
|
||||
.product-list li:hover button {
|
||||
opacity: .3;
|
||||
}
|
||||
.product-list li:hover button:hover {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.plugin-list {
|
||||
|
||||
}
|
||||
.plugin-list li {
|
||||
list-style: none;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #E6E9E9;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.plugin-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.plugin-list li .image {
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.plugin-list li .image img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
.plugin-list li .details p {
|
||||
padding: 0;
|
||||
margin: 3px 0 0 0;
|
||||
color: #808C8D;
|
||||
}
|
||||
.plugin-list li h4 {
|
||||
padding: 5px 0 0;
|
||||
margin: 0;
|
||||
color: #C03F31;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.theme-list li {
|
||||
float: left;
|
||||
padding: 0;
|
||||
margin: 0 10px 10px 0;
|
||||
list-style: none;
|
||||
border: 1px solid #E6E9E9;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.theme-list li:hover {
|
||||
border-color: transparent;
|
||||
}
|
||||
.theme-list li {
|
||||
-webkit-transition: border .2s linear;
|
||||
-moz-transition: border .2s linear;
|
||||
transition: border .2s linear;
|
||||
}
|
||||
.theme-list li .image {
|
||||
padding: 5px;
|
||||
}
|
||||
.theme-list li .image img {
|
||||
width: 210px;
|
||||
height: 140px;
|
||||
}
|
||||
.theme-list li:hover .image {
|
||||
opacity: 0;
|
||||
}
|
||||
.theme-list li .details {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.theme-list li:hover .details {
|
||||
opacity: 1;
|
||||
}
|
||||
.theme-list li h4 {
|
||||
padding: 15px 0 0;
|
||||
margin: 0;
|
||||
}
|
||||
.theme-list li p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: #999;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.suggested-products-container {
|
||||
}
|
||||
.suggested-products {
|
||||
padding: 0;
|
||||
}
|
||||
.suggested-products .product {
|
||||
padding: 0;
|
||||
}
|
||||
.suggested-products .image img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.suggested-themes .image img {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
}
|
||||
.suggested-products .image {
|
||||
float: left;
|
||||
position: relative;
|
||||
}
|
||||
.suggested-products .details {
|
||||
margin-left: 50px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.suggested-themes .details {
|
||||
margin-left: 70px;
|
||||
}
|
||||
.suggested-products .details h5 {
|
||||
margin: 0 0 3px;
|
||||
font-size: 14px;
|
||||
color: #C03F31;
|
||||
font-weight: 400;
|
||||
}
|
||||
.suggested-products .details p {
|
||||
font-size: 12px;
|
||||
}
|
||||
.suggested-products a {
|
||||
color: #777;
|
||||
background: #fff;
|
||||
padding: 5px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid #E6E9E9;
|
||||
}
|
||||
.suggested-products a:hover {
|
||||
color: #333;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
.suggested-products a:hover .image {
|
||||
}
|
||||
.suggested-products a:hover .image:after {
|
||||
content: "+";
|
||||
color: #999;
|
||||
font-size: 32px;
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 0;
|
||||
}
|
||||
.suggested-products a:hover .image img {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Typeahead
|
||||
*/
|
||||
|
||||
.product-search {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0 auto 0 auto;
|
||||
text-align: left;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.twitter-typeahead {
|
||||
width: 100%;
|
||||
}
|
||||
.typeahead,
|
||||
.tt-hint {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
padding: 8px 12px;
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
border: 1px solid #024e6a;
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
}
|
||||
.typeahead {
|
||||
background-color: #fff;
|
||||
border-color: #e0e0e0;
|
||||
}
|
||||
.tt-input {
|
||||
font-weight: 200;
|
||||
}
|
||||
.tt-input:focus {
|
||||
border-color: #E6E9E9;
|
||||
}
|
||||
.tt-hint {
|
||||
color: #999;
|
||||
font-weight: 200;
|
||||
}
|
||||
.tt-dropdown-menu {
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 3px;
|
||||
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
||||
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
||||
box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
||||
}
|
||||
.tt-suggestion {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.tt-suggestion + .tt-suggestion {
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
.tt-suggestions .product-details {
|
||||
padding: 5px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.tt-suggestions .product-image {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.tt-suggestions .product-image img {
|
||||
height: 45px;
|
||||
width: 45px;
|
||||
}
|
||||
.tt-suggestions .product-name {
|
||||
font-size: 20px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
.tt-suggestions .product-description {
|
||||
}
|
||||
|
||||
.tt-suggestion.tt-cursor {
|
||||
cursor: pointer;
|
||||
}
|
||||
.tt-suggestion.tt-cursor .product-details {
|
||||
color: #333;
|
||||
background: #f9f9f9;
|
||||
border-color: #f0f0f0;
|
||||
}
|
||||
.tt-suggestion.tt-cursor .product-details .product-image:after {
|
||||
content: "+";
|
||||
color: #999;
|
||||
font-size: 38px;
|
||||
display: block;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
text-align: center;
|
||||
line-height: 45px;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
}
|
||||
.tt-suggestion.tt-cursor .product-details .product-image img {
|
||||
opacity: .5;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
@import "../../../backend/assets/less/core/boot.less";
|
||||
@import "../../../../backend/assets/less/core/boot.less";
|
||||
|
||||
.control-updatelist {
|
||||
|
||||
|
|
@ -645,4 +645,40 @@ class PluginManager
|
|||
|
||||
return $result;
|
||||
}
|
||||
|
||||
//
|
||||
// Management
|
||||
//
|
||||
|
||||
/**
|
||||
* Completely roll back and delete a plugin from the system.
|
||||
* @param string $id Plugin code/namespace
|
||||
* @return void
|
||||
*/
|
||||
public function deletePlugin($id)
|
||||
{
|
||||
/*
|
||||
* Rollback plugin
|
||||
*/
|
||||
UpdateManager::instance()->rollbackPlugin($id);
|
||||
|
||||
/*
|
||||
* Delete from file system
|
||||
*/
|
||||
if ($pluginPath = PluginManager::instance()->getPluginPath($id)) {
|
||||
File::deleteDirectory($pluginPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tears down a plugin's database tables and rebuilds them.
|
||||
* @param string $id Plugin code/namespace
|
||||
* @return void
|
||||
*/
|
||||
public function refreshPlugin($id)
|
||||
{
|
||||
$manager = UpdateManager::instance();
|
||||
$manager->rollbackPlugin($id);
|
||||
$manager->updatePlugin($id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,14 +5,16 @@ use URL;
|
|||
use File;
|
||||
use Lang;
|
||||
use Http;
|
||||
use Cache;
|
||||
use Schema;
|
||||
use Config;
|
||||
use Carbon\Carbon;
|
||||
use ApplicationException;
|
||||
use Cms\Classes\ThemeManager;
|
||||
use System\Models\Parameters;
|
||||
use System\Models\PluginVersion;
|
||||
use ApplicationException;
|
||||
use System\Helpers\Cache as CacheHelper;
|
||||
use October\Rain\Filesystem\Zip;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
|
|
@ -48,6 +50,11 @@ class UpdateManager
|
|||
*/
|
||||
protected $pluginManager;
|
||||
|
||||
/**
|
||||
* @var Cms\Classes\ThemeManager
|
||||
*/
|
||||
protected $themeManager;
|
||||
|
||||
/**
|
||||
* @var System\Classes\VersionManager
|
||||
*/
|
||||
|
|
@ -68,12 +75,18 @@ class UpdateManager
|
|||
*/
|
||||
protected $disableCoreUpdates = false;
|
||||
|
||||
/**
|
||||
* @var array Cache of gateway products
|
||||
*/
|
||||
protected $productCache;
|
||||
|
||||
/**
|
||||
* Initialize this singleton.
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
$this->pluginManager = PluginManager::instance();
|
||||
$this->themeManager = ThemeManager::instance();
|
||||
$this->versionManager = VersionManager::instance();
|
||||
$this->tempDirectory = temp_path();
|
||||
$this->baseDirectory = base_path();
|
||||
|
|
@ -235,7 +248,7 @@ class UpdateManager
|
|||
*/
|
||||
$themes = [];
|
||||
foreach (array_get($result, 'themes', []) as $code => $info) {
|
||||
if (!$this->isThemeInstalled($code)) {
|
||||
if (!$this->themeManager->isInstalled($code)) {
|
||||
$themes[$code] = $info;
|
||||
}
|
||||
}
|
||||
|
|
@ -492,6 +505,17 @@ class UpdateManager
|
|||
// Themes
|
||||
//
|
||||
|
||||
/**
|
||||
* Looks up a theme from the update server.
|
||||
* @param string $name Theme name.
|
||||
* @return array Details about the theme.
|
||||
*/
|
||||
public function requestThemeDetails($name)
|
||||
{
|
||||
$result = $this->requestServerData('theme/detail', ['name' => $name]);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a theme from the update server.
|
||||
* @param string $name Theme name.
|
||||
|
|
@ -516,29 +540,118 @@ class UpdateManager
|
|||
throw new ApplicationException(Lang::get('system::lang.zip.extract_failed', ['file' => $filePath]));
|
||||
}
|
||||
|
||||
$this->setThemeInstalled($name);
|
||||
$this->themeManager->setInstalled($name);
|
||||
@unlink($filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a theme has ever been installed before.
|
||||
* @param string $name Theme code
|
||||
* @return boolean
|
||||
*/
|
||||
public function isThemeInstalled($name)
|
||||
//
|
||||
// Products
|
||||
//
|
||||
|
||||
public function requestProductDetails($codes, $type = null)
|
||||
{
|
||||
return array_key_exists($name, Parameters::get('system::theme.history', []));
|
||||
if ($type != 'plugin' && $type != 'theme')
|
||||
$type = 'plugin';
|
||||
|
||||
$codes = (array) $codes;
|
||||
$this->loadProductDetailCache();
|
||||
|
||||
/*
|
||||
* New products requested
|
||||
*/
|
||||
$newCodes = array_diff($codes, array_keys($this->productCache[$type]));
|
||||
if (count($newCodes)) {
|
||||
$dataCodes = [];
|
||||
$data = $this->requestServerData($type.'/details', ['names' => $newCodes]);
|
||||
foreach ($data as $product) {
|
||||
$code = array_get($product, 'code', -1);
|
||||
$this->cacheProductDetail($type, $code, $product);
|
||||
$dataCodes[] = $code;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cache unknown products
|
||||
*/
|
||||
$unknownCodes = array_diff($newCodes, $dataCodes);
|
||||
foreach ($unknownCodes as $code) {
|
||||
$this->cacheProductDetail($type, $code, -1);
|
||||
}
|
||||
|
||||
$this->saveProductDetailCache();
|
||||
}
|
||||
|
||||
/*
|
||||
* Build details from cache
|
||||
*/
|
||||
$result = [];
|
||||
$requestedDetails = array_intersect_key($this->productCache[$type], array_flip($codes));
|
||||
|
||||
foreach ($requestedDetails as $detail) {
|
||||
if ($detail === -1) continue;
|
||||
$result[] = $detail;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags a theme as being installed, so it is not downloaded twice.
|
||||
* @param string $name Theme code
|
||||
* Returns popular themes found on the marketplace.
|
||||
*/
|
||||
public function setThemeInstalled($name)
|
||||
public function requestPopularProducts($type = null)
|
||||
{
|
||||
$history = Parameters::get('system::theme.history', []);
|
||||
$history[$name] = Carbon::now()->timestamp;
|
||||
Parameters::set('system::theme.history', $history);
|
||||
if ($type != 'plugin' && $type != 'theme')
|
||||
$type = 'plugin';
|
||||
|
||||
$cacheKey = 'system-updates-popular-'.$type;
|
||||
|
||||
if (Cache::has($cacheKey)) {
|
||||
return @unserialize(Cache::get($cacheKey)) ?: [];
|
||||
}
|
||||
|
||||
$data = $this->requestServerData($type.'/popular');
|
||||
Cache::put($cacheKey, serialize($data), 60);
|
||||
|
||||
foreach ($data as $product) {
|
||||
$code = array_get($product, 'code', -1);
|
||||
$this->cacheProductDetail($type, $code, $product);
|
||||
}
|
||||
|
||||
$this->saveProductDetailCache();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function loadProductDetailCache()
|
||||
{
|
||||
$defaultCache = ['theme' => [], 'plugin' => []];
|
||||
$cacheKey = 'system-updates-product-details';
|
||||
|
||||
if (Cache::has($cacheKey)) {
|
||||
$this->productCache = @unserialize(Cache::get($cacheKey)) ?: $defaultCache;
|
||||
}
|
||||
else {
|
||||
$this->productCache = $defaultCache;
|
||||
}
|
||||
}
|
||||
|
||||
protected function saveProductDetailCache()
|
||||
{
|
||||
if ($this->productCache === null) {
|
||||
$this->loadProductDetailCache();
|
||||
}
|
||||
|
||||
$cacheKey = 'system-updates-product-details';
|
||||
$expiresAt = Carbon::now()->addDays(2);
|
||||
Cache::put($cacheKey, serialize($this->productCache), $expiresAt);
|
||||
}
|
||||
|
||||
protected function cacheProductDetail($type, $code, $data)
|
||||
{
|
||||
if ($this->productCache === null) {
|
||||
$this->loadProductDetailCache();
|
||||
}
|
||||
|
||||
$this->productCache[$type][$code] = $data;
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class Settings extends Controller
|
|||
$this->requiredPermissions = null;
|
||||
}
|
||||
|
||||
$this->addCss('/modules/system/assets/css/settings.css', 'core');
|
||||
$this->addCss('/modules/system/assets/css/settings/settings.css', 'core');
|
||||
|
||||
BackendMenu::setContext('October.System', 'system', 'settings');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ use Flash;
|
|||
use Config;
|
||||
use Backend;
|
||||
use Redirect;
|
||||
use Response;
|
||||
use BackendMenu;
|
||||
use Cms\Classes\ThemeManager;
|
||||
use Backend\Classes\Controller;
|
||||
use System\Models\Parameters;
|
||||
use System\Models\PluginVersion;
|
||||
|
|
@ -37,7 +39,7 @@ class Updates extends Controller
|
|||
parent::__construct();
|
||||
|
||||
$this->addJs('/modules/system/assets/js/updates/updates.js', 'core');
|
||||
$this->addCss('/modules/system/assets/css/updates.css', 'core');
|
||||
$this->addCss('/modules/system/assets/css/updates/updates.css', 'core');
|
||||
|
||||
BackendMenu::setContext('October.System', 'system', 'updates');
|
||||
SettingsManager::setContext('October.System', 'updates');
|
||||
|
|
@ -67,6 +69,31 @@ class Updates extends Controller
|
|||
return $this->asExtension('ListController')->index();
|
||||
}
|
||||
|
||||
/**
|
||||
* Install new plugins / themes
|
||||
*/
|
||||
public function install($tab = null)
|
||||
{
|
||||
if (get('search')) {
|
||||
return Response::make($this->onSearchProducts());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->bodyClass = 'compact-container breadcrumb-flush';
|
||||
$this->pageTitle = 'Install products';
|
||||
|
||||
$this->addJs('/modules/system/assets/js/updates/install.js', 'core');
|
||||
$this->addCss('/modules/system/assets/css/updates/install.css', 'core');
|
||||
|
||||
$this->vars['activeTab'] = $tab ?: 'plugins';
|
||||
$this->vars['installedPlugins'] = $this->getInstalledPlugins();
|
||||
$this->vars['installedThemes'] = $this->getInstalledThemes();
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
$this->handleError($ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
@ -132,12 +159,12 @@ class Updates extends Controller
|
|||
case 'completeUpdate':
|
||||
$manager->update();
|
||||
Flash::success(Lang::get('system::lang.updates.update_success'));
|
||||
return Backend::redirect('system/updates');
|
||||
return Redirect::refresh();
|
||||
|
||||
case 'completeInstall':
|
||||
$manager->update();
|
||||
Flash::success(Lang::get('system::lang.install.install_success'));
|
||||
return Backend::redirect('system/updates');
|
||||
return Redirect::refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -442,7 +469,7 @@ class Updates extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Removes or purges plugins from the system.
|
||||
* Rollback and remove plugins from the system.
|
||||
* @return void
|
||||
*/
|
||||
public function onRemovePlugins()
|
||||
|
|
@ -454,18 +481,7 @@ class Updates extends Controller
|
|||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rollback plugin
|
||||
*/
|
||||
$pluginCode = $object->code;
|
||||
UpdateManager::instance()->rollbackPlugin($pluginCode);
|
||||
|
||||
/*
|
||||
* Delete from file system
|
||||
*/
|
||||
if ($pluginPath = PluginManager::instance()->getPluginPath($pluginCode)) {
|
||||
File::deleteDirectory($pluginPath);
|
||||
}
|
||||
PluginManager::instance()->deletePlugin($object->code);
|
||||
}
|
||||
|
||||
Flash::success(Lang::get('system::lang.plugins.remove_success'));
|
||||
|
|
@ -474,6 +490,22 @@ class Updates extends Controller
|
|||
return $this->listRefresh('manage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback and remove a single plugin from the system.
|
||||
* @return void
|
||||
*/
|
||||
public function onRemovePlugin()
|
||||
{
|
||||
if ($pluginCode = post('code')) {
|
||||
|
||||
PluginManager::instance()->deletePlugin($pluginCode);
|
||||
|
||||
Flash::success(Lang::get('system::lang.plugins.remove_success'));
|
||||
}
|
||||
|
||||
return Redirect::refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds plugin database migrations.
|
||||
* @return void
|
||||
|
|
@ -482,19 +514,12 @@ class Updates extends Controller
|
|||
{
|
||||
if (($checkedIds = post('checked')) && is_array($checkedIds) && count($checkedIds)) {
|
||||
|
||||
$manager = UpdateManager::instance();
|
||||
|
||||
foreach ($checkedIds as $objectId) {
|
||||
if (!$object = PluginVersion::find($objectId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Refresh plugin
|
||||
*/
|
||||
$pluginCode = $object->code;
|
||||
$manager->rollbackPlugin($pluginCode);
|
||||
$manager->updatePlugin($pluginCode);
|
||||
PluginManager::instance()->refreshPlugin($object->code);
|
||||
}
|
||||
|
||||
Flash::success(Lang::get('system::lang.plugins.refresh_success'));
|
||||
|
|
@ -548,4 +573,145 @@ class Updates extends Controller
|
|||
|
||||
return Backend::redirect('system/updates/manage');
|
||||
}
|
||||
|
||||
//
|
||||
// Theme management
|
||||
//
|
||||
|
||||
/**
|
||||
* Validate the theme code and execute the theme installation
|
||||
*/
|
||||
public function onInstallTheme()
|
||||
{
|
||||
try {
|
||||
if (!$code = trim(post('code'))) {
|
||||
throw new ApplicationException(Lang::get('system::lang.install.missing_theme_name'));
|
||||
}
|
||||
|
||||
$manager = UpdateManager::instance();
|
||||
$result = $manager->requestThemeDetails($code);
|
||||
|
||||
if (!isset($result['code']) || !isset($result['hash'])) {
|
||||
throw new ApplicationException(Lang::get('system::lang.server.response_invalid'));
|
||||
}
|
||||
|
||||
$name = $result['code'];
|
||||
$hash = $result['hash'];
|
||||
$themes = [$name => $hash];
|
||||
$plugins = [];
|
||||
|
||||
/*
|
||||
* Update steps
|
||||
*/
|
||||
$updateSteps = $this->buildUpdateSteps(null, $plugins, $themes);
|
||||
|
||||
/*
|
||||
* Finish up
|
||||
*/
|
||||
$updateSteps[] = [
|
||||
'code' => 'completeInstall',
|
||||
'label' => Lang::get('system::lang.install.install_completing'),
|
||||
];
|
||||
|
||||
$this->vars['updateSteps'] = $updateSteps;
|
||||
|
||||
return $this->makePartial('execute');
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
$this->handleError($ex);
|
||||
return $this->makePartial('theme_form');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a single theme from the system.
|
||||
* @return void
|
||||
*/
|
||||
public function onRemoveTheme()
|
||||
{
|
||||
if ($themeCode = post('code')) {
|
||||
|
||||
ThemeManager::instance()->deleteTheme($themeCode);
|
||||
|
||||
Flash::success(trans('cms::lang.theme.delete_theme_success'));
|
||||
}
|
||||
|
||||
return Redirect::refresh();
|
||||
}
|
||||
|
||||
//
|
||||
// Product install
|
||||
//
|
||||
|
||||
public function onSearchProducts()
|
||||
{
|
||||
$searchType = get('search', 'plugins');
|
||||
$serverUri = $searchType == 'plugins' ? 'plugin/search' : 'theme/search';
|
||||
|
||||
$manager = UpdateManager::instance();
|
||||
return $manager->requestServerData($serverUri, ['query' => get('query')]);
|
||||
}
|
||||
|
||||
public function onGetPopularPlugins()
|
||||
{
|
||||
$installed = $this->getInstalledPlugins();
|
||||
$popular = UpdateManager::instance()->requestPopularProducts('plugin');
|
||||
$popular = $this->filterPopularProducts($popular, $installed);
|
||||
|
||||
return ['result' => $popular];
|
||||
}
|
||||
|
||||
public function onGetPopularThemes()
|
||||
{
|
||||
$installed = $this->getInstalledThemes();
|
||||
$popular = UpdateManager::instance()->requestPopularProducts('theme');
|
||||
$popular = $this->filterPopularProducts($popular, $installed);
|
||||
|
||||
return ['result' => $popular];
|
||||
}
|
||||
|
||||
protected function getInstalledPlugins()
|
||||
{
|
||||
$installed = PluginVersion::lists('code');
|
||||
$manager = UpdateManager::instance();
|
||||
return $manager->requestProductDetails($installed, 'plugin');
|
||||
}
|
||||
|
||||
protected function getInstalledThemes()
|
||||
{
|
||||
$history = Parameters::get('system::theme.history', []);
|
||||
$manager = UpdateManager::instance();
|
||||
$installed = $manager->requestProductDetails(array_keys($history), 'theme');
|
||||
|
||||
/*
|
||||
* Splice in the directory names
|
||||
*/
|
||||
foreach ($installed as $key => $data) {
|
||||
$code = array_get($data, 'code');
|
||||
$installed[$key]['dirName'] = array_get($history, $code, $code);
|
||||
}
|
||||
|
||||
return $installed;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove installed products from the collection
|
||||
*/
|
||||
protected function filterPopularProducts($popular, $installed)
|
||||
{
|
||||
$installedArray = [];
|
||||
foreach ($installed as $product) {
|
||||
$installedArray[] = array_get($product, 'code', -1);
|
||||
}
|
||||
|
||||
foreach ($popular as $key => $product) {
|
||||
$code = array_get($product, 'code');
|
||||
if (in_array($code, $installedArray)) {
|
||||
unset($popular[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $popular;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
<a
|
||||
href="javascript:;"
|
||||
data-request="onEmptyLog"
|
||||
data-request-confirm="<?= e(trans('backend::lang.list.delete_selected_confirm')) ?>"
|
||||
data-load-indicator="<?= e(trans('system::lang.event_log.empty_loading')) ?>"
|
||||
class="btn btn-default oc-icon-eraser">
|
||||
<?= e(trans('system::lang.event_log.empty_link')) ?>
|
||||
|
|
@ -13,7 +14,6 @@
|
|||
checked: $('.control-list').listWidget('getChecked')
|
||||
})"
|
||||
data-request="onDelete"
|
||||
data-request-confirm="<?= e(trans('backend::lang.list.delete_selected_confirm')) ?>"
|
||||
data-trigger-action="enable"
|
||||
data-trigger=".control-list input[type=checkbox]"
|
||||
data-trigger-condition="checked"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div class="padded-container list-header">
|
||||
<div class="padded-container container-flush">
|
||||
<?= $this->makeHintPartial('system_eventlogs_hint', 'hint') ?>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
<a
|
||||
href="javascript:;"
|
||||
data-request="onEmptyLog"
|
||||
data-request-confirm="<?= e(trans('backend::lang.list.delete_selected_confirm')) ?>"
|
||||
data-load-indicator="<?= e(trans('system::lang.request_log.empty_loading')) ?>"
|
||||
class="btn btn-default oc-icon-eraser">
|
||||
<?= e(trans('system::lang.request_log.empty_link')) ?>
|
||||
|
|
@ -13,7 +14,6 @@
|
|||
checked: $('.control-list').listWidget('getChecked')
|
||||
})"
|
||||
data-request="onDelete"
|
||||
data-request-confirm="<?= e(trans('backend::lang.list.delete_selected_confirm')) ?>"
|
||||
data-trigger-action="enable"
|
||||
data-trigger=".control-list input[type=checkbox]"
|
||||
data-trigger-condition="checked"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div class="padded-container list-header">
|
||||
<div class="padded-container container-flush">
|
||||
<?= $this->makeHintPartial('system_requestlogs_hint', 'hint') ?>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
|
||||
<div>
|
||||
|
||||
<!-- Search -->
|
||||
<form
|
||||
id="installPluginsForm"
|
||||
data-handler="onInstallPlugin"
|
||||
onsubmit="$.oc.installProcess.searchSubmit(this); return false">
|
||||
<div class="product-search">
|
||||
<input
|
||||
name="code"
|
||||
id="pluginSearchInput"
|
||||
class="product-search-input search-input-lg typeahead"
|
||||
placeholder="search plugins to install..."
|
||||
data-search-type="plugins"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-7">
|
||||
|
||||
<!-- Installed plugins -->
|
||||
<div id="pluginList"
|
||||
class="product-list-manager">
|
||||
|
||||
<h4 class="section-header">
|
||||
<a href="<?= Backend::url('system/updates') ?>">Installed plugins</a>
|
||||
<small>(<span class="product-counter"><?= count($installedPlugins) ?></span>)</small>
|
||||
</h4>
|
||||
|
||||
<?php if (!count($installedPlugins)): ?>
|
||||
<div class="product-list-empty">
|
||||
<p>There are no plugins installed from the marketplace.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<ul class="product-list plugin-list">
|
||||
<?php foreach ($installedPlugins as $plugin): ?>
|
||||
|
||||
<li data-code="<?= $plugin['code'] ?>">
|
||||
<div class="image">
|
||||
<img src="<?= $plugin['image'] ?>" alt="">
|
||||
</div>
|
||||
<div class="details">
|
||||
<h4><?= $plugin['name'] ?></h4>
|
||||
<p>by <?= $plugin['author'] ?></p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
aria-hidden="true"
|
||||
data-request="onRemovePlugin"
|
||||
data-request-data="code: '<?= $plugin['code'] ?>'"
|
||||
data-request-confirm="Are you sure you want to remove this?"
|
||||
data-stripe-load-indicator>
|
||||
×
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<?php endif ?>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
|
||||
<!-- Recommended extras -->
|
||||
<div class="suggested-products-container">
|
||||
<h4 class="section-header">Recommended</h4>
|
||||
<div class="scroll-panel">
|
||||
<div
|
||||
id="suggestedPlugins"
|
||||
class="suggested-products suggested-plugins"
|
||||
data-handler="onGetPopularPlugins"
|
||||
data-view="plugin/suggestion"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/template" data-partial="plugin/suggestion">
|
||||
<div class="product">
|
||||
<a
|
||||
data-control="popup"
|
||||
data-handler="onInstallPlugin"
|
||||
data-request-data="code: '{{code}}'"
|
||||
href="javascript:;">
|
||||
<div class="image"><img src="{{image}}" alt=""></div>
|
||||
<div class="details">
|
||||
<h5 class="text-overflow">{{code}}</h5>
|
||||
<p>{{description}}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</script>
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
|
||||
<div>
|
||||
|
||||
<!-- Search -->
|
||||
<form
|
||||
id="installThemesForm"
|
||||
data-handler="onInstallTheme"
|
||||
onsubmit="$.oc.installProcess.searchSubmit(this); return false">
|
||||
<div class="product-search">
|
||||
<input
|
||||
name="code"
|
||||
id="themeSearchInput"
|
||||
class="product-search-input search-input-lg typeahead"
|
||||
placeholder="search themes to install..."
|
||||
data-search-type="themes"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-7">
|
||||
|
||||
<!-- Installed themes -->
|
||||
<div id="themeList"
|
||||
class="product-list-manager"
|
||||
data-handler="onGetInstalledThemes"
|
||||
data-view="product/theme">
|
||||
|
||||
<h4 class="section-header">
|
||||
<a href="<?= Backend::url('cms/themes') ?>">Installed themes</a>
|
||||
<small>(<span class="product-counter">0</span>)</small>
|
||||
</h4>
|
||||
|
||||
<?php if (!count($installedThemes)): ?>
|
||||
<div class="product-list-empty">
|
||||
<p>There are no themes installed from the marketplace.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<ul class="product-list theme-list">
|
||||
<?php foreach ($installedThemes as $theme): ?>
|
||||
|
||||
<li data-code="<?= $theme['code'] ?>">
|
||||
<div class="image">
|
||||
<img src="<?= $theme['image'] ?>" alt="">
|
||||
</div>
|
||||
<div class="details">
|
||||
<h4><?= $theme['name'] ?></h4>
|
||||
<p>by <?= $theme['author'] ?></p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
aria-hidden="true"
|
||||
data-request="onRemoveTheme"
|
||||
data-request-data="code: '<?= $theme['dirName'] ?>'"
|
||||
data-request-confirm="Are you sure you want to remove this?"
|
||||
data-stripe-load-indicator>
|
||||
×
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<?php endif ?>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
|
||||
<!-- Recommended extras -->
|
||||
<div class="suggested-products-container">
|
||||
<h4 class="section-header">Recommended</h4>
|
||||
<div class="scroll-panel">
|
||||
<div
|
||||
id="suggestedThemes"
|
||||
class="suggested-products suggested-themes"
|
||||
data-handler="onGetPopularThemes"
|
||||
data-view="theme/suggestion"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/template" data-partial="theme/suggestion">
|
||||
<div class="product">
|
||||
<a
|
||||
data-control="popup"
|
||||
data-handler="onInstallTheme"
|
||||
data-request-data="code: '{{code}}'"
|
||||
href="javascript:;">
|
||||
<div class="image"><img src="{{image}}" alt=""></div>
|
||||
<div class="details">
|
||||
<h5 class="text-overflow">{{code}}</h5>
|
||||
<p>{{description}}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</script>
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
<?= Form::open() ?>
|
||||
|
||||
<div class="input-group" style="width: 207px">
|
||||
<input
|
||||
placeholder="<?= e(trans('backend::lang.list.search_prompt')) ?>"
|
||||
type="text"
|
||||
name="code"
|
||||
value=""
|
||||
class="form-control icon plus growable pull-right"
|
||||
autocomplete="off" />
|
||||
|
||||
<span class="input-group-btn">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-success"
|
||||
id="installPluginButton"
|
||||
data-control="popup"
|
||||
data-handler="onInstallPlugin">
|
||||
<?= e(trans('system::lang.install.plugin_label')) ?>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<?= Form::close() ?>
|
||||
|
|
@ -11,4 +11,9 @@
|
|||
class="btn btn-default oc-icon-puzzle-piece">
|
||||
<?= e(trans('system::lang.plugins.manage')) ?>
|
||||
</a>
|
||||
<a
|
||||
href="<?= Backend::url('system/updates/install') ?>"
|
||||
class="btn btn-success oc-icon-plus">
|
||||
Install plugins
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?= Form::open(['id' => 'themeForm']) ?>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="popup">×</button>
|
||||
<h4 class="modal-title"><?= e(trans('system::lang.install.theme_label')) ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<?php if ($this->fatalError): ?>
|
||||
<p class="flash-message static error"><?= $fatalError ?></p>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="themeCode"><?= e(trans('system::lang.theme.name.label')) ?></label>
|
||||
<input
|
||||
name="code"
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="themeCode"
|
||||
value="<?= post('code') ?>" />
|
||||
<p class="help-block"><?= e(trans('system::lang.theme.name.help')) ?></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
data-dismiss="popup"
|
||||
data-control="popup"
|
||||
data-handler="onInstallTheme">
|
||||
<?= e(trans('system::lang.install.theme_label')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
data-dismiss="popup">
|
||||
<?= e(trans('backend::lang.form.cancel')) ?>
|
||||
</button>
|
||||
</div>
|
||||
<script>
|
||||
setTimeout(
|
||||
function(){ $('#themeCode').select() },
|
||||
310
|
||||
)
|
||||
</script>
|
||||
<?= Form::close() ?>
|
||||
|
|
@ -9,4 +9,3 @@ noRecordsMessage: backend::lang.list.no_records
|
|||
|
||||
toolbar:
|
||||
buttons: list_toolbar
|
||||
search: list_search
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('system/updates') ?>"><?= e(trans('system::lang.updates.menu_label')) ?></a></li>
|
||||
<li><?= e(trans($this->pageTitle)) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<div class="control-tabs content-tabs tabs-flush" data-control="tab">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="<?= $activeTab == 'plugins' ? 'active' : '' ?>">
|
||||
<a
|
||||
href="#tabPlugins"
|
||||
data-tab-url="<?= Backend::url('system/updates/install/plugins') ?>">
|
||||
Plugins
|
||||
</a>
|
||||
</li>
|
||||
<li class="<?= $activeTab == 'themes' ? 'active' : '' ?>">
|
||||
<a
|
||||
href="#tabThemes"
|
||||
data-tab-url="<?= Backend::url('system/updates/install/themes') ?>">
|
||||
Themes
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane <?= $activeTab == 'plugins' ? 'active' : '' ?>">
|
||||
<div class="padded-container">
|
||||
<?= $this->makePartial('install_plugins') ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane <?= $activeTab == 'themes' ? 'active' : '' ?>">
|
||||
<div class="padded-container">
|
||||
<?= $this->makePartial('install_themes') ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<div class="padded-container">
|
||||
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
|
||||
<p><a href="<?= Backend::url('system/updates') ?>" class="btn btn-default"><?= e(trans('system::lang.settings.return')) ?></a></p>
|
||||
<p><a href="<?= Backend::url('cms/themes') ?>" class="btn btn-default"><?= e(trans('cms::lang.theme.return')) ?></a></p>
|
||||
</div>
|
||||
|
||||
<?php endif ?>
|
||||
|
|
@ -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.'
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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' => 'Просмотр приборной панели'
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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
|
||||
author:
|
||||
label: system::lang.updates.plugin_author
|
||||
sortable: false
|
||||
type: partial
|
||||
path: column_author
|
||||
Loading…
Reference in New Issue