diff --git a/cake/libs/model/behaviors/tree.php b/cake/libs/model/behaviors/tree.php index 9c766e2e2..9dafb0f33 100644 --- a/cake/libs/model/behaviors/tree.php +++ b/cake/libs/model/behaviors/tree.php @@ -360,8 +360,10 @@ class TreeBehavior extends ModelBehavior { return null; } - $results = $model->find('all', array('conditions' => array($scope, $model->escapeField($left) => '<= ' . $item[$left], $model->escapeField($right) => '>= ' . $item[$right]), - 'fields' => $fields, 'order' => array($model->escapeField($left) => 'asc'), 'recursive' => $recursive)); + $results = $model->find('all', array( + 'conditions' => array($scope, $model->escapeField($left) => '<= ' . $item[$left], $model->escapeField($right) => '>= ' . $item[$right]), + 'fields' => $fields, 'order' => array($model->escapeField($left) => 'asc'), 'recursive' => $recursive + )); return $results; } /** @@ -380,11 +382,15 @@ class TreeBehavior extends ModelBehavior { $id = $model->id; } extract($this->settings[$model->alias]); - list($node) = array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $id), - 'fields' => array($model->primaryKey, $left, $right, $parent), 'recursive' => -1))); + list($node) = array_values($model->find('first', array( + 'conditions' => array($scope, $model->escapeField() => $id), + 'fields' => array($model->primaryKey, $left, $right, $parent), 'recursive' => -1 + ))); if ($node[$parent]) { - list($parentNode) = array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $node[$parent]), - 'fields' => array($model->primaryKey, $left, $right), 'recursive' => -1))); + list($parentNode) = array_values($model->find('first', array( + 'conditions' => array($scope, $model->escapeField() => $node[$parent]), + 'fields' => array($model->primaryKey, $left, $right), 'recursive' => -1 + ))); if (($node[$right] + 1) == $parentNode[$right]) { return false; } @@ -424,11 +430,15 @@ class TreeBehavior extends ModelBehavior { $id = $model->id; } extract($this->settings[$model->alias]); - list($node) = array_values($model->find('first', array('conditions' => array($scope, $model->escapeField() => $id), - 'fields' => array($model->primaryKey, $left, $right, $parent ), 'recursive' => -1))); + list($node) = array_values($model->find('first', array( + 'conditions' => array($scope, $model->escapeField() => $id), + 'fields' => array($model->primaryKey, $left, $right, $parent ), 'recursive' => -1 + ))); if ($node[$parent]) { - list($parentNode) = array_values($model->find('first', array('conditions' => array( $scope, $model->escapeField() => $node[$parent]), - 'fields' => array($model->primaryKey, $left, $right), 'recursive' => -1))); + list($parentNode) = array_values($model->find('first', array( + 'conditions' => array($scope, $model->escapeField() => $node[$parent]), + 'fields' => array($model->primaryKey, $left, $right), 'recursive' => -1 + ))); if (($node[$left] - 1) == $parentNode[$left]) { return false; } @@ -489,7 +499,7 @@ class TreeBehavior extends ModelBehavior { } else { $parentId = $path[count($path) - 2][$model->alias][$model->primaryKey]; } - $model->updateAll(array($parent => $parentId), array($model->primaryKey => $array[$model->alias][$model->primaryKey])); + $model->updateAll(array($parent => $parentId), array($model->escapeField() => $array[$model->alias][$model->primaryKey])); } } } @@ -532,7 +542,10 @@ class TreeBehavior extends ModelBehavior { $model->id = $id; if ($delete) { - $model->updateAll(array($left => null, $right => null, $parent => null), array($model->primaryKey => $id)); + $model->updateAll( + array($model->escapeField($left) => null, $model->escapeField($right) => null, $model->escapeField($parent) => null), + array($model->escapeField() => $id) + ); return $model->delete($id); } else { $edge = $this->__getMax($model, $scope, $right); @@ -727,7 +740,7 @@ class TreeBehavior extends ModelBehavior { $field = $right; } if (is_string($conditions)) { - $conditions = array($field => $conditions); + $conditions = array($model->escapeField($field) => $conditions); } if ($scope != '1 = 1' && $scope) { if (is_string($scope)) { @@ -736,7 +749,7 @@ class TreeBehavior extends ModelBehavior { $conditions= array_merge($conditions, $scope); } } - $model->updateAll(array($field => $field . ' ' . $dir . ' ' . $shift), $conditions); + $model->updateAll(array($model->escapeField($field) => $model->escapeField($field) . ' ' . $dir . ' ' . $shift), $conditions); } } ?> \ No newline at end of file diff --git a/cake/libs/model/datasources/dbo/dbo_firebird.php b/cake/libs/model/datasources/dbo/dbo_firebird.php index 2921a3bd5..817bfdb1b 100644 --- a/cake/libs/model/datasources/dbo/dbo_firebird.php +++ b/cake/libs/model/datasources/dbo/dbo_firebird.php @@ -456,21 +456,26 @@ class DboFirebird extends DboSource { /** * Builds final SQL statement * + * @param string $type Query type * @param array $data Query data * @return string */ - function renderStatement($data) { + function renderStatement($type, $data) { extract($data); - if (preg_match('/offset\s+([0-9]+)/i', $limit, $offset)) { - $limit = preg_replace('/\s*offset.*$/i', '', $limit); - preg_match('/top\s+([0-9]+)/i', $limit, $limitVal); - $offset = intval($offset[1]) + intval($limitVal[1]); - $rOrder = $this->__switchSort($order); - list($order2, $rOrder) = array($this->__mapFields($order), $this->__mapFields($rOrder)); - return "SELECT * FROM (SELECT {$limit} * FROM (SELECT TOP {$offset} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order}) AS Set1 {$rOrder}) AS Set2 {$order2}"; + if (strtolower($type) == 'select') { + if (preg_match('/offset\s+([0-9]+)/i', $limit, $offset)) { + $limit = preg_replace('/\s*offset.*$/i', '', $limit); + preg_match('/top\s+([0-9]+)/i', $limit, $limitVal); + $offset = intval($offset[1]) + intval($limitVal[1]); + $rOrder = $this->__switchSort($order); + list($order2, $rOrder) = array($this->__mapFields($order), $this->__mapFields($rOrder)); + return "SELECT * FROM (SELECT {$limit} * FROM (SELECT TOP {$offset} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order}) AS Set1 {$rOrder}) AS Set2 {$order2}"; + } else { + return "SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order}"; + } } else { - return "SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order}"; + return parent::renderStatement($type, $data); } } /** diff --git a/cake/libs/model/datasources/dbo/dbo_mssql.php b/cake/libs/model/datasources/dbo/dbo_mssql.php index 123076a03..a345f51c3 100644 --- a/cake/libs/model/datasources/dbo/dbo_mssql.php +++ b/cake/libs/model/datasources/dbo/dbo_mssql.php @@ -510,20 +510,26 @@ class DboMssql extends DboSource { /** * Builds final SQL statement * + * @param string $type Query type * @param array $data Query data * @return string */ - function renderStatement($data) { + function renderStatement($type, $data) { extract($data); - if (preg_match('/offset\s+([0-9]+)/i', $limit, $offset)) { - $limit = preg_replace('/\s*offset.*$/i', '', $limit); - preg_match('/top\s+([0-9]+)/i', $limit, $limitVal); - $offset = intval($offset[1]) + intval($limitVal[1]); - $rOrder = $this->__switchSort($order); - list($order2, $rOrder) = array($this->__mapFields($order), $this->__mapFields($rOrder)); - return "SELECT * FROM (SELECT {$limit} * FROM (SELECT TOP {$offset} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order}) AS Set1 {$rOrder}) AS Set2 {$order2}"; + + if (strtolower($type) == 'select') { + if (preg_match('/offset\s+([0-9]+)/i', $limit, $offset)) { + $limit = preg_replace('/\s*offset.*$/i', '', $limit); + preg_match('/top\s+([0-9]+)/i', $limit, $limitVal); + $offset = intval($offset[1]) + intval($limitVal[1]); + $rOrder = $this->__switchSort($order); + list($order2, $rOrder) = array($this->__mapFields($order), $this->__mapFields($rOrder)); + return "SELECT * FROM (SELECT {$limit} * FROM (SELECT TOP {$offset} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order}) AS Set1 {$rOrder}) AS Set2 {$order2}"; + } else { + return "SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order}"; + } } else { - return "SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order}"; + return parent::renderStatement($type, $data); } } /** diff --git a/cake/libs/model/datasources/dbo_source.php b/cake/libs/model/datasources/dbo_source.php index 93908d690..2444bbc24 100644 --- a/cake/libs/model/datasources/dbo_source.php +++ b/cake/libs/model/datasources/dbo_source.php @@ -1058,12 +1058,11 @@ class DboSource extends DataSource { switch($type) { case 'hasOne': case 'belongsTo': + $conditions = $this->__mergeConditions( + $assocData['conditions'], + $this->getConstraint($type, $model, $linkModel, $alias, array_merge($assocData, compact('external'))) + ); if ($external) { - if ($type == 'hasOne') { - $conditions = $this->__mergeConditions($assocData['conditions'], array("{$alias}.{$assocData['foreignKey']}" => '{$__cakeID__$}')); - } elseif ($type == 'belongsTo') { - $conditions = $this->__mergeConditions($assocData['conditions'], array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}')); - } $query = array_merge($assocData, array( 'conditions' => $conditions, 'table' => $this->fullTableName($linkModel), @@ -1077,19 +1076,12 @@ class DboSource extends DataSource { $query = array_merge($query, array('order' => $assocData['order'], 'limit' => $limit)); } } else { - if ($type == 'hasOne') { - $conditions = $this->__mergeConditions($assocData['conditions'], array("{$alias}.{$assocData['foreignKey']}" => '{$__cakeIdentifier[' . "{$model->alias}.{$model->primaryKey}" . ']__$}')); - } elseif ($type == 'belongsTo') { - $conditions = $this->__mergeConditions($assocData['conditions'], array("{$model->alias}.{$assocData['foreignKey']}" => '{$__cakeIdentifier[' . "{$alias}.{$linkModel->primaryKey}" . ']__$}')); - } - $join = array( 'table' => $this->fullTableName($linkModel), 'alias' => $alias, 'type' => 'LEFT', 'conditions' => trim($this->conditions($conditions, true, false)) ); - $queryData['fields'] = array_merge($queryData['fields'], $fields); if (!empty($assocData['order'])) { @@ -1108,7 +1100,7 @@ class DboSource extends DataSource { )); $query = array( - 'conditions' => $this->__mergeConditions(array("{$alias}.{$assocData['foreignKey']}" => array('{$__cakeID__$}')), $assocData['conditions']), + 'conditions' => $this->__mergeConditions($this->getConstraint('hasMany', $model, $linkModel, $alias, $assocData), $assocData['conditions']), 'fields' => $assocData['fields'], 'table' => $this->fullTableName($linkModel), 'alias' => $alias, @@ -1125,10 +1117,10 @@ class DboSource extends DataSource { if (isset($assocData['with']) && !empty($assocData['with'])) { $joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']); list($with, $joinFields) = $this->getJoinModel($model, $assocData['with'], $joinKeys); + if (is_array($joinFields) && !empty($joinFields)) { $joinFields = $this->fields($model->{$with}, $model->{$with}->alias, $joinFields); $joinAssoc = $joinAlias = $model->{$with}->alias; - } else { $joinFields = array(); } @@ -1144,11 +1136,8 @@ class DboSource extends DataSource { 'joins' => array(array( 'table' => $joinTbl, 'alias' => $joinAssoc, - 'conditions' => array( - array("{$joinAlias}.{$assocData['foreignKey']}" => '{$__cakeID__$}'), - array("{$joinAlias}.{$assocData['associationForeignKey']}" => '{$__cakeIdentifier['."{$alias}.{$linkModel->primaryKey}".']__$}') - )) - ) + 'conditions' => $this->getConstraint('hasAndBelongsToMany', $model, $linkModel, $joinAlias, $assocData, $alias) + )) ); break; } @@ -1157,6 +1146,46 @@ class DboSource extends DataSource { } return null; } +/** + * Returns a conditions array for the constraint between two models + * + * @param string $type Association type + * @param object $model Model object + * @param array $association Association array + * @return array Conditions array defining the constraint between $model and $association + */ + function getConstraint($type, $model, $linkModel, $alias, $assoc, $alias2 = null) { + $assoc = array_merge(array('external' => false), $assoc); + + if (array_key_exists('foreignKey', $assoc) && empty($assoc['foreignKey'])) { + return array(); + } + + switch (true) { + case ($assoc['external'] && $type == 'hasOne'): + return array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'); + break; + case ($assoc['external'] && $type == 'belongsTo'): + return array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}'); + break; + case (!$assoc['external'] && $type == 'hasOne'): + return array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeIdentifier[' . "{$model->alias}.{$model->primaryKey}" . ']__$}'); + break; + case (!$assoc['external'] && $type == 'belongsTo'): + return array("{$model->alias}.{$assoc['foreignKey']}" => '{$__cakeIdentifier[' . "{$alias}.{$linkModel->primaryKey}" . ']__$}'); + break; + case ($type == 'hasMany'): + return array("{$alias}.{$assoc['foreignKey']}" => array('{$__cakeID__$}')); + break; + case ($type == 'hasAndBelongsToMany'): + return array( + array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'), + array("{$alias}.{$assoc['associationForeignKey']}" => '{$__cakeIdentifier['."{$alias2}.{$linkModel->primaryKey}".']__$}') + ); + break; + } + return array(); + } /** * Gets the name and fields to be used by a join model. This allows specifying join fields in the association definition. * @@ -1202,7 +1231,7 @@ class DboSource extends DataSource { } } } - return $this->renderStatement(array( + return $this->renderStatement('select', array( 'conditions' => $this->conditions($query['conditions']), 'fields' => join(', ', $query['fields']), 'table' => $query['table'], @@ -1217,10 +1246,27 @@ class DboSource extends DataSource { extract($data); return trim("{$type} JOIN {$table} {$alias} ON ({$conditions})"); } - - function renderStatement($data) { +/** + * Renders a final SQL statement by putting together the component parts in the correct order + * + * @param string $type + * @param array $data + * @return string + */ + function renderStatement($type, $data) { extract($data); - return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order} {$limit}"; + + switch (strtolower($type)) { + case 'select': + return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order} {$limit}"; + break; + case 'update': + return "UPDATE {$table} {$this->alias}{$alias} {$joins} SET {$fields} {$conditions}"; + break; + case 'delete': + return "DELETE {$alias} FROM {$table} {$this->alias}{$alias} {$joins} {$conditions}"; + break; + } } /** * Private method @@ -1267,9 +1313,9 @@ class DboSource extends DataSource { foreach ($combined as $field => $value) { if ($value === null) { - $updates[] = $this->name($field) . ' = NULL'; + $updates[] = $model->escapeField($field) . ' = NULL'; } else { - $update = $this->name($field) . ' = '; + $update = $model->escapeField($field) . ' = '; if ($conditions == null) { $update .= $this->value($value, $model->getColumnType($field)); } else { @@ -1283,11 +1329,13 @@ class DboSource extends DataSource { if ($conditions === false) { return false; } - $fields = join(',', $updates); + $fields = join(', ', $updates); $table = $this->fullTableName($model); $conditions = $this->conditions($conditions); + $alias = $this->name($model->alias); + $joins = implode(' ', $this->_getJoins($model)); - if (!$this->execute("UPDATE {$table} SET {$fields} {$conditions}")) { + if (!$this->execute($this->renderStatement('update', compact('table', 'alias', 'joins', 'fields', 'conditions')))) { $model->onError(); return false; } @@ -1307,15 +1355,40 @@ class DboSource extends DataSource { return false; } + $alias = $this->name($model->alias); $table = $this->fullTableName($model); $conditions = $this->conditions($query); + $joins = implode(' ', $this->_getJoins($model)); - if ($this->execute("DELETE FROM {$table} {$conditions}") === false) { + if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) { $model->onError(); return false; } return true; } +/** + * Returns an array of SQL JOIN fragments from a model's associations + * + * @param object $model + * @return array + */ + function _getJoins($model) { + $join = array(); + $joins = array_merge($model->getAssociated('hasOne'), $model->getAssociated('belongsTo')); + + foreach ($joins as $assoc) { + if (isset($model->{$assoc}) && $model->useDbConfig == $model->{$assoc}->useDbConfig) { + $assocData = $model->getAssociated($assoc); + $join[] = $this->buildJoinStatement(array( + 'table' => $this->fullTableName($model->{$assoc}), + 'alias' => $assoc, + 'type' => 'LEFT', + 'conditions' => trim($this->conditions($this->getConstraint($assocData['association'], $model, $model->{$assoc}, $assoc, $assocData), true, false)) + )); + } + } + return $join; + } /** * Deletes all the records in a table and resets the count of the auto-incrementing * primary key, where applicable. @@ -1341,7 +1414,7 @@ class DboSource extends DataSource { if (!$model->exists()) { return false; } - return array($model->primaryKey => (array)$model->getID()); + return array("{$model->alias}.{$model->primaryKey}" => (array)$model->getID()); } /** * Returns a key formatted like a string Model.fieldname(i.e. Post.title, or Country.name) @@ -2044,4 +2117,5 @@ class DboSource extends DataSource { return false; } } -?> + +?> \ No newline at end of file diff --git a/cake/libs/model/model.php b/cake/libs/model/model.php index 2351a5d6a..1ebdf1d35 100644 --- a/cake/libs/model/model.php +++ b/cake/libs/model/model.php @@ -223,6 +223,13 @@ class Model extends Overloadable { * @access public */ var $cacheSources = true; +/** + * Type of find query currently executing + * + * @var string + * @access public + */ + var $findQueryType = null; /** * Mapped behavior methods * @@ -1434,27 +1441,41 @@ class Model extends Overloadable { * @return boolean True on success, false on failure * @access public */ - function deleteAll($conditions, $cascade = true, $callbacks = true) { + function deleteAll($conditions, $cascade = true, $callbacks = false) { if (empty($conditions)) { return false; } - $records = $this->findAll($conditions, "{$this->alias}.{$this->primaryKey}", null, null, null, 0); + $db =& ConnectionManager::getDataSource($this->useDbConfig); - if (empty($records)) { - return false; - } - $ids = Set::extract($records, "{n}.{$this->alias}.{$this->primaryKey}"); + if (!$cascade && !$callbacks) { + return $db->delete($this, $conditions); + } else { + $ids = Set::extract( + $this->find('all', array_merge(array('fields' => "{$this->alias}.{$this->primaryKey}", 'recursive' => 0), compact('conditions'))), + "{n}.{$this->alias}.{$this->primaryKey}" + ); - foreach ($ids as $id) { - $this->_deleteLinks($id); + if (empty($ids)) { + return false; + } - if ($cascade) { - $this->_deleteDependent($id, $cascade); + if ($callbacks) { + $_id = $this->id; + + foreach ($ids as $id) { + $this->delete($id, $cascade); + } + $this->id = $_id; + } else { + foreach ($ids as $id) { + $this->_deleteLinks($id); + if ($cascade) { + $this->_deleteDependent($id, $cascade); + } + } + return $db->delete($this, array($this->alias . '.' . $this->primaryKey => $ids)); } } - - $db =& ConnectionManager::getDataSource($this->useDbConfig); - return $db->delete($this, array($this->primaryKey => $ids)); } /** * Returns true if a record with set id exists. @@ -1514,6 +1535,7 @@ class Model extends Overloadable { $type = $conditions; $query = $fields; } + $this->findQueryType = $type; $db =& ConnectionManager::getDataSource($this->useDbConfig); $this->id = $this->getID(); @@ -1576,6 +1598,7 @@ class Model extends Overloadable { } $results = $db->read($this, $query); $this->__resetAssociations(); + $this->findQueryType = null; switch ($type) { case 'all': @@ -1885,9 +1908,6 @@ class Model extends Overloadable { $ruleSet = array($ruleSet); } - if (isset($this->testing)) { - pr($ruleSet); - } foreach ($ruleSet as $index => $validator) { if (!is_array($validator)) { $validator = array('rule' => $validator); @@ -2054,6 +2074,9 @@ class Model extends Overloadable { $field = $this->primaryKey; } $db =& ConnectionManager::getDataSource($this->useDbConfig); + if (strpos($field, $db->name($alias)) === 0) { + return $field; + } return $db->name($alias) . '.' . $db->name($field); } /**