diff --git a/CHANGELOG.md b/CHANGELOG.md index 48ffd3a4e..2ae5b3064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +* **Build 18x** (2015-01-xx) + - Relation Controller now supports *has one* and *belongs to* relations (see Backend > Relations docs). + * **Build 179** (2015-01-18) - The Settings page is no longer governed by a global permission, each settings entry should have its own permission definition. diff --git a/modules/backend/behaviors/RelationController.php b/modules/backend/behaviors/RelationController.php index 1487ecbc5..f6c2caa30 100644 --- a/modules/backend/behaviors/RelationController.php +++ b/modules/backend/behaviors/RelationController.php @@ -259,13 +259,16 @@ class RelationController extends ControllerBehavior protected function evalManageMode() { switch ($this->relationType) { + case 'belongsTo': + $mode = 'list'; + break; + case 'belongsToMany': $mode = (isset($this->config->pivot)) ? 'pivot' : 'list'; break; case 'hasOne': case 'hasMany': - case 'belongsTo': $mode = 'form'; break; } @@ -317,10 +320,16 @@ class RelationController extends ControllerBehavior * @param string $field Relation definition. * @return array The relation element selector as the key, and the relation view contents are the value. */ - public function relationRefresh($field) + public function relationRefresh($field = null) { $field = $this->validateField($field); - return ['#'.$this->relationGetId('view') => $this->relationRenderView()]; + + $result = ['#'.$this->relationGetId('view') => $this->relationRenderView()]; + if ($toolbar = $this->relationRenderToolbar()) { + $result['#'.$this->relationGetId('toolbar')] = $toolbar; + } + + return $result; } /** @@ -375,7 +384,9 @@ class RelationController extends ControllerBehavior $this->vars['relationType'] = $this->relationType; $this->vars['relationSearchWidget'] = $this->searchWidget; $this->vars['relationToolbarWidget'] = $this->toolbarWidget; + $this->vars['relationManageMode'] = $this->manageMode; $this->vars['relationManageWidget'] = $this->manageWidget; + $this->vars['relationViewMode'] = $this->viewMode; $this->vars['relationViewWidget'] = $this->viewWidget; $this->vars['relationPivotWidget'] = $this->pivotWidget; $this->vars['relationSessionKey'] = $this->relationGetSessionKey(); @@ -454,18 +465,56 @@ class RelationController extends ControllerBehavior // // Overrides // - + /** * Controller override: Extend the query used for populating the list * after the default query is processed. * @param October\Rain\Database\Builder $query * @param string $field - * @param string $manageMode */ - public function relationExtendQuery($query, $field, $manageMode) + public function relationExtendQuery($query, $field) { } + // + // AJAX (Buttons) + // + + public function onRelationButtonAdd() + { + return $this->onRelationManageForm(); + } + + public function onRelationButtonCreate() + { + return $this->onRelationManageForm(); + } + + public function onRelationButtonDelete() + { + return $this->onRelationManageDelete(); + } + + public function onRelationButtonLink() + { + return $this->onRelationManageForm(); + } + + public function onRelationButtonUnlink() + { + return $this->onRelationManageRemove(); + } + + public function onRelationButtonRemove() + { + return $this->onRelationManageRemove(); + } + + public function onRelationButtonUpdate() + { + return $this->onRelationManageForm(); + } + // // AJAX // @@ -491,12 +540,18 @@ class RelationController extends ControllerBehavior public function onRelationManageCreate() { $this->beforeAjax(); - $saveData = $this->manageWidget->getSaveData(); - $newModel = $this->relationObject->create($saveData, $this->relationGetSessionKey(true)); - $newModel->commitDeferred($this->manageWidget->getSessionKey()); - return ['#'.$this->relationGetId('view') => $this->relationRenderView()]; + if ($this->viewMode == 'multi') { + $newModel = $this->relationObject->create($saveData, $this->relationGetSessionKey(true)); + $newModel->commitDeferred($this->manageWidget->getSessionKey()); + } + elseif ($this->viewMode == 'single') { + $this->viewWidget->setFormValues($saveData); + $this->viewWidget->model->save(); + } + + return $this->relationRefresh(); } /** @@ -505,9 +560,16 @@ class RelationController extends ControllerBehavior public function onRelationManageUpdate() { $this->beforeAjax(); - $saveData = $this->manageWidget->getSaveData(); - $this->relationObject->find($this->manageId)->save($saveData, $this->manageWidget->getSessionKey()); + + if ($this->viewMode == 'multi') { + $model = $this->relationObject->find($this->manageId); + $model->save($saveData, $this->manageWidget->getSessionKey()); + } + elseif ($this->viewMode == 'single') { + $this->viewWidget->setFormValues($saveData); + $this->viewWidget->model->save(); + } return ['#'.$this->relationGetId('view') => $this->relationRenderView()]; } @@ -519,18 +581,35 @@ class RelationController extends ControllerBehavior { $this->beforeAjax(); - if (($checkedIds = post('checked')) && is_array($checkedIds)) { - $relatedModel = $this->relationObject->getRelated(); - foreach ($checkedIds as $relationId) { - if (!$obj = $relatedModel->find($relationId)) { - continue; - } + /* + * Multiple (has many, belongs to many) + */ + if ($this->viewMode == 'multi') { + if (($checkedIds = post('checked')) && is_array($checkedIds)) { + $relatedModel = $this->relationObject->getRelated(); + foreach ($checkedIds as $relationId) { + if (!$obj = $relatedModel->find($relationId)) { + continue; + } - $obj->delete(); + $obj->delete(); + } } } + /* + * Single (belongs to, has one) + */ + elseif ($this->viewMode == 'single') { + $relatedModel = $this->viewWidget->model; + if ($relatedModel->exists) { + $relatedModel->delete(); + } - return ['#'.$this->relationGetId('view') => $this->relationRenderView()]; + $this->viewWidget->setFormValues([]); + $this->viewWidget->model = $this->relationModel; + } + + return $this->relationRefresh(); } /** @@ -540,31 +619,48 @@ class RelationController extends ControllerBehavior { $this->beforeAjax(); - if ($this->viewMode != 'multi') { - throw new ApplicationException(Lang::get('backend::lang.relation.invalid_action_single')); + $recordId = post('record_id'); + + /* + * Add + */ + if ($this->viewMode == 'multi') { + + $checkedIds = $recordId ? [$recordId] : post('checked'); + + if (is_array($checkedIds)) { + /* + * Remove existing relations from the array + */ + $existingIds = $this->findExistingRelationIds($checkedIds); + $checkedIds = array_diff($checkedIds, $existingIds); + $foreignKeyName = $this->relationModel->getKeyName(); + + $models = $this->relationModel->whereIn($foreignKeyName, $checkedIds)->get(); + foreach ($models as $model) { + + if ($this->model->exists) { + $this->relationObject->add($model); + } + else { + $this->relationObject->add($model, $this->relationGetSessionKey()); + } + } + } + } - - if (($checkedIds = post('checked')) && is_array($checkedIds)) { - /* - * Remove existing relations from the array - */ - $existingIds = $this->findExistingRelationIds($checkedIds); - $checkedIds = array_diff($checkedIds, $existingIds); - $foreignKeyName = $this->relationModel->getKeyName(); - - $models = $this->relationModel->whereIn($foreignKeyName, $checkedIds)->get(); - foreach ($models as $model) { - - if ($this->model->exists) { - $this->relationObject->add($model); - } - else { - $this->relationObject->add($model, $this->relationGetSessionKey()); - } + /* + * Link + */ + elseif ($this->viewMode == 'single') { + if ($recordId && ($model = $this->relationModel->find($recordId))) { + $this->relationObject->associate($model); + $this->relationObject->getParent()->save(); + $this->viewWidget->setFormValues($model->attributes); } } - return ['#'.$this->relationGetId('view') => $this->relationRenderView()]; + return $this->relationRefresh(); } /** @@ -574,15 +670,29 @@ class RelationController extends ControllerBehavior { $this->beforeAjax(); - if ($this->viewMode != 'multi') { - throw new ApplicationException(Lang::get('backend::lang.relation.invalid_action_single')); + $recordId = post('record_id'); + + /* + * Remove + */ + if ($this->viewMode == 'multi') { + + $checkedIds = $recordId ? [$recordId] : post('checked'); + + if (is_array($checkedIds)) { + $this->relationObject->detach($checkedIds); + } + } + /* + * Unlink + */ + elseif ($this->viewMode == 'single') { + $this->relationObject->dissociate(); + $this->relationObject->getParent()->save(); + $this->viewWidget->setFormValues([]); } - if (($checkedIds = post('checked')) && is_array($checkedIds)) { - $this->relationObject->detach($checkedIds); - } - - return ['#'.$this->relationGetId('view') => $this->relationRenderView()]; + return $this->relationRefresh(); } public function onRelationManagePivotForm() @@ -698,7 +808,7 @@ class RelationController extends ControllerBehavior $config->showCheckboxes = $this->getConfig('view[showCheckboxes]', !$this->readOnly); $defaultOnClick = sprintf( - "$.oc.relationBehavior.clickManageListRecord(:id, '%s', '%s')", + "$.oc.relationBehavior.clickViewListRecord(:id, '%s', '%s')", $this->field, $this->relationGetSessionKey() ); @@ -715,7 +825,7 @@ class RelationController extends ControllerBehavior */ $widget = $this->makeWidget('Backend\Widgets\Lists', $config); $widget->bindEvent('list.extendQuery', function ($query) { - $this->controller->relationExtendQuery($query, $this->field, $this->manageMode); + $this->controller->relationExtendQuery($query, $this->field); $this->relationObject->setQuery($query); if ($this->model->exists) { @@ -744,13 +854,18 @@ class RelationController extends ControllerBehavior * Single (belongs to, has one) */ elseif ($this->viewMode == 'single') { + $query = $this->relationObject; + $this->controller->relationExtendQuery($query, $this->field); + $model = $query->getResults() ?: $this->relationModel; + $config = $this->makeConfig($this->config->form); - $config->model = $this->relationModel; + $config->model = $model; $config->arrayName = class_basename($this->relationModel); $config->context = 'relation'; $config->alias = $this->alias . 'ViewForm'; $widget = $this->makeWidget('Backend\Widgets\Form', $config); + $widget->previewMode = true; } return $widget; @@ -786,6 +901,16 @@ class RelationController extends ControllerBehavior $config->showSorting = $this->getConfig('manage[showSorting]', true); $config->defaultSort = $this->getConfig('manage[defaultSort]'); $config->recordsPerPage = $this->getConfig('manage[recordsPerPage]'); + + if ($this->viewMode == 'single') { + $config->showCheckboxes = false; + $config->recordOnClick = sprintf( + "$.oc.relationBehavior.clickManageListRecord(:id, '%s', '%s')", + $this->field, + $this->relationGetSessionKey() + ); + } + $widget = $this->makeWidget('Backend\Widgets\Lists', $config); /* @@ -836,7 +961,7 @@ class RelationController extends ControllerBehavior */ if ($this->manageMode == 'pivot' || $this->manageMode == 'list') { $widget->bindEvent('list.extendQuery', function ($query) { - $this->controller->relationExtendQuery($query, $this->field, $this->manageMode); + $this->controller->relationExtendQuery($query, $this->field); /* * Where not in the current list of related records diff --git a/modules/backend/behaviors/relationcontroller/assets/css/relation.css b/modules/backend/behaviors/relationcontroller/assets/css/relation.css index a52d54882..defc86865 100644 --- a/modules/backend/behaviors/relationcontroller/assets/css/relation.css +++ b/modules/backend/behaviors/relationcontroller/assets/css/relation.css @@ -11,6 +11,9 @@ .relation-behavior .control-list:last-child > table { margin-bottom: 0; } +.relation-behavior.relation-view-single { + padding: 0 20px; +} .relation-flush .relation-behavior { border-top: 0; } diff --git a/modules/backend/behaviors/relationcontroller/assets/js/october.relation.js b/modules/backend/behaviors/relationcontroller/assets/js/october.relation.js index e6d768ddf..f0f46b2fa 100644 --- a/modules/backend/behaviors/relationcontroller/assets/js/october.relation.js +++ b/modules/backend/behaviors/relationcontroller/assets/js/october.relation.js @@ -6,6 +6,20 @@ var RelationBehavior = function() { this.clickManageListRecord = function(recordId, relationField, sessionKey) { + var oldPopup = $('#relationManagePopup') + + $.request('onRelationManageAdd', { + data: { + 'record_id': recordId, + '_relation_field': relationField, + '_session_key': sessionKey + } + }) + + oldPopup.popup('hide') + } + + this.clickViewListRecord = function(recordId, relationField, sessionKey) { var newPopup = $('') newPopup.popup({ diff --git a/modules/backend/behaviors/relationcontroller/assets/less/relation.less b/modules/backend/behaviors/relationcontroller/assets/less/relation.less index 61227fd7a..4d1936421 100644 --- a/modules/backend/behaviors/relationcontroller/assets/less/relation.less +++ b/modules/backend/behaviors/relationcontroller/assets/less/relation.less @@ -17,6 +17,10 @@ .control-list:last-child > table { margin-bottom: 0; } + + &.relation-view-single { + padding: 0 20px; + } } // Relation manager to sit flush to the element above diff --git a/modules/backend/behaviors/relationcontroller/partials/_button_add.htm b/modules/backend/behaviors/relationcontroller/partials/_button_add.htm index 65f0640c3..64e1988f5 100644 --- a/modules/backend/behaviors/relationcontroller/partials/_button_add.htm +++ b/modules/backend/behaviors/relationcontroller/partials/_button_add.htm @@ -1,6 +1,6 @@ trans($relationLabel)])) ?> diff --git a/modules/backend/behaviors/relationcontroller/partials/_button_create.htm b/modules/backend/behaviors/relationcontroller/partials/_button_create.htm index 77ada5bbe..b4e3ec610 100644 --- a/modules/backend/behaviors/relationcontroller/partials/_button_create.htm +++ b/modules/backend/behaviors/relationcontroller/partials/_button_create.htm @@ -1,6 +1,6 @@ trans($relationLabel)])) ?> diff --git a/modules/backend/behaviors/relationcontroller/partials/_button_delete.htm b/modules/backend/behaviors/relationcontroller/partials/_button_delete.htm index 108ff4af2..838887ec8 100644 --- a/modules/backend/behaviors/relationcontroller/partials/_button_delete.htm +++ b/modules/backend/behaviors/relationcontroller/partials/_button_delete.htm @@ -1,14 +1,24 @@ - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/modules/backend/behaviors/relationcontroller/partials/_button_link.htm b/modules/backend/behaviors/relationcontroller/partials/_button_link.htm new file mode 100644 index 000000000..40c2225bf --- /dev/null +++ b/modules/backend/behaviors/relationcontroller/partials/_button_link.htm @@ -0,0 +1,7 @@ + + trans($relationLabel)])) ?> + diff --git a/modules/backend/behaviors/relationcontroller/partials/_button_remove.htm b/modules/backend/behaviors/relationcontroller/partials/_button_remove.htm index 2ddd2770f..6d318b7fe 100644 --- a/modules/backend/behaviors/relationcontroller/partials/_button_remove.htm +++ b/modules/backend/behaviors/relationcontroller/partials/_button_remove.htm @@ -4,7 +4,7 @@ checked: $('#relationGetId('view') ?> .control-list').listWidget('getChecked') })" disabled="disabled" - data-request="onRelationManageRemove" + data-request="onRelationButtonRemove" data-trigger-type="enable" data-trigger="#relationGetId('view') ?> .control-list input[type=checkbox]" data-trigger-condition="checked" diff --git a/modules/backend/behaviors/relationcontroller/partials/_button_unlink.htm b/modules/backend/behaviors/relationcontroller/partials/_button_unlink.htm new file mode 100644 index 000000000..ba2e46e57 --- /dev/null +++ b/modules/backend/behaviors/relationcontroller/partials/_button_unlink.htm @@ -0,0 +1,8 @@ + + trans($relationLabel)])) ?> + diff --git a/modules/backend/behaviors/relationcontroller/partials/_button_update.htm b/modules/backend/behaviors/relationcontroller/partials/_button_update.htm new file mode 100644 index 000000000..0da1eec88 --- /dev/null +++ b/modules/backend/behaviors/relationcontroller/partials/_button_update.htm @@ -0,0 +1,8 @@ + + trans($relationLabel)])) ?> + diff --git a/modules/backend/behaviors/relationcontroller/partials/_container.htm b/modules/backend/behaviors/relationcontroller/partials/_container.htm index 9a9a8e856..9b2ddc976 100644 --- a/modules/backend/behaviors/relationcontroller/partials/_container.htm +++ b/modules/backend/behaviors/relationcontroller/partials/_container.htm @@ -1,6 +1,6 @@
+ class="relation-behavior relation-view-"> relationRenderToolbar()): ?> diff --git a/modules/backend/behaviors/relationcontroller/partials/_manage_list.htm b/modules/backend/behaviors/relationcontroller/partials/_manage_list.htm index 9dff8a217..0f1eaeb30 100644 --- a/modules/backend/behaviors/relationcontroller/partials/_manage_list.htm +++ b/modules/backend/behaviors/relationcontroller/partials/_manage_list.htm @@ -1,5 +1,5 @@ - -
+
+ -
- \ No newline at end of file + +
\ No newline at end of file diff --git a/modules/backend/behaviors/relationcontroller/partials/_toolbar.htm b/modules/backend/behaviors/relationcontroller/partials/_toolbar.htm index 9657b6042..7905f0a64 100644 --- a/modules/backend/behaviors/relationcontroller/partials/_toolbar.htm +++ b/modules/backend/behaviors/relationcontroller/partials/_toolbar.htm @@ -10,17 +10,22 @@ relationMakePartial('button_add') ?> relationMakePartial('button_remove') ?> - + relationMakePartial('button_link') ?> relationMakePartial('button_unlink') ?> - + - relationMakePartial('button_create') ?> - relationMakePartial('button_update') ?> - relationMakePartial('button_delete') ?> + model->exists): ?> + relationMakePartial('button_update', ['relationManageId' => $relationViewWidget->model->id]) ?> + relationMakePartial('button_delete') ?> + + relationMakePartial('button_create') ?> + + +
diff --git a/modules/backend/formwidgets/RecordFinder.php b/modules/backend/formwidgets/RecordFinder.php index 3e94b77fb..9d4e84040 100644 --- a/modules/backend/formwidgets/RecordFinder.php +++ b/modules/backend/formwidgets/RecordFinder.php @@ -95,7 +95,7 @@ class RecordFinder extends FormWidgetBase if (!$this->model->hasRelation($this->relationName)) { throw new SystemException(Lang::get('backend::lang.model.missing_relation', [ - 'class' => get_class($this->controller), + 'class' => get_class($this->model), 'relation' => $this->relationName ])); } diff --git a/modules/backend/formwidgets/Relation.php b/modules/backend/formwidgets/Relation.php index bfd8d97b7..dec7687eb 100644 --- a/modules/backend/formwidgets/Relation.php +++ b/modules/backend/formwidgets/Relation.php @@ -113,9 +113,10 @@ class Relation extends FormWidgetBase } elseif (in_array($this->relationType, ['belongsTo', 'hasOne'])) { $field->type = 'dropdown'; - $field->placeholder = $this->emptyOption; } + $field->placeholder = $this->emptyOption; + // 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))) { diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php index c368c58a6..6f86debef 100644 --- a/modules/backend/lang/en/lang.php +++ b/modules/backend/lang/en/lang.php @@ -196,6 +196,8 @@ return [ 'add' => "Add", 'add_selected' => "Add selected", 'add_a_new' => "Add a new :name", + 'link_selected' => "Link selected", + 'link_a_new' => "Link a new :name", 'cancel' => "Cancel", 'close' => "Close", 'add_name' => "Add :name", @@ -210,6 +212,11 @@ return [ 'delete' => "Delete", 'delete_name' => "Delete :name", 'delete_confirm' => "Are you sure?", + 'link' => "Link", + 'link_name' => "Link :name", + 'unlink' => "Unlink", + 'unlink_name' => "Unlink :name", + 'unlink_confirm' => "Are you sure?", ], 'model' => [ 'name' => 'Model', diff --git a/modules/backend/models/user/fields.yaml b/modules/backend/models/user/fields.yaml index 9c4ec61a0..4708a1e46 100644 --- a/modules/backend/models/user/fields.yaml +++ b/modules/backend/models/user/fields.yaml @@ -3,61 +3,61 @@ # =================================== fields: - first_name: - span: left - label: backend::lang.user.first_name + first_name: + span: left + label: backend::lang.user.first_name - last_name: - span: right - label: backend::lang.user.last_name + last_name: + span: right + label: backend::lang.user.last_name - login: - span: left - label: backend::lang.user.login + login: + span: left + label: backend::lang.user.login - email: - span: right - label: backend::lang.user.email + email: + span: right + label: backend::lang.user.email - password: - type: password - span: left - label: backend::lang.user.password + password: + type: password + span: left + label: backend::lang.user.password - password_confirmation: - type: password - span: right - label: backend::lang.user.password_confirmation + password_confirmation: + type: password + span: right + label: backend::lang.user.password_confirmation - send_invite: - context: create - type: checkbox - label: backend::lang.user.send_invite - comment: backend::lang.user.send_invite_comment + send_invite: + context: create + type: checkbox + label: backend::lang.user.send_invite + comment: backend::lang.user.send_invite_comment tabs: - fields: + fields: - permissions[superuser]: - context: [create, update] - tab: backend::lang.user.permissions - label: backend::lang.user.superuser - type: checkbox - comment: backend::lang.user.superuser_comment + permissions[superuser]: + context: [create, update] + tab: backend::lang.user.permissions + label: backend::lang.user.superuser + type: checkbox + comment: backend::lang.user.superuser_comment - groups: - context: [create, update] - tab: backend::lang.user.permissions - label: backend::lang.user.groups - commentAbove: backend::lang.user.groups_comment - type: checkboxlist + groups: + context: [create, update] + tab: backend::lang.user.permissions + label: backend::lang.user.groups + commentAbove: backend::lang.user.groups_comment + type: checkboxlist secondaryTabs: - fields: + fields: - avatar: - label: backend::lang.user.avatar - type: fileupload - mode: image - imageHeight: 260 - imageWidth: 260 + avatar: + label: backend::lang.user.avatar + type: fileupload + mode: image + imageHeight: 260 + imageWidth: 260 diff --git a/modules/cms/classes/Controller.php b/modules/cms/classes/Controller.php index d7769aff3..1040dccd8 100644 --- a/modules/cms/classes/Controller.php +++ b/modules/cms/classes/Controller.php @@ -329,6 +329,16 @@ class Controller extends BaseController $result = $template->render($this->vars); CmsException::unmask(); + /* + * Extensibility + */ + if ( + ($event = $this->fireEvent('page.display', [$url, $page, $result], true)) || + ($event = Event::fire('cms.page.display', [$this, $url, $page, $result], true)) + ) { + return $event; + } + if (!is_string($result)) { return $result; } @@ -412,8 +422,8 @@ class Controller extends BaseController /* * Extensibility */ - - Event::fire('cms.page.initComponents', [$this, $this->page]); + $this->fireEvent('page.initComponents', [$this->page, $this->layout]); + Event::fire('cms.page.initComponents', [$this, $this->page, $this->layout]); } /**