Permalink
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
match/app/src/Controller/StandardController.php
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
566 lines (450 sloc)
17.5 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* COmanage Match Standard Controller | |
* | |
* Portions licensed to the University Corporation for Advanced Internet | |
* Development, Inc. ("UCAID") under one or more contributor license agreements. | |
* See the NOTICE file distributed with this work for additional information | |
* regarding copyright ownership. | |
* | |
* UCAID licenses this file to you under the Apache License, Version 2.0 | |
* (the "License"); you may not use this file except in compliance with the | |
* License. You may obtain a copy of the License at: | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* | |
* @link http://www.internet2.edu/comanage COmanage Project | |
* @package match | |
* @since COmanage Match v1.0.0 | |
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) | |
*/ | |
declare(strict_types = 1); | |
namespace App\Controller; | |
use InvalidArgumentException; | |
class StandardController extends AppController { | |
// Pagination defaults should be set in each controller | |
public $pagination = []; | |
/** | |
* Handle an add action for a Standard object. | |
* | |
* @since COmanage Match v1.0.0 | |
*/ | |
public function add() { | |
// $this->name = Models (ie: from ModelsTable) | |
$modelsName = $this->name; | |
if($this->request->is('post')) { | |
// Try to save | |
$obj = $this->$modelsName->newEntity($this->request->getData()); | |
// This throws \Cake\ORM\Exception\RolledbackTransactionException if aborted | |
// in afterSave | |
if($this->$modelsName->save($obj)) { | |
$this->Flash->success(__('match.rs.saved')); | |
return $this->generateRedirect(); | |
} | |
$this->Flash->error(__('match.er.save', [$modelsName])); | |
// Pass $obj as context so the view can render validation errors | |
$this->set('vv_obj', $obj); | |
} else { | |
// Create an empty entity for FormHelper | |
$this->set('vv_obj', $this->$modelsName->newEmptyEntity()); | |
} | |
// PrimaryLinkTrait | |
$this->getPrimaryLink(); | |
// AutoViewVarsTrait | |
$this->populateAutoViewVars(); | |
// Default title is add new object | |
$this->set('vv_title', __('match.op.add.a', __('match.ct.'.$modelsName, [1]))); | |
// Let the view render | |
$this->render('/Standard/add-edit-view'); | |
} | |
/** | |
* Standard operations before the view is rendered. | |
* | |
* @since COmanage Match v1.2.0 | |
* @param EventInterface $event BeforeRender event | |
* @return \Cake\Http\Response HTTP Response | |
*/ | |
public function beforeRender(\Cake\Event\EventInterface $event) { | |
// Provide some hints to the views | |
$this->getFieldTypes(); | |
return parent::beforeRender($event); | |
} | |
/** | |
* Handle a delete action for a Standard object. | |
* | |
* @since COmanage Match v1.0.0 | |
* @param Integer $id Object ID | |
*/ | |
public function delete($id) { | |
// $this->name = Models (ie: from ModelsTable) | |
$modelsName = $this->name; | |
// Allow a delete via a POST or DELETE | |
$this->request->allowMethod(['post', 'delete']); | |
// Make sure the requested object exists | |
try { | |
$obj = $this->$modelsName->findById($id)->firstOrFail(); | |
$this->$modelsName->deleteOrFail($obj); | |
// Use the display field to generate the flash message | |
$field = $this->$modelsName->getDisplayField(); | |
if(!empty($obj->$field)) { | |
$this->Flash->success(__('match.rs.deleted.a', [$obj->$field])); | |
} else { | |
$this->Flash->success(__('match.rs.deleted')); | |
} | |
} | |
catch(\Exception $e) { | |
// findById throws Cake\Datasource\Exception\RecordNotFoundException | |
$this->Flash->error($e->getMessage()); | |
} | |
// Always return to index since there is no delete view | |
return $this->generateRedirect(); | |
} | |
/** | |
* Handle a duplicate action for a Standard object. | |
* | |
* @since COmanage Match v1.0.0 | |
* @param Integer $id Object ID | |
*/ | |
public function duplicate($id) { | |
// $this->name = Models (ie: from ModelsTable) | |
$modelsName = $this->name; | |
$associatedModels = []; | |
$query = $this->$modelsName->findById($id); | |
// AssociationTrait | |
if(method_exists($this->$modelsName, "getDuplicateContains")) { | |
$query = $query->contain($this->$modelsName->getDuplicateContains()); | |
$associatedModels = $this->$modelsName->getDuplicateContains(); | |
} | |
// Make sure the requested object exists | |
try { | |
$obj = $query->firstOrFail(); | |
// Create a new entity. We disable validation in case the validation rules | |
// changed after the original object was created. | |
$newObj = $this->$modelsName->newEntity($obj->toArray(), ['validate' => false]); | |
// The object ID is cleared by newEntity, but not the timestamps | |
unset($newObj->created); | |
unset($newObj->modified); | |
foreach($associatedModels as $am) { | |
$at = \Cake\Utility\Inflector::tableize($am); | |
if(!empty($newObj->$at)) { | |
foreach($newObj->$at as $ao) { | |
unset($ao->created); | |
unset($ao->modified); | |
} | |
} | |
} | |
// Edit the name of the parent object (but we don't rename related models) | |
if(!empty($newObj->name)) { | |
$newObj->name = __('match.fd.copy_of', [$newObj->name]); | |
} | |
if($this->$modelsName->save($newObj)) { | |
// Use the display field to generate the flash message | |
$field = $this->$modelsName->getDisplayField(); | |
if(!empty($newObj->$field)) { | |
$this->Flash->success(__('match.rs.duplicated.a', [$newObj->$field])); | |
} else { | |
$this->Flash->success(__('match.rs.duplicated')); | |
} | |
// Redirect to edit view | |
$redirect = [ | |
'action' => 'edit', | |
$newObj->id | |
]; | |
return $this->redirect($redirect); | |
} else { | |
// It's hard to get a specific failure reason to render... | |
$this->Flash->error(__('match.er.duplicate')); | |
} | |
} | |
catch(\Exception $e) { | |
// findById throws Cake\Datasource\Exception\RecordNotFoundException | |
$this->Flash->error($e->getMessage()); | |
} | |
return $this->generateRedirect(); | |
} | |
/** | |
* Handle an edit action for a Standard object. | |
* | |
* @since COmanage Match v1.0.0 | |
* @param Integer $id Object ID | |
*/ | |
public function edit($id) { | |
// $this->name = Models (ie: from ModelsTable) | |
$modelsName = $this->name; | |
// $tableName = models | |
$tableName = $this->$modelsName->getTable(); | |
$query = $this->$modelsName->findById($id); | |
// AssociationTrait | |
if(method_exists($this->$modelsName, "getEditContains")) { | |
$query = $query->contain($this->$modelsName->getEditContains()); | |
} | |
try { | |
// Pull the current record | |
$obj = $query->firstOrFail(); | |
if($this->request->is(['post', 'put'])) { | |
// This is an update request | |
$opts = []; | |
// AssociationTrait | |
if(method_exists($this->$modelsName, "getPatchAssociated")) { | |
$opts['associated'] = $this->$modelsName->getPatchAssociated(); | |
} | |
// Attempt the update the record | |
$this->$modelsName->patchEntity($obj, $this->request->getData(), $opts); | |
// This throws \Cake\ORM\Exception\RolledbackTransactionException if aborted | |
// in afterSave | |
if($this->$modelsName->save($obj)) { | |
$this->Flash->success(__('match.rs.saved')); | |
return $this->generateRedirect(); | |
} | |
$this->Flash->error(__('match.er.save', [$modelsName])); | |
} | |
} | |
catch(\Exception $e) { | |
// findById throws Cake\Datasource\Exception\RecordNotFoundException | |
$this->Flash->error($e->getMessage()); | |
return $this->generateRedirect(); | |
} | |
$this->set('vv_obj', $obj); | |
// PrimaryLinkTrait | |
$this->getPrimaryLink(); | |
// AutoViewVarsTrait | |
$this->populateAutoViewVars($obj); | |
// Default view title is edit object display field | |
$field = $this->$modelsName->getDisplayField(); | |
if(!empty($obj->$field)) { | |
$this->set('vv_title', __('match.op.edit.a', $obj->$field)); | |
} else { | |
$this->set('vv_title', __('match.op.edit.a', __('match.ct.'.$modelsName, [1]))); | |
} | |
// Let the view render | |
$this->render('/Standard/add-edit-view'); | |
} | |
/** | |
* Generate a redirect for a Standard Object operation. | |
* | |
* @since COmanage Match v1.0.0 | |
* @return \Cake\Http\Response | |
*/ | |
public function generateRedirect() { | |
$redirect = ['action' => 'index']; | |
$link = $this->getPrimaryLink(true); | |
if(!empty($link)) { | |
$redirect['?'] = [ $link['linkattr'] => $link['linkvalue'] ]; | |
} | |
return $this->redirect($redirect); | |
} | |
/** | |
* Make a list of fields types suitable for FieldHelper | |
* | |
* @since COmanage Match v1.2.0 | |
*/ | |
protected function getFieldTypes() { | |
// $this->name = Models (ie: from ModelsTable) | |
$modelsName = $this->name; | |
// $table = the actual table object | |
$table = $this->$modelsName; | |
$schema = $table->getSchema(); | |
// We don't pass the schema object as is, partly because cake might change it | |
// and partly to simplify access to the parts the views (FieldHelper, really) | |
// actually need. | |
// Note the schema does have field lengths for strings, but typeMap | |
// doesn't return them and we're not doing anything with them at the moment. | |
$this->set('vv_field_types', $schema->typeMap()); | |
} | |
/** | |
* Generate an index for a set of Standard Objects. | |
* | |
* @since COmanage Match v1.0.0 | |
*/ | |
public function index() { | |
// $this->name = Models | |
$modelsName = $this->name; | |
// $table = the actual table object | |
$table = $this->$modelsName; | |
// $tableName = models | |
$tableName = $table->getTable(); | |
$query = null; | |
// PrimaryLinkTrait | |
$link = $this->getPrimaryLink(); | |
// AutoViewVarsTrait | |
$this->populateAutoViewVars(); | |
// Filter on link attribute, except for MatchgridRecords where the link value | |
// (matchgrid_id) is implied by the table itself | |
if(!empty($link['linkattr']) && $modelsName != 'MatchgridRecords') { | |
// If a link attribute is defined but no value is provided, then query | |
// where the link attribute is NULL | |
$query = $table->find()->where([$link['linkattr'].' IS' => $this->request->getQuery($link['linkattr'])]); | |
} else { | |
try { | |
$query = $table->find(); | |
} | |
catch(\Cake\Database\Exception $e) { | |
if($modelsName == 'MatchgridRecords') { | |
$this->Flash->error(__('match.er.mg.notfound', [$e->getMessage()])); | |
return $this->redirect([ | |
'controller' => 'matchgrids', | |
'action' => 'manage', | |
$this->cur_mg->id | |
]); | |
} else { | |
// Rethrow the exception | |
throw new \RuntimeException($e->getMessage()); | |
} | |
} | |
} | |
// QueryModificationTrait | |
if(method_exists($table, "getIndexContains") | |
&& $table->getIndexContains()) { | |
$query->contain($table->getIndexContains()); | |
} | |
// SearchFilterTrait | |
if(method_exists($table, "getSearchableAttributes")) { | |
$searchableAttributes = $table->getSearchableAttributes(); | |
if(!empty($searchableAttributes)) { | |
foreach(array_keys($searchableAttributes) as $attribute) { | |
if(!empty($this->request->getQuery($attribute))) { | |
$query = $table->whereFilter($query, $attribute, $this->request->getQuery($attribute)); | |
} | |
} | |
$this->set('vv_searchable_attributes', $searchableAttributes); | |
} | |
} | |
// The Cake documents describe $this->paginate (which worked in Cake 2), | |
// but it doesn't seem to work in Cake 3/4. So we just use $this->pagination | |
// ourselves here. | |
$this->set($tableName, $this->paginate($query, $this->pagination)); | |
$this->set('vv_tablename', $tableName); | |
$this->set('vv_modelname', $modelsName); | |
// Default index view title is model name | |
$this->set('vv_title', __('match.ct.'.$modelsName, [99])); | |
// Let the view render | |
$this->render('/Standard/index'); | |
} | |
/** | |
* Populate any auto view variables, as requested via AutoViewVarsTrait. | |
* | |
* @since COmanage Match v1.0.0 | |
* @param object $obj Current object (eg: from edit), if set | |
*/ | |
protected function populateAutoViewVars(object $obj=null) { | |
// $this->name = Models | |
$modelsName = $this->name; | |
// Populate certain view vars (eg: selects) automatically. | |
// AutoViewVarsTrait | |
if(method_exists($this->$modelsName, "getAutoViewVars") | |
&& $this->$modelsName->getAutoViewVars()) { | |
foreach($this->$modelsName->getAutoViewVars() as $vvar => $avv) { | |
switch($avv['type']) { | |
case 'enum': | |
// We just want the localized text strings for the defined constants | |
$class = '\\App\\Lib\\Enum\\'.$avv['class']; | |
$this->set($vvar, $class::getLocalizedConsts()); | |
break; | |
// "auxiliary", "list, and "select" do basically the same thing, but | |
// "auxiliary" returns the full object, while the other two return a | |
// hash suitable for a select. "list" allows customized fields. | |
case 'auxiliary': | |
case 'list': | |
case 'select': | |
// We assume $modelName has a direct relationship to $avv['model'] | |
$avvmodel = $avv['model']; | |
$this->$avvmodel = $this->fetchTable($avvmodel); | |
if($avv['type'] == 'auxiliary') { | |
$query = $this->$avvmodel->find(); | |
} else { | |
$fields = []; | |
if($avv['type'] == 'list') { | |
$fields = $avv['fields']; | |
} | |
$query = $this->$avvmodel->find('list', $fields); | |
} | |
if(!empty($avv['find'])) { | |
if($avv['find'] == 'filterPrimaryLink') { | |
// We're filtering the requested model, not our current model. | |
// See if the requested key is available, and if so run the find. | |
$linkFilter = $this->$modelsName->getPrimaryLink(); | |
if($linkFilter) { | |
// Try to find the $linkFilter value | |
$v = null; | |
// We might have been passed an object with the current value | |
if($obj && !empty($obj->$linkFilter)) { | |
$v = $obj->$linkFilter; | |
} elseif(!empty($this->request->getQuery($linkFilter))) { | |
$v = $this->request->getQuery($linkFilter); | |
} | |
// XXX also need to check getData()? | |
if($v) { | |
$query = $query->where([$linkFilter => $v]); | |
} | |
} | |
} elseif($avv['find'] == 'filterMatchgrid') { | |
// In many/most cases, filterPrimaryLink is also filterMatchgrid, | |
// but for indirect models this will force the filter to be on | |
// the matchgrid instead of the primary link | |
// For now we only support direct relations to matchgrid | |
$query->where(['matchgrid_id' => $this->cur_mg->id]); | |
} else { | |
// Use the specified finder, if configured | |
$query = $query->find($avv['find']); | |
} | |
} | |
// CO-1681 -- port to PE? | |
if(!empty($avv['order'])) { | |
$query = $query->order($avv['order']); | |
} | |
$this->set($vvar, $query->toArray()); | |
break; | |
default: | |
throw new \LogicException('Unknown Auto View Var Type {0}', [$avv['type']]); | |
break; | |
} | |
} | |
} | |
} | |
/** | |
* Handle a view action for a Standard object. | |
* | |
* @since COmanage Match v1.1.0 | |
* @param Integer $id Object ID | |
*/ | |
public function view($id) { | |
// $this->name = Models (ie: from ModelsTable) | |
$modelsName = $this->name; | |
// $tableName = models | |
$tableName = $this->$modelsName->getTable(); | |
$query = $this->$modelsName->findById($id); | |
// AssociationTrait | |
if(method_exists($this->$modelsName, "getViewContains")) { | |
$query = $query->contain($this->$modelsName->getViewContains()); | |
} | |
try { | |
// Pull the current record | |
$obj = $query->firstOrFail(); | |
} | |
catch(\Exception $e) { | |
// findById throws Cake\Datasource\Exception\RecordNotFoundException | |
$this->Flash->error($e->getMessage()); | |
return $this->generateRedirect(); | |
} | |
$this->set('vv_obj', $obj); | |
// PrimaryLinkTrait | |
$this->getPrimaryLink(); | |
// AutoViewVarsTrait | |
$this->populateAutoViewVars($obj); | |
// Default view title is edit object display field | |
$field = $this->$modelsName->getDisplayField(); | |
if(!empty($obj->$field)) { | |
$this->set('vv_title', __('match.op.view.a', $obj->$field)); | |
} else { | |
$this->set('vv_title', __('match.op.view.a', __('match.ct.'.$modelsName, [1]))); | |
} | |
// Let the view render | |
$this->render('/Standard/add-edit-view'); | |
} | |
} |