<?php /** * COmanage Match App 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 Cake\Controller\Controller; use Cake\Datasource\Exception; use Cake\Datasource\Exception\RecordNotFoundException; use Cake\Event\EventInterface; use InvalidArgumentException; class AppController extends Controller { // If set, the current requested Matchgrid ID. Note this may be *unauthenticated* // and so should not be trusted without further authorization. protected $cur_mg = null; // If set, the current primary link. protected $cur_pl = null; /** * Initialization callback. * * @since COmanage Match v1.0.0 */ public function initialize(): void { parent::initialize(); // Load Components used by most or all controllers $this->loadComponent('RequestHandler', [ // As of Cake v3.6.7 need to disable this to suppress v4.0.0 deprecation warnings. 'enableBeforeRedirect' => false ]); $this->loadComponent('Flash'); $this->loadComponent('Auth', [ // We want to use isAuthorized in each controller for request authorization 'authorize' => [ 'Controller' ], // This corresponds to EnvAuthenticate 'authenticate' => [ 'Env' ], ]); /* * Enable the following components for recommended CakePHP security settings. * see https://book.cakephp.org/3.0/en/controllers/components/security.html */ $this->loadComponent('Security'); // CSRF Protection is enabled via in Middleware via Application.php. // This is the COmanage AuthorizationComponent, not to be confused with // Cake's AuthComponent, or the use of Controller Authorization. $this->loadComponent('Authorization'); } /** * Callback run prior to the request action. * * @since COmanage Match v1.0.0 * @param \Cake\Event\EventInterface $event Cake Event */ public function beforeFilter(EventInterface $event) { parent::beforeFilter($event); // Determine the timezone $this->setTZ(); // Determine the requested Matchgrid $this->setMatchgrid(); } /** * Callback run prior to the view rendering. * * @since COmanage Match v1.0.0 * @param \Cake\Event\EventInterface $event Cake Event */ public function beforeRender(EventInterface $event) { parent::beforeRender($event); // The current user, if authenticated $curUser = $this->request->getSession()->read('Auth.User'); $this->set('vv_user', $curUser); // The current Matchgrid, as determined in beforeFilter() $mgid = null; if($this->cur_mg) { $mgid = $this->cur_mg->id; } // Available Matchgrids $this->Matchgrids = $this->fetchTable('Matchgrids'); $this->set('vv_matchgrids', $this->Matchgrids->find('all')->find('activeMatchGrids')->order(['table_name' => 'ASC'])->toArray()); // The set of menu permissions, so the layout knows what to render if(isset($this->Authorization) && $curUser) { // Ordinarily $this->Authorization will be set, but under certain error conditions // it won't, which will prevent error messages from rendering $this->set('vv_menu_permissions', $this->Authorization->menuPermissions($this->request->getSession()->read('Auth.User.username'), $mgid)); } } /** * Obtain information about the Standard Object's Primary Link, if set. * The $vv_primary_link view variable is also set. * * @since COmanage Match v1.0.0 * @param boolean $lookup If true, get the value of the primary link, not just the attribute * @return array Array holding the primary link attribute, and optionally its value * @throws \RuntimeException */ protected function getPrimaryLink(bool $lookup=false) { // Did we already figure this out? (But only if $lookup) if($lookup && isset($this->cur_pl['linkvalue'])) { return $this->cur_pl; } // $this->name = Models $modelsName = $this->name; // $modelName = Model $modelName = \Cake\Utility\Inflector::singularize($this->name); $this->cur_pl = []; // PrimaryLinkTrait if(method_exists($this->$modelsName, "getPrimaryLink") && $this->$modelsName->getPrimaryLink()) { $this->cur_pl['linkattr'] = $this->$modelsName->getPrimaryLink(); $this->set('vv_primary_link', $this->cur_pl['linkattr']); if($lookup) { // Try to find a value if($this->request->is('get')) { // If this action allows unkeyed, asserted primary link IDs, check the query // string (eg: 'add' or 'index' allow matchgrid_id to be passed in) if($this->$modelsName->allowUnkeyedPrimaryLink($this->request->getParam('action')) && $this->request->getQuery($this->cur_pl['linkattr'])) { $this->cur_pl['linkvalue'] = $this->request->getQuery($this->cur_pl['linkattr']); } elseif($this->$modelsName->allowLookupPrimaryLink($this->request->getParam('action'))) { // Try to map the requested object ID $param = (int)$this->request->getParam('pass.0'); if(!empty($param)) { $this->cur_pl['linkvalue'] = $this->$modelsName->calculatePrimaryLinkId($param); } } } elseif($this->request->is('post') || $this->request->is('put')) { // Look in the data for the primary link ID if(!empty($this->request->getData($this->cur_pl['linkattr']))) { $this->cur_pl['linkvalue'] = $this->request->getData($this->cur_pl['linkattr']); } elseif(!empty($this->request->getData($modelName . "." . $this->cur_pl['linkattr']))) { $this->cur_pl['linkvalue'] = $this->request->getData($modelName . "." . $this->cur_pl['linkattr']); } elseif($this->$modelsName->allowUnkeyedPrimaryLink($this->request->getParam('action')) && $this->request->getQuery($this->cur_pl['linkattr'])) { // If this action allows unkeyed, asserted primary link IDs, check the query // string (eg: 'add' or 'index' allow matchgrid_id to be passed in). Note we // do this even though we're in post/put. $this->cur_pl['linkvalue'] = $this->request->getQuery($this->cur_pl['linkattr']); } elseif($this->$modelsName->allowLookupPrimaryLink($this->request->getParam('action'))) { // Try to map the requested object ID (this is probably a delete, so no attribute in post body) $param = (int)$this->request->getParam('pass.0'); if(!empty($param)) { $this->cur_pl['linkvalue'] = $this->$modelsName->calculatePrimaryLinkId($param); } } } if(empty($this->cur_pl['linkvalue']) && !$this->$modelsName->allowEmptyPrimaryLink()) { throw new \RuntimeException(__('match.er.primary_link', [ $this->cur_pl['linkattr'] ])); } } if(!empty($this->cur_pl['linkvalue'])) { // Look up the link value to find the related entity $linkModelName = $this->$modelsName->getPrimaryLinkTableName(); $linkModel = $this->getTableLocator()->get($linkModelName); $this->set('vv_primary_link_model', $linkModelName); $this->set('vv_primary_link_obj', $linkModel->findById($this->cur_pl['linkvalue'])->firstOrFail()); } } return $this->cur_pl; } /** * Determine the (requested) current Matchgrid and make it available to the * rest of the application. * * @since COmanage Match v1.0.0 * @throws Cake\Datasource\Exception\RecordNotFoundException * @throws \InvalidArgumentException */ protected function setMatchgrid() { // Note: TierApiController overrides this. // $this->name = Models $modelsName = $this->name; // $modelName = Model $modelName = \Cake\Utility\Inflector::singularize($this->name); if(!method_exists($this->$modelsName, "requiresMatchgrid") || !$this->$modelsName->requiresMatchgrid()) { // Nothing to do, matchgrid not required by this model/controller return; } // Not all models have matchgrid as their primary link. This will also // trigger setting of the viewVar for breadcrumbs and anything else. // PrimaryLinkTrait $link = $this->getPrimaryLink(true); // Try to find the requested matchgrid $mgid = null; // If this action allows unkeyed, asserted primary link IDs, check the query // string (eg: 'add' or 'index' allow matchgrid_id to be passed in), and // possibly dereference it if the primary key is not matchgrid_id. if($this->$modelsName->allowUnkeyedPrimaryLink($this->request->getParam('action'))) { if($link['linkattr'] == 'matchgrid_id') { // Simply accept the passed matchgrid ID if($this->request->is('get')) { $mgid = $this->request->getQuery('matchgrid_id'); } elseif($this->request->is('post') || $this->request->is('put')) { if(!empty($this->request->getData('matchgrid_id'))) { $mgid = $this->request->getData('matchgrid_id'); } elseif(!empty($this->request->getData($modelName . ".matchgrid_id"))) { $mgid = $this->request->getData($modelName . ".matchgrid_id"); } elseif($this->name == 'MatchgridRecords' && !empty($this->request->getQuery('matchgrid_id'))) { // As a special case for MatchgridRecords, we accept the matchgrid_id // as a get parameter even though the action is post/put, since this // is how it is provided for a delete action. $mgid = $this->request->getQuery('matchgrid_id'); } } } else { // We already have the primary link object in a viewvar $ViewBuilder = $this->viewBuilder(); $plObj = $ViewBuilder->getVar('vv_primary_link_obj'); if(!empty($plObj->matchgrid_id)) { $mgid = $plObj->matchgrid_id; } } } elseif($this->$modelsName->allowLookupPrimaryLink($this->request->getParam('action'))) { // Try to map the requested object ID $param = (int)$this->request->getParam('pass.0'); if(!empty($param)) { $mgid = $this->$modelsName->calculateMatchgridId($param); } } if(!$mgid && !$this->$modelsName->allowEmptyMatchgrid()) { // If we get this far without a Matchgrid ID, something went wrong. throw new \RuntimeException(__('match.er.mgid')); } if($mgid) { $this->Matchgrids = $this->fetchTable('Matchgrids'); // This throws Cake\Datasource\Exception\RecordNotFoundException which // we just let pass up the stack. $this->cur_mg = $this->Matchgrids->findById($mgid)->firstOrFail(); $this->set('vv_cur_mg', $this->cur_mg); } } /** * Determine the current timezone and make it available to the * rest of the application. * * @since COmanage Match v1.0.0 */ protected function setTZ() { // $this->name = Models $modelsName = $this->name; // See if we've collected it from the browser in a previous page load. Otherwise // use the system default. If the user set a preferred timezone, we'll catch that below. $tz = date_default_timezone_get(); if(!empty($_COOKIE['cm_match_tz_auto'])) { // We have an auto-detected timezone from a previous page render from the browser. // Note we don't call date_default_timezone_set() because we still want to record // times internally in UTC (at the expense of having to convert back and forth). $tz = $_COOKIE['cm_match_tz_auto']; } $this->set('vv_tz', $tz); if($this->$modelsName->behaviors()->has('Timezone')) { // Tell TimezoneBehavior what the current timezone is $this->$modelsName->setTimeZone($tz); } } }