diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index 1e4a99a..59a1eff 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -151,54 +151,18 @@ protected function registerBackendPermissions() protected function registerBackendWidgets() { WidgetManager::instance()->registerFormWidgets(function ($manager) { - $manager->registerFormWidget('Backend\FormWidgets\CodeEditor', [ - 'label' => 'Code editor', - 'code' => 'codeeditor' - ]); - $manager->registerFormWidget('Backend\FormWidgets\RichEditor', [ - 'label' => 'Rich editor', - 'code' => 'richeditor' - ]); - $manager->registerFormWidget('Backend\FormWidgets\MarkdownEditor', [ - 'label' => 'Markdown editor', - 'code' => 'markdown' - ]); - $manager->registerFormWidget('Backend\FormWidgets\FileUpload', [ - 'label' => 'File uploader', - 'code' => 'fileupload' - ]); - $manager->registerFormWidget('Backend\FormWidgets\Relation', [ - 'label' => 'Relationship', - 'code' => 'relation' - ]); - $manager->registerFormWidget('Backend\FormWidgets\DatePicker', [ - 'label' => 'Date picker', - 'code' => 'datepicker' - ]); - $manager->registerFormWidget('Backend\FormWidgets\TimePicker', [ - 'label' => 'Time picker', - 'code' => 'timepicker' - ]); - $manager->registerFormWidget('Backend\FormWidgets\ColorPicker', [ - 'label' => 'Color picker', - 'code' => 'colorpicker' - ]); - $manager->registerFormWidget('Backend\FormWidgets\DataTable', [ - 'label' => 'Data Table', - 'code' => 'datatable' - ]); - $manager->registerFormWidget('Backend\FormWidgets\RecordFinder', [ - 'label' => 'Record Finder', - 'code' => 'recordfinder' - ]); - $manager->registerFormWidget('Backend\FormWidgets\Repeater', [ - 'label' => 'Repeater', - 'code' => 'repeater' - ]); - $manager->registerFormWidget('Backend\FormWidgets\TagList', [ - 'label' => 'Tag List', - 'code' => 'taglist' - ]); + $manager->registerFormWidget('Backend\FormWidgets\CodeEditor', 'codeeditor'); + $manager->registerFormWidget('Backend\FormWidgets\RichEditor', 'richeditor'); + $manager->registerFormWidget('Backend\FormWidgets\MarkdownEditor', 'markdown'); + $manager->registerFormWidget('Backend\FormWidgets\FileUpload', 'fileupload'); + $manager->registerFormWidget('Backend\FormWidgets\Relation', 'relation'); + $manager->registerFormWidget('Backend\FormWidgets\DatePicker', 'datepicker'); + $manager->registerFormWidget('Backend\FormWidgets\TimePicker', 'timepicker'); + $manager->registerFormWidget('Backend\FormWidgets\ColorPicker', 'colorpicker'); + $manager->registerFormWidget('Backend\FormWidgets\DataTable', 'datatable'); + $manager->registerFormWidget('Backend\FormWidgets\RecordFinder', 'recordfinder'); + $manager->registerFormWidget('Backend\FormWidgets\Repeater', 'repeater'); + $manager->registerFormWidget('Backend\FormWidgets\TagList', 'taglist'); }); } diff --git a/modules/backend/assets/less/controls/filelist.less b/modules/backend/assets/less/controls/filelist.less index 1f2030e..cc9b3de 100644 --- a/modules/backend/assets/less/controls/filelist.less +++ b/modules/backend/assets/less/controls/filelist.less @@ -225,7 +225,7 @@ &.single-line { ul { li a span.title { - text-overflow: ellipsis; + text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } diff --git a/modules/backend/behaviors/FormController.php b/modules/backend/behaviors/FormController.php index 7eab913..86d2495 100644 --- a/modules/backend/behaviors/FormController.php +++ b/modules/backend/behaviors/FormController.php @@ -1,5 +1,6 @@ controller->formBeforeCreate($model); $modelsToSave = $this->prepareModelsToSave($model, $this->formWidget->getSaveData()); - foreach ($modelsToSave as $modelToSave) { - $modelToSave->save(null, $this->formWidget->getSessionKey()); - } + Db::transaction(function() use ($modelsToSave) { + foreach ($modelsToSave as $modelToSave) { + $modelToSave->save(null, $this->formWidget->getSessionKey()); + } + }); $this->controller->formAfterSave($model); $this->controller->formAfterCreate($model); @@ -267,9 +270,11 @@ public function update_onSave($recordId = null, $context = null) $this->controller->formBeforeUpdate($model); $modelsToSave = $this->prepareModelsToSave($model, $this->formWidget->getSaveData()); - foreach ($modelsToSave as $modelToSave) { - $modelToSave->save(null, $this->formWidget->getSessionKey()); - } + Db::transaction(function() use ($modelsToSave) { + foreach ($modelsToSave as $modelToSave) { + $modelToSave->save(null, $this->formWidget->getSessionKey()); + } + }); $this->controller->formAfterSave($model); $this->controller->formAfterUpdate($model); diff --git a/modules/backend/behaviors/ImportExportController.php b/modules/backend/behaviors/ImportExportController.php index be0fc6f..4cf14c3 100644 --- a/modules/backend/behaviors/ImportExportController.php +++ b/modules/backend/behaviors/ImportExportController.php @@ -273,7 +273,7 @@ public function prepareImportVars() public function importRender() { - return $this->importExportMakePartial('import'); + return $this->importExportMakePartial('container_import'); } public function importGetModel() @@ -456,7 +456,7 @@ public function prepareExportVars() public function exportRender() { - return $this->importExportMakePartial('export'); + return $this->importExportMakePartial('container_export'); } public function exportGetModel() @@ -538,7 +538,7 @@ protected function processExportColumnsFromPost() protected function checkUseListExportMode() { - if (!$listDefinition = $this->getConfig('export[useList]')) { + if (!$useList = $this->getConfig('export[useList]')) { return false; } @@ -546,6 +546,13 @@ protected function checkUseListExportMode() throw new ApplicationException(Lang::get('backend::lang.import_export.behavior_missing_uselist_error')); } + if (is_array($useList)) { + $listDefinition = array_get($useList, 'definition'); + } + else { + $listDefinition = $useList; + } + $this->exportFromList($listDefinition); } @@ -594,12 +601,20 @@ public function exportFromList($definition = null, $options = []) /* * Add records */ + $getter = $this->getConfig('export[useList][raw]', false) + ? 'getColumnValueRaw' + : 'getColumnValue'; + $model = $widget->prepareModel(); $results = $model->get(); foreach ($results as $result) { $record = []; foreach ($columns as $column) { - $record[] = $widget->getColumnValue($result, $column); + $value = $widget->$getter($result, $column); + if (is_array($value)) { + $value = implode('|', $value); + } + $record[] = $value; } $csv->insertOne($record); } diff --git a/modules/backend/behaviors/ListController.php b/modules/backend/behaviors/ListController.php index 400cd60..8f84b76 100644 --- a/modules/backend/behaviors/ListController.php +++ b/modules/backend/behaviors/ListController.php @@ -137,6 +137,7 @@ public function makeList($definition = null) 'showCheckboxes', 'showTree', 'treeExpanded', + 'customViewPath', ]; foreach ($configFieldsToTransfer as $field) { @@ -342,18 +343,8 @@ public function listRender($definition = null) 'toolbar' => null, 'filter' => null, 'list' => null, - 'topPartial' => null, - 'sidePartial' => null ]; - if (isset($listConfig->topPartial)) { - $vars['topPartial'] = $listConfig->topPartial; - } - - if (isset($listConfig->sidePartial)) { - $vars['sidePartial'] = $listConfig->sidePartial; - } - if (isset($this->toolbarWidgets[$definition])) { $vars['toolbar'] = $this->toolbarWidgets[$definition]; } @@ -364,7 +355,23 @@ public function listRender($definition = null) $vars['list'] = $this->listWidgets[$definition]; - return $this->makePartial('list', $vars); + return $this->listMakePartial('container', $vars); + } + + /** + * Controller accessor for making partials within this behavior. + * @param string $partial + * @param array $params + * @return string Partial contents + */ + public function listMakePartial($partial, $params = []) + { + $contents = $this->controller->makePartial('list_'.$partial, $params + $this->vars, false); + if (!$contents) { + $contents = $this->makePartial($partial, $params); + } + + return $contents; } /** diff --git a/modules/backend/behaviors/RelationController.php b/modules/backend/behaviors/RelationController.php index 2efc9d3..657bdcb 100644 --- a/modules/backend/behaviors/RelationController.php +++ b/modules/backend/behaviors/RelationController.php @@ -33,7 +33,7 @@ class RelationController extends ControllerBehavior /** * @var const Postback parameter for read only mode. */ - const PARAM_READ_ONLY = '_relation_read_only'; + const PARAM_EXTRA_CONFIG = '_relation_extra_config'; /** * @var Backend\Classes\WidgetBase Reference to the search widget object. @@ -80,6 +80,11 @@ class RelationController extends ControllerBehavior */ protected $originalConfig; + /** + * @var array Config provided by the relationRender method + */ + protected $extraConfig; + /** * @var bool Has the behavior been initialized. */ @@ -242,8 +247,8 @@ public function prepareVars() $this->vars['relationViewWidget'] = $this->viewWidget; $this->vars['relationViewModel'] = $this->viewModel; $this->vars['relationPivotWidget'] = $this->pivotWidget; - $this->vars['relationReadOnly'] = $this->readOnly ? 1 : 0; $this->vars['relationSessionKey'] = $this->relationGetSessionKey(); + $this->vars['relationExtraConfig'] = $this->extraConfig; } /** @@ -305,8 +310,13 @@ public function initRelation($model, $field = null) throw new ApplicationException(Lang::get('backend::lang.relation.missing_definition', compact('field'))); } + if ($extraConfig = post(self::PARAM_EXTRA_CONFIG)) { + $this->applyExtraConfig($extraConfig); + } + $this->alias = camel_case('relation ' . $field); $this->config = $this->makeConfig($this->getConfig($field), $this->requiredRelationProperties); + $this->controller->relationExtendConfig($this->config, $this->field, $this->model); /* * Relationship details @@ -316,6 +326,7 @@ public function initRelation($model, $field = null) $this->relationObject = $this->model->{$field}(); $this->relationModel = $this->relationObject->getRelated(); + $this->readOnly = $this->getConfig('readOnly'); $this->deferredBinding = $this->getConfig('deferredBinding') || !$this->model->exists; $this->toolbarButtons = $this->evalToolbarButtons(); $this->viewMode = $this->evalViewMode(); @@ -342,7 +353,7 @@ public function initRelation($model, $field = null) * View widget */ if ($this->viewWidget = $this->makeViewWidget()) { - $this->controller->relationExtendViewWidget($this->viewWidget, $this->field); + $this->controller->relationExtendViewWidget($this->viewWidget, $this->field, $this->model); $this->viewWidget->bindToController(); } @@ -350,7 +361,7 @@ public function initRelation($model, $field = null) * Manage widget */ if ($this->manageWidget = $this->makeManageWidget()) { - $this->controller->relationExtendManageWidget($this->manageWidget, $this->field); + $this->controller->relationExtendManageWidget($this->manageWidget, $this->field, $this->model); $this->manageWidget->bindToController(); } @@ -359,17 +370,10 @@ public function initRelation($model, $field = null) */ if ($this->manageMode == 'pivot') { if ($this->pivotWidget = $this->makePivotWidget()) { - $this->controller->relationExtendPivotWidget($this->pivotWidget, $this->field); + $this->controller->relationExtendPivotWidget($this->pivotWidget, $this->field, $this->model); $this->pivotWidget->bindToController(); } } - - /* - * Read only mode - */ - if (post(self::PARAM_READ_ONLY, $this->getConfig('readOnly'))) { - $this->makeReadOnly(); - } } /** @@ -380,26 +384,29 @@ public function initRelation($model, $field = null) */ public function relationRender($field, $options = []) { - $field = $this->validateField($field); - + /* + * Session key + */ if (is_string($options)) { $options = ['sessionKey' => $options]; } - /* - * Session key - */ if (isset($options['sessionKey'])) { $this->sessionKey = $options['sessionKey']; } /* - * Read only mode + * Apply options and extra config */ - if (isset($options['readOnly']) && $options['readOnly']) { - $this->makeReadOnly(); - } + $allowConfig = ['readOnly', 'recordUrl', 'recordOnClick']; + $extraConfig = array_only($options, $allowConfig); + $this->extraConfig = $extraConfig; + $this->applyExtraConfig($extraConfig, $field); + /* + * Initialize + */ + $this->validateField($field); $this->prepareVars(); /* @@ -525,9 +532,11 @@ protected function makeToolbarWidget() /* * Add buttons to toolbar */ - $defaultButtons = $this->toolbarButtons - ? '~/modules/backend/behaviors/relationcontroller/partials/_toolbar.htm' - : null; + $defaultButtons = null; + + if (!$this->readOnly && $this->toolbarButtons) { + $defaultButtons = '~/modules/backend/behaviors/relationcontroller/partials/_toolbar.htm'; + } $defaultConfig['buttons'] = $this->getConfig('view[toolbarPartial]', $defaultButtons); @@ -551,8 +560,9 @@ protected function makeToolbarWidget() /* * No buttons, no search should mean no toolbar */ - if (empty($toolbarConfig->search) && empty($toolbarConfig->buttons)) + if (empty($toolbarConfig->search) && empty($toolbarConfig->buttons)) { return; + } $toolbarWidget = $this->makeWidget('Backend\Widgets\Toolbar', $toolbarConfig); $toolbarWidget->cssClasses[] = 'list-header'; @@ -585,6 +595,8 @@ protected function makeSearchWidget() protected function makeViewWidget() { + $widget = null; + /* * Multiple (has many, belongs to many) */ @@ -595,15 +607,14 @@ protected function makeViewWidget() $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]', true); + $config->showCheckboxes = $this->getConfig('view[showCheckboxes]', !$this->readOnly); $config->recordUrl = $this->getConfig('view[recordUrl]', null); $defaultOnClick = sprintf( - "$.oc.relationBehavior.clickViewListRecord(':%s', '%s', '%s', %s)", + "$.oc.relationBehavior.clickViewListRecord(':%s', '%s', '%s')", $this->relationModel->getKeyName(), - $this->field, - $this->relationGetSessionKey(), - $this->readOnly ? 1 : 0 + $this->relationGetId(), + $this->relationGetSessionKey() ); if ($config->recordUrl) { @@ -661,7 +672,10 @@ protected function makeViewWidget() /* * Allows pivot data to enter the fray */ - if ($this->relationType == 'belongsToMany') { + if ($this->relationType == 'belongsToMany' + || $this->relationType == 'morphToMany' + || $this->relationType == 'morphedByMany' + ) { $this->relationObject->setQuery($query->getQuery()); return $this->relationObject; } @@ -733,7 +747,7 @@ protected function makeManageWidget() $config->recordOnClick = sprintf( "$.oc.relationBehavior.clickManageListRecord(:%s, '%s', '%s')", $this->relationModel->getKeyName(), - $this->field, + $this->relationGetId(), $this->relationGetSessionKey() ); } @@ -744,7 +758,7 @@ protected function makeManageWidget() $config->recordOnClick = sprintf( "$.oc.relationBehavior.clickManagePivotListRecord(:%s, '%s', '%s')", $this->relationModel->getKeyName(), - $this->field, + $this->relationGetId(), $this->relationGetSessionKey() ); } @@ -792,7 +806,10 @@ protected function makeManageWidget() */ elseif ($this->manageMode == 'form') { - $config = $this->makeConfigForMode('manage', 'form'); + if (!$config = $this->makeConfigForMode('manage', 'form', false)) { + return null; + } + $config->model = $this->relationModel; $config->arrayName = class_basename($this->relationModel); $config->context = $this->evalFormContext('manage', !!$this->manageId); @@ -1260,12 +1277,23 @@ public function onRelationManagePivotUpdate() // Overrides // + /** + * Provides an opportunity to manipulate the field configuration. + * @param object $config + * @param string $field + * @param October\Rain\Database\Model $model + */ + public function relationExtendConfig($config, $field, $model) + { + } + /** * Provides an opportunity to manipulate the view widget. * @param Backend\Classes\WidgetBase $widget * @param string $field + * @param October\Rain\Database\Model $model */ - public function relationExtendViewWidget($widget, $field) + public function relationExtendViewWidget($widget, $field, $model) { } @@ -1273,8 +1301,9 @@ public function relationExtendViewWidget($widget, $field) * Provides an opportunity to manipulate the manage widget. * @param Backend\Classes\WidgetBase $widget * @param string $field + * @param October\Rain\Database\Model $model */ - public function relationExtendManageWidget($widget, $field) + public function relationExtendManageWidget($widget, $field, $model) { } @@ -1282,8 +1311,9 @@ public function relationExtendManageWidget($widget, $field) * Provides an opportunity to manipulate the pivot widget. * @param Backend\Classes\WidgetBase $widget * @param string $field + * @param October\Rain\Database\Model $model */ - public function relationExtendPivotWidget($widget, $field) + public function relationExtendPivotWidget($widget, $field, $model) { } @@ -1344,10 +1374,11 @@ protected function evalToolbarButtons() switch ($this->relationType) { case 'hasMany': case 'morphMany': + case 'morphToMany': + case 'morphedByMany': case 'belongsToMany': return ['create', 'add', 'delete', 'remove']; - case 'morphMany': - return ['create', 'delete']; + case 'hasOne': case 'morphOne': case 'belongsTo': @@ -1368,10 +1399,11 @@ protected function evalViewMode() switch ($this->relationType) { case 'hasMany': case 'morphMany': + case 'morphToMany': + case 'morphedByMany': case 'belongsToMany': return 'multi'; - case 'morphMany': - return 'multi'; + case 'hasOne': case 'morphOne': case 'belongsTo': @@ -1398,7 +1430,7 @@ protected function evalManageTitle() else { return 'backend::lang.relation.add_a_new'; } - break; + break; case 'form': if ($this->readOnly) { return 'backend::lang.relation.preview_name'; @@ -1406,7 +1438,7 @@ protected function evalManageTitle() else { return 'backend::lang.relation.update_name'; } - break; + break; } } @@ -1437,6 +1469,8 @@ protected function evalManageMode() case 'belongsTo': return 'list'; + case 'morphToMany': + case 'morphedByMany': case 'belongsToMany': if (isset($this->config->pivot)) return 'pivot'; elseif ($this->eventTarget == 'list') return 'form'; @@ -1448,8 +1482,6 @@ protected function evalManageMode() case 'morphMany': if ($this->eventTarget == 'button-add') return 'list'; else return 'form'; - case 'morphMany': - return 'form'; } } @@ -1476,19 +1508,32 @@ protected function evalFormContext($mode = 'manage', $exists = false) } /** - * Apply read only mode. + * Apply extra configuration */ - protected function makeReadOnly() + protected function applyExtraConfig($config, $field = null) { - $this->readOnly = true; + if (!$field) { + $field = $this->field; + } - if ($this->toolbarWidget && !$this->getConfig('view[toolbarPartial]', false)) { - $this->toolbarWidget->buttons = null; + if (!$config || !isset($this->originalConfig->{$field})) { + return; } - if ($this->viewWidget && $this->viewMode == 'multi' && !$this->getConfig('view[showCheckboxes]', false)) { - $this->viewWidget->showCheckboxes = false; + if ( + !is_array($config) && + (!$config = @json_decode(@base64_decode($config), true)) + ) { + return; } + + $parsedConfig = array_only($config, ['readOnly']); + $parsedConfig['view'] = array_only($config, ['recordUrl', 'recordOnClick']); + + $this->originalConfig->{$field} = array_replace_recursive( + $this->originalConfig->{$field}, + $parsedConfig + ); } /** diff --git a/modules/backend/behaviors/importexportcontroller/partials/_container_export.htm b/modules/backend/behaviors/importexportcontroller/partials/_container_export.htm new file mode 100644 index 0000000..f7042e0 --- /dev/null +++ b/modules/backend/behaviors/importexportcontroller/partials/_container_export.htm @@ -0,0 +1,9 @@ +
- = e(trans('backend::lang.import_export.upload_valid_csv')) ?>. + = e(trans('backend::lang.import_export.upload_valid_csv')) ?>
diff --git a/modules/backend/behaviors/listcontroller/partials/_container.htm b/modules/backend/behaviors/listcontroller/partials/_container.htm new file mode 100644 index 0000000..037ba06 --- /dev/null +++ b/modules/backend/behaviors/listcontroller/partials/_container.htm @@ -0,0 +1,9 @@ + + = $toolbar->render() ?> + + + + = $filter->render() ?> + + += $list->render() ?> diff --git a/modules/backend/behaviors/relationcontroller/assets/js/october.relation.js b/modules/backend/behaviors/relationcontroller/assets/js/october.relation.js index 85c0528..5ab7e07 100644 --- a/modules/backend/behaviors/relationcontroller/assets/js/october.relation.js +++ b/modules/backend/behaviors/relationcontroller/assets/js/october.relation.js @@ -9,38 +9,41 @@ $(el).closest('.control-list').listWidget('toggleChecked', [el]) } - this.clickViewListRecord = function(recordId, relationField, sessionKey, readOnly) { - var newPopup = $('') + this.clickViewListRecord = function(recordId, relationId, sessionKey) { + var newPopup = $(''), + $container = $('#'+relationId), + requestData = paramToObj('data-request-data', $container.data('request-data')) newPopup.popup({ handler: 'onRelationClickViewList', size: 'huge', - extraData: { + extraData: $.extend({}, requestData, { 'manage_id': recordId, - '_relation_field': relationField, - '_session_key': sessionKey, - '_relation_read_only': readOnly ? 1 : 0 - } + '_session_key': sessionKey + }) }) } - this.clickManageListRecord = function(recordId, relationField, sessionKey) { - var oldPopup = $('#relationManagePopup') + this.clickManageListRecord = function(recordId, relationId, sessionKey) { + var oldPopup = $('#relationManagePopup'), + $container = $('#'+relationId), + requestData = paramToObj('data-request-data', $container.data('request-data')) $.request('onRelationClickManageList', { - data: { + data: $.extend({}, requestData, { 'record_id': recordId, - '_relation_field': relationField, '_session_key': sessionKey - } + }) }) oldPopup.popup('hide') } - this.clickManagePivotListRecord = function(foreignId, relationField, sessionKey) { + this.clickManagePivotListRecord = function(foreignId, relationId, sessionKey) { var oldPopup = $('#relationManagePivotPopup'), - newPopup = $('') + newPopup = $(''), + $container = $('#'+relationId), + requestData = paramToObj('data-request-data', $container.data('request-data')) if (oldPopup.length) { oldPopup.popup('hide') @@ -49,11 +52,10 @@ newPopup.popup({ handler: 'onRelationClickManageListPivot', size: 'huge', - extraData: { + extraData: $.extend({}, requestData, { 'foreign_id': foreignId, - '_relation_field': relationField, '_session_key': sessionKey - } + }) }) } @@ -71,6 +73,18 @@ }) } + function paramToObj(name, value) { + if (value === undefined) value = '' + if (typeof value == 'object') return value + + try { + return JSON.parse(JSON.stringify(eval("({" + value + "})"))) + } + catch (e) { + throw new Error('Error parsing the '+name+' attribute value. '+e) + } + } + } $.oc.relationBehavior = new RelationBehavior; diff --git a/modules/backend/behaviors/relationcontroller/partials/_container.htm b/modules/backend/behaviors/relationcontroller/partials/_container.htm index e23b398..eb66e96 100644 --- a/modules/backend/behaviors/relationcontroller/partials/_container.htm +++ b/modules/backend/behaviors/relationcontroller/partials/_container.htm @@ -1,6 +1,6 @@