| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- <?php
- App::uses('ModelBehavior', 'Model');
- /**
- * LinkableBehavior
- * Light-weight approach for data mining on deep relations between models.
- * Join tables based on model relations to easily enable right to left find operations.
- * Original behavior by rafaelbandeira3 on GitHub.
- * Includes modifications from Terr, n8man, and Chad Jablonski
- *
- * Licensed under The MIT License
- * Redistributions of files must retain the above copyright notice.
- *
- * GiulianoB ( https://github.com/giulianob/linkable )
- *
- * @version 1.0;
- *
- * @version 1.1:
- * -Brought in improvements and test cases from Terr. However, THIS VERSION OF LINKABLE IS NOT DROP IN COMPATIBLE WITH Terr's VERSION!
- * -If fields aren't specified, will now return all columns of that model
- * -No need to specify the foreign key condition if a custom condition is given. Linkable will automatically include the foreign key relationship.
- * -Ability to specify the exact condition Linkable should use (e.g. $this->Post->find('first', array('link' => array('User' => array('conditions' => array('exactly' => 'User.last_post_id = Post.id'))))) )
- * This is usually required when doing on-the-fly joins since Linkable generally assumes a belongsTo relationship when no specific relationship is found and may produce invalid foreign key conditions.
- * -Linkable will no longer break queries that use SQL COUNTs
- *
- * @version 1.2:
- * @modified Mark Scherer
- * - works with cakephp2.x (89.84 test coverage)
- *
- * @deprecated Not actively maintained anymore, please see https://github.com/lorenzo/linkable for the most revent version.
- */
- class LinkableBehavior extends ModelBehavior {
- protected $_key = 'link';
- protected $_options = [
- 'type' => true, 'table' => true, 'alias' => true,
- 'conditions' => true, 'fields' => true, 'reference' => true,
- 'class' => true, 'defaults' => true
- ];
- protected $_defaultConfig = ['type' => 'LEFT'];
- public function beforeFind(Model $Model, $query) {
- if (isset($query[$this->_key])) {
- $optionsDefaults = $this->_defaultConfig + ['reference' => $Model->alias, $this->_key => []];
- $optionsKeys = $this->_options + [$this->_key => true];
- // If containable is being used, then let it set the recursive!
- if (empty($query['contain'])) {
- $query = array_merge(['joins' => []], $query, ['recursive' => -1]);
- } else {
- $query = array_merge(['joins' => []], $query);
- }
- $iterators[] = $query[$this->_key];
- $cont = 0;
- do {
- $iterator = $iterators[$cont];
- $defaults = $optionsDefaults;
- if (isset($iterator['defaults'])) {
- $defaults = array_merge($defaults, $iterator['defaults']);
- unset($iterator['defaults']);
- }
- $iterations = Set::normalize($iterator);
- foreach ($iterations as $alias => $options) {
- if ($options === null) {
- $options = [];
- }
- $options = array_merge($defaults, compact('alias'), $options);
- if (empty($options['alias'])) {
- throw new InvalidArgumentException(sprintf('%s::%s must receive aliased links', get_class($this), __FUNCTION__));
- }
- // try to find the class name - important for aliased relations
- foreach ($Model->belongsTo as $relationAlias => $relation) {
- if (empty($relation['className']) || $relationAlias !== $options['alias']) {
- continue;
- }
- $options['class'] = $relation['className'];
- break;
- }
- foreach ($Model->hasOne as $relationAlias => $relation) {
- if (empty($relation['className']) || $relationAlias !== $options['alias']) {
- continue;
- }
- $options['class'] = $relation['className'];
- break;
- }
- foreach ($Model->hasMany as $relationAlias => $relation) {
- if (empty($relation['className']) || $relationAlias !== $options['alias']) {
- continue;
- }
- $options['class'] = $relation['className'];
- break;
- }
- // guess it then
- if (empty($options['table']) && empty($options['class'])) {
- $options['class'] = $options['alias'];
- } elseif (!empty($options['table']) && empty($options['class'])) {
- $options['class'] = Inflector::classify($options['table']);
- }
- // the incoming model to be linked in query using class and alias
- $_Model = ClassRegistry::init(['class' => $options['class'], 'alias' => $options['alias']]);
- // the already in query model that links to $_Model
- $Reference = ClassRegistry::init($options['reference']);
- $db = $_Model->getDataSource();
- $associations = $_Model->getAssociated();
- if (isset($Reference->belongsTo[$_Model->alias])) {
- $type = 'hasOne';
- $association = $Reference->belongsTo[$_Model->alias];
- } elseif (isset($Reference->hasOne[$_Model->alias])) {
- $type = 'belongsTo';
- $association = $Reference->hasOne[$_Model->alias];
- } elseif (isset($Reference->hasMany[$_Model->alias])) {
- $type = 'belongsTo';
- $association = $Reference->hasMany[$_Model->alias];
- } elseif (isset($associations[$Reference->alias])) {
- $type = $associations[$Reference->alias];
- $association = $_Model->{$type}[$Reference->alias];
- } else {
- $_Model->bindModel(['belongsTo' => [$Reference->alias]]);
- $type = 'belongsTo';
- $association = $_Model->{$type}[$Reference->alias];
- $_Model->unbindModel(['belongsTo' => [$Reference->alias]]);
- }
- if (!isset($options['conditions'])) {
- $options['conditions'] = [];
- } elseif (!is_array($options['conditions'])) {
- // Support for string conditions
- $options['conditions'] = [$options['conditions']];
- }
- if (isset($options['conditions']['exactly'])) {
- if (is_array($options['conditions']['exactly'])) {
- $options['conditions'] = reset($options['conditions']['exactly']);
- } else {
- $options['conditions'] = [$options['conditions']['exactly']];
- }
- } else {
- if ($type === 'belongsTo') {
- $modelKey = $_Model->escapeField($association['foreignKey']);
- $modelKey = str_replace($_Model->alias, $options['alias'], $modelKey);
- $referenceKey = $Reference->escapeField($Reference->primaryKey);
- $options['conditions'][] = "{$referenceKey} = {$modelKey}";
- } elseif ($type === 'hasAndBelongsToMany') {
- // try to determine fields by HABTM model
- if (isset($association['with'])) {
- $Link = $_Model->{$association['with']};
- if (isset($Link->belongsTo[$_Model->alias])) {
- $modelLink = $Link->escapeField($Link->belongsTo[$_Model->alias]['foreignKey']);
- }
- if (isset($Link->belongsTo[$Reference->alias])) {
- $referenceLink = $Link->escapeField($Link->belongsTo[$Reference->alias]['foreignKey']);
- }
- } else {
- $Link = $_Model->{Inflector::classify($association['joinTable'])};
- }
- // try to determine fields by current model relation settings
- if (empty($modelLink) && !empty($_Model->{$type}[$Reference->alias]['foreignKey'])) {
- $modelLink = $Link->escapeField($_Model->{$type}[$Reference->alias]['foreignKey']);
- }
- if (empty($referenceLink) && !empty($_Model->{$type}[$Reference->alias]['associationForeignKey'])) {
- $referenceLink = $Link->escapeField($_Model->{$type}[$Reference->alias]['associationForeignKey']);
- }
- // fallback to defaults otherwise
- if (empty($modelLink)) {
- $modelLink = $Link->escapeField($association['foreignKey']);
- }
- if (empty($referenceLink)) {
- $referenceLink = $Link->escapeField($association['associationForeignKey']);
- }
- $referenceKey = $Reference->escapeField();
- $query['joins'][] = [
- 'alias' => $Link->alias,
- 'table' => $Link->table, //$Link->getDataSource()->fullTableName($Link),
- 'conditions' => "{$referenceLink} = {$referenceKey}",
- 'type' => 'LEFT',
- ];
- $modelKey = $_Model->escapeField();
- $modelKey = str_replace($_Model->alias, $options['alias'], $modelKey);
- $options['conditions'][] = "{$modelLink} = {$modelKey}";
- } else {
- $referenceKey = $Reference->escapeField($association['foreignKey']);
- $modelKey = $_Model->escapeField($_Model->primaryKey);
- $modelKey = str_replace($_Model->alias, $options['alias'], $modelKey);
- $options['conditions'][] = "{$modelKey} = {$referenceKey}";
- }
- }
- if (empty($options['table'])) {
- $options['table'] = $_Model->table;
- }
- // Decide whether we should mess with the fields or not
- // If this query is a COUNT query then we just leave it alone
- if (!isset($query['fields']) || is_array($query['fields']) || strpos($query['fields'], 'COUNT(*)') === false) {
- if (!empty($options['fields'])) {
- if ($options['fields'] === true && !empty($association['fields'])) {
- $options['fields'] = $db->fields($_Model, null, $association['fields']);
- } elseif ($options['fields'] === true) {
- $options['fields'] = $db->fields($_Model);
- } else {
- $options['fields'] = $db->fields($_Model, null, $options['fields']);
- }
- } elseif (!isset($options['fields']) || (isset($options['fields']) && !is_array($options['fields']))) {
- if (!empty($association['fields'])) {
- $options['fields'] = $db->fields($_Model, null, $association['fields']);
- } else {
- $options['fields'] = $db->fields($_Model);
- }
- }
- if (!empty($options['class']) && $options['class'] !== $alias) {
- //$options['fields'] = str_replace($options['class'], $alias, $options['fields']);
- }
- if (is_array($query['fields'])) {
- $query['fields'] = array_merge($query['fields'], $options['fields']);
- } else {
- // If user didn't specify any fields then select all fields by default (just as find would)
- $query['fields'] = array_merge($db->fields($Model), $options['fields']);
- }
- }
- $options[$this->_key] = array_merge($options[$this->_key], array_diff_key($options, $optionsKeys));
- $options = array_intersect_key($options, $optionsKeys);
- if (!empty($options[$this->_key])) {
- $iterators[] = $options[$this->_key] + ['defaults' => array_merge($defaults, ['reference' => $options['class']])];
- }
- $query['joins'][] = array_intersect_key($options, ['type' => true, 'alias' => true, 'table' => true, 'conditions' => true]);
- }
- $cont++;
- $notDone = isset($iterators[$cont]);
- } while ($notDone);
- }
- unset($query['link']);
- return $query;
- }
- }
|