dbo_source.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: dbo_source.php 8267 2009-08-01 18:25:25Z gwoo $ */
00003 /**
00004  * Short description for file.
00005  *
00006  * Long description for file
00007  *
00008  * PHP versions 4 and 5
00009  *
00010  * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
00011  * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00012  *
00013  * Licensed under The MIT License
00014  * Redistributions of files must retain the above copyright notice.
00015  *
00016  * @filesource
00017  * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00018  * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00019  * @package       cake
00020  * @subpackage    cake.cake.libs.model.datasources
00021  * @since         CakePHP(tm) v 0.10.0.1076
00022  * @version       $Revision: 8267 $
00023  * @modifiedby    $LastChangedBy: gwoo $
00024  * @lastmodified  $Date: 2009-08-01 14:25:25 -0400 (Sat, 01 Aug 2009) $
00025  * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
00026  */
00027 App::import('Core', array('Set', 'String'));
00028 
00029 /**
00030  * DboSource
00031  *
00032  * Creates DBO-descendant objects from a given db connection configuration
00033  *
00034  * @package       cake
00035  * @subpackage    cake.cake.libs.model.datasources
00036  */
00037 class DboSource extends DataSource {
00038 /**
00039  * Description string for this Database Data Source.
00040  *
00041  * @var unknown_type
00042  */
00043     var $description = "Database Data Source";
00044 /**
00045  * index definition, standard cake, primary, index, unique
00046  *
00047  * @var array
00048  */
00049     var $index = array('PRI' => 'primary', 'MUL' => 'index', 'UNI' => 'unique');
00050 /**
00051  * Database keyword used to assign aliases to identifiers.
00052  *
00053  * @var string
00054  */
00055     var $alias = 'AS ';
00056 /**
00057  * Caches fields quoted in DboSource::name()
00058  *
00059  * @var array
00060  */
00061     var $fieldCache = array();
00062 /**
00063  * Bypass automatic adding of joined fields/associations.
00064  *
00065  * @var boolean
00066  */
00067     var $__bypass = false;
00068 /**
00069  * The set of valid SQL operations usable in a WHERE statement
00070  *
00071  * @var array
00072  */
00073     var $__sqlOps = array('like', 'ilike', 'or', 'not', 'in', 'between', 'regexp', 'similar to');
00074 /**
00075  * Index of basic SQL commands
00076  *
00077  * @var array
00078  * @access protected
00079  */
00080     var $_commands = array(
00081         'begin'    => 'BEGIN',
00082         'commit'   => 'COMMIT',
00083         'rollback' => 'ROLLBACK'
00084     );
00085 /**
00086  * Constructor
00087  */
00088     function __construct($config = null, $autoConnect = true) {
00089         if (!isset($config['prefix'])) {
00090             $config['prefix'] = '';
00091         }
00092         parent::__construct($config);
00093         $this->fullDebug = Configure::read() > 1;
00094 
00095         if ($autoConnect) {
00096             return $this->connect();
00097         } else {
00098             return true;
00099         }
00100     }
00101 /**
00102  * Reconnects to database server with optional new settings
00103  *
00104  * @param array $config An array defining the new configuration settings
00105  * @return boolean True on success, false on failure
00106  */
00107     function reconnect($config = null) {
00108         $this->disconnect();
00109         $this->setConfig($config);
00110         $this->_sources = null;
00111 
00112         return $this->connect();
00113     }
00114 /**
00115  * Prepares a value, or an array of values for database queries by quoting and escaping them.
00116  *
00117  * @param mixed $data A value or an array of values to prepare.
00118  * @param string $column The column into which this data will be inserted
00119  * @param boolean $read Value to be used in READ or WRITE context
00120  * @return mixed Prepared value or array of values.
00121  */
00122     function value($data, $column = null, $read = true) {
00123         if (is_array($data) && !empty($data)) {
00124             return array_map(
00125                 array(&$this, 'value'),
00126                 $data, array_fill(0, count($data), $column), array_fill(0, count($data), $read)
00127             );
00128         } elseif (is_object($data) && isset($data->type)) {
00129             if ($data->type == 'identifier') {
00130                 return $this->name($data->value);
00131             } elseif ($data->type == 'expression') {
00132                 return $data->value;
00133             }
00134         } elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
00135             return $data;
00136         } else {
00137             return null;
00138         }
00139     }
00140 /**
00141  * Returns an object to represent a database identifier in a query
00142  *
00143  * @param string $identifier
00144  * @return object An object representing a database identifier to be used in a query
00145  */
00146     function identifier($identifier) {
00147         $obj = new stdClass();
00148         $obj->type = 'identifier';
00149         $obj->value = $identifier;
00150         return $obj;
00151     }
00152 /**
00153  * Returns an object to represent a database expression in a query
00154  *
00155  * @param string $expression
00156  * @return object An object representing a database expression to be used in a query
00157  */
00158     function expression($expression) {
00159         $obj = new stdClass();
00160         $obj->type = 'expression';
00161         $obj->value = $expression;
00162         return $obj;
00163     }
00164 /**
00165  * Executes given SQL statement.
00166  *
00167  * @param string $sql SQL statement
00168  * @return unknown
00169  */
00170     function rawQuery($sql) {
00171         $this->took = $this->error = $this->numRows = false;
00172         return $this->execute($sql);
00173     }
00174 /**
00175  * Queries the database with given SQL statement, and obtains some metadata about the result
00176  * (rows affected, timing, any errors, number of rows in resultset). The query is also logged.
00177  * If DEBUG is set, the log is shown all the time, else it is only shown on errors.
00178  *
00179  * @param string $sql
00180  * @param array $options
00181  * @return mixed Resource or object representing the result set, or false on failure
00182  */
00183     function execute($sql, $options = array()) {
00184         $defaults = array('stats' => true, 'log' => $this->fullDebug);
00185         $options = array_merge($defaults, $options);
00186 
00187         if ($options['stats']) {
00188             $t = getMicrotime();
00189             $this->_result = $this->_execute($sql);
00190             $this->took = round((getMicrotime() - $t) * 1000, 0);
00191             $this->affected = $this->lastAffected();
00192             $this->error = $this->lastError();
00193             $this->numRows = $this->lastNumRows();
00194         }
00195 
00196         if ($options['log']) {
00197             $this->logQuery($sql);
00198         }
00199 
00200         if ($this->error) {
00201             $this->showQuery($sql);
00202             return false;
00203         }
00204         return $this->_result;
00205     }
00206 /**
00207  * DataSource Query abstraction
00208  *
00209  * @return resource Result resource identifier
00210  */
00211     function query() {
00212         $args     = func_get_args();
00213         $fields   = null;
00214         $order    = null;
00215         $limit    = null;
00216         $page     = null;
00217         $recursive = null;
00218 
00219         if (count($args) == 1) {
00220             return $this->fetchAll($args[0]);
00221 
00222         } elseif (count($args) > 1 && (strpos(strtolower($args[0]), 'findby') === 0 || strpos(strtolower($args[0]), 'findallby') === 0)) {
00223             $params = $args[1];
00224 
00225             if (strpos(strtolower($args[0]), 'findby') === 0) {
00226                 $all  = false;
00227                 $field = Inflector::underscore(preg_replace('/^findBy/i', '', $args[0]));
00228             } else {
00229                 $all  = true;
00230                 $field = Inflector::underscore(preg_replace('/^findAllBy/i', '', $args[0]));
00231             }
00232 
00233             $or = (strpos($field, '_or_') !== false);
00234             if ($or) {
00235                 $field = explode('_or_', $field);
00236             } else {
00237                 $field = explode('_and_', $field);
00238             }
00239             $off = count($field) - 1;
00240 
00241             if (isset($params[1 + $off])) {
00242                 $fields = $params[1 + $off];
00243             }
00244 
00245             if (isset($params[2 + $off])) {
00246                 $order = $params[2 + $off];
00247             }
00248 
00249             if (!array_key_exists(0, $params)) {
00250                 return false;
00251             }
00252 
00253             $c = 0;
00254             $conditions = array();
00255 
00256             foreach ($field as $f) {
00257                 $conditions[$args[2]->alias . '.' . $f] = $params[$c];
00258                 $c++;
00259             }
00260 
00261             if ($or) {
00262                 $conditions = array('OR' => $conditions);
00263             }
00264 
00265             if ($all) {
00266                 if (isset($params[3 + $off])) {
00267                     $limit = $params[3 + $off];
00268                 }
00269 
00270                 if (isset($params[4 + $off])) {
00271                     $page = $params[4 + $off];
00272                 }
00273 
00274                 if (isset($params[5 + $off])) {
00275                     $recursive = $params[5 + $off];
00276                 }
00277                 return $args[2]->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));
00278             } else {
00279                 if (isset($params[3 + $off])) {
00280                     $recursive = $params[3 + $off];
00281                 }
00282                 return $args[2]->find('first', compact('conditions', 'fields', 'order', 'recursive'));
00283             }
00284         } else {
00285             if (isset($args[1]) && $args[1] === true) {
00286                 return $this->fetchAll($args[0], true);
00287             } else if (isset($args[1]) && !is_array($args[1]) ) {
00288                 return $this->fetchAll($args[0], false);
00289             } else if (isset($args[1]) && is_array($args[1])) {
00290                 $offset = 0;
00291                 if (isset($args[2])) {
00292                     $cache = $args[2];
00293                 } else {
00294                     $cache = true;
00295                 }
00296                 $args[1] = array_map(array(&$this, 'value'), $args[1]);
00297                 return $this->fetchAll(String::insert($args[0], $args[1]), $cache);
00298             }
00299         }
00300     }
00301 /**
00302  * Returns a row from current resultset as an array
00303  *
00304  * @return array The fetched row as an array
00305  */
00306     function fetchRow($sql = null) {
00307         if (!empty($sql) && is_string($sql) && strlen($sql) > 5) {
00308             if (!$this->execute($sql)) {
00309                 return null;
00310             }
00311         }
00312 
00313         if ($this->hasResult()) {
00314             $this->resultSet($this->_result);
00315             $resultRow = $this->fetchResult();
00316             return $resultRow;
00317         } else {
00318             return null;
00319         }
00320     }
00321 /**
00322  * Returns an array of all result rows for a given SQL query.
00323  * Returns false if no rows matched.
00324  *
00325  * @param string $sql SQL statement
00326  * @param boolean $cache Enables returning/storing cached query results
00327  * @return array Array of resultset rows, or false if no rows matched
00328  */
00329     function fetchAll($sql, $cache = true, $modelName = null) {
00330         if ($cache && isset($this->_queryCache[$sql])) {
00331             if (preg_match('/^\s*select/i', $sql)) {
00332                 return $this->_queryCache[$sql];
00333             }
00334         }
00335 
00336         if ($this->execute($sql)) {
00337             $out = array();
00338 
00339             $first = $this->fetchRow();
00340             if ($first != null) {
00341                 $out[] = $first;
00342             }
00343             while ($this->hasResult() && $item = $this->fetchResult()) {
00344                 $out[] = $item;
00345             }
00346 
00347             if ($cache) {
00348                 if (strpos(trim(strtolower($sql)), 'select') !== false) {
00349                     $this->_queryCache[$sql] = $out;
00350                 }
00351             }
00352             return $out;
00353 
00354         } else {
00355             return false;
00356         }
00357     }
00358 /**
00359  * Returns a single field of the first of query results for a given SQL query, or false if empty.
00360  *
00361  * @param string $name Name of the field
00362  * @param string $sql SQL query
00363  * @return unknown
00364  */
00365     function field($name, $sql) {
00366         $data = $this->fetchRow($sql);
00367 
00368         if (!isset($data[$name]) || empty($data[$name])) {
00369             return false;
00370         } else {
00371             return $data[$name];
00372         }
00373     }
00374 /**
00375  * Returns a quoted name of $data for use in an SQL statement.
00376  * Strips fields out of SQL functions before quoting.
00377  *
00378  * @param string $data
00379  * @return string SQL field
00380  */
00381     function name($data) {
00382         if ($data == '*') {
00383             return '*';
00384         }
00385         if (is_object($data) && isset($data->type)) {
00386             return $data->value;
00387         }
00388         $array = is_array($data);
00389         $data = (array)$data;
00390         $count = count($data);
00391 
00392         for ($i = 0; $i < $count; $i++) {
00393             if ($data[$i] == '*') {
00394                 continue;
00395             }
00396             if (strpos($data[$i], '(') !== false && preg_match_all('/([^(]*)\((.*)\)(.*)/', $data[$i], $fields)) {
00397                 $fields = Set::extract($fields, '{n}.0');
00398 
00399                 if (!empty($fields[1])) {
00400                     if (!empty($fields[2])) {
00401                         $data[$i] = $fields[1] . '(' . $this->name($fields[2]) . ')' . $fields[3];
00402                     } else {
00403                         $data[$i] = $fields[1] . '()' . $fields[3];
00404                     }
00405                 }
00406             }
00407             $data[$i] = str_replace('.', $this->endQuote . '.' . $this->startQuote, $data[$i]);
00408             $data[$i] = $this->startQuote . $data[$i] . $this->endQuote;
00409             $data[$i] = str_replace($this->startQuote . $this->startQuote, $this->startQuote, $data[$i]);
00410             $data[$i] = str_replace($this->startQuote . '(', '(', $data[$i]);
00411             $data[$i] = str_replace(')' . $this->startQuote, ')', $data[$i]);
00412 
00413             if (preg_match('/\s+AS\s+/', $data[$i])) {
00414                 if (preg_match('/\w+\s+AS\s+/', $data[$i])) {
00415                     $quoted = $this->endQuote . ' AS ' . $this->startQuote;
00416                     $data[$i] = str_replace(' AS ', $quoted, $data[$i]);
00417                 } else {
00418                     $quoted = ' AS ' . $this->startQuote;
00419                     $data[$i] = str_replace(' AS ', $quoted, $data[$i]) . $this->endQuote;
00420                 }
00421             }
00422 
00423             if (!empty($this->endQuote) && $this->endQuote == $this->startQuote) {
00424                 if (substr_count($data[$i], $this->endQuote) % 2 == 1) {
00425                     if (substr($data[$i], -2) == $this->endQuote . $this->endQuote) {
00426                         $data[$i] = substr($data[$i], 0, -1);
00427                     } else {
00428                         $data[$i] = trim($data[$i], $this->endQuote);
00429                     }
00430                 }
00431             }
00432             if (strpos($data[$i], '*')) {
00433                 $data[$i] = str_replace($this->endQuote . '*' . $this->endQuote, '*', $data[$i]);
00434             }
00435             $data[$i] = str_replace($this->endQuote . $this->endQuote, $this->endQuote, $data[$i]);
00436         }
00437         return (!$array) ? $data[0] : $data;
00438     }
00439 /**
00440  * Checks if it's connected to the database
00441  *
00442  * @return boolean True if the database is connected, else false
00443  */
00444     function isConnected() {
00445         return $this->connected;
00446     }
00447 /**
00448  * Checks if the result is valid
00449  *
00450  * @return boolean True if the result is valid else false
00451  */
00452     function hasResult() {
00453         return is_resource($this->_result);
00454     }
00455 /**
00456  * Outputs the contents of the queries log.
00457  *
00458  * @param boolean $sorted
00459  */
00460     function showLog($sorted = false) {
00461         if ($sorted) {
00462             $log = sortByKey($this->_queriesLog, 'took', 'desc', SORT_NUMERIC);
00463         } else {
00464             $log = $this->_queriesLog;
00465         }
00466 
00467         if ($this->_queriesCnt > 1) {
00468             $text = 'queries';
00469         } else {
00470             $text = 'query';
00471         }
00472 
00473         if (PHP_SAPI != 'cli') {
00474             print ("<table class=\"cake-sql-log\" id=\"cakeSqlLog_" . preg_replace('/[^A-Za-z0-9_]/', '_', uniqid(time(), true)) . "\" summary=\"Cake SQL Log\" cellspacing=\"0\" border = \"0\">\n<caption>({$this->configKeyName}) {$this->_queriesCnt} {$text} took {$this->_queriesTime} ms</caption>\n");
00475             print ("<thead>\n<tr><th>Nr</th><th>Query</th><th>Error</th><th>Affected</th><th>Num. rows</th><th>Took (ms)</th></tr>\n</thead>\n<tbody>\n");
00476 
00477             foreach ($log as $k => $i) {
00478                 print ("<tr><td>" . ($k + 1) . "</td><td>" . h($i['query']) . "</td><td>{$i['error']}</td><td style = \"text-align: right\">{$i['affected']}</td><td style = \"text-align: right\">{$i['numRows']}</td><td style = \"text-align: right\">{$i['took']}</td></tr>\n");
00479             }
00480             print ("</tbody></table>\n");
00481         } else {
00482             foreach ($log as $k => $i) {
00483                 print (($k + 1) . ". {$i['query']} {$i['error']}\n");
00484             }
00485         }
00486     }
00487 /**
00488  * Log given SQL query.
00489  *
00490  * @param string $sql SQL statement
00491  * @todo: Add hook to log errors instead of returning false
00492  */
00493     function logQuery($sql) {
00494         $this->_queriesCnt++;
00495         $this->_queriesTime += $this->took;
00496         $this->_queriesLog[] = array(
00497             'query' => $sql,
00498             'error'     => $this->error,
00499             'affected'  => $this->affected,
00500             'numRows'   => $this->numRows,
00501             'took'      => $this->took
00502         );
00503         if (count($this->_queriesLog) > $this->_queriesLogMax) {
00504             array_pop($this->_queriesLog);
00505         }
00506         if ($this->error) {
00507             return false;
00508         }
00509     }
00510 /**
00511  * Output information about an SQL query. The SQL statement, number of rows in resultset,
00512  * and execution time in microseconds. If the query fails, an error is output instead.
00513  *
00514  * @param string $sql Query to show information on.
00515  */
00516     function showQuery($sql) {
00517         $error = $this->error;
00518         if (strlen($sql) > 200 && !$this->fullDebug && Configure::read() > 1) {
00519             $sql = substr($sql, 0, 200) . '[...]';
00520         }
00521         if (Configure::read() > 0) {
00522             $out = null;
00523             if ($error) {
00524                 trigger_error("<span style = \"color:Red;text-align:left\"><b>SQL Error:</b> {$this->error}</span>", E_USER_WARNING);
00525             } else {
00526                 $out = ("<small>[Aff:{$this->affected} Num:{$this->numRows} Took:{$this->took}ms]</small>");
00527             }
00528             pr(sprintf("<p style = \"text-align:left\"><b>Query:</b> %s %s</p>", $sql, $out));
00529         }
00530     }
00531 /**
00532  * Gets full table name including prefix
00533  *
00534  * @param mixed $model
00535  * @param boolean $quote
00536  * @return string Full quoted table name
00537  */
00538     function fullTableName($model, $quote = true) {
00539         if (is_object($model)) {
00540             $table = $model->tablePrefix . $model->table;
00541         } elseif (isset($this->config['prefix'])) {
00542             $table = $this->config['prefix'] . strval($model);
00543         } else {
00544             $table = strval($model);
00545         }
00546         if ($quote) {
00547             return $this->name($table);
00548         }
00549         return $table;
00550     }
00551 /**
00552  * The "C" in CRUD
00553  *
00554  * @param Model $model
00555  * @param array $fields
00556  * @param array $values
00557  * @return boolean Success
00558  */
00559     function create(&$model, $fields = null, $values = null) {
00560         $id = null;
00561 
00562         if ($fields == null) {
00563             unset($fields, $values);
00564             $fields = array_keys($model->data);
00565             $values = array_values($model->data);
00566         }
00567         $count = count($fields);
00568 
00569         for ($i = 0; $i < $count; $i++) {
00570             $valueInsert[] = $this->value($values[$i], $model->getColumnType($fields[$i]), false);
00571         }
00572         for ($i = 0; $i < $count; $i++) {
00573             $fieldInsert[] = $this->name($fields[$i]);
00574             if ($fields[$i] == $model->primaryKey) {
00575                 $id = $values[$i];
00576             }
00577         }
00578         $query = array(
00579             'table' => $this->fullTableName($model),
00580             'fields' => join(', ', $fieldInsert),
00581             'values' => join(', ', $valueInsert)
00582         );
00583 
00584         if ($this->execute($this->renderStatement('create', $query))) {
00585             if (empty($id)) {
00586                 $id = $this->lastInsertId($this->fullTableName($model, false), $model->primaryKey);
00587             }
00588             $model->setInsertID($id);
00589             $model->id = $id;
00590             return true;
00591         } else {
00592             $model->onError();
00593             return false;
00594         }
00595     }
00596 /**
00597  * The "R" in CRUD
00598  *
00599  * @param Model $model
00600  * @param array $queryData
00601  * @param integer $recursive Number of levels of association
00602  * @return unknown
00603  */
00604     function read(&$model, $queryData = array(), $recursive = null) {
00605         $queryData = $this->__scrubQueryData($queryData);
00606 
00607         $null = null;
00608         $array = array();
00609         $linkedModels = array();
00610         $this->__bypass = false;
00611         $this->__booleans = array();
00612 
00613         if ($recursive === null && isset($queryData['recursive'])) {
00614             $recursive = $queryData['recursive'];
00615         }
00616 
00617         if (!is_null($recursive)) {
00618             $_recursive = $model->recursive;
00619             $model->recursive = $recursive;
00620         }
00621 
00622         if (!empty($queryData['fields'])) {
00623             $this->__bypass = true;
00624             $queryData['fields'] = $this->fields($model, null, $queryData['fields']);
00625         } else {
00626             $queryData['fields'] = $this->fields($model);
00627         }
00628 
00629         $_associations = $model->__associations;
00630 
00631         if ($model->recursive == -1) {
00632             $_associations = array();
00633         } else if ($model->recursive == 0) {
00634             unset($_associations[2], $_associations[3]);
00635         }
00636 
00637         foreach ($_associations as $type) {
00638             foreach ($model->{$type} as $assoc => $assocData) {
00639                 $linkModel =& $model->{$assoc};
00640                 $external = isset($assocData['external']);
00641 
00642                 if ($model->useDbConfig == $linkModel->useDbConfig) {
00643                     if (true === $this->generateAssociationQuery($model, $linkModel, $type, $assoc, $assocData, $queryData, $external, $null)) {
00644                         $linkedModels[$type . '/' . $assoc] = true;
00645                     }
00646                 }
00647             }
00648         }
00649 
00650         $query = $this->generateAssociationQuery($model, $null, null, null, null, $queryData, false, $null);
00651 
00652         $resultSet = $this->fetchAll($query, $model->cacheQueries, $model->alias);
00653 
00654         if ($resultSet === false) {
00655             $model->onError();
00656             return false;
00657         }
00658 
00659         $filtered = $this->__filterResults($resultSet, $model);
00660 
00661         if ($model->recursive > -1) {
00662             foreach ($_associations as $type) {
00663                 foreach ($model->{$type} as $assoc => $assocData) {
00664                     $linkModel =& $model->{$assoc};
00665 
00666                     if (empty($linkedModels[$type . '/' . $assoc])) {
00667                         if ($model->useDbConfig == $linkModel->useDbConfig) {
00668                             $db =& $this;
00669                         } else {
00670                             $db =& ConnectionManager::getDataSource($linkModel->useDbConfig);
00671                         }
00672                     } elseif ($model->recursive > 1 && ($type == 'belongsTo' || $type == 'hasOne')) {
00673                         $db =& $this;
00674                     }
00675 
00676                     if (isset($db)) {
00677                         $stack = array($assoc);
00678                         $db->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $array, true, $resultSet, $model->recursive - 1, $stack);
00679                         unset($db);
00680                     }
00681                 }
00682             }
00683             $this->__filterResults($resultSet, $model, $filtered);
00684         }
00685 
00686         if (!is_null($recursive)) {
00687             $model->recursive = $_recursive;
00688         }
00689         return $resultSet;
00690     }
00691 /**
00692  * Private method.  Passes association results thru afterFind filters of corresponding model
00693  *
00694  * @param array $results Reference of resultset to be filtered
00695  * @param object $model Instance of model to operate against
00696  * @param array $filtered List of classes already filtered, to be skipped
00697  * @return return
00698  */
00699     function __filterResults(&$results, &$model, $filtered = array()) {
00700         $filtering = array();
00701         $count = count($results);
00702 
00703         for ($i = 0; $i < $count; $i++) {
00704             if (is_array($results[$i])) {
00705                 $classNames = array_keys($results[$i]);
00706                 $count2 = count($classNames);
00707 
00708                 for ($j = 0; $j < $count2; $j++) {
00709                     $className = $classNames[$j];
00710                     if ($model->alias != $className && !in_array($className, $filtered)) {
00711                         if (!in_array($className, $filtering)) {
00712                             $filtering[] = $className;
00713                         }
00714 
00715                         if (isset($model->{$className}) && is_object($model->{$className})) {
00716                             $data = $model->{$className}->afterFind(array(array($className => $results[$i][$className])), false);
00717                         }
00718                         if (isset($data[0][$className])) {
00719                             $results[$i][$className] = $data[0][$className];
00720                         }
00721                     }
00722                 }
00723             }
00724         }
00725         return $filtering;
00726     }
00727 /**
00728  * Enter description here...
00729  *
00730  * @param Model $model
00731  * @param unknown_type $linkModel
00732  * @param string $type Association type
00733  * @param unknown_type $association
00734  * @param unknown_type $assocData
00735  * @param unknown_type $queryData
00736  * @param unknown_type $external
00737  * @param unknown_type $resultSet
00738  * @param integer $recursive Number of levels of association
00739  * @param array $stack
00740  */
00741     function queryAssociation(&$model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive, $stack) {
00742         if ($query = $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet)) {
00743             if (!isset($resultSet) || !is_array($resultSet)) {
00744                 if (Configure::read() > 0) {
00745                     echo '<div style = "font: Verdana bold 12px; color: #FF0000">' . sprintf(__('SQL Error in model %s:', true), $model->alias) . ' ';
00746                     if (isset($this->error) && $this->error != null) {
00747                         echo $this->error;
00748                     }
00749                     echo '</div>';
00750                 }
00751                 return null;
00752             }
00753             $count = count($resultSet);
00754 
00755             if ($type === 'hasMany' && empty($assocData['limit']) && !empty($assocData['foreignKey'])) {
00756                 $ins = $fetch = array();
00757                 for ($i = 0; $i < $count; $i++) {
00758                     if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) {
00759                         $ins[] = $in;
00760                     }
00761                 }
00762 
00763                 if (!empty($ins)) {
00764                     $fetch = $this->fetchAssociated($model, $query, $ins);
00765                 }
00766 
00767                 if (!empty($fetch) && is_array($fetch)) {
00768                     if ($recursive > 0) {
00769                         foreach ($linkModel->__associations as $type1) {
00770                             foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
00771                                 $deepModel =& $linkModel->{$assoc1};
00772                                 $tmpStack = $stack;
00773                                 $tmpStack[] = $assoc1;
00774 
00775                                 if ($linkModel->useDbConfig === $deepModel->useDbConfig) {
00776                                     $db =& $this;
00777                                 } else {
00778                                     $db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
00779                                 }
00780                                 $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
00781                             }
00782                         }
00783                     }
00784                 }
00785                 $this->__filterResults($fetch, $model);
00786                 return $this->__mergeHasMany($resultSet, $fetch, $association, $model, $linkModel, $recursive);
00787             } elseif ($type === 'hasAndBelongsToMany') {
00788                 $ins = $fetch = array();
00789                 for ($i = 0; $i < $count; $i++) {
00790                     if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) {
00791                         $ins[] = $in;
00792                     }
00793                 }
00794                 if (!empty($ins)) {
00795                     if (count($ins) > 1) {
00796                         $query = str_replace('{$__cakeID__$}', '(' .join(', ', $ins) .')', $query);
00797                         $query = str_replace('= (', 'IN (', $query);
00798                         $query = str_replace('=  (', 'IN (', $query);
00799                     } else {
00800                         $query = str_replace('{$__cakeID__$}',$ins[0], $query);
00801                     }
00802 
00803                     $query = str_replace('  WHERE 1 = 1', '', $query);
00804                 }
00805 
00806                 $foreignKey = $model->hasAndBelongsToMany[$association]['foreignKey'];
00807                 $joinKeys = array($foreignKey, $model->hasAndBelongsToMany[$association]['associationForeignKey']);
00808                 list($with, $habtmFields) = $model->joinModel($model->hasAndBelongsToMany[$association]['with'], $joinKeys);
00809                 $habtmFieldsCount = count($habtmFields);
00810                 $q = $this->insertQueryData($query, null, $association, $assocData, $model, $linkModel, $stack);
00811 
00812                 if ($q != false) {
00813                     $fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias);
00814                 } else {
00815                     $fetch = null;
00816                 }
00817             }
00818 
00819             for ($i = 0; $i < $count; $i++) {
00820                 $row =& $resultSet[$i];
00821 
00822                 if ($type !== 'hasAndBelongsToMany') {
00823                     $q = $this->insertQueryData($query, $resultSet[$i], $association, $assocData, $model, $linkModel, $stack);
00824                     if ($q != false) {
00825                         $fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias);
00826                     } else {
00827                         $fetch = null;
00828                     }
00829                 }
00830                 $selfJoin = false;
00831 
00832                 if ($linkModel->name === $model->name) {
00833                     $selfJoin = true;
00834                 }
00835 
00836                 if (!empty($fetch) && is_array($fetch)) {
00837                     if ($recursive > 0) {
00838                         foreach ($linkModel->__associations as $type1) {
00839                             foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
00840                                 $deepModel =& $linkModel->{$assoc1};
00841 
00842                                 if (($type1 === 'belongsTo') || ($deepModel->alias === $model->alias && $type === 'belongsTo') || ($deepModel->alias != $model->alias)) {
00843                                     $tmpStack = $stack;
00844                                     $tmpStack[] = $assoc1;
00845                                     if ($linkModel->useDbConfig == $deepModel->useDbConfig) {
00846                                         $db =& $this;
00847                                     } else {
00848                                         $db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
00849                                     }
00850                                     $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
00851                                 }
00852                             }
00853                         }
00854                     }
00855                     if ($type == 'hasAndBelongsToMany') {
00856                         $uniqueIds = $merge = array();
00857 
00858                         foreach ($fetch as $j => $data) {
00859                             if (
00860                                 (isset($data[$with]) && $data[$with][$foreignKey] === $row[$model->alias][$model->primaryKey])
00861                             ) {
00862                                 if ($habtmFieldsCount <= 2) {
00863                                     unset($data[$with]);
00864                                 }
00865                                 $merge[] = $data;
00866                             }
00867                         }
00868                         if (empty($merge) && !isset($row[$association])) {
00869                             $row[$association] = $merge;
00870                         } else {
00871                             $this->__mergeAssociation($resultSet[$i], $merge, $association, $type);
00872                         }
00873                     } else {
00874                         $this->__mergeAssociation($resultSet[$i], $fetch, $association, $type, $selfJoin);
00875                     }
00876                     if (isset($resultSet[$i][$association])) {
00877                         $resultSet[$i][$association] = $linkModel->afterFind($resultSet[$i][$association]);
00878                     }
00879                 } else {
00880                     $tempArray[0][$association] = false;
00881                     $this->__mergeAssociation($resultSet[$i], $tempArray, $association, $type, $selfJoin);
00882                 }
00883             }
00884         }
00885     }
00886 /**
00887  * A more efficient way to fetch associations.  Woohoo!
00888  *
00889  * @param model $model      Primary model object
00890  * @param string $query     Association query
00891  * @param array $ids        Array of IDs of associated records
00892  * @return array Association results
00893  */
00894     function fetchAssociated($model, $query, $ids) {
00895         $query = str_replace('{$__cakeID__$}', join(', ', $ids), $query);
00896         if (count($ids) > 1) {
00897             $query = str_replace('= (', 'IN (', $query);
00898             $query = str_replace('=  (', 'IN (', $query);
00899         }
00900         return $this->fetchAll($query, $model->cacheQueries, $model->alias);
00901     }
00902 /**
00903  * mergeHasMany - Merge the results of hasMany relations.
00904  *
00905  *
00906  * @param array $resultSet Data to merge into
00907  * @param array $merge Data to merge
00908  * @param string $association Name of Model being Merged
00909  * @param object $model Model being merged onto
00910  * @param object $linkModel Model being merged
00911  * @return void
00912  **/
00913     function __mergeHasMany(&$resultSet, $merge, $association, &$model, &$linkModel) {
00914         foreach ($resultSet as $i => $value) {
00915             $count = 0;
00916             $merged[$association] = array();
00917             foreach ($merge as $j => $data) {
00918                 if (isset($value[$model->alias]) && $value[$model->alias][$model->primaryKey] === $data[$association][$model->hasMany[$association]['foreignKey']]) {
00919                     if (count($data) > 1) {
00920                         $data = array_merge($data[$association], $data);
00921                         unset($data[$association]);
00922                         foreach ($data as $key => $name) {
00923                             if (is_numeric($key)) {
00924                                 $data[$association][] = $name;
00925                                 unset($data[$key]);
00926                             }
00927                         }
00928                         $merged[$association][] = $data;
00929                     } else {
00930                         $merged[$association][] = $data[$association];
00931                     }
00932                 }
00933                 $count++;
00934             }
00935             if (isset($value[$model->alias])) {
00936                 $resultSet[$i] = Set::pushDiff($resultSet[$i], $merged);
00937                 unset($merged);
00938             }
00939         }
00940     }
00941 /**
00942  * Enter description here...
00943  *
00944  * @param unknown_type $data
00945  * @param unknown_type $merge
00946  * @param unknown_type $association
00947  * @param unknown_type $type
00948  * @param boolean $selfJoin
00949  */
00950     function __mergeAssociation(&$data, $merge, $association, $type, $selfJoin = false) {
00951         if (isset($merge[0]) && !isset($merge[0][$association])) {
00952             $association = Inflector::pluralize($association);
00953         }
00954 
00955         if ($type == 'belongsTo' || $type == 'hasOne') {
00956             if (isset($merge[$association])) {
00957                 $data[$association] = $merge[$association][0];
00958             } else {
00959                 if (count($merge[0][$association]) > 1) {
00960                     foreach ($merge[0] as $assoc => $data2) {
00961                         if ($assoc != $association) {
00962                             $merge[0][$association][$assoc] = $data2;
00963                         }
00964                     }
00965                 }
00966                 if (!isset($data[$association])) {
00967                     if ($merge[0][$association] != null) {
00968                         $data[$association] = $merge[0][$association];
00969                     } else {
00970                         $data[$association] = array();
00971                     }
00972                 } else {
00973                     if (is_array($merge[0][$association])) {
00974                         foreach ($data[$association] as $k => $v) {
00975                             if (!is_array($v)) {
00976                                 $dataAssocTmp[$k] = $v;
00977                             }
00978                         }
00979 
00980                         foreach ($merge[0][$association] as $k => $v) {
00981                             if (!is_array($v)) {
00982                                 $mergeAssocTmp[$k] = $v;
00983                             }
00984                         }
00985                         $dataKeys = array_keys($data);
00986                         $mergeKeys = array_keys($merge[0]);
00987 
00988                         if ($mergeKeys[0] === $dataKeys[0] || $mergeKeys === $dataKeys) {
00989                             $data[$association][$association] = $merge[0][$association];
00990                         } else {
00991                             $diff = Set::diff($dataAssocTmp, $mergeAssocTmp);
00992                             $data[$association] = array_merge($merge[0][$association], $diff);
00993                         }
00994                     } elseif ($selfJoin && array_key_exists($association, $merge[0])) {
00995                         $data[$association] = array_merge($data[$association], array($association => array()));
00996                     }
00997                 }
00998             }
00999         } else {
01000             if (isset($merge[0][$association]) && $merge[0][$association] === false) {
01001                 if (!isset($data[$association])) {
01002                     $data[$association] = array();
01003                 }
01004             } else {
01005                 foreach ($merge as $i => $row) {
01006                     if (count($row) == 1) {
01007                         if (empty($data[$association]) || (isset($data[$association]) && !in_array($row[$association], $data[$association]))) {
01008                             $data[$association][] = $row[$association];
01009                         }
01010                     } else if (!empty($row)) {
01011                         $tmp = array_merge($row[$association], $row);
01012                         unset($tmp[$association]);
01013                         $data[$association][] = $tmp;
01014                     }
01015                 }
01016             }
01017         }
01018     }
01019 /**
01020  * Generates an array representing a query or part of a query from a single model or two associated models
01021  *
01022  * @param Model $model
01023  * @param Model $linkModel
01024  * @param string $type
01025  * @param string $association
01026  * @param array $assocData
01027  * @param array $queryData
01028  * @param boolean $external
01029  * @param array $resultSet
01030  * @return mixed
01031  */
01032     function generateAssociationQuery(&$model, &$linkModel, $type, $association = null, $assocData = array(), &$queryData, $external = false, &$resultSet) {
01033         $queryData = $this->__scrubQueryData($queryData);
01034         $assocData = $this->__scrubQueryData($assocData);
01035 
01036         if (empty($queryData['fields'])) {
01037             $queryData['fields'] = $this->fields($model, $model->alias);
01038         } elseif (!empty($model->hasMany) && $model->recursive > -1) {
01039             $assocFields = $this->fields($model, $model->alias, array("{$model->alias}.{$model->primaryKey}"));
01040             $passedFields = $this->fields($model, $model->alias, $queryData['fields']);
01041 
01042             if (count($passedFields) === 1) {
01043                 $match = strpos($passedFields[0], $assocFields[0]);
01044                 $match1 = strpos($passedFields[0], 'COUNT(');
01045                 if ($match === false && $match1 === false) {
01046                     $queryData['fields'] = array_merge($passedFields, $assocFields);
01047                 } else {
01048                     $queryData['fields'] = $passedFields;
01049                 }
01050             } else {
01051                 $queryData['fields'] = array_merge($passedFields, $assocFields);
01052             }
01053             unset($assocFields, $passedFields);
01054         }
01055 
01056         if ($linkModel == null) {
01057             return $this->buildStatement(
01058                 array(
01059                     'fields' => array_unique($queryData['fields']),
01060                     'table' => $this->fullTableName($model),
01061                     'alias' => $model->alias,
01062                     'limit' => $queryData['limit'],
01063                     'offset' => $queryData['offset'],
01064                     'joins' => $queryData['joins'],
01065                     'conditions' => $queryData['conditions'],
01066                     'order' => $queryData['order'],
01067                     'group' => $queryData['group']
01068                 ),
01069                 $model
01070             );
01071         }
01072         if ($external && !empty($assocData['finderQuery'])) {
01073             return $assocData['finderQuery'];
01074         }
01075 
01076         $alias = $association;
01077         $self = ($model->name == $linkModel->name);
01078         $fields = array();
01079 
01080         if ((!$external && in_array($type, array('hasOne', 'belongsTo')) && $this->__bypass === false) || $external) {
01081             $fields = $this->fields($linkModel, $alias, $assocData['fields']);
01082         }
01083         if (empty($assocData['offset']) && !empty($assocData['page'])) {
01084             $assocData['offset'] = ($assocData['page'] - 1) * $assocData['limit'];
01085         }
01086         $assocData['limit'] = $this->limit($assocData['limit'], $assocData['offset']);
01087 
01088         switch ($type) {
01089             case 'hasOne':
01090             case 'belongsTo':
01091                 $conditions = $this->__mergeConditions(
01092                     $assocData['conditions'],
01093                     $this->getConstraint($type, $model, $linkModel, $alias, array_merge($assocData, compact('external', 'self')))
01094                 );
01095 
01096                 if (!$self && $external) {
01097                     foreach ($conditions as $key => $condition) {
01098                         if (is_numeric($key) && strpos($condition, $model->alias . '.') !== false) {
01099                             unset($conditions[$key]);
01100                         }
01101                     }
01102                 }
01103 
01104                 if ($external) {
01105                     $query = array_merge($assocData, array(
01106                         'conditions' => $conditions,
01107                         'table' => $this->fullTableName($linkModel),
01108                         'fields' => $fields,
01109                         'alias' => $alias,
01110                         'group' => null
01111                     ));
01112                     $query = array_merge(array('order' => $assocData['order'], 'limit' => $assocData['limit']), $query);
01113                 } else {
01114                     $join = array(
01115                         'table' => $this->fullTableName($linkModel),
01116                         'alias' => $alias,
01117                         'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT',
01118                         'conditions' => trim($this->conditions($conditions, true, false, $model))
01119                     );
01120                     $queryData['fields'] = array_merge($queryData['fields'], $fields);
01121 
01122                     if (!empty($assocData['order'])) {
01123                         $queryData['order'][] = $assocData['order'];
01124                     }
01125                     if (!in_array($join, $queryData['joins'])) {
01126                         $queryData['joins'][] = $join;
01127                     }
01128                     return true;
01129                 }
01130             break;
01131             case 'hasMany':
01132                 $assocData['fields'] = $this->fields($linkModel, $alias, $assocData['fields']);
01133                 if (!empty($assocData['foreignKey'])) {
01134                     $assocData['fields'] = array_merge($assocData['fields'], $this->fields($linkModel, $alias, array("{$alias}.{$assocData['foreignKey']}")));
01135                 }
01136                 $query = array(
01137                     'conditions' => $this->__mergeConditions($this->getConstraint('hasMany', $model, $linkModel, $alias, $assocData), $assocData['conditions']),
01138                     'fields' => array_unique($assocData['fields']),
01139                     'table' => $this->fullTableName($linkModel),
01140                     'alias' => $alias,
01141                     'order' => $assocData['order'],
01142                     'limit' => $assocData['limit'],
01143                     'group' => null
01144                 );
01145             break;
01146             case 'hasAndBelongsToMany':
01147                 $joinFields = array();
01148                 $joinAssoc = null;
01149 
01150                 if (isset($assocData['with']) && !empty($assocData['with'])) {
01151                     $joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']);
01152                     list($with, $joinFields) = $model->joinModel($assocData['with'], $joinKeys);
01153 
01154                     $joinTbl = $this->fullTableName($model->{$with});
01155                     $joinAlias = $joinTbl;
01156 
01157                     if (is_array($joinFields) && !empty($joinFields)) {
01158                         $joinFields = $this->fields($model->{$with}, $model->{$with}->alias, $joinFields);
01159                         $joinAssoc = $joinAlias = $model->{$with}->alias;
01160                     } else {
01161                         $joinFields = array();
01162                     }
01163                 } else {
01164                     $joinTbl = $this->fullTableName($assocData['joinTable']);
01165                     $joinAlias = $joinTbl;
01166                 }
01167                 $query = array(
01168                     'conditions' => $assocData['conditions'],
01169                     'limit' => $assocData['limit'],
01170                     'table' => $this->fullTableName($linkModel),
01171                     'alias' => $alias,
01172                     'fields' => array_merge($this->fields($linkModel, $alias, $assocData['fields']), $joinFields),
01173                     'order' => $assocData['order'],
01174                     'group' => null,
01175                     'joins' => array(array(
01176                         'table' => $joinTbl,
01177                         'alias' => $joinAssoc,
01178                         'conditions' => $this->getConstraint('hasAndBelongsToMany', $model, $linkModel, $joinAlias, $assocData, $alias)
01179                     ))
01180                 );
01181             break;
01182         }
01183         if (isset($query)) {
01184             return $this->buildStatement($query, $model);
01185         }
01186         return null;
01187     }
01188 /**
01189  * Returns a conditions array for the constraint between two models
01190  *
01191  * @param string $type Association type
01192  * @param object $model Model object
01193  * @param array $association Association array
01194  * @return array Conditions array defining the constraint between $model and $association
01195  */
01196     function getConstraint($type, $model, $linkModel, $alias, $assoc, $alias2 = null) {
01197         $assoc = array_merge(array('external' => false, 'self' => false), $assoc);
01198 
01199         if (array_key_exists('foreignKey', $assoc) && empty($assoc['foreignKey'])) {
01200             return array();
01201         }
01202 
01203         switch (true) {
01204             case ($assoc['external'] && $type == 'hasOne'):
01205                 return array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}');
01206             break;
01207             case ($assoc['external'] && $type == 'belongsTo'):
01208                 return array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}');
01209             break;
01210             case (!$assoc['external'] && $type == 'hasOne'):
01211                 return array("{$alias}.{$assoc['foreignKey']}" => $this->identifier("{$model->alias}.{$model->primaryKey}"));
01212             break;
01213             case (!$assoc['external'] && $type == 'belongsTo'):
01214                 return array("{$model->alias}.{$assoc['foreignKey']}" => $this->identifier("{$alias}.{$linkModel->primaryKey}"));
01215             break;
01216             case ($type == 'hasMany'):
01217                 return array("{$alias}.{$assoc['foreignKey']}" => array('{$__cakeID__$}'));
01218             break;
01219             case ($type == 'hasAndBelongsToMany'):
01220                 return array(
01221                     array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'),
01222                     array("{$alias}.{$assoc['associationForeignKey']}" => $this->identifier("{$alias2}.{$linkModel->primaryKey}"))
01223                 );
01224             break;
01225         }
01226         return array();
01227     }
01228 /**
01229  * Builds and generates a JOIN statement from an array.  Handles final clean-up before conversion.
01230  *
01231  * @param array $join An array defining a JOIN statement in a query
01232  * @return string An SQL JOIN statement to be used in a query
01233  * @see DboSource::renderJoinStatement()
01234  * @see DboSource::buildStatement()
01235  */
01236     function buildJoinStatement($join) {
01237         $data = array_merge(array(
01238             'type' => null,
01239             'alias' => null,
01240             'table' => 'join_table',
01241             'conditions' => array()
01242         ), $join);
01243 
01244         if (!empty($data['alias'])) {
01245             $data['alias'] = $this->alias . $this->name($data['alias']);
01246         }
01247         if (!empty($data['conditions'])) {
01248             $data['conditions'] = trim($this->conditions($data['conditions'], true, false));
01249         }
01250         return $this->renderJoinStatement($data);
01251     }
01252 /**
01253  * Builds and generates an SQL statement from an array.  Handles final clean-up before conversion.
01254  *
01255  * @param array $query An array defining an SQL query
01256  * @param object $model The model object which initiated the query
01257  * @return string An executable SQL statement
01258  * @see DboSource::renderStatement()
01259  */
01260     function buildStatement($query, $model) {
01261         $query = array_merge(array('offset' => null, 'joins' => array()), $query);
01262         if (!empty($query['joins'])) {
01263             $count = count($query['joins']);
01264             for ($i = 0; $i < $count; $i++) {
01265                 if (is_array($query['joins'][$i])) {
01266                     $query['joins'][$i] = $this->buildJoinStatement($query['joins'][$i]);
01267                 }
01268             }
01269         }
01270         return $this->renderStatement('select', array(
01271             'conditions' => $this->conditions($query['conditions'], true, true, $model),
01272             'fields' => join(', ', $query['fields']),
01273             'table' => $query['table'],
01274             'alias' => $this->alias . $this->name($query['alias']),
01275             'order' => $this->order($query['order']),
01276             'limit' => $this->limit($query['limit'], $query['offset']),
01277             'joins' => join(' ', $query['joins']),
01278             'group' => $this->group($query['group'])
01279         ));
01280     }
01281 /**
01282  * Renders a final SQL JOIN statement
01283  *
01284  * @param array $data
01285  * @return string
01286  */
01287     function renderJoinStatement($data) {
01288         extract($data);
01289         return trim("{$type} JOIN {$table} {$alias} ON ({$conditions})");
01290     }
01291 /**
01292  * Renders a final SQL statement by putting together the component parts in the correct order
01293  *
01294  * @param string $type
01295  * @param array $data
01296  * @return string
01297  */
01298     function renderStatement($type, $data) {
01299         extract($data);
01300         $aliases = null;
01301 
01302         switch (strtolower($type)) {
01303             case 'select':
01304                 return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}";
01305             break;
01306             case 'create':
01307                 return "INSERT INTO {$table} ({$fields}) VALUES ({$values})";
01308             break;
01309             case 'update':
01310                 if (!empty($alias)) {
01311                     $aliases = "{$this->alias}{$alias} {$joins} ";
01312                 }
01313                 return "UPDATE {$table} {$aliases}SET {$fields} {$conditions}";
01314             break;
01315             case 'delete':
01316                 if (!empty($alias)) {
01317                     $aliases = "{$this->alias}{$alias} {$joins} ";
01318                 }
01319                 return "DELETE {$alias} FROM {$table} {$aliases}{$conditions}";
01320             break;
01321             case 'schema':
01322                 foreach (array('columns', 'indexes') as $var) {
01323                     if (is_array(${$var})) {
01324                         ${$var} = "\t" . join(",\n\t", array_filter(${$var}));
01325                     }
01326                 }
01327                 if (trim($indexes) != '') {
01328                     $columns .= ',';
01329                 }
01330                 return "CREATE TABLE {$table} (\n{$columns}{$indexes});";
01331             break;
01332             case 'alter':
01333             break;
01334         }
01335     }
01336 /**
01337  * Merges a mixed set of string/array conditions
01338  *
01339  * @return array
01340  */
01341     function __mergeConditions($query, $assoc) {
01342         if (empty($assoc)) {
01343             return $query;
01344         }
01345 
01346         if (is_array($query)) {
01347             return array_merge((array)$assoc, $query);
01348         }
01349 
01350         if (!empty($query)) {
01351             $query = array($query);
01352             if (is_array($assoc)) {
01353                 $query = array_merge($query, $assoc);
01354             } else {
01355                 $query[] = $assoc;
01356             }
01357             return $query;
01358         }
01359 
01360         return $assoc;
01361     }
01362 /**
01363  * Generates and executes an SQL UPDATE statement for given model, fields, and values.
01364  * For databases that do not support aliases in UPDATE queries.
01365  *
01366  * @param Model $model
01367  * @param array $fields
01368  * @param array $values
01369  * @param mixed $conditions
01370  * @return boolean Success
01371  */
01372     function update(&$model, $fields = array(), $values = null, $conditions = null) {
01373         if ($values == null) {
01374             $combined = $fields;
01375         } else {
01376             $combined = array_combine($fields, $values);
01377         }
01378         $fields = join(', ', $this->_prepareUpdateFields($model, $combined, empty($conditions)));
01379 
01380         $alias = $joins = null;
01381         $table = $this->fullTableName($model);
01382         $conditions = $this->_matchRecords($model, $conditions);
01383 
01384         if ($conditions === false) {
01385             return false;
01386         }
01387         $query = compact('table', 'alias', 'joins', 'fields', 'conditions');
01388 
01389         if (!$this->execute($this->renderStatement('update', $query))) {
01390             $model->onError();
01391             return false;
01392         }
01393         return true;
01394     }
01395 /**
01396  * Quotes and prepares fields and values for an SQL UPDATE statement
01397  *
01398  * @param Model $model
01399  * @param array $fields
01400  * @param boolean $quoteValues If values should be quoted, or treated as SQL snippets
01401  * @param boolean $alias Include the model alias in the field name
01402  * @return array Fields and values, quoted and preparted
01403  * @access protected
01404  */
01405     function _prepareUpdateFields(&$model, $fields, $quoteValues = true, $alias = false) {
01406         $quotedAlias = $this->startQuote . $model->alias . $this->endQuote;
01407 
01408         $updates = array();
01409         foreach ($fields as $field => $value) {
01410             if ($alias && strpos($field, '.') === false) {
01411                 $quoted = $model->escapeField($field);
01412             } elseif (!$alias && strpos($field, '.') !== false) {
01413                 $quoted = $this->name(str_replace($quotedAlias . '.', '', str_replace(
01414                     $model->alias . '.', '', $field
01415                 )));
01416             } else {
01417                 $quoted = $this->name($field);
01418             }
01419 
01420             if ($value === null) {
01421                 $updates[] = $quoted . ' = NULL';
01422                 continue;
01423             }
01424             $update = $quoted . ' = ';
01425 
01426             if ($quoteValues) {
01427                 $update .= $this->value($value, $model->getColumnType($field), false);
01428             } elseif (!$alias) {
01429                 $update .= str_replace($quotedAlias . '.', '', str_replace(
01430                     $model->alias . '.', '', $value
01431                 ));
01432             } else {
01433                 $update .= $value;
01434             }
01435             $updates[] =  $update;
01436         }
01437         return $updates;
01438     }
01439 /**
01440  * Generates and executes an SQL DELETE statement.
01441  * For databases that do not support aliases in UPDATE queries.
01442  *
01443  * @param Model $model
01444  * @param mixed $conditions
01445  * @return boolean Success
01446  */
01447     function delete(&$model, $conditions = null) {
01448         $alias = $joins = null;
01449         $table = $this->fullTableName($model);
01450         $conditions = $this->_matchRecords($model, $conditions);
01451 
01452         if ($conditions === false) {
01453             return false;
01454         }
01455 
01456         if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) {
01457             $model->onError();
01458             return false;
01459         }
01460         return true;
01461     }
01462 /**
01463  * Gets a list of record IDs for the given conditions.  Used for multi-record updates and deletes
01464  * in databases that do not support aliases in UPDATE/DELETE queries.
01465  *
01466  * @param Model $model
01467  * @param mixed $conditions
01468  * @return array List of record IDs
01469  * @access protected
01470  */
01471     function _matchRecords(&$model, $conditions = null) {
01472         if ($conditions === true) {
01473             $conditions = $this->conditions(true);
01474         } elseif ($conditions === null) {
01475             $conditions = $this->conditions($this->defaultConditions($model, $conditions, false), true, true, $model);
01476         } else {
01477             $idList = $model->find('all', array(
01478                 'fields' => "{$model->alias}.{$model->primaryKey}",
01479                 'conditions' => $conditions
01480             ));
01481 
01482             if (empty($idList)) {
01483                 return false;
01484             }
01485             $conditions = $this->conditions(array(
01486                 $model->primaryKey => Set::extract($idList, "{n}.{$model->alias}.{$model->primaryKey}")
01487             ));
01488         }
01489         return $conditions;
01490     }
01491 /**
01492  * Returns an array of SQL JOIN fragments from a model's associations
01493  *
01494  * @param object $model
01495  * @return array
01496  */
01497     function _getJoins($model) {
01498         $join = array();
01499         $joins = array_merge($model->getAssociated('hasOne'), $model->getAssociated('belongsTo'));
01500 
01501         foreach ($joins as $assoc) {
01502             if (isset($model->{$assoc}) && $model->useDbConfig == $model->{$assoc}->useDbConfig) {
01503                 $assocData = $model->getAssociated($assoc);
01504                 $join[] = $this->buildJoinStatement(array(
01505                     'table' => $this->fullTableName($model->{$assoc}),
01506                     'alias' => $assoc,
01507                     'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT',
01508                     'conditions' => trim($this->conditions(
01509                         $this->getConstraint($assocData['association'], $model, $model->{$assoc}, $assoc, $assocData),
01510                         true, false, $model
01511                     ))
01512                 ));
01513             }
01514         }
01515         return $join;
01516     }
01517 /**
01518  * Returns an SQL calculation, i.e. COUNT() or MAX()
01519  *
01520  * @param model $model
01521  * @param string $func Lowercase name of SQL function, i.e. 'count' or 'max'
01522  * @param array $params Function parameters (any values must be quoted manually)
01523  * @return string An SQL calculation function
01524  * @access public
01525  */
01526     function calculate(&$model, $func, $params = array()) {
01527         $params = (array)$params;
01528 
01529         switch (strtolower($func)) {
01530             case 'count':
01531                 if (!isset($params[0])) {
01532                     $params[0] = '*';
01533                 }
01534                 if (!isset($params[1])) {
01535                     $params[1] = 'count';
01536                 }
01537                 return 'COUNT(' . $this->name($params[0]) . ') AS ' . $this->name($params[1]);
01538             case 'max':
01539             case 'min':
01540                 if (!isset($params[1])) {
01541                     $params[1] = $params[0];
01542                 }
01543                 return strtoupper($func) . '(' . $this->name($params[0]) . ') AS ' . $this->name($params[1]);
01544             break;
01545         }
01546     }
01547 /**
01548  * Deletes all the records in a table and resets the count of the auto-incrementing
01549  * primary key, where applicable.
01550  *
01551  * @param mixed $table A string or model class representing the table to be truncated
01552  * @return boolean  SQL TRUNCATE TABLE statement, false if not applicable.
01553  * @access public
01554  */
01555     function truncate($table) {
01556         return $this->execute('TRUNCATE TABLE ' . $this->fullTableName($table));
01557     }
01558 /**
01559  * Begin a transaction
01560  *
01561  * @param model $model
01562  * @return boolean True on success, false on fail
01563  * (i.e. if the database/model does not support transactions,
01564  * or a transaction has not started).
01565  */
01566     function begin(&$model) {
01567         if (parent::begin($model) && $this->execute($this->_commands['begin'])) {
01568             $this->_transactionStarted = true;
01569             return true;
01570         }
01571         return false;
01572     }
01573 /**
01574  * Commit a transaction
01575  *
01576  * @param model $model
01577  * @return boolean True on success, false on fail
01578  * (i.e. if the database/model does not support transactions,
01579  * or a transaction has not started).
01580  */
01581     function commit(&$model) {
01582         if (parent::commit($model) && $this->execute($this->_commands['commit'])) {
01583             $this->_transactionStarted = false;
01584             return true;
01585         }
01586         return false;
01587     }
01588 /**
01589  * Rollback a transaction
01590  *
01591  * @param model $model
01592  * @return boolean True on success, false on fail
01593  * (i.e. if the database/model does not support transactions,
01594  * or a transaction has not started).
01595  */
01596     function rollback(&$model) {
01597         if (parent::rollback($model) && $this->execute($this->_commands['rollback'])) {
01598             $this->_transactionStarted = false;
01599             return true;
01600         }
01601         return false;
01602     }
01603 /**
01604  * Creates a default set of conditions from the model if $conditions is null/empty.
01605  *
01606  * @param object $model
01607  * @param mixed  $conditions
01608  * @param boolean $useAlias Use model aliases rather than table names when generating conditions
01609  * @return mixed
01610  */
01611     function defaultConditions(&$model, $conditions, $useAlias = true) {
01612         if (!empty($conditions)) {
01613             return $conditions;
01614         }
01615         if (!$model->exists()) {
01616             return false;
01617         }
01618         $alias = $model->alias;
01619 
01620         if (!$useAlias) {
01621             $alias = $this->fullTableName($model, false);
01622         }
01623         return array("{$alias}.{$model->primaryKey}" => $model->getID());
01624     }
01625 /**
01626  * Returns a key formatted like a string Model.fieldname(i.e. Post.title, or Country.name)
01627  *
01628  * @param unknown_type $model
01629  * @param unknown_type $key
01630  * @param unknown_type $assoc
01631  * @return string
01632  */
01633     function resolveKey($model, $key, $assoc = null) {
01634         if (empty($assoc)) {
01635             $assoc = $model->alias;
01636         }
01637         if (!strpos('.', $key)) {
01638             return $this->name($model->alias) . '.' . $this->name($key);
01639         }
01640         return $key;
01641     }
01642 /**
01643  * Private helper method to remove query metadata in given data array.
01644  *
01645  * @param array $data
01646  * @return array
01647  */
01648     function __scrubQueryData($data) {
01649         foreach (array('conditions', 'fields', 'joins', 'order', 'limit', 'offset', 'group') as $key) {
01650             if (!isset($data[$key]) || empty($data[$key])) {
01651                 $data[$key] = array();
01652             }
01653         }
01654         return $data;
01655     }
01656 /**
01657  * Generates the fields list of an SQL query.
01658  *
01659  * @param Model $model
01660  * @param string $alias Alias tablename
01661  * @param mixed $fields
01662  * @param boolean $quote If false, returns fields array unquoted
01663  * @return array
01664  */
01665     function fields(&$model, $alias = null, $fields = array(), $quote = true) {
01666         if (empty($alias)) {
01667             $alias = $model->alias;
01668         }
01669         if (empty($fields)) {
01670             $fields = array_keys($model->schema());
01671         } elseif (!is_array($fields)) {
01672             $fields = String::tokenize($fields);
01673         }
01674         $fields = array_values(array_filter($fields));
01675 
01676         if (!$quote) {
01677             return $fields;
01678         }
01679         $count = count($fields);
01680 
01681         if ($count >= 1 && !in_array($fields[0], array('*', 'COUNT(*)'))) {
01682             for ($i = 0; $i < $count; $i++) {
01683                 if (!preg_match('/^.+\\(.*\\)/', $fields[$i])) {
01684                     $prepend = '';
01685 
01686                     if (strpos($fields[$i], 'DISTINCT') !== false) {
01687                         $prepend = 'DISTINCT ';
01688                         $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i]));
01689                     }
01690                     $dot = strpos($fields[$i], '.');
01691 
01692                     if ($dot === false) {
01693                         $prefix = !(
01694                             strpos($fields[$i], ' ') !== false ||
01695                             strpos($fields[$i], '(') !== false
01696                         );
01697                         $fields[$i] = $this->name(($prefix ? $alias . '.' : '') . $fields[$i]);
01698                     } else {
01699                         $value = array();
01700                         $comma = strpos($fields[$i], ',');
01701                         if ($comma === false) {
01702                             $build = explode('.', $fields[$i]);
01703                             if (!Set::numeric($build)) {
01704                                 $fields[$i] = $this->name($build[0] . '.' . $build[1]);
01705                             }
01706                             $comma = String::tokenize($fields[$i]);
01707                             foreach ($comma as $string) {
01708                                 if (preg_match('/^[0-9]+\.[0-9]+$/', $string)) {
01709                                     $value[] = $string;
01710                                 } else {
01711                                     $build = explode('.', $string);
01712                                     $value[] = $this->name(trim($build[0]) . '.' . trim($build[1]));
01713                                 }
01714                             }
01715                             $fields[$i] = implode(', ', $value);
01716                         }
01717                     }
01718                     $fields[$i] = $prepend . $fields[$i];
01719                 } elseif (preg_match('/\(([\.\w]+)\)/', $fields[$i], $field)) {
01720                     if (isset($field[1])) {
01721                         if (strpos($field[1], '.') === false) {
01722                             $field[1] = $this->name($alias . '.' . $field[1]);
01723                         } else {
01724                             $field[0] = explode('.', $field[1]);
01725                             if (!Set::numeric($field[0])) {
01726                                 $field[0] = join('.', array_map(array($this, 'name'), $field[0]));
01727                                 $fields[$i] = preg_replace('/\(' . $field[1] . '\)/', '(' . $field[0] . ')', $fields[$i], 1);
01728                             }
01729                         }
01730                     }
01731                 }
01732             }
01733         }
01734         return array_unique($fields);
01735     }
01736 /**
01737  * Creates a WHERE clause by parsing given conditions data.
01738  *
01739  * @param mixed $conditions Array or string of conditions
01740  * @param boolean $quoteValues If true, values should be quoted
01741  * @param boolean $where If true, "WHERE " will be prepended to the return value
01742  * @param Model $model A reference to the Model instance making the query
01743  * @return string SQL fragment
01744  */
01745     function conditions($conditions, $quoteValues = true, $where = true, $model = null) {
01746         $clause = $out = '';
01747 
01748         if ($where) {
01749             $clause = ' WHERE ';
01750         }
01751 
01752         if (is_array($conditions) && !empty($conditions)) {
01753             $out = $this->conditionKeysToString($conditions, $quoteValues, $model);
01754 
01755             if (empty($out)) {
01756                 return $clause . ' 1 = 1';
01757             }
01758             return $clause . join(' AND ', $out);
01759         }
01760 
01761         if (empty($conditions) || trim($conditions) == '' || $conditions === true) {
01762             return $clause . '1 = 1';
01763         }
01764         $clauses = '/^WHERE\\x20|^GROUP\\x20BY\\x20|^HAVING\\x20|^ORDER\\x20BY\\x20/i';
01765 
01766         if (preg_match($clauses, $conditions, $match)) {
01767             $clause = '';
01768         }
01769         if (trim($conditions) == '') {
01770             $conditions = ' 1 = 1';
01771         } else {
01772             $conditions = $this->__quoteFields($conditions);
01773         }
01774         return $clause . $conditions;
01775     }
01776 /**
01777  * Creates a WHERE clause by parsing given conditions array.  Used by DboSource::conditions().
01778  *
01779  * @param array $conditions Array or string of conditions
01780  * @param boolean $quoteValues If true, values should be quoted
01781  * @param Model $model A reference to the Model instance making the query
01782  * @return string SQL fragment
01783  */
01784     function conditionKeysToString($conditions, $quoteValues = true, $model = null) {
01785         $c = 0;
01786         $out = array();
01787         $data = $columnType = null;
01788         $bool = array('and', 'or', 'not', 'and not', 'or not', 'xor', '||', '&&');
01789 
01790         foreach ($conditions as $key => $value) {
01791             $join = ' AND ';
01792             $not = null;
01793 
01794             if (is_array($value)) {
01795                 $valueInsert = (
01796                     !empty($value) &&
01797                     (substr_count($key, '?') == count($value) || substr_count($key, ':') == count($value))
01798                 );
01799             }
01800 
01801             if (is_numeric($key) && empty($value)) {
01802                 continue;
01803             } elseif (is_numeric($key) && is_string($value)) {
01804                 $out[] = $not . $this->__quoteFields($value);
01805             } elseif ((is_numeric($key) && is_array($value)) || in_array(strtolower(trim($key)), $bool)) {
01806                 if (in_array(strtolower(trim($key)), $bool)) {
01807                     $join = ' ' . strtoupper($key) . ' ';
01808                 } else {
01809                     $key = $join;
01810                 }
01811                 $value = $this->conditionKeysToString($value, $quoteValues, $model);
01812 
01813                 if (strpos($join, 'NOT') !== false) {
01814                     if (strtoupper(trim($key)) == 'NOT') {
01815                         $key = 'AND ' . trim($key);
01816                     }
01817                     $not = 'NOT ';
01818                 }
01819 
01820                 if (empty($value[1])) {
01821                     if ($not) {
01822                         $out[] = $not . '(' . $value[0] . ')';
01823                     } else {
01824                         $out[] = $value[0] ;
01825                     }
01826                 } else {
01827                     $out[] = '(' . $not . '(' . join(') ' . strtoupper($key) . ' (', $value) . '))';
01828                 }
01829 
01830             } else {
01831                 if (is_object($value) && isset($value->type)) {
01832                     if ($value->type == 'identifier') {
01833                         $data .= $this->name($key) . ' = ' . $this->name($value->value);
01834                     } elseif ($value->type == 'expression') {
01835                         if (is_numeric($key)) {
01836                             $data .= $value->value;
01837                         } else {
01838                             $data .= $this->name($key) . ' = ' . $value->value;
01839                         }
01840                     }
01841                 } elseif (is_array($value) && !empty($value) && !$valueInsert) {
01842                     $keys = array_keys($value);
01843                     if (array_keys($value) === array_values(array_keys($value))) {
01844                         $count = count($value);
01845                         if ($count === 1) {
01846                             $data = $this->__quoteFields($key) . ' = (';
01847                         } else {
01848                             $data = $this->__quoteFields($key) . ' IN (';
01849                         }
01850                         if ($quoteValues || strpos($value[0], '-!') !== 0) {
01851                             if (is_object($model)) {
01852                                 $columnType = $model->getColumnType($key);
01853                             }
01854                             $data .= join(', ', $this->value($value, $columnType));
01855                         }
01856                         $data .= ')';
01857                     } else {
01858                         $ret = $this->conditionKeysToString($value, $quoteValues, $model);
01859                         if (count($ret) > 1) {
01860                             $data = '(' . join(') AND (', $ret) . ')';
01861                         } elseif (isset($ret[0])) {
01862                             $data = $ret[0];
01863                         }
01864                     }
01865                 } elseif (is_numeric($key) && !empty($value)) {
01866                     $data = $this->__quoteFields($value);
01867                 } else {
01868                     $data = $this->__parseKey($model, trim($key), $value);
01869                 }
01870 
01871                 if ($data != null) {
01872                     if (preg_match('/^\(\(\((.+)\)\)\)$/', $data)) {
01873                         $data = substr($data, 1, strlen($data) - 2);
01874                     }
01875                     $out[] = $data;
01876                     $data = null;
01877                 }
01878             }
01879             $c++;
01880         }
01881         return $out;
01882     }
01883 /**
01884  * Extracts a Model.field identifier and an SQL condition operator from a string, formats
01885  * and inserts values, and composes them into an SQL snippet.
01886  *
01887  * @param Model $model Model object initiating the query
01888  * @param string $key An SQL key snippet containing a field and optional SQL operator
01889  * @param mixed $value The value(s) to be inserted in the string
01890  * @return string
01891  * @access private
01892  */
01893     function __parseKey($model, $key, $value) {
01894         $operatorMatch = '/^((' . join(')|(', $this->__sqlOps);
01895         $operatorMatch .= '\\x20)|<[>=]?(?![^>]+>)\\x20?|[>=!]{1,3}(?!<)\\x20?)/is';
01896         $bound = (strpos($key, '?') !== false || (is_array($value) && strpos($key, ':') !== false));
01897 
01898         if (!strpos($key, ' ')) {
01899             $operator = '=';
01900         } else {
01901             list($key, $operator) = explode(' ', trim($key), 2);
01902 
01903             if (!preg_match($operatorMatch, trim($operator)) && strpos($operator, ' ') !== false) {
01904                 $key = $key . ' ' . $operator;
01905                 $split = strrpos($key, ' ');
01906                 $operator = substr($key, $split);
01907                 $key = substr($key, 0, $split);
01908             }
01909         }
01910 
01911 
01912         $type = (is_object($model) ? $model->getColumnType($key) : null);
01913 
01914         $null = ($value === null || (is_array($value) && empty($value)));
01915 
01916         if (strtolower($operator) === 'not') {
01917             $data = $this->conditionKeysToString(
01918                 array($operator => array($key => $value)), true, $model
01919             );
01920             return $data[0];
01921         }
01922 
01923         $value = $this->value($value, $type);
01924 
01925         if ($key !== '?') {
01926             $isKey = (strpos($key, '(') !== false || strpos($key, ')') !== false);
01927             $key = $isKey ? $this->__quoteFields($key) : $this->name($key);
01928         }
01929 
01930         if ($bound) {
01931             return String::insert($key . ' ' . trim($operator), $value);
01932         }
01933 
01934         if (!preg_match($operatorMatch, trim($operator))) {
01935             $operator .= ' =';
01936         }
01937         $operator = trim($operator);
01938 
01939         if (is_array($value)) {
01940             $value = join(', ', $value);
01941 
01942             switch ($operator) {
01943                 case '=':
01944                     $operator = 'IN';
01945                 break;
01946                 case '!=':
01947                 case '<>':
01948                     $operator = 'NOT IN';
01949                 break;
01950             }
01951             $value = "({$value})";
01952         } elseif ($null) {
01953             switch ($operator) {
01954                 case '=':
01955                     $operator = 'IS';
01956                 break;
01957                 case '!=':
01958                 case '<>':
01959                     $operator = 'IS NOT';
01960                 break;
01961             }
01962         }
01963 
01964         return "{$key} {$operator} {$value}";
01965     }
01966 /**
01967  * Quotes Model.fields
01968  *
01969  * @param string $conditions
01970  * @return string or false if no match
01971  * @access private
01972  */
01973     function __quoteFields($conditions) {
01974         $start = $end  = null;
01975         $original = $conditions;
01976 
01977         if (!empty($this->startQuote)) {
01978             $start = preg_quote($this->startQuote);
01979         }
01980         if (!empty($this->endQuote)) {
01981             $end = preg_quote($this->endQuote);
01982         }
01983         $conditions = str_replace(array($start, $end), '', $conditions);
01984         preg_match_all('/(?:[\'\"][^\'\"\\\]*(?:\\\.[^\'\"\\\]*)*[\'\"])|([a-z0-9_' . $start . $end . ']*\\.[a-z0-9_' . $start . $end . ']*)/i', $conditions, $replace, PREG_PATTERN_ORDER);
01985 
01986         if (isset($replace['1']['0'])) {
01987             $pregCount = count($replace['1']);
01988 
01989             for ($i = 0; $i < $pregCount; $i++) {
01990                 if (!empty($replace['1'][$i]) && !is_numeric($replace['1'][$i])) {
01991                     $conditions = preg_replace('/\b' . preg_quote($replace['1'][$i]) . '\b/', $this->name($replace['1'][$i]), $conditions);
01992                 }
01993             }
01994             return $conditions;
01995         }
01996         return $original;
01997     }
01998 /**
01999  * Returns a limit statement in the correct format for the particular database.
02000  *
02001  * @param integer $limit Limit of results returned
02002  * @param integer $offset Offset from which to start results
02003  * @return string SQL limit/offset statement
02004  */
02005     function limit($limit, $offset = null) {
02006         if ($limit) {
02007             $rt = '';
02008             if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) {
02009                 $rt = ' LIMIT';
02010             }
02011 
02012             if ($offset) {
02013                 $rt .= ' ' . $offset . ',';
02014             }
02015 
02016             $rt .= ' ' . $limit;
02017             return $rt;
02018         }
02019         return null;
02020     }
02021 /**
02022  * Returns an ORDER BY clause as a string.
02023  *
02024  * @param string $key Field reference, as a key (i.e. Post.title)
02025  * @param string $direction Direction (ASC or DESC)
02026  * @return string ORDER BY clause
02027  */
02028     function order($keys, $direction = 'ASC') {
02029         if (is_string($keys) && strpos($keys, ',') && !preg_match('/\(.+\,.+\)/', $keys)) {
02030             $keys = array_map('trim', explode(',', $keys));
02031         }
02032 
02033         if (is_array($keys)) {
02034             $keys = array_filter($keys);
02035         }
02036 
02037         if (empty($keys) || (is_array($keys) && count($keys) && isset($keys[0]) && empty($keys[0]))) {
02038             return '';
02039         }
02040 
02041         if (is_array($keys)) {
02042             $keys = (Set::countDim($keys) > 1) ? array_map(array(&$this, 'order'), $keys) : $keys;
02043 
02044             foreach ($keys as $key => $value) {
02045                 if (is_numeric($key)) {
02046                     $key = $value = ltrim(str_replace('ORDER BY ', '', $this->order($value)));
02047                     $value = (!preg_match('/\\x20ASC|\\x20DESC/i', $key) ? ' ' . $direction : '');
02048                 } else {
02049                     $value = ' ' . $value;
02050                 }
02051 
02052                 if (!preg_match('/^.+\\(.*\\)/', $key) && !strpos($key, ',')) {
02053                     if (preg_match('/\\x20ASC|\\x20DESC/i', $key, $dir)) {
02054                         $dir = $dir[0];
02055                         $key = preg_replace('/\\x20ASC|\\x20DESC/i', '', $key);
02056                     } else {
02057                         $dir = '';
02058                     }
02059                     $key = trim($key);
02060                     if (!preg_match('/\s/', $key)) {
02061                         $key = $this->name($key);
02062                     }
02063                     $key .= ' ' . trim($dir);
02064                 }
02065                 $order[] = $this->order($key . $value);
02066             }
02067             return ' ORDER BY ' . trim(str_replace('ORDER BY', '', join(',', $order)));
02068         }
02069         $keys = preg_replace('/ORDER\\x20BY/i', '', $keys);
02070 
02071         if (strpos($keys, '.')) {
02072             preg_match_all('/([a-zA-Z0-9_]{1,})\\.([a-zA-Z0-9_]{1,})/', $keys, $result, PREG_PATTERN_ORDER);
02073             $pregCount = count($result[0]);
02074 
02075             for ($i = 0; $i < $pregCount; $i++) {
02076                 if (!is_numeric($result[0][$i])) {
02077                     $keys = preg_replace('/' . $result[0][$i] . '/', $this->name($result[0][$i]), $keys);
02078                 }
02079             }
02080             $result = ' ORDER BY ' . $keys;
02081             return $result . (!preg_match('/\\x20ASC|\\x20DESC/i', $keys) ? ' ' . $direction : '');
02082 
02083         } elseif (preg_match('/(\\x20ASC|\\x20DESC)/i', $keys, $match)) {
02084             $direction = $match[1];
02085             return ' ORDER BY ' . preg_replace('/' . $match[1] . '/', '', $keys) . $direction;
02086         }
02087         return ' ORDER BY ' . $keys . ' ' . $direction;
02088     }
02089 /**
02090  * Create a GROUP BY SQL clause
02091  *
02092  * @param string $group Group By Condition
02093  * @return mixed string condition or null
02094  */
02095     function group($group) {
02096         if ($group) {
02097             if (is_array($group)) {
02098                 $group = join(', ', $group);
02099             }
02100             return ' GROUP BY ' . $this->__quoteFields($group);
02101         }
02102         return null;
02103     }
02104 /**
02105  * Disconnects database, kills the connection and says the connection is closed,
02106  * and if DEBUG is turned on, the log for this object is shown.
02107  *
02108  */
02109     function close() {
02110         if (Configure::read() > 1) {
02111             $this->showLog();
02112         }
02113         $this->disconnect();
02114     }
02115 /**
02116  * Checks if the specified table contains any record matching specified SQL
02117  *
02118  * @param Model $model Model to search
02119  * @param string $sql SQL WHERE clause (condition only, not the "WHERE" part)
02120  * @return boolean True if the table has a matching record, else false
02121  */
02122     function hasAny(&$Model, $sql) {
02123         $sql = $this->conditions($sql);
02124         $table = $this->fullTableName($Model);
02125         $alias = $this->alias . $this->name($Model->alias);
02126         $where = $sql ? "{$sql}" : ' WHERE 1 = 1';
02127         $id = $Model->escapeField();
02128 
02129         $out = $this->fetchRow("SELECT COUNT({$id}) {$this->alias}count FROM {$table} {$alias}{$where}");
02130 
02131         if (is_array($out)) {
02132             return $out[0]['count'];
02133         }
02134         return false;
02135     }
02136 /**
02137  * Gets the length of a database-native column description, or null if no length
02138  *
02139  * @param string $real Real database-layer column type (i.e. "varchar(255)")
02140  * @return mixed An integer or string representing the length of the column
02141  */
02142     function length($real) {
02143         if (!preg_match_all('/([\w\s]+)(?:\((\d+)(?:,(\d+))?\))?(\sunsigned)?(\szerofill)?/', $real, $result)) {
02144             trigger_error(__('FIXME: Can\'t parse field: ' . $real, true), E_USER_WARNING);
02145             $col = str_replace(array(')', 'unsigned'), '', $real);
02146             $limit = null;
02147 
02148             if (strpos($col, '(') !== false) {
02149                 list($col, $limit) = explode('(', $col);
02150             }
02151             if ($limit != null) {
02152                 return intval($limit);
02153             }
02154             return null;
02155         }
02156 
02157         $types = array(
02158             'int' => 1, 'tinyint' => 1, 'smallint' => 1, 'mediumint' => 1, 'integer' => 1, 'bigint' => 1
02159         );
02160 
02161         list($real, $type, $length, $offset, $sign, $zerofill) = $result;
02162         $typeArr = $type;
02163         $type = $type[0];
02164         $length = $length[0];
02165         $offset = $offset[0];
02166 
02167         $isFloat = in_array($type, array('dec', 'decimal', 'float', 'numeric', 'double'));
02168         if ($isFloat && $offset) {
02169             return $length.','.$offset;
02170         }
02171 
02172         if (($real[0] == $type) && (count($real) == 1)) {
02173             return null;
02174         }
02175 
02176         if (isset($types[$type])) {
02177             $length += $types[$type];
02178             if (!empty($sign)) {
02179                 $length--;
02180             }
02181         } elseif (in_array($type, array('enum', 'set'))) {
02182             $length = 0;
02183             foreach ($typeArr as $key => $enumValue) {
02184                 if ($key == 0) {
02185                     continue;
02186                 }
02187                 $tmpLength = strlen($enumValue);
02188                 if ($tmpLength > $length) {
02189                     $length = $tmpLength;
02190                 }
02191             }
02192         }
02193         return intval($length);
02194     }
02195 /**
02196  * Translates between PHP boolean values and Database (faked) boolean values
02197  *
02198  * @param mixed $data Value to be translated
02199  * @return mixed Converted boolean value
02200  */
02201     function boolean($data) {
02202         if ($data === true || $data === false) {
02203             if ($data === true) {
02204                 return 1;
02205             }
02206             return 0;
02207         } else {
02208             return !empty($data);
02209         }
02210     }
02211 /**
02212  * Inserts multiple values into a table
02213  *
02214  * @param string $table
02215  * @param string $fields
02216  * @param array $values
02217  * @access protected
02218  */
02219     function insertMulti($table, $fields, $values) {
02220         $table = $this->fullTableName($table);
02221         if (is_array($fields)) {
02222             $fields = join(', ', array_map(array(&$this, 'name'), $fields));
02223         }
02224         $count = count($values);
02225         for ($x = 0; $x < $count; $x++) {
02226             $this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}");
02227         }
02228     }
02229 /**
02230  * Returns an array of the indexes in given datasource name.
02231  *
02232  * @param string $model Name of model to inspect
02233  * @return array Fields in table. Keys are column and unique
02234  */
02235     function index($model) {
02236         return false;
02237     }
02238 /**
02239  * Generate a database-native schema for the given Schema object
02240  *
02241  * @param object $schema An instance of a subclass of CakeSchema
02242  * @param string $tableName Optional.  If specified only the table name given will be generated.
02243  *   Otherwise, all tables defined in the schema are generated.
02244  * @return string
02245  */
02246     function createSchema($schema, $tableName = null) {
02247         if (!is_a($schema, 'CakeSchema')) {
02248             trigger_error(__('Invalid schema object', true), E_USER_WARNING);
02249             return null;
02250         }
02251         $out = '';
02252 
02253         foreach ($schema->tables as $curTable => $columns) {
02254             if (!$tableName || $tableName == $curTable) {
02255                 $cols = $colList = $indexes = array();
02256                 $primary = null;
02257                 $table = $this->fullTableName($curTable);
02258 
02259                 foreach ($columns as $name => $col) {
02260                     if (is_string($col)) {
02261                         $col = array('type' => $col);
02262                     }
02263                     if (isset($col['key']) && $col['key'] == 'primary') {
02264                         $primary = $name;
02265                     }
02266                     if ($name !== 'indexes') {
02267                         $col['name'] = $name;
02268                         if (!isset($col['type'])) {
02269                             $col['type'] = 'string';
02270                         }
02271                         $cols[] = $this->buildColumn($col);
02272                     } else {
02273                         $indexes = array_merge($indexes, $this->buildIndex($col, $table));
02274                     }
02275                 }
02276                 if (empty($indexes) && !empty($primary)) {
02277                     $col = array('PRIMARY' => array('column' => $primary, 'unique' => 1));
02278                     $indexes = array_merge($indexes, $this->buildIndex($col, $table));
02279                 }
02280                 $columns = $cols;
02281                 $out .= $this->renderStatement('schema', compact('table', 'columns', 'indexes')) . "\n\n";
02282             }
02283         }
02284         return $out;
02285     }
02286 /**
02287  * Generate a alter syntax from  CakeSchema::compare()
02288  *
02289  * @param unknown_type $schema
02290  * @return unknown
02291  */
02292     function alterSchema($compare, $table = null) {
02293         return false;
02294     }
02295 /**
02296  * Generate a "drop table" statement for the given Schema object
02297  *
02298  * @param object $schema An instance of a subclass of CakeSchema
02299  * @param string $table Optional.  If specified only the table name given will be generated.
02300  *   Otherwise, all tables defined in the schema are generated.
02301  * @return string
02302  */
02303     function dropSchema($schema, $table = null) {
02304         if (!is_a($schema, 'CakeSchema')) {
02305             trigger_error(__('Invalid schema object', true), E_USER_WARNING);
02306             return null;
02307         }
02308         $out = '';
02309 
02310         foreach ($schema->tables as $curTable => $columns) {
02311             if (!$table || $table == $curTable) {
02312                 $out .= 'DROP TABLE ' . $this->fullTableName($curTable) . ";\n";
02313             }
02314         }
02315         return $out;
02316     }
02317 /**
02318  * Generate a database-native column schema string
02319  *
02320  * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]),
02321  *   where options can be 'default', 'length', or 'key'.
02322  * @return string
02323  */
02324     function buildColumn($column) {
02325         $name = $type = null;
02326         extract(array_merge(array('null' => true), $column));
02327 
02328         if (empty($name) || empty($type)) {
02329             trigger_error('Column name or type not defined in schema', E_USER_WARNING);
02330             return null;
02331         }
02332 
02333         if (!isset($this->columns[$type])) {
02334             trigger_error("Column type {$type} does not exist", E_USER_WARNING);
02335             return null;
02336         }
02337 
02338         $real = $this->columns[$type];
02339         $out = $this->name($name) . ' ' . $real['name'];
02340 
02341         if (isset($real['limit']) || isset($real['length']) || isset($column['limit']) || isset($column['length'])) {
02342             if (isset($column['length'])) {
02343                 $length = $column['length'];
02344             } elseif (isset($column['limit'])) {
02345                 $length = $column['limit'];
02346             } elseif (isset($real['length'])) {
02347                 $length = $real['length'];
02348             } else {
02349                 $length = $real['limit'];
02350             }
02351             $out .= '(' . $length . ')';
02352         }
02353 
02354         if (($column['type'] == 'integer' || $column['type'] == 'float' ) && isset($column['default']) && $column['default'] === '') {
02355             $column['default'] = null;
02356         }
02357 
02358         if (isset($column['key']) && $column['key'] == 'primary' && $type == 'integer') {
02359             $out .= ' ' . $this->columns['primary_key']['name'];
02360         } elseif (isset($column['key']) && $column['key'] == 'primary') {
02361             $out .= ' NOT NULL';
02362         } elseif (isset($column['default']) && isset($column['null']) && $column['null'] == false) {
02363             $out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL';
02364         } elseif (isset($column['default'])) {
02365             $out .= ' DEFAULT ' . $this->value($column['default'], $type);
02366         } elseif (isset($column['null']) && $column['null'] == true) {
02367             $out .= ' DEFAULT NULL';
02368         } elseif (isset($column['null']) && $column['null'] == false) {
02369             $out .= ' NOT NULL';
02370         }
02371         return $out;
02372     }
02373 /**
02374  * Format indexes for create table
02375  *
02376  * @param array $indexes
02377  * @param string $table
02378  * @return array
02379  */
02380     function buildIndex($indexes, $table = null) {
02381         $join = array();
02382         foreach ($indexes as $name => $value) {
02383             $out = '';
02384             if ($name == 'PRIMARY') {
02385                 $out .= 'PRIMARY ';
02386                 $name = null;
02387             } else {
02388                 if (!empty($value['unique'])) {
02389                     $out .= 'UNIQUE ';
02390                 }
02391                 $name = $this->startQuote . $name . $this->endQuote;
02392             }
02393             if (is_array($value['column'])) {
02394                 $out .= 'KEY ' . $name . ' (' . join(', ', array_map(array(&$this, 'name'), $value['column'])) . ')';
02395             } else {
02396                 $out .= 'KEY ' . $name . ' (' . $this->name($value['column']) . ')';
02397             }
02398             $join[] = $out;
02399         }
02400         return $join;
02401     }
02402 /**
02403  * Guesses the data type of an array
02404  *
02405  * @param string $value
02406  * @return void
02407  * @access public
02408  */
02409     function introspectType($value) {
02410         if (!is_array($value)) {
02411             if ($value === true || $value === false) {
02412                 return 'boolean';
02413             }
02414             if (is_float($value) && floatval($value) === $value) {
02415                 return 'float';
02416             }
02417             if (is_int($value) && intval($value) === $value) {
02418                 return 'integer';
02419             }
02420             if (is_string($value) && strlen($value) > 255) {
02421                 return 'text';
02422             }
02423             return 'string';
02424         }
02425 
02426         $isAllFloat = $isAllInt = true;
02427         $containsFloat = $containsInt = $containsString = false;
02428         foreach ($value as $key => $valElement) {
02429             $valElement = trim($valElement);
02430             if (!is_float($valElement) && !preg_match('/^[\d]+\.[\d]+$/', $valElement)) {
02431                 $isAllFloat = false;
02432             } else {
02433                 $containsFloat = true;
02434                 continue;
02435             }
02436             if (!is_int($valElement) && !preg_match('/^[\d]+$/', $valElement)) {
02437                 $isAllInt = false;
02438             } else {
02439                 $containsInt = true;
02440                 continue;
02441             }
02442             $containsString = true;
02443         }
02444 
02445         if ($isAllFloat) {
02446             return 'float';
02447         }
02448         if ($isAllInt) {
02449             return 'integer';
02450         }
02451 
02452         if ($containsInt && !$containsString) {
02453             return 'integer';
02454         }
02455         return 'string';
02456     }
02457 }
02458 ?>

Generated on Sun Nov 22 00:30:53 2009 for CakePHP 1.2.x.x (v1.2.4.8284) by doxygen 1.4.7