Merge branch 'develop' into media-manager

This commit is contained in:
alekseybobkov 2015-03-26 18:37:24 -07:00
commit 5731ad0db7
39 changed files with 1601 additions and 317 deletions

View File

@ -1,3 +1,9 @@
* **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).
- Record Finder form widget now supports nested attributes for relations.
- List columns now support using array names (eg: `relation[attribute]`) which acts as an alias for the `valueFrom` option with `searchable` and `sortable` disabled.
* **Build 226** (2015-03-16)
- Form Tabs now support specifying a default tab using the `defaultTab` option (see Backend > Forms docs).
- Improved the Theme management features: Edit properties, import, export, duplicate and delete.

View File

@ -98,6 +98,7 @@ class ServiceProvider extends ModuleServiceProvider
'category' => SettingsManager::CATEGORY_SYSTEM,
'icon' => 'icon-paint-brush',
'class' => 'Backend\Models\BrandSettings',
'permissions' => ['backend.manage_branding'],
'order' => 500
],
'editor' => [
@ -134,7 +135,7 @@ class ServiceProvider extends ModuleServiceProvider
'category' => SettingsManager::CATEGORY_LOGS,
'icon' => 'icon-lock',
'url' => Backend::url('backend/accesslogs'),
'permissions' => ['backend.access_admin_logs'],
'permissions' => ['system.access_logs'],
'order' => 800
]
]);
@ -152,6 +153,10 @@ class ServiceProvider extends ModuleServiceProvider
'backend.manage_users' => [
'label' => 'system::lang.permissions.manage_other_administrators',
'tab' => 'system::lang.permissions.name'
],
'backend.manage_branding' => [
'label' => 'system::lang.permissions.manage_branding',
'tab' => 'system::lang.permissions.name'
]
]);
});

View File

@ -183,6 +183,7 @@ table.table.data tbody.clickable{cursor:pointer;-webkit-user-select:none;-moz-us
table.table.data tbody td.column-compact{padding:0}
table.table.data tfoot a{color:#666666;text-decoration:none}
table.table.data tfoot td,table.table.data tfoot th{border-color:#e2e2e2;padding:10px 15px}
table.table.data th.list-cell-type-switch,table.table.data td.list-cell-type-switch{text-align:center}
table.table.data .list-checkbox{padding-left:16px;padding-right:8px;width:52px;vertical-align:top;border-right:1px solid #eeeeee}
table.table.data .list-checkbox .checkbox{margin:0}
table.table.data .list-checkbox .custom-checkbox{position:relative;top:5px;left:-2px}

View File

@ -137,7 +137,7 @@ this.scrollClassContainer=this.options.scrollClassContainer?$(this.options.scrol
if(this.options.scrollMarkerContainer)
$(this.options.scrollMarkerContainer).append($('<span class="before scroll-marker"></span><span class="after scroll-marker"></span>'))
$el.mousewheel(function(event){if(!self.options.allowScroll)
return;var offset=self.options.vertical?((event.deltaFactor*event.deltaY)*-1):((event.deltaFactor*event.deltaX)*-1)
return;var offset=self.options.vertical?((event.deltaFactor*event.deltaY)*-1):(event.deltaFactor*event.deltaX)
return!scrollWheel(offset)})
$el.on('mousedown',function(event){startDrag(event)
return false})
@ -806,7 +806,7 @@ if(isTouch){this.$el.on('touchstart',function(event){var touchEvent=event.origin
event.stopPropagation()}})}
else{this.$thumb.on('mousedown',function(event){startDrag(event)})
this.$track.on('mouseup',function(event){moveDrag(event)})}
$el.mousewheel(function(event){var offset=self.options.vertical?((event.deltaFactor*event.deltaY)*-1):((event.deltaFactor*event.deltaX)*-1)
$el.mousewheel(function(event){var offset=self.options.vertical?((event.deltaFactor*event.deltaY)*-1):(event.deltaFactor*event.deltaX)
return!scrollWheel(offset*self.options.scrollSpeed)})
$el.on('oc.scrollbar.gotoStart',function(event){self.options.vertical?$el.scrollTop(0):$el.scrollLeft(0)
self.update()

View File

@ -60,7 +60,7 @@
var offset = self.options.vertical
? ((event.deltaFactor * event.deltaY) * -1)
: ((event.deltaFactor * event.deltaX) * -1)
: (event.deltaFactor * event.deltaX)
return !scrollWheel(offset)
})

View File

@ -68,7 +68,7 @@
$el.mousewheel(function (event){
var offset = self.options.vertical
? ((event.deltaFactor * event.deltaY) * -1)
: ((event.deltaFactor * event.deltaX) * -1)
: (event.deltaFactor * event.deltaX)
return !scrollWheel(offset * self.options.scrollSpeed)
})

View File

@ -272,6 +272,11 @@ table.table.data {
}
}
th.list-cell-type-switch,
td.list-cell-type-switch {
text-align: center;
}
.list-checkbox {
padding-left: 16px;
padding-right: 8px;

View File

@ -7,7 +7,6 @@ use Event;
use Input;
use Redirect;
use Backend;
use Backend\Classes\FormField;
use Backend\Classes\ControllerBehavior;
use October\Rain\Router\Helper as RouterHelper;
use ApplicationException;
@ -22,6 +21,8 @@ use Exception;
*/
class FormController extends ControllerBehavior
{
use \Backend\Traits\FormModelSaver;
/**
* @var string Default context for "create" pages.
*/
@ -59,11 +60,6 @@ class FormController extends ControllerBehavior
*/
protected $context;
/**
* @var array List of prepared models that require saving.
*/
protected $modelsToSave = [];
/**
* @var Model The initialized model used by the form.
*/
@ -701,43 +697,4 @@ class FormController extends ControllerBehavior
});
}
//
// Internals
//
protected function prepareModelsToSave($model, $saveData)
{
$this->modelsToSave = [];
$this->setModelAttributes($model, $saveData);
return $this->modelsToSave;
}
/**
* Sets a data collection to a model attributes, relations will also be set.
* @param array $saveData Data to save.
* @param Model $model Model to save to
* @return array The collection of models to save.
*/
protected function setModelAttributes($model, $saveData)
{
$this->modelsToSave[] = $model;
if (!is_array($saveData)) {
return;
}
$singularTypes = ['belongsTo', 'hasOne', 'morphOne'];
foreach ($saveData as $attribute => $value) {
if (
is_array($value) &&
$model->hasRelation($attribute) &&
in_array($model->getRelationType($attribute), $singularTypes)
) {
$this->setModelAttributes($model->{$attribute}, $value);
}
elseif ($value !== FormField::NO_SAVE_DATA) {
$model->{$attribute} = $value;
}
}
}
}

View File

@ -17,6 +17,8 @@ use October\Rain\Database\Model;
*/
class RelationController extends ControllerBehavior
{
use \Backend\Traits\FormModelSaver;
/**
* @var const Postback parameter for the active relationship field.
*/
@ -272,89 +274,6 @@ class RelationController extends ControllerBehavior
}
}
/**
* Determine the default buttons based on the model relationship type.
* @return string
*/
protected function evalToolbarButtons()
{
if ($buttons = $this->getConfig('view[toolbarButtons]')) {
return is_array($buttons)
? $buttons
: array_map('trim', explode('|', $buttons));
}
switch ($this->relationType) {
case 'hasMany':
case 'belongsToMany':
return ['create', 'add', 'delete', 'remove'];
case 'hasOne':
case 'belongsTo':
return ['create', 'update', 'link', 'delete', 'unlink'];
}
}
/**
* Determine the view mode based on the model relationship type.
* @return string
*/
protected function evalViewMode()
{
if ($this->forceViewMode) {
return $this->forceViewMode;
}
switch ($this->relationType) {
case 'hasMany':
case 'belongsToMany':
return 'multi';
case 'hasOne':
case 'belongsTo':
return 'single';
}
}
/**
* Determine the management mode based on the relation type and settings.
* @return string
*/
protected function evalManageMode()
{
if ($mode = post(self::PARAM_MODE)) {
return $mode;
}
if ($this->forceManageMode) {
return $this->forceManageMode;
}
switch ($this->eventTarget) {
case 'button-create':
case 'button-update':
return 'form';
case 'button-link':
return 'list';
}
switch ($this->relationType) {
case 'belongsTo':
return 'list';
case 'belongsToMany':
if (isset($this->config->pivot)) return 'pivot';
elseif ($this->eventTarget == 'list') return 'form';
else return 'list';
case 'hasOne':
case 'hasMany':
if ($this->eventTarget == 'button-add') return 'list';
else return 'form';
}
}
/**
* Renders the relationship manager.
* @param string $field The relationship field.
@ -889,8 +808,14 @@ class RelationController extends ControllerBehavior
{
$this->beforeAjax();
$foreignKeyName = $this->relationModel->getQualifiedKeyName();
$hydratedModel = $this->relationObject->where($foreignKeyName, $this->manageId)->first();
$saveData = $this->pivotWidget->getSaveData();
$this->relationObject->updateExistingPivot($this->manageId, $saveData, true);
$modelsToSave = $this->prepareModelsToSave($hydratedModel, $saveData);
foreach ($modelsToSave as $modelToSave) {
$modelToSave->save();
}
return ['#'.$this->relationGetId('view') => $this->relationRenderView()];
}
@ -960,7 +885,7 @@ class RelationController extends ControllerBehavior
* Multiple (has many, belongs to many)
*/
if ($this->viewMode == 'multi') {
$config = $this->makeConfig($this->config->list);
$config = $this->makeConfigForMode('view', 'list');
$config->model = $this->relationModel;
$config->alias = $this->alias . 'ViewList';
$config->showSorting = $this->getConfig('view[showSorting]', true);
@ -1027,7 +952,7 @@ class RelationController extends ControllerBehavior
$this->controller->relationExtendQuery($query, $this->field);
$this->viewModel = $query->getResults() ?: $this->relationModel;
$config = $this->makeConfig($this->config->form);
$config = $this->makeConfigForMode('view', 'form');
$config->model = $this->viewModel;
$config->arrayName = class_basename($this->relationModel);
$config->context = 'relation';
@ -1046,13 +971,14 @@ class RelationController extends ControllerBehavior
/*
* Pivot
*/
if ($this->manageMode == 'pivot' && isset($this->config->list)) {
$config = $this->makeConfig($this->config->list);
if ($this->manageMode == 'pivot') {
$config = $this->makeConfigForMode('manage', 'list');
$config->model = $this->relationModel;
$config->alias = $this->alias . 'ManagePivotList';
$config->showSetup = false;
$config->defaultSort = $this->getConfig('pivot[defaultSort]');
$config->recordsPerPage = $this->getConfig('pivot[recordsPerPage]');
$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,
@ -1078,8 +1004,8 @@ class RelationController extends ControllerBehavior
/*
* List
*/
elseif ($this->manageMode == 'list' && isset($this->config->list)) {
$config = $this->makeConfig($this->config->list);
elseif ($this->manageMode == 'list') {
$config = $this->makeConfigForMode('manage', 'list');
$config->model = $this->relationModel;
$config->alias = $this->alias . 'ManageList';
$config->showSetup = false;
@ -1116,7 +1042,7 @@ class RelationController extends ControllerBehavior
/*
* Form
*/
elseif ($this->manageMode == 'form' && isset($this->config->form)) {
elseif ($this->manageMode == 'form') {
/*
* Determine supplied form context
@ -1131,7 +1057,7 @@ class RelationController extends ControllerBehavior
}
}
$config = $this->makeConfig($this->config->form);
$config = $this->makeConfigForMode('manage', 'form');
$config->model = $this->relationModel;
$config->arrayName = class_basename($this->relationModel);
$config->context = $context ?: 'relation';
@ -1179,7 +1105,7 @@ class RelationController extends ControllerBehavior
protected function makePivotWidget()
{
$config = $this->makeConfig($this->config->pivot);
$config = $this->makeConfigForMode('pivot', 'form');
$config->model = $this->relationModel;
$config->arrayName = class_basename($this->relationModel);
$config->context = 'relation';
@ -1189,10 +1115,11 @@ class RelationController extends ControllerBehavior
* Existing record
*/
if ($this->manageId) {
$config->model = $this->relationModel->find($this->manageId);
$config->data = $this->relationObject->newPivotStatementForId($this->manageId)->first();
$foreignKeyName = $this->relationModel->getQualifiedKeyName();
$hydratedModel = $this->relationObject->where($foreignKeyName, $this->manageId)->first();
if (!$config->model || !$config->data) {
$config->model = $hydratedModel;
if (!$config->model) {
throw new ApplicationException(Lang::get('backend::lang.model.not_found', [
'class' => get_class($config->model), 'id' => $this->manageId
]));
@ -1222,4 +1149,134 @@ class RelationController extends ControllerBehavior
return $this->sessionKey = FormHelper::getSessionKey();
}
//
// Helpers
//
/**
* Determine the default buttons based on the model relationship type.
* @return string
*/
protected function evalToolbarButtons()
{
if ($buttons = $this->getConfig('view[toolbarButtons]')) {
return is_array($buttons)
? $buttons
: array_map('trim', explode('|', $buttons));
}
switch ($this->relationType) {
case 'hasMany':
case 'belongsToMany':
return ['create', 'add', 'delete', 'remove'];
case 'hasOne':
case 'belongsTo':
return ['create', 'update', 'link', 'delete', 'unlink'];
}
}
/**
* Determine the view mode based on the model relationship type.
* @return string
*/
protected function evalViewMode()
{
if ($this->forceViewMode) {
return $this->forceViewMode;
}
switch ($this->relationType) {
case 'hasMany':
case 'belongsToMany':
return 'multi';
case 'hasOne':
case 'belongsTo':
return 'single';
}
}
/**
* Determine the management mode based on the relation type and settings.
* @return string
*/
protected function evalManageMode()
{
if ($mode = post(self::PARAM_MODE)) {
return $mode;
}
if ($this->forceManageMode) {
return $this->forceManageMode;
}
switch ($this->eventTarget) {
case 'button-create':
case 'button-update':
return 'form';
case 'button-link':
return 'list';
}
switch ($this->relationType) {
case 'belongsTo':
return 'list';
case 'belongsToMany':
if (isset($this->config->pivot)) return 'pivot';
elseif ($this->eventTarget == 'list') return 'form';
else return 'list';
case 'hasOne':
case 'hasMany':
if ($this->eventTarget == 'button-add') return 'list';
else return 'form';
}
}
/**
* Returns the configuration for a mode (view, manage, pivot) for an
* expected type (list, form). Uses fallback configuration.
*/
protected function makeConfigForMode($mode = 'view', $type = 'list')
{
$config = null;
/*
* Look for $this->config->view['list']
*/
if (
isset($this->config->{$mode}) &&
array_key_exists($type, $this->config->{$mode})
) {
$config = $this->config->{$mode}[$type];
}
/*
* Look for $this->config->list
*/
elseif (isset($this->config->{$type})) {
$config = $this->config->{$type};
}
/*
* Apply substitutes:
*
* - view.list => manage.list
*/
if (!$config) {
if ($mode == 'manage' && $type == 'list') {
return $this->makeConfigForMode('view', $type);
}
throw new ApplicationException('Missing configuration for '.$mode.'.'.$type.' in RelationController definition '.$this->field);
}
return $this->makeConfig($config);
}
}

View File

@ -5,6 +5,7 @@
<!-- Passable fields -->
<input type="hidden" name="manage_id" value="<?= $relationManageId ?>" />
<input type="hidden" name="_relation_field" value="<?= $relationField ?>" />
<input type="hidden" name="_relation_mode" value="form" />
<input type="hidden" name="_relation_session_key" value="<?= $relationSessionKey ?>" />
<div class="modal-header">

View File

@ -487,6 +487,8 @@ class FormField
/**
* Returns a value suitable for the field id property.
* @param string $suffix Specify a suffix string
* @return string
*/
public function getId($suffix = null)
{

View File

@ -1,5 +1,7 @@
<?php namespace Backend\Classes;
use October\Rain\Html\Helper as HtmlHelper;
/**
* List Columns definition
* A translation of the list column configuration
@ -147,4 +149,31 @@ class ListColumn
return $config;
}
/**
* Returns a HTML valid name for the column name.
* @return string
*/
public function getName()
{
return HtmlHelper::nameToId($this->columnName);
}
/**
* Returns a value suitable for the column id property.
* @param string $suffix Specify a suffix string
* @return string
*/
public function getId($suffix = null)
{
$id = 'column';
$id .= '-'.$this->columnName;
if ($suffix) {
$id .= '-'.$suffix;
}
return HtmlHelper::nameToId($id);
}
}

View File

@ -25,7 +25,7 @@ class AccessLogs extends Controller
'Backend.Behaviors.ListController'
];
public $requiredPermissions = ['system.access_access_logs'];
public $requiredPermissions = ['system.access_logs'];
public $listConfig = 'config_list.yaml';

View File

@ -14,15 +14,29 @@ use ApplicationException;
*/
class DataTable extends FormWidgetBase
{
/**
* {@inheritDoc}
*/
protected $defaultAlias = 'datatable';
//
// Configurable properties
//
/**
* @var string Table size
*/
protected $size = 'large';
public $size = 'large';
/**
* @var bool Allow rows to be sorted
* @todo Not implemented...
*/
public $rowSorting = false;
//
// Object properties
//
/**
* {@inheritDoc}
*/
protected $defaultAlias = 'datatable';
/**
* @var Backend\Widgets\Table Table widget
@ -34,7 +48,11 @@ class DataTable extends FormWidgetBase
*/
public function init()
{
$this->size = $this->getConfig('size', $this->size);
$this->fillFromConfig([
'size',
'rowSorting',
]);
$this->table = $this->makeTableWidget();
$this->table->bindToController();
}
@ -64,6 +82,23 @@ class DataTable extends FormWidgetBase
$this->populateTableWidget();
$this->vars['table'] = $this->table;
$this->vars['size'] = $this->size;
$this->vars['rowSorting'] = $this->rowSorting;
}
/**
* {@inheritDoc}
*/
public function getLoadValue()
{
$value = (array) parent::getLoadValue();
// Sync the array keys as the ID to make the
// table widget happy!
foreach ($value as $key => $_value) {
$value[$key] = ['id' => $key] + (array) $_value;
}
return $value;
}
/**
@ -82,6 +117,12 @@ class DataTable extends FormWidgetBase
$result += $records;
}
// We should be dealing with a simple array, so
// strip out the id columns in the final array.
foreach ($result as $key => $_result) {
unset($result[$key]['id']);
}
return $result;
}
@ -97,6 +138,8 @@ class DataTable extends FormWidgetBase
// all records at once. -ab
$records = $this->getLoadValue() ?: [];
$dataSource->purge();
$dataSource->initRecords((array) $records);
}

View File

@ -4,7 +4,7 @@ use Str;
use Input;
use Validator;
use System\Models\File;
use SystemException;
use ApplicationException;
use Backend\Classes\FormField;
use Backend\Classes\FormWidgetBase;
use ValidationException;
@ -182,6 +182,14 @@ class FileUpload extends FormWidgetBase
protected function getRelationObject()
{
list($model, $attribute) = $this->resolveModelAttribute($this->valueFrom);
if (!$model->hasRelation($attribute)) {
throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', [
'class' => get_class($model),
'relation' => $attribute
]));
}
return $model->{$attribute}();
}
@ -230,7 +238,7 @@ class FileUpload extends FormWidgetBase
return $this->makePartial('config_form');
}
throw new SystemException('Unable to find file, it may no longer exist');
throw new ApplicationException('Unable to find file, it may no longer exist');
}
/**
@ -248,7 +256,7 @@ class FileUpload extends FormWidgetBase
return ['item' => $file->toArray()];
}
throw new SystemException('Unable to find file, it may no longer exist');
throw new ApplicationException('Unable to find file, it may no longer exist');
}
catch (Exception $ex) {
return json_encode(['error' => $ex->getMessage()]);
@ -300,7 +308,7 @@ class FileUpload extends FormWidgetBase
}
if (!$uploadedFile->isValid()) {
throw new SystemException('File is not valid');
throw new ApplicationException('File is not valid');
}
$fileRelation = $this->getRelationObject();

View File

@ -1,8 +1,8 @@
<?php namespace Backend\FormWidgets;
use Lang;
use ApplicationException;
use Backend\Classes\FormWidgetBase;
use SystemException;
/**
* Record Finder
@ -54,16 +54,6 @@ class RecordFinder extends FormWidgetBase
*/
protected $defaultAlias = 'recordfinder';
/**
* @var string Relationship type
*/
public $relationType;
/**
* @var string Relationship name
*/
public $relationName;
/**
* @var Model Relationship model
*/
@ -91,16 +81,6 @@ class RecordFinder extends FormWidgetBase
'descriptionFrom',
]);
$this->relationName = $this->valueFrom;
$this->relationType = $this->model->getRelationType($this->relationName);
if (!$this->model->hasRelation($this->relationName)) {
throw new SystemException(Lang::get('backend::lang.model.missing_relation', [
'class' => get_class($this->model),
'relation' => $this->relationName
]));
}
if (post('recordfinder_flag')) {
$this->listWidget = $this->makeListWidget();
$this->listWidget->bindToController();
@ -120,6 +100,25 @@ class RecordFinder extends FormWidgetBase
}
}
/**
* Returns the model of a relation type,
* supports nesting via HTML array.
* @return Relation
*/
protected function getRelationModel()
{
list($model, $attribute) = $this->resolveModelAttribute($this->valueFrom);
if (!$model->hasRelation($attribute)) {
throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', [
'class' => get_class($model),
'relation' => $attribute
]));
}
return $model->makeRelation($attribute);
}
/**
* {@inheritDoc}
*/
@ -220,7 +219,7 @@ class RecordFinder extends FormWidgetBase
protected function makeListWidget()
{
$config = $this->makeConfig($this->getConfig('list'));
$config->model = $this->model->makeRelation($this->relationName);
$config->model = $this->getRelationModel();
$config->alias = $this->alias . 'List';
$config->showSetup = false;
$config->showCheckboxes = false;

View File

@ -42,16 +42,6 @@ class Relation extends FormWidgetBase
*/
protected $defaultAlias = 'relation';
/**
* @var string Relationship type
*/
public $relationType;
/**
* @var string Relationship name
*/
public $relationName;
/**
* @var FormField Object used for rendering a simple field type
*/
@ -67,16 +57,6 @@ class Relation extends FormWidgetBase
'descriptionFrom',
'emptyOption',
]);
$this->relationName = $this->valueFrom;
$this->relationType = $this->model->getRelationType($this->relationName);
if (!$this->model->hasRelation($this->relationName)) {
throw new SystemException(Lang::get(
'backend::lang.model.missing_relation',
['class'=>get_class($this->model), 'relation'=>$this->relationName]
));
}
}
/**
@ -104,15 +84,17 @@ class Relation extends FormWidgetBase
return $this->renderFormField = RelationBase::noConstraints(function () {
$field = clone $this->formField;
$relationObject = $this->getRelationObject();
$query = $relationObject->newQuery();
list($model, $attribute) = $this->resolveModelAttribute($this->relationName);
$relatedObj = $model->makeRelation($attribute);
$query = $model->{$attribute}()->newQuery();
list($model, $attribute) = $this->resolveModelAttribute($this->valueFrom);
$relationType = $model->getRelationType($attribute);
$relationModel = $model->makeRelation($attribute);
if (in_array($this->relationType, ['belongsToMany', 'morphToMany', 'morphedByMany', 'hasMany'])) {
if (in_array($relationType, ['belongsToMany', 'morphToMany', 'morphedByMany', 'hasMany'])) {
$field->type = 'checkboxlist';
}
elseif (in_array($this->relationType, ['belongsTo', 'hasOne'])) {
elseif (in_array($relationType, ['belongsTo', 'hasOne'])) {
$field->type = 'dropdown';
}
@ -120,8 +102,8 @@ class Relation extends FormWidgetBase
// It is safe to assume that if the model and related model are of
// the exact same class, then it cannot be related to itself
if ($model->exists && (get_class($model) == get_class($relatedObj))) {
$query->where($relatedObj->getKeyName(), '<>', $model->getKey());
if ($model->exists && (get_class($model) == get_class($relationModel))) {
$query->where($relationModel->getKeyName(), '<>', $model->getKey());
}
// Even though "no constraints" is applied, belongsToMany constrains the query
@ -129,11 +111,11 @@ class Relation extends FormWidgetBase
$query->getQuery()->getQuery()->joins = [];
$treeTraits = ['October\Rain\Database\Traits\NestedTree', 'October\Rain\Database\Traits\SimpleTree'];
if (count(array_intersect($treeTraits, class_uses($relatedObj))) > 0) {
$field->options = $query->listsNested($this->nameFrom, $relatedObj->getKeyName());
if (count(array_intersect($treeTraits, class_uses($relationModel))) > 0) {
$field->options = $query->listsNested($this->nameFrom, $relationModel->getKeyName());
}
else {
$field->options = $query->lists($this->nameFrom, $relatedObj->getKeyName());
$field->options = $query->lists($this->nameFrom, $relationModel->getKeyName());
}
return $field;
@ -155,4 +137,25 @@ class Relation extends FormWidgetBase
return $value;
}
/**
* Returns the value as a relation object from the model,
* supports nesting via HTML array.
* @return Relation
*/
protected function getRelationObject()
{
list($model, $attribute) = $this->resolveModelAttribute($this->valueFrom);
if (!$model->hasRelation($attribute)) {
throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', [
'class' => get_class($model),
'relation' => $attribute
]));
}
return $model->{$attribute}();
}
}

View File

@ -1,13 +1,52 @@
.field-repeater {
padding-top: 5px;
}
.field-repeater .field-repeater-item {
.field-repeater ul.field-repeater-items,
.field-repeater li.field-repeater-item {
padding: 0;
margin: 0;
list-style: none;
}
.field-repeater ul.field-repeater-items > li.dragged {
opacity: .7;
position: absolute;
padding-top: 15px;
padding-right: 15px;
z-index: 2000;
background-color: #ffffff;
border: 1px dashed #dbdee0;
}
.field-repeater ul.field-repeater-items > li.dragged .repeater-item-remove {
opacity: 0;
}
.field-repeater ul.field-repeater-items > li.placeholder {
display: block;
position: relative;
height: 25px;
margin-bottom: 5px;
}
.field-repeater ul.field-repeater-items > li.placeholder:before {
display: block;
position: absolute;
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
text-decoration: inherit;
-webkit-font-smoothing: antialiased;
*margin-right: .3em;
content: "\f054";
color: #d35714;
left: -10px;
top: 8px;
z-index: 2000;
}
.field-repeater li.field-repeater-item {
position: relative;
margin-left: 5px;
padding-left: 15px;
border-left: 1px solid #dbdee0;
}
.field-repeater .field-repeater-item:before {
.field-repeater li.field-repeater-item:before {
color: #bdc3c7;
font-family: FontAwesome;
font-weight: normal;
@ -21,11 +60,11 @@
left: -4px;
top: -2px;
}
.field-repeater .field-repeater-item .field-repeater-form {
.field-repeater li.field-repeater-item .field-repeater-form {
position: relative;
top: -7px;
}
.field-repeater .field-repeater-item .repeater-item-handle {
.field-repeater li.field-repeater-item .repeater-item-handle {
position: absolute;
top: -6px;
left: -10px;
@ -33,28 +72,29 @@
background: #f9f9f9;
cursor: move;
opacity: 0;
border-radius: 999px;
-webkit-transition: opacity 0.5s;
transition: opacity 0.5s;
}
.field-repeater .field-repeater-item .repeater-item-handle:hover {
.field-repeater li.field-repeater-item .repeater-item-handle:hover {
color: #999;
}
.field-repeater .field-repeater-item .repeater-item-remove {
.field-repeater li.field-repeater-item .repeater-item-remove {
position: absolute;
top: -10px;
right: 0;
z-index: 90;
}
.field-repeater .field-repeater-item .repeater-item-handle,
.field-repeater .field-repeater-item .repeater-item-remove {
.field-repeater li.field-repeater-item .repeater-item-handle,
.field-repeater li.field-repeater-item .repeater-item-remove {
width: 20px;
height: 20px;
text-align: center;
}
.field-repeater .field-repeater-item:hover .repeater-item-handle,
.field-repeater .field-repeater-item:active .repeater-item-handle,
.field-repeater .field-repeater-item:hover .repeater-item-remove,
.field-repeater .field-repeater-item:active .repeater-item-remove {
.field-repeater li.field-repeater-item:hover .repeater-item-handle,
.field-repeater li.field-repeater-item:active .repeater-item-handle,
.field-repeater li.field-repeater-item:hover .repeater-item-remove,
.field-repeater li.field-repeater-item:active .repeater-item-remove {
opacity: 1;
}
.field-repeater .field-repeater-add-item {

View File

@ -30,14 +30,24 @@
}
Repeater.prototype.init = function() {
// Public properties
this.something = false
// Init with no arguments
this.bindSorting()
}
Repeater.prototype.someFunction = function() {
// Do stuff
Repeater.prototype.bindSorting = function() {
var sortableOptions = {
// useAnimation: true,
handle: '.repeater-item-handle',
nested: false
}
$('ul.field-repeater-items', this.$el).sortable(sortableOptions)
}
Repeater.prototype.unbind = function() {
this.$el.find('ul.field-repeater-items').sortable('destroy')
this.$el.removeData('oc.repeater')
}
// FIELD REPEATER PLUGIN DEFINITION
@ -55,7 +65,7 @@
if (typeof option == 'string') result = data[option].apply(data, args)
if (typeof result != 'undefined') return false
})
return result ? result : this
}
@ -72,8 +82,8 @@
// FIELD REPEATER DATA-API
// ===============
$(document).on('click.oc.myplugin', '[data-control="fieldrepeater"]', function() {
$(this).fieldRepeater()
$(document).render(function() {
$('[data-control="fieldrepeater"]').fieldRepeater()
});
}(window.jQuery);

View File

@ -4,7 +4,46 @@
padding-top: 5px;
.field-repeater-item {
ul.field-repeater-items,
li.field-repeater-item {
padding: 0;
margin: 0;
list-style: none;
}
ul.field-repeater-items > li {
&.dragged {
opacity: .7;
position: absolute;
padding-top: 15px;
padding-right: 15px;
z-index: 2000;
background-color: @body-bg;
border: 1px dashed #dbdee0;
.repeater-item-remove {
opacity: 0;
}
}
&.placeholder {
display: block;
position: relative;
height: 25px;
margin-bottom: 5px;
&:before {
display: block;
position: absolute;
.icon(@chevron-right);
color: #d35714;
left: -10px;
top: 8px;
z-index: 2000;
}
}
}
li.field-repeater-item {
position: relative;
margin-left: 5px;
padding-left: 15px;
@ -32,6 +71,7 @@
background: @color-body-bg;
cursor: move;
opacity: 0;
border-radius: 999px;
.transition(~'opacity 0.5s');
&:hover {
color: #999;

View File

@ -1,13 +1,13 @@
<div class="field-repeater">
<div class="field-repeater" data-control="fieldrepeater">
<div id="<?= $this->getId('items') ?>" class="field-repeater-items">
<ul id="<?= $this->getId('items') ?>" class="field-repeater-items">
<?php foreach ($this->formWidgets as $index => $widget): ?>
<?= $this->makePartial('repeater_item', [
'widget' => $widget,
'indexValue' => $index
]) ?>
<?php endforeach ?>
</div>
</ul>
<div class="field-repeater-add-item loading-indicator-container indicator-center">
<a

View File

@ -1,4 +1,4 @@
<div class="field-repeater-item">
<li class="field-repeater-item">
<div class="repeater-item-handle">
<i class="icon-bars"></i>
@ -25,4 +25,4 @@
<input type="hidden" name="<?= $indexName ?>" value="<?= $indexValue ?>" />
</div>
</li>

View File

@ -1,13 +1,16 @@
<?php
return [
'auth' => [
'title' => 'Zone d\'administration'
],
'field' => [
'invalid_type' => 'Type de champ invalide :type.',
'options_method_not_exists' => 'The model class :model must define a method :method() returning options for the ":field" form field.',
'options_method_not_exists' => 'La classe modèle :model doit définir une méthode :method() renvoyant des options pour le champ de formulaire ":field".',
],
'widget' => [
'not_registered' => "La classe ':name' du widget non enregistrée",
'not_bound' => "La classe ':name' du widget n'a pas pu s'authentifier avec le controller",
'not_bound' => "La classe ':name' du widget n'a pas pu s'authentifier avec le contrôleur",
],
'page' => [
'untitled' => "Sans titre",
@ -34,9 +37,9 @@ return [
'enter_new_password' => "Entrez votre nouveau mot de passe",
'password_reset' => "Réinitialiser le mot de passe",
'restore_success' => "Un email contenant les instructions de réinitialisation de mot de passe a été envoyé sur l'email de votre compte.",
'restore_error' => "A user could not be found with a login value of ':login'",
'reset_success' => "Mot de passe réinitialisé avec succes. You may now sign in.",
'reset_error' => "Invalid password reset data supplied. Please try again!",
'restore_error' => "L'identifiant ':login' ne correspond à aucun utilisateur",
'reset_success' => "Mot de passe réinitialisé avec succès. Vous pouvez maintenant vous connecter.",
'reset_error' => "Données de réinitialisation du mot de passe invalides. Veuillez réessayer !",
'reset_fail' => "Réinitialisation du mot de passe impossible!",
'apply' => 'Appliquer',
'cancel' => 'Annuler',
@ -47,7 +50,24 @@ return [
'menu_label' => 'Tableau de bord',
'widget_label' => 'Widget',
'widget_width' => 'Taille',
'full_width' => 'Plein écra',
'add_widget' => 'Ajouter un widget',
'widget_inspector_title' => 'Configuration du Widget',
'widget_inspector_description' => 'Configurer le widget',
'widget_columns_label' => 'Width :columns',
'widget_columns_description' => 'La longueur du widget, a nombre entre 1 et 10.',
'widget_columns_error' => 'Veuillez définir la longueur du widget, un nombre entre 1 et 10.',
'columns' => '{1} colonne|[2,Inf] colonnes',
'widget_new_row_label' => 'Forcer une nouvelle ligne',
'widget_new_row_description' => 'Placer le widget sur une nouvelle ligne.',
'widget_title_label' => 'Titre du Widget',
'widget_title_error' => 'Le titre du Widget est obligatoire.',
'status' => [
'widget_title_default' => 'Etat du système',
'online' => 'en ligne',
'maintenance' => 'en cours de maintenance',
'update_available' => '{0} mise à jour disponible!|{1} mise à jour disponible!|[2,Inf] mises à jour disponibles!'
]
],
'user' => [
'name' => 'Administrateur',
@ -61,39 +81,65 @@ return [
'full_name' => "Nom complet",
'email' => "Email",
'groups' => "Groupes",
'groups_comment' => "Specify which groups this person belongs to.",
'groups_comment' => "Précisez à quel(s) groupe(s) cette personne appartient.",
'avatar' => "Avatar",
'password' => "Mot de passe",
'password_confirmation' => "Confirmer le mot de passe",
'superuser' => "Super User",
'superuser_comment' => "Check this box to allow this person to access all areas.",
'permissions' => 'Permissions',
'superuser' => "Super Utilisateur",
'superuser_comment' => "Cochez cette case pour autoriser cet utilisateur à accéder a l'ensemble des zones.",
'send_invite' => 'Envoyer une invitation par email',
'send_invite_comment' => 'Use this checkbox to send an invitation to the user by email',
'delete_confirm' => 'Voulez-vous vraiment supprimer cette administrateurr?',
'send_invite_comment' => 'Cochez cette case pour envoyer une invitation aux utilisateurs par email.',
'delete_confirm' => 'Voulez-vous vraiment supprimer cet administrateurr?',
'return' => 'Retour à la liste des administrateurs',
'allow' => 'Autoriser',
'inherit' => 'Hériter',
'deny' => 'Interdire',
'group' => [
'name' => 'Groupe',
'name_field' => 'Nom',
'description_field' => 'Description',
'is_new_user_default_field' => 'Inclure les nouveaux administrateurs dans ce groupe, par défaut.',
'code_field' => 'Code',
'code_comment' => 'Entrer un code d\'accès unique si vous souhaitez accéder a ce groupe via une API.',
'menu_label' => 'Groupes',
'list_title' => 'Gérer les groupes',
'new' => 'Ajouter un groupe administrateur',
'delete_confirm' => 'Voulez-vous vraiment supprimer ce groupe administrateur?',
'return' => 'Retour à la liste de groupe',
'delete_confirm' => 'Voulez-vous vraiment supprimer ce groupe d\'administrateurs ?',
'return' => 'Retour à la liste des groupes',
],
'preferences' => [
'not_authenticated' => 'There is no an authenticated user to load or save preferences for.'
'not_authenticated' => 'Il n\'y a aucun utilisateur identifié à qui charger ou changer les préférences.'
]
],
'list' => [
'default_title' => 'Liste',
'search_prompt' => 'Rechercher...',
'no_records' => 'There are no records in this view.',
'missing_model' => 'List behavior used in :class does not have a model defined.',
'missing_column' => 'There are no column definitions for :columns.',
'missing_columns' => 'List used in :class has no list columns defined.',
'missing_definition' => "List behavior does not contain a column for ':field'.",
'behavior_not_ready' => 'List behavior has not been initialized, check that you have called makeLists() in your controller.',
'invalid_column_datetime' => "Column value ':column' is not a DateTime object, are you missing a \$dates reference in the Model?",
'no_records' => 'Il n\'y a aucun résultat dans cette vue.',
'missing_model' => 'La liste utilisée dans la classe :class n\'a pas de modèle défini.',
'missing_column' => 'Il n\'y a pas de définition pour la colonne :columns.',
'missing_columns' => 'La liste utilisée dans la classe :class n\'a pas de colonne de liste définie.',
'missing_definition' => 'La liste utilisée ne contient de pas de colonne pour le champ \':field\'.',
'behavior_not_ready' => 'La liste utilisée n\'a pas été initialisée, vérifiez que vous avez appellé la méthode makeLists() dans votre contrôleur.',
'invalid_column_datetime' => 'La valeur de la colonne \':column\' n\'est pas un object DateTime, est-ce qu\'il vous manque une référence de \$dates dans votre Modèle ?',
'pagination' => 'Enregistrements affichés: :from-:to sur :total',
'prev_page' => 'Page précédente',
'next_page' => 'Page suivante',
'loading' => 'Chargement...',
'setup_title' => 'Installation de la liste',
'setup_help' => 'Cochez les colonnes que vous souhaitez voir dans la liste. Vous pouvez modifier l\'ordre des colonnes en les glissant vers le haut ou le bas.',
'records_per_page' => 'Enregistrements par page',
'records_per_page_help' => 'Choisissez le nombre d\'enregistrements à afficher. Notez qu\'un nombre d\'enregistrements trop élevé sur une seule page peut réduire les performances.',
'delete_selected' => 'Supprimer la sélection',
'delete_selected_empty' => 'Il n\'y a aucun enregistrement a supprimer',
'delete_selected_confirm' => 'Supprimer les enregistrements sélectionnés ?',
'delete_selected_success' => 'Les enregistrements ont bien été supprimés.',
],
'fileupload' => [
'attachment' => 'Pièce-jointe',
'help' => 'Ajouter un titre et une description pour cette pièce-jointe.',
'title_label' => 'Titre',
'description_label' => 'Description'
],
'form' => [
'create_title' => "Nouveau :name",
@ -102,63 +148,95 @@ return [
'create_success' => 'Le :name a été créé avec succès',
'update_success' => 'Le :name a été modifié avec succès',
'delete_success' => 'Le :name a été supprimé avec succès',
'missing_id' => "Form record ID has not been specified.",
'missing_model' => 'Form behavior used in :class does not have a model defined.',
'missing_definition' => "Form behavior does not contain a field for ':field'.",
'not_found' => 'Form record with an ID of :id could not be found.',
'missing_id' => "L'ID de l'enregistrement du formulaire n'est pas précisé.",
'missing_model' => 'Le formulaire utilisé dans la classe :class n\'a pas de modèle défini.',
'missing_definition' => "Le formulaire utilisé n'a pas de champ pour ':field'.",
'not_found' => 'Aucun enregistrement ne correspond a l\'ID :id.',
'action_confirm' => 'Êtes-vous certain(e) ?',
'create' => 'Créer',
'create_and_close' => 'Créer et fermer',
'creating' => 'Création en cours...',
'creating_name' => 'Création de :name en cours...',
'save' => 'Enregistrer',
'save_and_close' => 'Enregistrer et fermer',
'saving' => 'Enregistrement en cours...',
'saving_name' => 'Enregistrement de :name en cours...',
'delete' => 'Supprimer',
'deleting' => 'Suppression en cours...',
'undefined_tab' => 'Misc',
'field_off' => 'Off',
'field_on' => 'On',
'deleting_name' => 'Suppression de :name en cours...',
'reset_default' => 'Restaurer les valeurs par défaut',
'resetting' => 'Restauration',
'resetting_name' => 'Restauration de :name',
'undefined_tab' => 'Divers',
'field_off' => 'Désactivé',
'field_on' => 'Activé',
'add' => 'Ajouter',
'apply' => 'Appliquer',
'cancel' => 'Annuler',
'close' => 'Fermer',
'confirm' => 'Confirmer',
'reload' => 'Recharger',
'ok' => 'OK',
'or' => 'et',
'or' => 'ou',
'confirm_tab_close' => 'Voulez-vous vraiment fermer cet onglet? Les modifications que vous avez faites seront perdues.',
'behavior_not_ready' => 'Form behavior has not been initialized, check that you have called initForm() in your controller.',
'preview_no_files_message' => 'Files are not uploaded',
'behavior_not_ready' => 'Le formulaire n\' a pas encore été initialisé, vérifiez que vous avez bien appellé la méthode initForm() dans votre contrôleur.',
'preview_no_files_message' => 'Les fichiers ne sont pas envoyés.',
'select' => 'Sélectionner',
'select_all' => 'tout',
'select_none' => 'aucun',
'select_placeholder' => 'Sélectionnez une valeur',
'insert_row' => 'Insérer une ligne',
'delete_row' => 'Supprimer une ligne',
'concurrency_file_changed_title' => 'Le fichier à été modifié',
'concurrency_file_changed_description' => "Le fichier que vous êtes en train d'éditer à été modifié sur le disque par un autre utilisateur. Vous pouvez soit recharger le fichier depuis le disque (en perdant donc vos propres changements) ou bien écraser ce fichier avec vos propres modifications.."
],
'relation' => [
'missing_definition' => "Relation behavior does not contain a definition for ':field'.",
'missing_model' => "Relation behavior used in :class does not have a model defined.",
'invalid_action_single' => "This action cannot be performed on a singular relationship.",
'invalid_action_multi' => "This action cannot be performed on a multiple relationship.",
'add' => "Add",
'missing_config' => "La relation n'a pas de configuration pour ':config'.",
'missing_definition' => "La relation n'a pas de définition pour le champ ':field'.",
'missing_model' => "La relation utilisée dans la classe :class n'a pas de modèle défini.",
'invalid_action_single' => "Cette action ne peut être effectuée sur une relation singulière.",
'invalid_action_multi' => "Cette action ne peut être effectuée sur une relation multiple.",
'help' => "Cliquez sur un élément pour ajouter",
'related_data' => "Donnée liée :name",
'add' => "Ajouter",
'add_a_new' => "Ajouter un nouveau :name",
'add_selected' => "Ajouter la sélection",
'link_selected' => "Lier la sélection",
'link_a_new' => "Lier un nouveau :name",
'cancel' => "Annuler",
'close' => "Fermer",
'add_name' => "Ajouter :name",
'create' => "Créer",
'create_name' => "Créer :name",
'create_name' => "Création de :name",
'update' => "Mettre à jour",
'update_name' => "Mise à jour :name",
'remove' => "Remove",
'remove_name' => "Remove :name",
'update_name' => "Mise à jour de :name",
'preview' => "Aperçu",
'preview_name' => "Aperçu de :name",
'remove' => "Retirer",
'remove_name' => "Retirer :name",
'delete' => "Supprimer",
'delete_name' => "Suppression :name",
'delete_name' => "Suppression de :name",
'delete_confirm' => "Êtes vous certain(e) ?",
'link' => "Lier",
'link_name' => "Lier :name",
'unlink' => "Séparer",
'unlink_name' => "Séparer :name",
'unlink_confirm' => "Êtes vous certain(e) ?",
],
'model' => [
'name' => "Model",
'not_found' => "Model ':class' with an ID of :id could not be found",
'missing_id' => "There is no ID specified for looking up the model record.",
'missing_relation' => "Model ':class' does not contain a definition for ':relation'.",
'invalid_class' => "Model :model used in :class is not valid, it must inherit the \Model class.",
'mass_assignment_failed' => "Mass assignment failed for Model attribute ':attribute'.",
'name' => "Modèle",
'not_found' => "Aucun modèle ':class' ne correspond à l'ID :id",
'missing_id' => "Il manque l'ID de l'enregistrement.",
'missing_relation' => "Le modèle ':class' ne contient pas de définition ':relation'.",
'missing_method' => "Le modèle ':class' ne contient pas de méthode ':method'.",
'invalid_class' => "Le modèle :model utilisé dans la classe :class est invalide, il doit hériter la classe \Model.",
'mass_assignment_failed' => "L'assignement de masse a échoué pour l'attribut de modèle ':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' => 'Conseils de configuration système',
'tips_description' => 'Il y a des éléments a prendre en compte pour configurer le système proprement.',
'permissions' => 'Le répertoire :name ou ses sous-dossiers n\'ont pas les droits d\'écriture pour PHP. Veuillez modifier les droits de ce répertoire pour le serveur web.',
'extension' => 'L\'extension PHP :name n\'est pas installée. Veuillez installer la librairie et activer l\'extension.'
],
'editor' => [
'menu_label' => 'Préférences de l\'éditeur de code',
@ -166,24 +244,42 @@ return [
'font_size' => 'Taille de police',
'tab_size' => 'Taille de tabulation',
'use_hard_tabs' => 'Indentation par tabulation',
'code_folding' => 'Code folding',
'word_wrap' => 'Word wrap',
'code_folding' => 'Masquage du code',
'word_wrap' => 'Retour à la ligne',
'highlight_active_line' => 'Sélectionner la ligne active',
'show_invisibles' => 'Afficher les caractères invisibles',
'show_gutter' => 'Afficher le gutter',
'theme' => 'Color scheme',
'show_gutter' => 'Afficher les numéros de ligne',
'theme' => 'Schéma de couleurs',
],
'tooltips' => [
'preview_website' => 'Aperçu du site'
],
'mysettings' => [
'menu_label' => 'Mes Paramètres',
'menu_label' => 'Mes paramètres',
'menu_description' => 'Paramètres en relation avec votre compte',
],
'myaccount' => [
'menu_label' => 'Mon compte',
'menu_description' => 'Modifier les informations de votre compte comme le nom, l\'email ou le mot de passe.',
'menu_keywords' => 'security login'
'menu_keywords' => 'sécurité compte'
],
'branding' => [
'menu_label' => 'Personnalisation de l\'interface d\'administration',
'menu_description' => 'Personnaliser l\'interface d\'administration comme le nom, les couleurs ou le logo.',
'brand' => 'Marque',
'logo' => 'Logo',
'logo_description' => 'Envoyer un logo personnalisé pour utiliser dans le interface d\'administration.',
'app_name' => 'Nom de l\'application',
'app_name_description' => 'Ce nom est affiché comme titre dans le interface d\'administration.',
'app_tagline' => 'Slogan de l\'application',
'app_tagline_description' => 'Ce slogan est affiché sur la page d\'inscription à l\'interface d\'administration.',
'colors' => 'Couleurs',
'primary_light' => 'Primaire (Claire)',
'primary_dark' => 'Primaire (Foncée)',
'secondary_light' => 'Secondaire (Claire)',
'secondary_dark' => 'Secondaire (Foncée)',
'styles' => 'Styles',
'custom_stylesheet' => 'Feuille de style personnalisée (CSS)'
],
'backend_preferences' => [
'menu_label' => 'Préférences de l\'administration',
@ -192,9 +288,9 @@ return [
'locale_comment' => 'Choisir une langue.',
],
'access_log' => [
'hint' => 'This log displays a list of successful sign in attempts by administrators. Records are kept for a total of :days days.',
'hint' => 'Ce log affiche une liste d\'inscriptions en attente d\'approbation par un administrateur. Les enregistrements sont sauvegardés pendant :days jours.',
'menu_label' => 'Log des accès',
'menu_description' => 'Affiche la liste des utilisateurs connecté avec succès à l\'administration.',
'menu_description' => 'Affiche la liste des utilisateurs connectés avec succès à l\'administration.',
'created_at' => 'Date & heure',
'login' => 'Identifiant',
'ip_address' => 'Addresse IP',
@ -202,4 +298,7 @@ return [
'last_name' => 'Nom',
'email' => 'Email',
],
'filter' => [
'all' => 'tous'
],
];

View File

@ -168,8 +168,8 @@ return [
'resetting' => 'Redefinindo',
'resetting_name' => 'Redefinindo :name',
'undefined_tab' => 'Outros',
'field_off' => 'Desligado',
'field_on' => 'Ligado',
'field_off' => 'Desl',
'field_on' => 'Lig',
'add' => 'Adicionar',
'apply' => 'Aplicar',
'cancel' => 'Cancelar',

View File

@ -0,0 +1,304 @@
<?php
return [
'auth' => [
'title' => '管理区域'
],
'field' => [
'invalid_type' => '不合法的字段类型 :type.',
'options_method_not_exists' => "model class :model 必须定义方法 :method() 返回了':field' form 字段的options."
],
'widget' => [
'not_registered' => "widget class name ':name' 还没注册",
'not_bound' => "widget class name ':name' 没绑到 controller"
],
'page' => [
'untitled' => 'Untitled',
'access_denied' => [
'label' => '访问拒绝',
'help' => "你没有访问这个页面需要的权限.",
'cms_link' => '返回后台'
]
],
'partial' => [
'not_found_name' => "partial ':name' 没找到."
],
'account' => [
'sign_out' => '登出',
'login' => '登录',
'reset' => '重置',
'restore' => '还原',
'login_placeholder' => '登录',
'password_placeholder' => '密码',
'forgot_password' => '忘记你的密码?',
'enter_email' => '输入你的email',
'enter_login' => '输入账号',
'email_placeholder' => 'email',
'enter_new_password' => '输入新密码',
'password_reset' => '密码重置',
'restore_success' => '密码重置的邮件已发往你的邮箱.',
'restore_error' => "找不到用户 ':login'",
'reset_success' => '你的密码已经重置成功. 你现在可以登录了.',
'reset_error' => '密码重置失败. 请重试!',
'reset_fail' => '不能重置你的密码!',
'apply' => '应用',
'cancel' => '取消',
'delete' => '删除',
'ok' => 'OK'
],
'dashboard' => [
'menu_label' => 'Dashboard',
'widget_label' => 'Widget',
'widget_width' => 'Width',
'full_width' => '全部宽度',
'add_widget' => '增加widget',
'widget_inspector_title' => 'Widget配置',
'widget_inspector_description' => '配置报表widget',
'widget_columns_label' => 'Width :columns',
'widget_columns_description' => 'widget宽度, 1 到 10.',
'widget_columns_error' => '请输入 widget 宽度, 1 到 10.',
'columns' => '{1} column|[2,Inf] columns',
'widget_new_row_label' => '强制新列',
'widget_new_row_description' => '把 widget 放到新列.',
'widget_title_label' => 'Widget 标题',
'widget_title_error' => '需要 Widget 标题.',
'status' => [
'widget_title_default' => '系统状态',
'online' => '在线',
'maintenance' => '维护中',
'update_available' => '{0} 更新可用!|{1} 更新可用!|[2,Inf] 更新可用!'
]
],
'user' => [
'name' => '管理员',
'menu_label' => '管理员',
'menu_description' => '管理后台管理员用户, 组和权限.',
'list_title' => '管理',
'new' => '新管理员',
'login' => '登录',
'first_name' => '名',
'last_name' => '姓',
'full_name' => '全民',
'email' => '邮件',
'groups' => '团队',
'groups_comment' => '指明这个人属于哪个组.',
'avatar' => '头像',
'password' => '密码',
'password_confirmation' => '确认密码',
'permissions' => '权限',
'superuser' => '超级用户',
'superuser_comment' => '选中并允许这个人访问全部区域.',
'send_invite' => '发送邀请邮件',
'send_invite_comment' => '使用 checkbox 给用户发送邀请邮件',
'delete_confirm' => '你真的想要删除这个管理员?',
'return' => '返回管理员列表',
'allow' => '允许',
'inherit' => '继承',
'deny' => '拒绝',
'group' => [
'name' => '组',
'name_field' => '名字',
'description_field' => '描述',
'is_new_user_default_field' => '默认增加新管理员到这个组',
'code_field' => '代码',
'code_comment' => '如果你想访问 API, 请输入唯一码.',
'menu_label' => '群组',
'list_title' => '管理群组',
'new' => '新管理组',
'delete_confirm' => '你真的想要删除这个管理组?',
'return' => '返回组列表',
],
'preferences' => [
'not_authenticated' => '没有认证用户加载或保存设置.'
]
],
'list' => [
'default_title' => '列表',
'search_prompt' => '搜索...',
'no_records' => '当前视图中没有记录.',
'missing_model' => ':class 中的列表没有定义好的model.',
'missing_column' => '没有 :columns 的栏定义.',
'missing_columns' => ':class 中使用的列表没有定义好的栏.',
'missing_definition' => "列表不包含 ':field' 栏.",
'behavior_not_ready' => '列表没有初始化, 确认你的controller中调用了makeLists().',
'invalid_column_datetime' => "栏值 ':column' 不是 DateTime 对象, 缺少了 \$dates 在 Model 中的引用吗?",
'pagination' => '显示记录: :from-:to :total',
'prev_page' => '之前页',
'next_page' => '下一页',
'loading' => '加载中...',
'setup_title' => '建立列表',
'setup_help' => '使用 checkboxes 选择你想在列表中看到的栏. 你可以通过拖拽调整栏的位置.',
'records_per_page' => '每页的记录',
'records_per_page_help' => '选择每页想显示的记录数量. 请注意一页中太多记录可能会降低性能.',
'delete_selected' => '删除选择的',
'delete_selected_empty' => '没有需要删除的记录.',
'delete_selected_confirm' => '删除选中的记录?',
'delete_selected_success' => '成功删除选择的记录.',
],
'fileupload' => [
'attachment' => '附件',
'help' => '给附件添加标题和描述.',
'title_label' => '标题',
'description_label' => '描述'
],
'form' => [
'create_title' => '新 :name',
'update_title' => '编辑 :name',
'preview_title' => '预览 :name',
'create_success' => ':name 创建成功',
'update_success' => ':name 更新成功',
'delete_success' => ':name 删除成功',
'missing_id' => '表单记录ID没有指定.',
'missing_model' => ':class 中使用的表单没有定义的model.',
'missing_definition' => "表单不包含字段 ':field'.",
'not_found' => '表单 ID :id 找不到.',
'action_confirm' => '你确定?',
'create' => '创建',
'create_and_close' => '创建和关闭',
'creating' => '创建中...',
'creating_name' => '创建 :name...',
'save' => '保存',
'save_and_close' => '保存和关闭',
'saving' => '保存...',
'saving_name' => '保存 :name...',
'delete' => '删除',
'deleting' => '删除中...',
'deleting_name' => '删除 :name...',
'reset_default' => '重置到默认',
'resetting' => '重置',
'resetting_name' => '重置 :name',
'undefined_tab' => '杂项',
'field_off' => '关',
'field_on' => '开',
'add' => '增加',
'apply' => '应用',
'cancel' => '取消',
'close' => '关闭',
'confirm' => '确认',
'reload' => '重载',
'ok' => 'OK',
'or' => '或',
'confirm_tab_close' => '你真的想要关闭这个标签吗? 未保存的改变会丢失.',
'behavior_not_ready' => '表单还没初始化, 确保你调用了controller中的 initForm().',
'preview_no_files_message' => '文件没有上传',
'select' => '选择',
'select_all' => 'all',
'select_none' => 'none',
'select_placeholder' => '请选择',
'insert_row' => '插入行',
'delete_row' => '删除行',
'concurrency_file_changed_title' => '文件改动',
'concurrency_file_changed_description' => "你正在编辑的文件正在被其他用户修改. 你可以重载或覆盖磁盘上的文件."
],
'relation' => [
'missing_config' => "关系没有':config'的配置文件.",
'missing_definition' => "关系不包含 ':field' 的定义.",
'missing_model' => "用于 :class 的关系没有定义好的model.",
'invalid_action_single' => "这个操作不能在单一关系上执行.",
'invalid_action_multi' => "这个操作不能在多重关系上执行.",
'help' => "点击增加",
'related_data' => "相关的 :name",
'add' => "增加",
'add_selected' => "增加选中的",
'add_a_new' => "增加一个新的 :name",
'link_selected' => "关联选中",
'link_a_new' => "关联一个新的 :name",
'cancel' => "取消",
'close' => "关闭",
'add_name' => "增加 :name",
'create' => "创建",
'create_name' => "创建 :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' => 'Model',
'not_found' => "Model ':class' ID :id 找不到",
'missing_id' => '没有指定的ID查找model记录.',
'missing_relation' => "Model ':class' 不包含 ':relation'.",
'missing_method' => "Model ':class' 不包含 ':method'.",
'invalid_class' => "Model :model 在 :class 中是不合法的, 必须继承 \Model class.",
'mass_assignment_failed' => "针对Model属性':attribute'的大量赋值失败."
],
'warnings' => [
'tips' => '系统配置技巧',
'tips_description' => '你需要注意那些issue, 以使系统配置正确.',
'permissions' => '目录 :name 或子目录对PHP不可写. 请对这个目录上的webserver设置正确的权限.',
'extension' => 'PHP扩展 :name 没安装. 请安装这个库并且激活扩展.'
],
'editor' => [
'menu_label' => '代码编辑器选项',
'menu_description' => '自定义代码编辑器选项, 比如字体大小和颜色主题.',
'font_size' => '字体大小',
'tab_size' => '标签大小',
'use_hard_tabs' => '使用tabs缩进',
'code_folding' => '代码折叠',
'word_wrap' => '自动换行',
'highlight_active_line' => '高亮活动的行',
'show_invisibles' => '显示隐藏字符',
'show_gutter' => '显示gutter',
'theme' => '色彩主题'
],
'tooltips' => [
'preview_website' => '预览网站'
],
'mysettings' => [
'menu_label' => '我的设置',
'menu_description' => '设置涉及到你的管理帐号'
],
'myaccount' => [
'menu_label' => '我的账户',
'menu_description' => '更新你的账户细节, 比如名字, 邮件地址和密码.',
'menu_keywords' => '安全登录'
],
'branding' => [
'menu_label' => '自定义后台',
'menu_description' => '自定义管理区域, 比如名字, 颜色和logo.',
'brand' => '品牌',
'logo' => 'Logo',
'logo_description' => '上传自定义logo到后台.',
'app_name' => 'App名字',
'app_name_description' => '这个名字显示在后台的标题区域.',
'app_tagline' => 'App 标语',
'app_tagline_description' => '名字显示在后台的登录界面.',
'colors' => '颜色',
'primary_light' => '主要 (Light)',
'primary_dark' => '主要 (Dark)',
'secondary_light' => '次要 (Light)',
'secondary_dark' => '次要 (Dark)',
'styles' => '样式',
'custom_stylesheet' => '自定义样式'
],
'backend_preferences' => [
'menu_label' => '后台设置',
'menu_description' => '管理你的后台设置, 比如希望使用的语言.',
'locale' => '语言',
'locale_comment' => '选择你希望使用的本地语言.'
],
'access_log' => [
'hint' => '这个log显示了管理员成功登录的信息. 记录保持:days天.',
'menu_label' => '访问日志',
'menu_description' => '查看 successful back-end user sign ins.',
'created_at' => '日期 & 时间',
'login' => '登录',
'ip_address' => 'IP地址',
'first_name' => '名',
'last_name' => '姓',
'email' => 'Email'
],
'filter' => [
'all' => 'all'
]
];

View File

@ -0,0 +1,69 @@
<?php namespace Backend\Traits;
use Backend\Classes\FormField;
/**
* Form Model Saver Trait
*
* Special logic for applying form data (usually from postback) and
* applying it to a model and its relationships. This is a customized,
* safer and simplified version of $model->push().
*
* Usage:
*
* $modelsToSave = $this->prepareModelsToSave($model, [...]);
*
* foreach ($modelsToSave as $modelToSave) {
* $modelToSave->save();
* }
*
* @package october\backend
* @author Alexey Bobkov, Samuel Georges
*/
trait FormModelSaver
{
/**
* @var array List of prepared models that require saving.
*/
protected $modelsToSave = [];
protected function prepareModelsToSave($model, $saveData)
{
$this->modelsToSave = [];
$this->setModelAttributes($model, $saveData);
return $this->modelsToSave;
}
/**
* Sets a data collection to a model attributes, relations will also be set.
* @param array $saveData Data to save.
* @param Model $model Model to save to
* @return array The collection of models to save.
*/
protected function setModelAttributes($model, $saveData)
{
$this->modelsToSave[] = $model;
if (!is_array($saveData)) {
return;
}
$singularTypes = ['belongsTo', 'hasOne', 'morphOne'];
foreach ($saveData as $attribute => $value) {
$isNested = $attribute == 'pivot' || (
$model->hasRelation($attribute) &&
in_array($model->getRelationType($attribute), $singularTypes)
);
if ($isNested && is_array($value)) {
$this->setModelAttributes($model->{$attribute}, $value);
}
elseif ($value !== FormField::NO_SAVE_DATA) {
$model->{$attribute} = $value;
}
}
}
}

View File

@ -432,7 +432,7 @@ class Lists extends WidgetBase
* Apply a supplied search term for primary columns
*/
if (count($primarySearchable) > 0) {
$query->orWhere(function ($innerQuery) use ($primarySearchable) {
$query->where(function ($innerQuery) use ($primarySearchable) {
$innerQuery->searchWhere($this->searchTerm, $primarySearchable);
});
}
@ -660,6 +660,12 @@ class Lists extends WidgetBase
$label = studly_case($name);
}
if (strpos($name, '[') !== false && strpos($name, ']') !== false) {
$config['valueFrom'] = $name;
$config['sortable'] = false;
$config['searchable'] = false;
}
$columnType = isset($config['type']) ? $config['type'] : null;
$column = new ListColumn($name, $label);

View File

@ -19,7 +19,7 @@
<?php $index = 0; foreach ($columns as $key => $column): ?>
<?php $index++; ?>
<td data-title="<?= e(trans($column->label)) ?>" class="list-cell-index-<?= $index ?> list-cell-name-<?= $column->columnName ?> list-cell-type-<?= $column->type ?> <?= $column->cssClass ?>">
<td data-title="<?= e(trans($column->label)) ?>" class="list-cell-index-<?= $index ?> list-cell-name-<?= $column->getName() ?> list-cell-type-<?= $column->type ?> <?= $column->cssClass ?>">
<?php if ($index == 1 && ($url = $this->getRecordUrl($record))): ?>
<a <?= $this->getRecordOnClick($record) ?> href="<?= $url ?>">
<?= $this->getColumnValue($record, $column) ?>

View File

@ -18,7 +18,7 @@
<?php if ($showSorting && $column->sortable): ?>
<th
<?php if ($column->width): ?>style="width: <?= $column->width ?>"<?php endif ?>
class="<?= $this->sortColumn==$column->columnName?'sort-'.$this->sortDirection.' active':'sort-desc' ?> list-cell-name-<?= $column->columnName ?>"
class="<?= $this->sortColumn==$column->columnName?'sort-'.$this->sortDirection.' active':'sort-desc' ?> list-cell-name-<?= $column->getName() ?> list-cell-type-<?= $column->type ?>"
>
<a
href="javascript:;"
@ -31,7 +31,7 @@
<?php else: ?>
<th
<?php if ($column->width): ?>style="width: <?= $column->width ?>"<?php endif ?>
class="list-cell-name-<?= $column->columnName ?>"
class="list-cell-name-<?= $column->getName() ?> list-cell-type-<?= $column->type ?>"
>
<span><?= $this->getHeaderValue($column) ?></span>
</th>

View File

@ -353,9 +353,9 @@
dataContainer.setAttribute('type', 'hidden')
dataContainer.setAttribute('data-container', 'data-container')
dataContainer.value = records[i][columnName] !== undefined ?
records[i][columnName] :
""
dataContainer.value = records[i][columnName] !== undefined
? records[i][columnName]
: ""
cellContentContainer.setAttribute('class', 'content-container')
@ -391,9 +391,10 @@
Table.prototype.fetchRecords = function(onSuccess) {
this.dataSource.getRecords(
this.navigation.getPageFirstRowOffset(),
this.navigation.getPageFirstRowOffset(),
this.options.recordsPerPage,
onSuccess)
onSuccess
)
}
Table.prototype.updateScrollbar = function() {

View File

@ -144,6 +144,7 @@ class ServiceProvider extends ModuleServiceProvider
'category' => SettingsManager::CATEGORY_CMS,
'icon' => 'icon-picture-o',
'url' => Backend::URL('cms/themes'),
'permissions' => ['system.manage_themes'],
'order' => 200
],
'maintenance_settings' => [
@ -152,6 +153,7 @@ class ServiceProvider extends ModuleServiceProvider
'category' => SettingsManager::CATEGORY_CMS,
'icon' => 'icon-plug',
'class' => 'Cms\Models\MaintenanceSettings',
'permissions' => ['system.manage_themes'],
'order' => 400
],
]);

View File

@ -114,10 +114,7 @@ class Page extends CmsCompoundObject
* assuming that the method is called not during the front-end
* request processing.
*/
$controller = Controller::getController();
if (!$controller) {
$controller = new Controller;
}
$controller = Controller::getController() ?: new Controller;
return $controller->pageUrl($page, $params, true);
}
@ -188,7 +185,8 @@ class Page extends CmsCompoundObject
}
$page = self::loadCached($theme, $item->reference);
$pageUrl = self::url($item->reference);
$controller = Controller::getController() ?: new Controller;
$pageUrl = $controller->pageUrl($item->reference, [], false);
$result = [];
$result['url'] = $pageUrl;

View File

@ -0,0 +1,232 @@
<?php
return [
'cms_object' => [
'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' 错误. 请检查写权限.",
'delete_success' => '模板成功删除: :count.',
'file_name_required' => '需要文件名字段.'
],
'theme' => [
'not_found_name' => "主题 ':name' 没找到.",
'active' => [
'not_set' => '活动主题没设置.',
'not_found' => '活动主题找不到.'
],
'edit' => [
'not_set' => '编辑主题没设置.',
'not_found' => '编辑主题没找到.',
'not_match' => "你尝试访问的对象不属于正在编辑的主题. 请重载页面."
],
'settings_menu' => '前端主题',
'settings_menu_description' => '预览安装的主题, 选择一个活动主题.',
'name_label' => '名字',
'name_create_placeholder' => '新主题名字',
'author_label' => '作者',
'author_placeholder' => '人或公司名',
'description_label' => '描述',
'description_placeholder' => '主题描述',
'homepage_label' => '主页',
'homepage_placeholder' => '网站地址',
'code_label' => '代码',
'code_placeholder' => '发行主题的唯一码',
'dir_name_label' => '目录名',
'dir_name_create_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 主题商店中查找更多主题',
'return' => '返回主题列表',
],
'maintenance' => [
'settings_menu' => '维护模式',
'settings_menu_description' => '配置维护模式页面和开关设置.',
'is_enabled' => '启用维护模式',
'is_enabled_comment' => '当启用时, 网站访问者会看到下述页面.'
],
'page' => [
'not_found_name' => "页面 ':name' 找不到",
'not_found' => [
'label' => '页面找不到',
'help' => '请求的页面找不到.'
],
'custom_error' => [
'label' => '页面错误',
'help' => "很抱歉, 有一些地方发生了错误导致页面不能显示."
],
'menu_label' => '页面',
'unsaved_label' => '未保存页面',
'no_list_records' => '找不到页面',
'new' => '新页面',
'invalid_url' => '不合法的URL格式. URL可以正斜杠开头, 包含数字, 拉丁字母和下面的字符: ._-[]:?|/+*^$',
'delete_confirm_multiple' => '真的想要删除选择的页面吗?',
'delete_confirm_single' => '真的想要删除这个页面吗?',
'no_layout' => '-- 没有布局 --'
],
'layout' => [
'not_found_name' => "布局 ':name' 找不到",
'menu_label' => '布局',
'unsaved_label' => '未保存布局',
'no_list_records' => '找不到布局',
'new' => '新布局',
'delete_confirm_multiple' => '你真的想要删除选中的布局?',
'delete_confirm_single' => '你真的想要删除这个布局?'
],
'partial' => [
'not_found_name' => "partial ':name' 找不到.",
'invalid_name' => '不合法的 partial 名: :name.',
'menu_label' => 'Partials',
'unsaved_label' => '未保存的 partial(s)',
'no_list_records' => '找不到 partials',
'delete_confirm_multiple' => '你真的想要删除选择的 partials?',
'delete_confirm_single' => '你真的想要删除这个 partial?',
'new' => '新 partial'
],
'content' => [
'not_found_name' => "内容文件 ':name' 找不到.",
'menu_label' => '内容',
'unsaved_label' => '未保存内容',
'no_list_records' => '找不到内容文件',
'delete_confirm_multiple' => '你真的想要删除选中的文件或目录吗?',
'delete_confirm_single' => '你真的想要删除这个内容文件?',
'new' => '新内容文件'
],
'ajax_handler' => [
'invalid_name' => '不合法的 AJAX 处理器: :name.',
'not_found' => " AJAX 处理器 ':name' 找不到."
],
'cms' => [
'menu_label' => 'CMS'
],
'sidebar' => [
'add' => '增加',
'search' => '搜索...'
],
'editor' => [
'settings' => '设置',
'title' => '标题',
'new_title' => '新文件标题',
'url' => 'URL',
'filename' => '文件名',
'layout' => '布局',
'description' => '描述',
'preview' => '预览',
'meta' => 'Meta',
'meta_title' => 'Meta 标题',
'meta_description' => 'Meta 描述',
'markup' => 'Markup',
'code' => '代码',
'content' => '内容',
'hidden' => '隐藏',
'hidden_comment' => '隐藏页面只能被登录的后台用户访问.',
'enter_fullscreen' => '进入全屏模式',
'exit_fullscreen' => '退出全屏模式'
],
'asset' => [
'menu_label' => 'Assets',
'unsaved_label' => '未保存的Assets',
'drop_down_add_title' => '增加...',
'drop_down_operation_title' => '动作...',
'upload_files' => '上传文件',
'create_file' => '新建文件',
'create_directory' => '新建目录',
'directory_popup_title' => '新目录',
'directory_name' => '目录名',
'rename' => '重命名',
'delete' => '删除',
'move' => '移动',
'select' => '选择',
'new' => '新文件',
'rename_popup_title' => '重命名',
'rename_new_name' => '新名称',
'invalid_path' => '路径名称只能包含数字, 拉丁字母和以下字符: _-/',
'error_deleting_file' => '删除文件 :name 错误.',
'error_deleting_dir_not_empty' => '删除目录 :name 错误. 目录不为空.',
'error_deleting_dir' => '删除文件 :name 错误.',
'invalid_name' => '名称只能包含数字, 拉丁字母, 空格和以下字符: _-',
'original_not_found' => '原始文件或目录找不到',
'already_exists' => '文件或目录已存在',
'error_renaming' => '重命名文件或目录错误',
'name_cant_be_empty' => '名字不能为空',
'too_large' => '上传的文件太大. 最大文件大小是 :max_size',
'type_not_allowed' => '只有下面的文件类型是允许的: :allowed_types',
'file_not_valid' => '文件不合法',
'error_uploading_file' => "上传文件错误 ':name': :error",
'move_please_select' => '请选择',
'move_destination' => '目标目录',
'move_popup_title' => '移动 assets',
'move_button' => '移动',
'selected_files_not_found' => '选择的文件找不到',
'select_destination_dir' => '请选择目标目录',
'destination_not_found' => '目标目录找不到',
'error_moving_file' => '移动文件 :file 错误',
'error_moving_directory' => '移动目录 :dir 错误',
'error_deleting_directory' => '删除原始目录 :dir 错误',
'path' => '路径'
],
'component' => [
'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'=> '模板保存成功.'
],
'permissions' => [
'name' => 'Cms',
'manage_content' => '管理内容',
'manage_assets' => '管理assets',
'manage_pages' => '管理页面',
'manage_layouts' => '管理布局',
'manage_partials' => '管理partials',
'manage_themes' => '管理主题'
]
];

View File

@ -150,7 +150,7 @@ class ServiceProvider extends ModuleServiceProvider
'label' => 'system::lang.settings.menu_label',
'icon' => 'icon-cog',
'url' => Backend::url('system/settings'),
'permissions' => ['backend.manage_users', 'system.*'],
'permissions' => [],
'order' => 1000
]
]);
@ -175,6 +175,10 @@ class ServiceProvider extends ModuleServiceProvider
'label' => 'system::lang.permissions.manage_software_updates',
'tab' => 'system::lang.permissions.name'
],
'system.access_logs' => [
'label' => 'system::lang.permissions.access_logs',
'tab' => 'system::lang.permissions.name'
],
'system.manage_mail_settings' => [
'label' => 'system::lang.permissions.manage_mail_settings',
'tab' => 'system::lang.permissions.name'
@ -258,7 +262,7 @@ class ServiceProvider extends ModuleServiceProvider
'category' => SettingsManager::CATEGORY_LOGS,
'icon' => 'icon-exclamation-triangle',
'url' => Backend::url('system/eventlogs'),
'permissions' => ['system.access_event_logs'],
'permissions' => ['system.access_logs'],
'order' => 800
],
'request_logs' => [
@ -267,7 +271,7 @@ class ServiceProvider extends ModuleServiceProvider
'category' => SettingsManager::CATEGORY_LOGS,
'icon' => 'icon-file-o',
'url' => Backend::url('system/requestlogs'),
'permissions' => ['system.access_request_logs'],
'permissions' => ['system.access_logs'],
'order' => 800
],
'mail_settings' => [

View File

@ -26,7 +26,7 @@ class EventLogs extends Controller
'Backend.Behaviors.ListController'
];
public $requiredPermissions = ['system.access_event_logs'];
public $requiredPermissions = ['system.access_logs'];
public $formConfig = 'config_form.yaml';

View File

@ -26,7 +26,7 @@ class RequestLogs extends Controller
'Backend.Behaviors.ListController'
];
public $requiredPermissions = ['system.access_request_logs'];
public $requiredPermissions = ['system.access_logs'];
public $formConfig = 'config_form.yaml';

View File

@ -254,9 +254,11 @@ return [
'name' => 'System',
'manage_system_settings' => 'Manage system settings',
'manage_software_updates' => 'Manage software updates',
'access_logs' => 'View system logs',
'manage_mail_templates' => 'Manage mail templates',
'manage_mail_settings' => 'Manage mail settings',
'manage_other_administrators' => 'Manage other administrators',
'view_the_dashboard' => 'View the dashboard'
'view_the_dashboard' => 'View the dashboard',
'manage_branding' => 'Customize the back-end'
]
];

View File

@ -0,0 +1,263 @@
<?php
return [
'app' => [
'name' => 'October CMS',
'tagline' => '回到basics'
],
'locale' => [
'en' => 'English',
'de' => 'German',
'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',
'zh-cn' => 'Chinese (China)'
],
'directory' => [
'create_fail' => '不能创建目录: :name'
],
'file' => [
'create_fail' => '不能创建文件: :name'
],
'combiner' => [
'not_found' => "混合文件 ':name' 没找到."
],
'system' => [
'name' => '系统',
'menu_label' => '系统',
'categories' => [
'cms' => 'CMS',
'misc' => 'Misc',
'logs' => 'Logs',
'mail' => 'Mail',
'shop' => 'Shop',
'team' => 'Team',
'users' => 'Users',
'system' => 'System',
'social' => 'Social',
'events' => 'Events',
'customers' => 'Customers',
'my_settings' => 'My Settings'
]
],
'plugin' => [
'unnamed' => '未命名的插件',
'name' => [
'label' => '插件名称',
'help' => '输入插件的唯一代码. 比如 RainLab.Blog'
]
],
'plugins' => [
'manage' => '管理插件',
'enable_or_disable' => '启用或禁用',
'enable_or_disable_title' => '启用或禁用插件',
'remove' => '移除',
'refresh' => '刷新',
'disabled_label' => '禁用',
'disabled_help' => '被禁用的插件被应用程序忽略了.',
'selected_amount' => '选中的插件: :数目',
'remove_confirm' => '你确定吗?',
'remove_success' => '成功从系统移除这些插件.',
'refresh_confirm' => '你确定吗?',
'refresh_success' => '成功刷新了系统中的插件.',
'disable_confirm' => '你确定吗?',
'disable_success' => '成功禁用了这些插件.',
'enable_success' => '成功启用了这些插件',
'unknown_plugin' => '插件从文件系统中移除了.'
],
'project' => [
'name' => '项目',
'owner_label' => '拥有者',
'attach' => '增加项目',
'detach' => '删除项目',
'none' => '没有',
'id' => [
'label' => '项目ID',
'help' => '如何找到你的项目ID',
'missing' => '请确认你想使用的项目ID.'
],
'detach_confirm' => '你确定要删除这个项目吗?',
'unbind_success' => '项目删除成功.'
],
'settings' => [
'menu_label' => '设置',
'not_found' => '不能找到特定的设置.',
'missing_model' => '设置页缺少Model定义.',
'update_success' => ':name 的设置更新成功了.',
'return' => '返回系统设置',
'search' => '搜索'
],
'mail' => [
'log_file' => '日志文件',
'menu_label' => '邮件配置',
'menu_description' => '管理邮件配置.',
'general' => '常规',
'method' => '邮件方法',
'sender_name' => '发送者名称',
'sender_email' => '发送者邮件',
'php_mail' => 'PHP mail',
'sendmail' => 'Sendmail',
'smtp' => 'SMTP',
'smtp_address' => 'SMTP 地址',
'smtp_authorization' => '需要 SMTP 认证',
'smtp_authorization_comment' => '使用 checkbox 如果你的SMTP服务器需要认证.',
'smtp_username' => '用户名',
'smtp_password' => '密码',
'smtp_port' => 'SMTP 端口',
'smtp_ssl' => '需要SSL连接',
'sendmail' => 'Sendmail',
'sendmail_path' => 'Sendmail 路径',
'sendmail_path_comment' => '请确认 Sendmail 路径.',
'mailgun' => 'Mailgun',
'mailgun_domain' => 'Mailgun 域名',
'mailgun_domain_comment' => '请确认 Mailgun 域名.',
'mailgun_secret' => 'Mailgun Secret',
'mailgun_secret_comment' => '输入你的 Mailgun API key.',
'mandrill' => 'Mandrill',
'mandrill_secret' => 'Mandrill Secret',
'mandrill_secret_comment' => '输入你的 Mandrill API key.'
],
'mail_templates' => [
'menu_label' => '邮件模板',
'menu_description' => '编辑发送到用户和管理员的邮件模板, 管理邮件布局.',
'new_template' => '新模板',
'new_layout' => '新布局',
'template' => '模板',
'templates' => '模板',
'menu_layouts_label' => '邮件布局',
'layout' => '布局',
'layouts' => '布局',
'name' => '名称',
'name_comment' => '指向这个模板的唯一名称',
'code' => '代码',
'code_comment' => '指向这个模板的唯一代码',
'subject' => '标题',
'subject_comment' => '邮箱消息标题',
'description' => '描述',
'content_html' => 'HTML',
'content_css' => 'CSS',
'content_text' => '纯文本',
'test_send' => '发送测试消息',
'test_success' => '测试消息已经成功发送.',
'return' => '返回模板列表'
],
'install' => [
'project_label' => '加入项目',
'plugin_label' => '安装插件',
'missing_plugin_name' => '请指明要安装的插件.',
'install_completing' => '完成安装过程',
'install_success' => '插件安装成功.'
],
'updates' => [
'title' => '管理更新',
'name' => '软件更新',
'menu_label' => '更新',
'menu_description' => '更新系统, 管理并安装插件和主题.',
'check_label' => '检查更新',
'retry_label' => '重试',
'plugin_name' => '名字',
'plugin_description' => '描述',
'plugin_version' => '版本',
'plugin_author' => '作者',
'core_build' => '当前build',
'core_build_old' => '当前build :build',
'core_build_new' => 'Build :build',
'core_build_new_help' => '新 build 可用.',
'core_downloading' => '下载应用程序',
'core_extracting' => '解压应用程序',
'plugins' => '插件',
'disabled' => '禁用',
'plugin_downloading' => '下载插件: :name',
'plugin_extracting' => '解压插件: :name',
'plugin_version_none' => '新插件',
'plugin_version_old' => '当前 v:version',
'plugin_version_new' => 'v:version',
'theme_label' => '主题',
'theme_new_install' => '新主题安装.',
'theme_downloading' => '下载主题: :name',
'theme_extracting' => '解压主题: :name',
'update_label' => '更新软件',
'update_completing' => '完成更新过程',
'update_loading' => '加载可用更新...',
'update_success' => '更新完成.',
'update_failed_label' => '更新失败',
'force_label' => '强制更新',
'found' => [
'label' => '发现新更新!',
'help' => '点击更新.'
],
'none' => [
'label' => '没有更新',
'help' => '没发现新更新.'
]
],
'server' => [
'connect_error' => '连接服务器失败.',
'response_not_found' => '找不到更新服务器.',
'response_invalid' => '服务器的异常返回.',
'response_empty' => '服务器的空返回.',
'file_error' => '服务器发送失败.',
'file_corrupt' => '服务器文件被占用.'
],
'behavior' => [
'missing_property' => 'Class :class 必须定义属性 $:property 被 :behavior behavior 使用.'
],
'config' => [
'not_found' => '不能查找配置文件 :file 为 :location 定义.',
'required' => "配置 :location 必须有 ':property'."
],
'zip' => [
'extract_failed' => "不能解压缩文件 ':file'."
],
'event_log' => [
'hint' => '日志显示了程序中的潜在错误, 比如异常和调试信息.',
'menu_label' => '事件日志',
'menu_description' => '查看系统日志信息, 包括时间和详细信息.',
'empty_link' => '空事件日志',
'empty_loading' => '空事件日志...',
'empty_success' => '成功清空时间日志.',
'return_link' => '返回时间日志',
'id' => 'ID',
'id_label' => '事件 ID',
'created_at' => '时间和日期',
'message' => '消息',
'level' => '级别'
],
'request_log' => [
'hint' => '这个日志显示了需要注意的浏览器请求. 比如如果一个访问者打开一个没有的CMS页面, 一条返回状态404的记录被创建.',
'menu_label' => '请求日志',
'menu_description' => '查看坏的或者重定向的请求, 比如页面找不到(404).',
'empty_link' => '空请求日志',
'empty_loading' => '空请求日志...',
'empty_success' => '成功清空请求日志.',
'return_link' => '返回请求日志',
'id' => 'ID',
'id_label' => '登录ID',
'count' => '柜台',
'referer' => 'Referers',
'url' => 'URL',
'status_code' => '状态'
],
'permissions' => [
'name' => '系统',
'manage_system_settings' => '管理系统设置',
'manage_software_updates' => '管理软件更新',
'manage_mail_templates' => '管理邮件模板',
'manage_mail_settings' => '管理邮件设置',
'manage_other_administrators' => '管理其他管理员',
'view_the_dashboard' => '查看dashboard'
]
];

View File

@ -0,0 +1,98 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| such as the size rules. Feel free to tweak each of these messages.
|
*/
"accepted" => ":attribute 必须被接受.",
"active_url" => ":attribute 不是一个有效的URL.",
"after" => ":attribute 必须是 :date 之后的一个日期.",
"alpha" => ":attribute 只能包含字母.",
"alpha_dash" => ":attribute 只能包含字母, 数字和-.",
"alpha_num" => ":attribute 只能包含字母和数字.",
"array" => ":attribute 只能是一个数组.",
"before" => ":attribute 必须是 :date 之前的一个日期.",
"between" => array(
"numeric" => ":attribute 在 :min - :max 之间.",
"file" => ":attribute 在 :min - :max kilobytes之间.",
"string" => ":attribute 在 :min - :max 字符之间.",
"array" => ":attribute 在 :min - :max 个之间.",
),
"confirmed" => ":attribute 的确认不满足.",
"date" => ":attribute 不是一个合法的日期.",
"date_format" => ":attribute 不符合 :format 格式.",
"different" => ":attribute 和 :other 必须不同.",
"digits" => ":attribute 必须是 :digits.",
"digits_between" => ":attribute 必须在 :min :max 之间.",
"email" => ":attribute 格式无效.",
"exists" => "选中的 :attribute 无效.",
"image" => ":attribute 必须是图片.",
"in" => "选中的 :attribute 无效.",
"integer" => ":attribute 必须是数字.",
"ip" => ":attribute 必须是一个有效的IP地址.",
"max" => array(
"numeric" => ":attribute 不能大于 :max.",
"file" => ":attribute 不能大于 :max kilobytes.",
"string" => ":attribute 不能超过 :max 字符.",
"array" => ":attribute 不能超过 :max 个.",
),
"mimes" => ":attribute 必须是一个: :values 类型的文件.",
"min" => array(
"numeric" => ":attribute 必须至少 :min.",
"file" => ":attribute 必须至少 :min kilobytes.",
"string" => ":attribute 必须至少 :min 字符.",
"array" => ":attribute 必须至少 :min 个.",
),
"not_in" => "选中的 :attribute 无效.",
"numeric" => ":attribute 必须是一个数字.",
"regex" => ":attribute 格式无效.",
"required" => "需要 :attribute 字段.",
"required_if" => "需要 :attribute 字段, 当 :other 是 :value.",
"required_with" => "需要 :attribute 字段, 当 :values 是当前值.",
"required_without" => "需要 :attribute 字段, 当 :values 不是当前值.",
"same" => ":attribute 和 :other 必须匹配.",
"size" => array(
"numeric" => ":attribute 必须是 :size.",
"file" => ":attribute 必须是 :size kilobytes.",
"string" => ":attribute 必须是 :size 字符.",
"array" => ":attribute 必须是 :size 个.",
),
"unique" => ":attribute 已占用.",
"url" => ":attribute 格式无效.",
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => array(),
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap attribute place-holders
| with something more reader friendly such as E-Mail Address instead
| of "email". This simply helps us make messages a little cleaner.
|
*/
'attributes' => array(),
);