1<?php 2 3App::uses('CakeEventListener', 'Event'); 4App::uses('CrudListener', 'Crud.Controller/Crud'); 5 6/** 7 * Implements beforeRender event listener to set related models' lists to 8 * the view 9 * 10 * Licensed under The MIT License 11 * For full copyright and license information, please see the LICENSE.txt 12 */ 13class RelatedModelsListener extends CrudListener { 14 15/** 16 * Gets the list of associated model lists to be fetched for an action 17 * 18 * @param string $action name of the action 19 * @return array 20 */ 21 public function models($action = null) { 22 $settings = $this->_action($action)->relatedModels(); 23 if ($settings === true) { 24 $ModelInstance = $this->_model(); 25 return array_merge( 26 $ModelInstance->getAssociated('belongsTo'), 27 $ModelInstance->getAssociated('hasAndBelongsToMany') 28 ); 29 } 30 31 if (empty($settings)) { 32 return array(); 33 } 34 35 if (is_string($settings)) { 36 $settings = array($settings); 37 } 38 39 return $settings; 40 } 41 42/** 43 * Find and publish all related models to the view 44 * for an action 45 * 46 * @param NULL|string $action If NULL the current action will be used 47 * @return void 48 */ 49 public function publishRelatedModels($action = null) { 50 $models = $this->models($action); 51 52 if (empty($models)) { 53 return; 54 } 55 56 $Controller = $this->_controller(); 57 58 foreach ($models as $modelName) { 59 $associationType = $this->_getAssociationType($modelName); 60 $associatedModel = $this->_getModelInstance($modelName, $associationType); 61 62 $viewVar = Inflector::variable(Inflector::pluralize($associatedModel->alias)); 63 if (array_key_exists($viewVar, $Controller->viewVars)) { 64 continue; 65 } 66 67 $query = $this->_getBaseQuery($associatedModel, $associationType); 68 69 $subject = $this->_trigger('beforeRelatedModel', compact('modelName', 'query', 'viewVar', 'associationType', 'associatedModel')); 70 $items = $this->_findRelatedItems($associatedModel, $subject->query); 71 $subject = $this->_trigger('afterRelatedModel', compact('modelName', 'items', 'viewVar', 'associationType', 'associatedModel')); 72 73 $Controller->set($subject->viewVar, $subject->items); 74 } 75 } 76 77/** 78 * Fetches related models' list and sets them to a variable for the view 79 * 80 * @codeCoverageIgnore 81 * @param CakeEvent $event 82 * @return void 83 */ 84 public function beforeRender(CakeEvent $event) { 85 $this->publishRelatedModels(); 86 } 87 88/** 89 * Execute the DB query to find the related items 90 * 91 * @param Model $Model 92 * @param array $query 93 * @return array 94 */ 95 protected function _findRelatedItems(Model $Model, $query) { 96 if ($this->_hasTreeBehavior($Model)) { 97 return $Model->generateTreeList( 98 $query['conditions'], 99 $query['keyPath'], 100 $query['valuePath'], 101 $query['spacer'], 102 $query['recursive'] 103 ); 104 } 105 106 return $Model->find('list', $query); 107 } 108 109/** 110 * Get the base query to find the related items for an associated model 111 * 112 * @param Model $associatedModel 113 * @param string $associationType 114 * @return array 115 */ 116 protected function _getBaseQuery(Model $associatedModel, $associationType = null) { 117 $query = array(); 118 119 if ($associationType === 'belongsTo') { 120 $PrimaryModel = $this->_model(); 121 $query['conditions'][] = $PrimaryModel->belongsTo[$associatedModel->alias]['conditions']; 122 } 123 124 if ($this->_hasTreeBehavior($associatedModel)) { 125 $TreeBehavior = $this->_getTreeBehavior($associatedModel); 126 $query = array( 127 'keyPath' => null, 128 'valuePath' => null, 129 'spacer' => '_', 130 'recursive' => $TreeBehavior->settings[$associatedModel->alias]['recursive'] 131 ); 132 133 if (empty($query['conditions'])) { 134 $query['conditions'][] = $TreeBehavior->settings[$associatedModel->alias]['scope']; 135 } 136 } 137 138 return $query; 139 } 140 141/** 142 * Returns model instance based on its name 143 * 144 * @param string $modelName 145 * @param string $associationType 146 * @return Model 147 */ 148 protected function _getModelInstance($modelName, $associationType = null) { 149 $PrimaryModel = $this->_model(); 150 151 if (isset($PrimaryModel->{$modelName})) { 152 return $PrimaryModel->{$modelName}; 153 } 154 155 $Controller = $this->_controller(); 156 if (isset($Controller->{$modelName}) && $Controller->{$modelName} instanceOf Model) { 157 return $Controller->{$modelName}; 158 } 159 160 if ($associationType && !empty($PrimaryModel->{$associationType}[$modelName]['className'])) { 161 return $this->_classRegistryInit($PrimaryModel->{$associationType}[$modelName]['className']); 162 } 163 164 return $this->_classRegistryInit($modelName); 165 } 166 167/** 168 * Returns model's association type with controller's model 169 * 170 * @param string $modelName 171 * @return string|null Association type if found else null 172 */ 173 protected function _getAssociationType($modelName) { 174 $associated = $this->_model()->getAssociated(); 175 return isset($associated[$modelName]) ? $associated[$modelName] : null; 176 } 177 178/** 179 * Check if a model has the Tree behavior attached or not 180 * 181 * @codeCoverageIgnore 182 * @param Model $Model 183 * @return boolean 184 */ 185 protected function _hasTreeBehavior(Model $Model) { 186 return $Model->Behaviors->attached('Tree'); 187 } 188 189/** 190 * Get the TreeBehavior from a model 191 * 192 * @codeCoverageIgnore 193 * @param Model $Model 194 * @return TreeBehavior 195 */ 196 protected function _getTreeBehavior(Model $Model) { 197 return $Model->Behaviors->Tree; 198 } 199 200/** 201 * Wrapper for ClassRegistry::init for easier testing 202 * 203 * @codeCoverageIgnore 204 * @return Model 205 */ 206 protected function _classRegistryInit($modelName) { 207 return ClassRegistry::init($modelName); 208 } 209 210} 211