dbo_source.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: dbo__source_8php-source.html 675 2008-12-26 00:27:14Z 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.
00012  *                              1785 E. Sahara Avenue, Suite 490-204
00013  *                              Las Vegas, Nevada 89104
00014  *
00015  * Licensed under The MIT License
00016  * Redistributions of files must retain the above copyright notice.
00017  *
00018  * @filesource
00019  * @copyright       Copyright 2005-2008, Cake Software Foundation, Inc.
00020  * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00021  * @package         cake
00022  * @subpackage      cake.cake.libs.model.datasources
00023  * @since           CakePHP(tm) v 0.10.0.1076
00024  * @version         $Revision: 675 $
00025  * @modifiedby      $LastChangedBy: gwoo $
00026  * @lastmodified    $Date: 2008-12-25 16:27:14 -0800 (Thu, 25 Dec 2008) $
00027  * @license         http://www.opensource.org/licenses/mit-license.php The MIT License
00028  */
00029 uses('set');
00030 /**
00031  * DboSource
00032  *
00033  * Creates DBO-descendant objects from a given db connection configuration
00034  *
00035  * @package     cake
00036  * @subpackage  cake.cake.libs.model.datasources
00037  */
00038 class DboSource extends DataSource {
00039 /**
00040  * Description string for this Database Data Source.
00041  *
00042  * @var unknown_type
00043  */
00044     var $description = "Database Data Source";
00045 /**
00046  * index definition, standard cake, primary, index, unique
00047  *
00048  * @var array
00049  */
00050     var $index = array('PRI'=> 'primary', 'MUL'=> 'index', 'UNI'=>'unique');
00051 /**
00052  * Enter description here...
00053  *
00054  * @var unknown_type
00055  */
00056     var $startQuote = null;
00057 /**
00058  * Enter description here...
00059  *
00060  * @var unknown_type
00061  */
00062     var $endQuote = null;
00063 /**
00064  * Enter description here...
00065  *
00066  * @var unknown_type
00067  */
00068     var $alias = 'AS ';
00069 /**
00070  * Enter description here...
00071  *
00072  * @var unknown_type
00073  */
00074     var $goofyLimit = false;
00075 /**
00076  * Enter description here...
00077  *
00078  * @var unknown_type
00079  */
00080     var $__bypass = false;
00081 /**
00082  * The set of valid SQL operations usable in a WHERE statement
00083  *
00084  * @var array
00085  */
00086     var $__sqlOps = array('like', 'ilike', 'or', 'not', 'in', 'between', 'regexp', 'similar to');
00087 /**
00088  * Constructor
00089  */
00090     function __construct($config = null, $autoConnect = true) {
00091         $this->debug = Configure::read() > 0;
00092         $this->fullDebug = Configure::read() > 1;
00093         parent::__construct($config);
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         if ($config != null) {
00110             $this->config = array_merge($this->_baseConfig, $config);
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  * @return mixed Prepared value or array of values.
00119  */
00120     function value($data, $column = null) {
00121         if (is_array($data)) {
00122             $out = array();
00123             $keys = array_keys($data);
00124             $count = count($data);
00125             for ($i = 0; $i < $count; $i++) {
00126                 $out[$keys[$i]] = $this->value($data[$keys[$i]]);
00127             }
00128             return $out;
00129         } elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
00130             return $data;
00131         }
00132         return null;
00133     }
00134 /**
00135  * Executes given SQL statement.
00136  *
00137  * @param string $sql SQL statement
00138  * @return unknown
00139  */
00140     function rawQuery($sql) {
00141         $this->took = $this->error = $this->numRows = false;
00142         return $this->execute($sql);
00143     }
00144 /**
00145  * Queries the database with given SQL statement, and obtains some metadata about the result
00146  * (rows affected, timing, any errors, number of rows in resultset). The query is also logged.
00147  * If DEBUG is set, the log is shown all the time, else it is only shown on errors.
00148  *
00149  * @param string $sql
00150  * @return unknown
00151  */
00152     function execute($sql) {
00153         $t = getMicrotime();
00154         $this->_result = $this->_execute($sql);
00155         $this->affected = $this->lastAffected();
00156         $this->took = round((getMicrotime() - $t) * 1000, 0);
00157         $this->error = $this->lastError();
00158         $this->numRows = $this->lastNumRows($this->_result);
00159 
00160         if ($this->fullDebug && Configure::read() > 1) {
00161             $this->logQuery($sql);
00162         }
00163 
00164         if ($this->error) {
00165             $this->showQuery($sql);
00166             return false;
00167         } else {
00168             return $this->_result;
00169         }
00170     }
00171 /**
00172  * DataSource Query abstraction
00173  *
00174  * @return resource Result resource identifier
00175  */
00176     function query() {
00177         $args     = func_get_args();
00178         $fields   = null;
00179         $order    = null;
00180         $limit    = null;
00181         $page     = null;
00182         $recursive = null;
00183 
00184         if (count($args) == 1) {
00185             return $this->fetchAll($args[0]);
00186 
00187         } elseif (count($args) > 1 && (strpos(strtolower($args[0]), 'findby') === 0 || strpos(strtolower($args[0]), 'findallby') === 0)) {
00188             $params = $args[1];
00189 
00190             if (strpos(strtolower($args[0]), 'findby') === 0) {
00191                 $all  = false;
00192                 $field = Inflector::underscore(preg_replace('/findBy/i', '', $args[0]));
00193             } else {
00194                 $all  = true;
00195                 $field = Inflector::underscore(preg_replace('/findAllBy/i', '', $args[0]));
00196             }
00197 
00198             $or = (strpos($field, '_or_') !== false);
00199             if ($or) {
00200                 $field = explode('_or_', $field);
00201             } else {
00202                 $field = explode('_and_', $field);
00203             }
00204             $off = count($field) - 1;
00205 
00206             if (isset($params[1 + $off])) {
00207                 $fields = $params[1 + $off];
00208             }
00209 
00210             if (isset($params[2 + $off])) {
00211                 $order = $params[2 + $off];
00212             }
00213 
00214             if (!array_key_exists(0, $params)) {
00215                 return false;
00216             }
00217 
00218             $c = 0;
00219             $query = array();
00220             foreach ($field as $f) {
00221                 if (!is_array($params[$c]) && !empty($params[$c]) && $params[$c] !== true && $params[$c] !== false) {
00222                     $query[$args[2]->alias . '.' . $f] = '= ' . $params[$c];
00223                 } else {
00224                     $query[$args[2]->alias . '.' . $f] = $params[$c];
00225                 }
00226                 $c++;
00227             }
00228 
00229             if ($or) {
00230                 $query = array('OR' => $query);
00231             }
00232 
00233             if ($all) {
00234 
00235                 if (isset($params[3 + $off])) {
00236                     $limit = $params[3 + $off];
00237                 }
00238 
00239                 if (isset($params[4 + $off])) {
00240                     $page = $params[4 + $off];
00241                 }
00242 
00243                 if (isset($params[5 + $off])) {
00244                     $recursive = $params[5 + $off];
00245                 }
00246                 return $args[2]->findAll($query, $fields, $order, $limit, $page, $recursive);
00247             } else {
00248                 if (isset($params[3 + $off])) {
00249                     $recursive = $params[3 + $off];
00250                 }
00251                 return $args[2]->find($query, $fields, $order, $recursive);
00252             }
00253         } else {
00254             if (isset($args[1]) && $args[1] === true) {
00255                 return $this->fetchAll($args[0], true);
00256             }
00257             return $this->fetchAll($args[0], false);
00258         }
00259     }
00260 /**
00261  * Returns a row from current resultset as an array .
00262  *
00263  * @return array The fetched row as an array
00264  */
00265     function fetchRow($sql = null) {
00266 
00267         if (!empty($sql) && is_string($sql) && strlen($sql) > 5) {
00268             if (!$this->execute($sql)) {
00269                 return null;
00270             }
00271         }
00272 
00273         if (is_resource($this->_result) || is_object($this->_result)) {
00274             $this->resultSet($this->_result);
00275             $resultRow = $this->fetchResult();
00276             return $resultRow;
00277         } else {
00278             return null;
00279         }
00280     }
00281 /**
00282  * Returns an array of all result rows for a given SQL query.
00283  * Returns false if no rows matched.
00284  *
00285  * @param string $sql SQL statement
00286  * @param boolean $cache Enables returning/storing cached query results
00287  * @return array Array of resultset rows, or false if no rows matched
00288  */
00289     function fetchAll($sql, $cache = true, $modelName = null) {
00290         if ($cache && isset($this->_queryCache[$sql])) {
00291             if (preg_match('/^\s*select/i', $sql)) {
00292                 return $this->_queryCache[$sql];
00293             }
00294         }
00295 
00296         if ($this->execute($sql)) {
00297             $out = array();
00298 
00299             while ($item = $this->fetchRow()) {
00300                 $out[] = $item;
00301             }
00302 
00303             if ($cache) {
00304                 if (strpos(trim(strtolower($sql)), 'select') !== false) {
00305                     $this->_queryCache[$sql] = $out;
00306                 }
00307             }
00308             return $out;
00309 
00310         } else {
00311             return false;
00312         }
00313     }
00314 /**
00315  * Returns a single field of the first of query results for a given SQL query, or false if empty.
00316  *
00317  * @param string $name Name of the field
00318  * @param string $sql SQL query
00319  * @return unknown
00320  */
00321     function field($name, $sql) {
00322         $data = $this->fetchRow($sql);
00323 
00324         if (!isset($data[$name]) || empty($data[$name])) {
00325             return false;
00326         } else {
00327             return $data[$name];
00328         }
00329     }
00330 /**
00331  * Returns a quoted name of $data for use in an SQL statement.
00332  * Strips fields out of SQL functions before quoting.
00333  *
00334  * @param string $data
00335  * @return string SQL field
00336  */
00337     function name($data) {
00338         if (preg_match_all('/([^(]*)\((.*)\)(.*)/', $data, $fields)) {
00339             $fields = Set::extract($fields, '{n}.0');
00340             if (!empty($fields[1])) {
00341                 if (!empty($fields[2])) {
00342                     return $fields[1] . '(' . $this->name($fields[2]) . ')' . $fields[3];
00343                 } else {
00344                     return $fields[1] . '()' . $fields[3];
00345                 }
00346             }
00347         }
00348         if ($data == '*') {
00349             return '*';
00350         }
00351         $data = $this->startQuote . str_replace('.', $this->endQuote . '.' . $this->startQuote, $data) . $this->endQuote;
00352         $data = str_replace($this->startQuote . $this->startQuote, $this->startQuote, $data);
00353 
00354         if (!empty($this->endQuote) && $this->endQuote == $this->startQuote) {
00355             $oddMatches = substr_count($data, $this->endQuote);
00356             if ($oddMatches % 2 == 1) {
00357                 $data = trim($data, $this->endQuote);
00358             }
00359         }
00360         return str_replace($this->endQuote . $this->endQuote, $this->endQuote, $data);
00361     }
00362 /**
00363  * Checks if it's connected to the database
00364  *
00365  * @return boolean True if the database is connected, else false
00366  */
00367     function isConnected() {
00368         return $this->connected;
00369     }
00370 /**
00371  * Outputs the contents of the queries log.
00372  *
00373  * @param boolean $sorted
00374  */
00375     function showLog($sorted = false) {
00376         if ($sorted) {
00377             $log = sortByKey($this->_queriesLog, 'took', 'desc', SORT_NUMERIC);
00378         } else {
00379             $log = $this->_queriesLog;
00380         }
00381 
00382         if ($this->_queriesCnt > 1) {
00383             $text = 'queries';
00384         } else {
00385             $text = 'query';
00386         }
00387 
00388         if (php_sapi_name() != 'cli') {
00389             print ("<table class=\"cakeSqlLog\" id=\"cakeSqlLog_" . preg_replace('/[^A-Za-z0-9_]/', '_', uniqid(time(), true)) . "\" summary=\"Cake SQL Log\" cellspacing=\"0\" border = \"0\">\n<caption>{$this->_queriesCnt} {$text} took {$this->_queriesTime} ms</caption>\n");
00390             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");
00391 
00392             foreach ($log as $k => $i) {
00393                 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");
00394             }
00395             print ("</tbody></table>\n");
00396         } else {
00397             foreach ($log as $k => $i) {
00398                 print (($k + 1) . ". {$i['query']} {$i['error']}\n");
00399             }
00400         }
00401     }
00402 /**
00403  * Log given SQL query.
00404  *
00405  * @param string $sql SQL statement
00406  * @todo: Add hook to log errors instead of returning false
00407  */
00408     function logQuery($sql) {
00409         $this->_queriesCnt++;
00410         $this->_queriesTime += $this->took;
00411         $this->_queriesLog[] = array('query' => $sql,
00412                     'error'     => $this->error,
00413                     'affected'  => $this->affected,
00414                     'numRows'   => $this->numRows,
00415                     'took'      => $this->took
00416         );
00417         if (count($this->_queriesLog) > $this->_queriesLogMax) {
00418             array_pop($this->_queriesLog);
00419         }
00420         if ($this->error) {
00421             return false;
00422         }
00423     }
00424 /**
00425  * Output information about an SQL query. The SQL statement, number of rows in resultset,
00426  * and execution time in microseconds. If the query fails, an error is output instead.
00427  *
00428  * @param string $sql Query to show information on.
00429  */
00430     function showQuery($sql) {
00431         $error = $this->error;
00432         if (strlen($sql) > 200 && !$this->fullDebug && Configure::read() > 1) {
00433             $sql = substr($sql, 0, 200) . '[...]';
00434         }
00435 
00436         if (($this->debug || $error) && Configure::read() > 0) {
00437             e("<p style = \"text-align:left\"><b>Query:</b> {$sql} ");
00438             if ($error) {
00439                 trigger_error("<span style = \"color:Red;text-align:left\"><b>SQL Error:</b> {$this->error}</span>", E_USER_WARNING);
00440             } else {
00441                 e("<small>[Aff:{$this->affected} Num:{$this->numRows} Took:{$this->took}ms]</small>");
00442             }
00443             print ('</p>');
00444         }
00445     }
00446 /**
00447  * Gets full table name including prefix
00448  *
00449  * @param mixed $model
00450  * @param boolean $quote
00451  * @return string Full quoted table name
00452  */
00453     function fullTableName($model, $quote = true) {
00454         if (is_object($model)) {
00455             $table = $model->tablePrefix . $model->table;
00456         } elseif (isset($this->config['prefix'])) {
00457             $table = $this->config['prefix'] . strval($model);
00458         } else {
00459             $table = strval($model);
00460         }
00461         if ($quote) {
00462             return $this->name($table);
00463         }
00464         return $table;
00465     }
00466 /**
00467  * The "C" in CRUD
00468  *
00469  * @param Model $model
00470  * @param array $fields
00471  * @param array $values
00472  * @return boolean Success
00473  */
00474     function create(&$model, $fields = null, $values = null) {
00475         $fieldInsert = array();
00476         $valueInsert = array();
00477         $id = null;
00478 
00479         if ($fields == null) {
00480             unset($fields, $values);
00481             $fields = array_keys($model->data);
00482             $values = array_values($model->data);
00483         }
00484         $count = count($fields);
00485 
00486         for ($i = 0; $i < $count; $i++) {
00487             $fieldInsert[] = $this->name($fields[$i]);
00488             if ($fields[$i] == $model->primaryKey) {
00489                 $id = $values[$i];
00490             }
00491         }
00492         $count = count($values);
00493 
00494         for ($i = 0; $i < $count; $i++) {
00495             $valueInsert[] = $this->value($values[$i], $model->getColumnType($fields[$i]));
00496         }
00497 
00498         if ($this->execute('INSERT INTO ' . $this->fullTableName($model) . ' (' . join(',', $fieldInsert). ') VALUES (' . join(',', $valueInsert) . ')')) {
00499             if (empty($id)) {
00500                 $id = $this->lastInsertId($this->fullTableName($model, false), $model->primaryKey);
00501             }
00502             $model->setInsertID($id);
00503             $model->id = $id;
00504             return true;
00505         } else {
00506             $model->onError();
00507             return false;
00508         }
00509     }
00510 /**
00511  * The "R" in CRUD
00512  *
00513  * @param Model $model
00514  * @param array $queryData
00515  * @param integer $recursive Number of levels of association
00516  * @return unknown
00517  */
00518     function read(&$model, $queryData = array(), $recursive = null) {
00519 
00520         $this->__scrubQueryData($queryData);
00521         $null = null;
00522         $array = array();
00523         $linkedModels = array();
00524         $this->__bypass = false;
00525 
00526         if ($recursive === null && isset($queryData['recursive'])) {
00527             $recursive = $queryData['recursive'];
00528         }
00529 
00530         if (!is_null($recursive)) {
00531             $_recursive = $model->recursive;
00532             $model->recursive = $recursive;
00533         }
00534 
00535         if (!empty($queryData['fields'])) {
00536             $this->__bypass = true;
00537             $queryData['fields'] = $this->fields($model, null, $queryData['fields']);
00538         } else {
00539             $queryData['fields'] = $this->fields($model);
00540         }
00541 
00542         foreach ($model->__associations as $type) {
00543             foreach ($model->{$type} as $assoc => $assocData) {
00544                 if ($model->recursive > -1) {
00545                     $linkModel =& $model->{$assoc};
00546 
00547                     $external = isset($assocData['external']);
00548                     if ($model->alias == $linkModel->alias && $type != 'hasAndBelongsToMany' && $type != 'hasMany') {
00549                         if (true === $this->generateSelfAssociationQuery($model, $linkModel, $type, $assoc, $assocData, $queryData, $external, $null)) {
00550                             $linkedModels[] = $type . '/' . $assoc;
00551                         }
00552                     } else {
00553                         if ($model->useDbConfig == $linkModel->useDbConfig) {
00554                             if (true === $this->generateAssociationQuery($model, $linkModel, $type, $assoc, $assocData, $queryData, $external, $null)) {
00555                                 $linkedModels[] = $type . '/' . $assoc;
00556                             }
00557                         }
00558                     }
00559                 }
00560             }
00561         }
00562         // Build final query SQL
00563         $query = $this->generateAssociationQuery($model, $null, null, null, null, $queryData, false, $null);
00564         $resultSet = $this->fetchAll($query, $model->cacheQueries, $model->alias);
00565 
00566         if ($resultSet === false) {
00567             $model->onError();
00568             return false;
00569         }
00570 
00571         $filtered = $this->__filterResults($resultSet, $model);
00572 
00573         if ($model->recursive > 0) {
00574             foreach ($model->__associations as $type) {
00575                 foreach ($model->{$type} as $assoc => $assocData) {
00576                     $db = null;
00577                     $linkModel =& $model->{$assoc};
00578 
00579                     if (!in_array($type . '/' . $assoc, $linkedModels)) {
00580                         if ($model->useDbConfig == $linkModel->useDbConfig) {
00581                             $db =& $this;
00582                         } else {
00583                             $db =& ConnectionManager::getDataSource($linkModel->useDbConfig);
00584                         }
00585                     } elseif ($model->recursive > 1 && ($type == 'belongsTo' || $type == 'hasOne')) {
00586                         // Do recursive joins on belongsTo and hasOne relationships
00587                         $db =& $this;
00588                     } else {
00589                         unset ($db);
00590                     }
00591 
00592                     if (isset($db) && $db != null) {
00593                         $stack = array($assoc);
00594                         $db->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $array, true, $resultSet, $model->recursive - 1, $stack);
00595                         unset($db);
00596                     }
00597                 }
00598             }
00599             $this->__filterResults($resultSet, $model, $filtered);
00600         }
00601 
00602         if (!is_null($recursive)) {
00603             $model->recursive = $_recursive;
00604         }
00605         return $resultSet;
00606     }
00607 /**
00608  * Private method.  Passes association results thru afterFind filter of corresponding model
00609  *
00610  * @param unknown_type $results
00611  * @param unknown_type $model
00612  * @param unknown_type $filtered
00613  * @return unknown
00614  */
00615     function __filterResults(&$results, &$model, $filtered = array()) {
00616 
00617         $filtering = array();
00618         $associations = array_merge($model->belongsTo, $model->hasOne, $model->hasMany, $model->hasAndBelongsToMany);
00619         $count = count($results);
00620 
00621         for ($i = 0; $i < $count; $i++) {
00622             if (is_array($results[$i])) {
00623                 $keys = array_keys($results[$i]);
00624                 $count2 = count($keys);
00625 
00626                 for ($j = 0; $j < $count2; $j++) {
00627                     $className = $key = $keys[$j];
00628 
00629                     if ($model->alias != $className && !in_array($key, $filtered)) {
00630                         if (!in_array($key, $filtering)) {
00631                             $filtering[] = $key;
00632                         }
00633 
00634                         if (isset($model->{$className}) && is_object($model->{$className})) {
00635                             $data = $model->{$className}->afterFind(array(array($key => $results[$i][$key])), false);
00636                         }
00637                         if (isset($data[0][$key])) {
00638                             $results[$i][$key] = $data[0][$key];
00639                         }
00640                     }
00641                 }
00642             }
00643         }
00644         return $filtering;
00645     }
00646 /**
00647  * Enter description here...
00648  *
00649  * @param Model $model
00650  * @param unknown_type $linkModel
00651  * @param string $type Association type
00652  * @param unknown_type $association
00653  * @param unknown_type $assocData
00654  * @param unknown_type $queryData
00655  * @param unknown_type $external
00656  * @param unknown_type $resultSet
00657  * @param integer $recursive Number of levels of association
00658  * @param array $stack
00659  */
00660     function queryAssociation(&$model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive, $stack) {
00661 
00662         if ($query = $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet)) {
00663             if (!isset($resultSet) || !is_array($resultSet)) {
00664                 if (Configure::read() > 0) {
00665                     e('<div style = "font: Verdana bold 12px; color: #FF0000">SQL Error in model ' . $model->alias . ': ');
00666                     if (isset($this->error) && $this->error != null) {
00667                         e($this->error);
00668                     }
00669                     e('</div>');
00670                 }
00671                 return null;
00672             }
00673             $count = count($resultSet);
00674 
00675             if ($type === 'hasMany' && (!isset($assocData['limit']) || empty($assocData['limit']))) {
00676                 $ins = $fetch = array();
00677                 for ($i = 0; $i < $count; $i++) {
00678                     if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) {
00679                         $ins[] = $in;
00680                     }
00681                 }
00682 
00683                 if (!empty($ins)) {
00684                     $query = r('{$__cakeID__$}', join(', ', $ins), $query);
00685                     $fetch = $this->fetchAll($query, $model->cacheQueries, $model->alias);
00686                 }
00687 
00688                 if (!empty($fetch) && is_array($fetch)) {
00689                     if ($recursive > 0) {
00690 
00691                         foreach ($linkModel->__associations as $type1) {
00692                             foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
00693 
00694                                 $deepModel =& $linkModel->{$assocData1['className']};
00695                                 if ($deepModel->alias != $model->alias) {
00696                                     $tmpStack = $stack;
00697                                     $tmpStack[] = $assoc1;
00698                                     if ($linkModel->useDbConfig == $deepModel->useDbConfig) {
00699                                         $db =& $this;
00700                                     } else {
00701                                         $db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
00702                                     }
00703                                     $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
00704                                 }
00705                             }
00706                         }
00707                     }
00708                 }
00709                 return $this->__mergeHasMany($resultSet, $fetch, $association, $model, $linkModel, $recursive);
00710             }
00711             for ($i = 0; $i < $count; $i++) {
00712 
00713                 $row =& $resultSet[$i];
00714                 $q = $this->insertQueryData($query, $resultSet[$i], $association, $assocData, $model, $linkModel, $stack);
00715 
00716                 if ($q != false) {
00717                     $fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias);
00718                 } else {
00719                     $fetch = null;
00720                 }
00721 
00722                 if (!empty($fetch) && is_array($fetch)) {
00723                     if ($recursive > 0) {
00724 
00725                         foreach ($linkModel->__associations as $type1) {
00726                             foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
00727 
00728                                 $deepModel =& $linkModel->{$assocData1['className']};
00729                                 if ($deepModel->alias != $model->alias) {
00730                                     $tmpStack = $stack;
00731                                     $tmpStack[] = $assoc1;
00732                                     if ($linkModel->useDbConfig == $deepModel->useDbConfig) {
00733                                         $db =& $this;
00734                                     } else {
00735                                         $db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
00736                                     }
00737                                     $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
00738                                 }
00739                             }
00740                         }
00741                     }
00742                     $this->__mergeAssociation($resultSet[$i], $fetch, $association, $type);
00743                     $resultSet[$i][$association] = $linkModel->afterfind($resultSet[$i][$association]);
00744 
00745                 } else {
00746                     $tempArray[0][$association] = false;
00747                     $this->__mergeAssociation($resultSet[$i], $tempArray, $association, $type);
00748                 }
00749             }
00750         }
00751     }
00752 
00753     function __mergeHasMany(&$resultSet, $merge, $association, &$model, &$linkModel) {
00754         foreach ($resultSet as $i => $value) {
00755             $count = 0;
00756             $merged[$association] = array();
00757             foreach ($merge as $j => $data) {
00758                 if (isset($value[$model->alias]) && $value[$model->alias][$model->primaryKey] === $data[$association][$model->hasMany[$association]['foreignKey']]) {
00759                     if (count($data) > 1) {
00760                         $data = array_merge($data[$association], $data);
00761                         unset($data[$association]);
00762                         foreach ($data as $key => $name) {
00763                             if (is_numeric($key)) {
00764                                 $data[$association][] = $name;
00765                                 unset($data[$key]);
00766                             }
00767                         }
00768                         $merged[$association][] = $data;
00769                     } else {
00770                         $merged[$association][] = $data[$association];
00771                     }
00772                 }
00773                 $count++;
00774             }
00775             if (isset($value[$model->alias])) {
00776                 $resultSet[$i] = Set::pushDiff($resultSet[$i], $merged);
00777                 unset($merged);
00778             }
00779         }
00780     }
00781 /**
00782  * Enter description here...
00783  *
00784  * @param unknown_type $data
00785  * @param unknown_type $merge
00786  * @param unknown_type $association
00787  * @param unknown_type $type
00788  */
00789     function __mergeAssociation(&$data, $merge, $association, $type) {
00790 
00791         if (isset($merge[0]) && !isset($merge[0][$association])) {
00792             $association = Inflector::pluralize($association);
00793         }
00794 
00795         if ($type == 'belongsTo' || $type == 'hasOne') {
00796             if (isset($merge[$association])) {
00797                 $data[$association] = $merge[$association][0];
00798             } else {
00799                 if (count($merge[0][$association]) > 1) {
00800                     foreach ($merge[0] as $assoc => $data2) {
00801                         if ($assoc != $association) {
00802                             $merge[0][$association][$assoc] = $data2;
00803                         }
00804                     }
00805                 }
00806                 if (!isset($data[$association])) {
00807                     if ($merge[0][$association] != null) {
00808                         $data[$association] = $merge[0][$association];
00809                     } else {
00810                         $data[$association] = array();
00811                     }
00812                 } else {
00813                     if (is_array($merge[0][$association])) {
00814                         foreach ($data[$association] as $k => $v) {
00815                             if (!is_array($v)) {
00816                                 $dataAssocTmp[$k] = $v;
00817                             }
00818                         }
00819 
00820                         foreach ($merge[0][$association] as $k => $v) {
00821                             if (!is_array($v)) {
00822                                 $mergeAssocTmp[$k] = $v;
00823                             }
00824                         }
00825 
00826                         if (array_keys($merge[0]) === array_keys($data)) {
00827                             $data[$association][$association] = $merge[0][$association];
00828                         } else {
00829                             $diff = Set::diff($dataAssocTmp, $mergeAssocTmp);
00830                             $data[$association] = array_merge($merge[0][$association], $diff);
00831                         }
00832                     }
00833                 }
00834             }
00835         } else {
00836             if ($merge[0][$association] === false) {
00837                 if (!isset($data[$association])) {
00838                     $data[$association] = array();
00839                 }
00840             } else {
00841                 foreach ($merge as $i => $row) {
00842                     if (count($row) == 1) {
00843                         $data[$association][] = $row[$association];
00844                     } else {
00845                         $tmp = array_merge($row[$association], $row);
00846                         unset($tmp[$association]);
00847                         $data[$association][] = $tmp;
00848                     }
00849                 }
00850             }
00851         }
00852     }
00853 /**
00854  * Enter description here...
00855  *
00856  * @param unknown_type $model
00857  * @param unknown_type $linkModel
00858  * @param unknown_type $type
00859  * @param unknown_type $association
00860  * @param unknown_type $assocData
00861  * @param unknown_type $queryData
00862  * @param unknown_type $external
00863  * @param unknown_type $resultSet
00864  * @return unknown
00865  */
00866     function generateSelfAssociationQuery(&$model, &$linkModel, $type, $association = null, $assocData = array(), &$queryData, $external = false, &$resultSet) {
00867         $alias = $association;
00868         if (empty($alias) && !empty($linkModel)) {
00869             $alias = $linkModel->alias;
00870         }
00871 
00872         if (!isset($queryData['selfJoin'])) {
00873             $queryData['selfJoin'] = array();
00874 
00875             $self = array(
00876                 'fields'    => $this->fields($model, null, $queryData['fields']),
00877                 'joins' => array(array(
00878                     'table' => $this->fullTableName($linkModel),
00879                     'alias' => $alias,
00880                     'type' => 'LEFT',
00881                     'conditions' => array(
00882                         $model->escapeField($assocData['foreignKey']) => '{$__cakeIdentifier[' . "{$alias}.{$linkModel->primaryKey}" . ']__$}'))
00883                     ),
00884                 'table' => $this->fullTableName($model),
00885                 'alias' => $model->alias,
00886                 'limit' => $queryData['limit'],
00887                 'offset'    => $queryData['offset'],
00888                 'conditions'=> $queryData['conditions'],
00889                 'order' => $queryData['order']
00890             );
00891 
00892             if (!empty($assocData['conditions'])) {
00893                 $self['joins'][0]['conditions'] = trim($this->conditions(array_merge($self['joins'][0]['conditions'], (array)$assocData['conditions']), true, false));
00894             }
00895 
00896             if (!empty($queryData['joins'])) {
00897                 foreach ($queryData['joins'] as $join) {
00898                     $self['joins'][] = $join;
00899                 }
00900             }
00901 
00902             if ($this->__bypass === false) {
00903                 $self['fields'] = array_merge($self['fields'], $this->fields($linkModel, $alias, (isset($assocData['fields']) ? $assocData['fields'] : '')));
00904             }
00905 
00906             if (!in_array($self, $queryData['selfJoin'])) {
00907                 $queryData['selfJoin'][] = $self;
00908                 return true;
00909             }
00910 
00911         } elseif (isset($linkModel)) {
00912             return $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet);
00913 
00914         } else {
00915             $result = $queryData['selfJoin'][0];
00916             if (!empty($queryData['joins'])) {
00917                 foreach ($queryData['joins'] as $join) {
00918                     if (!in_array($join, $result['joins'])) {
00919                         $result['joins'][] = $join;
00920                     }
00921                 }
00922             }
00923             if (!empty($queryData['conditions'])) {
00924                 $result['conditions'] = trim($this->conditions(array_merge((array)$result['conditions'], $assocData['conditions']), true, false));
00925             }
00926             if (!empty($queryData['fields'])) {
00927                 $result['fields'] = array_unique(array_merge($result['fields'], $queryData['fields']));
00928             }
00929             $sql = $this->buildStatement($result, $model);
00930             return $sql;
00931         }
00932     }
00933 /**
00934  * Enter description here...
00935  *
00936  * @param Model $model
00937  * @param unknown_type $linkModel
00938  * @param unknown_type $type
00939  * @param unknown_type $association
00940  * @param unknown_type $assocData
00941  * @param unknown_type $queryData
00942  * @param unknown_type $external
00943  * @param unknown_type $resultSet
00944  * @return unknown
00945  */
00946     function generateAssociationQuery(&$model, &$linkModel, $type, $association = null, $assocData = array(), &$queryData, $external = false, &$resultSet) {
00947         $this->__scrubQueryData($queryData);
00948         $this->__scrubQueryData($assocData);
00949         $joinedOnSelf = false;
00950 
00951         if (empty($queryData['fields'])) {
00952             $queryData['fields'] = $this->fields($model, $model->alias);
00953         } elseif (!empty($model->hasMany) && $model->recursive > -1) {
00954             $assocFields = $this->fields($model, $model->alias, array("{$model->alias}.{$model->primaryKey}"));
00955             $passedFields = $this->fields($model, $model->alias, $queryData['fields']);
00956 
00957             if (count($passedFields) === 1) {
00958                 $match = strpos($passedFields[0], $assocFields[0]);
00959                 $match1 = strpos($passedFields[0], 'COUNT(');
00960                 if ($match === false && $match1 === false) {
00961                     $queryData['fields'] = array_unique(array_merge($passedFields, $assocFields));
00962                 } else {
00963                     $queryData['fields'] = $passedFields;
00964                 }
00965             } else {
00966                 $queryData['fields'] = array_unique(array_merge($passedFields, $assocFields));
00967             }
00968             unset($assocFields, $passedFields);
00969         }
00970 
00971         if ($linkModel == null) {
00972             if (array_key_exists('selfJoin', $queryData)) {
00973                 return $this->generateSelfAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet);
00974             } else {
00975                 return $this->buildStatement(array(
00976                     'fields' => array_unique($queryData['fields']),
00977                     'table' => $this->fullTableName($model),
00978                     'alias' => $model->alias,
00979                     'limit' => $queryData['limit'],
00980                     'offset' => $queryData['offset'],
00981                     'joins' => $queryData['joins'],
00982                     'conditions' => $queryData['conditions'],
00983                     'order' => $queryData['order']), $model
00984                 );
00985             }
00986         }
00987         $alias = $association;
00988 
00989         if ($model->alias == $linkModel->alias) {
00990             $joinedOnSelf = true;
00991         }
00992 
00993         if ($external && isset($assocData['finderQuery'])) {
00994             if (!empty($assocData['finderQuery'])) {
00995                 return $assocData['finderQuery'];
00996             }
00997         }
00998 
00999         if ((!$external && in_array($type, array('hasOne', 'belongsTo')) && $this->__bypass === false) || $external) {
01000             $fields = $this->fields($linkModel, $alias, $assocData['fields']);
01001         } else {
01002             $fields = array();
01003         }
01004         $limit = '';
01005 
01006         if (isset($assocData['limit'])) {
01007             if ((!isset($assocData['offset']) || (empty($assocData['offset']))) && isset($assocData['page'])) {
01008                 $assocData['offset'] = ($assocData['page'] - 1) * $assocData['limit'];
01009             } elseif (!isset($assocData['offset'])) {
01010                 $assocData['offset'] = null;
01011             }
01012             $limit = $this->limit($assocData['limit'], $assocData['offset']);
01013         }
01014 
01015         switch($type) {
01016             case 'hasOne':
01017             case 'belongsTo':
01018                 if ($external) {
01019                     if ($type == 'hasOne') {
01020                         $conditions = $this->__mergeConditions($assocData['conditions'], array("{$alias}.{$assocData['foreignKey']}" => '{$__cakeID__$}'));
01021                     } elseif ($type == 'belongsTo') {
01022                         $conditions = $this->__mergeConditions($assocData['conditions'], array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}'));
01023                     }
01024                     $query = array_merge($assocData, array(
01025                         'conditions' => $conditions,
01026                         'table' => $this->fullTableName($linkModel),
01027                         'fields' => $fields,
01028                         'alias' => $alias
01029                     ));
01030 
01031                     if ($type == 'belongsTo') {
01032                         // Dunno if we should be doing this for hasOne also...?
01033                         // Or maybe not doing it at all...?
01034                         $query = array_merge($query, array('order' => $assocData['order'], 'limit' => $limit));
01035                     }
01036                 } else {
01037                     if ($type == 'hasOne') {
01038                         $conditions = $this->__mergeConditions($assocData['conditions'], array("{$alias}.{$assocData['foreignKey']}" => '{$__cakeIdentifier[' . "{$model->alias}.{$model->primaryKey}" . ']__$}'));
01039                     } elseif ($type == 'belongsTo') {
01040                         $conditions = $this->__mergeConditions($assocData['conditions'], array("{$model->alias}.{$assocData['foreignKey']}" => '{$__cakeIdentifier[' . "{$alias}.{$linkModel->primaryKey}" . ']__$}'));
01041                     }
01042 
01043                     $join = array(
01044                         'table' => $this->fullTableName($linkModel),
01045                         'alias' => $alias,
01046                         'type' => 'LEFT',
01047                         'conditions' => trim($this->conditions($conditions, true, false))
01048                     );
01049 
01050                     $queryData['fields'] = array_merge($queryData['fields'], $fields);
01051 
01052                     if (!empty($assocData['order'])) {
01053                         $hasCount = false;
01054                         foreach ($queryData['fields'] as $field) {
01055                             if (stripos($field, 'COUNT(*)') !== false) {
01056                                 $hasCount = true;
01057                                 break;
01058                             }
01059                         }
01060 
01061                         $putOrderByFields = true;
01062                         if ($hasCount) {
01063                             $orders = spliti(' ASC| DESC|,', $assocData['order']);
01064                             foreach ($orders as $order) {
01065                                 $order = trim($order);
01066                                 if (!empty($order) && !in_array($order, $queryData['fields'])) {
01067                                     $putOrderByFields = false;
01068                                     break;
01069                                 }
01070                             }
01071                         }
01072 
01073                         if ($putOrderByFields) {
01074                             $queryData['order'][] = $assocData['order'];
01075                         }
01076                     }
01077 
01078                     if (!in_array($join, $queryData['joins'])) {
01079                         $queryData['joins'][] = $join;
01080                     }
01081                     return true;
01082                 }
01083             break;
01084             case 'hasMany':
01085                 $assocData['fields'] = array_unique(array_merge(
01086                     $this->fields($linkModel, $alias, $assocData['fields']),
01087                     $this->fields($linkModel, $alias, array("{$alias}.{$assocData['foreignKey']}"))
01088                 ));
01089 
01090                 $query = array(
01091                     'conditions' => $this->__mergeConditions(array("{$alias}.{$assocData['foreignKey']}" => array('{$__cakeID__$}')), $assocData['conditions']),
01092                     'fields' => $assocData['fields'],
01093                     'table' => $this->fullTableName($linkModel),
01094                     'alias' => $alias,
01095                     'order' => $assocData['order'],
01096                     'limit' => $limit
01097                 );
01098             break;
01099             case 'hasAndBelongsToMany':
01100                 $joinTbl = $this->fullTableName($assocData['joinTable']);
01101                 $joinFields = array();
01102                 $joinAssoc = null;
01103                 $joinAlias = $joinTbl;
01104 
01105                 if (isset($assocData['with']) && !empty($assocData['with'])) {
01106                     $joinAssoc = $joinAlias = $model->{$assocData['with']}->name;
01107                     $joinFields = $model->{$assocData['with']}->loadInfo();
01108                     $joinFields = $joinFields->extract('{n}.name');
01109 
01110                     if (is_array($joinFields) && !empty($joinFields) && count($joinFields) > 2) {
01111                         $joinFields = $this->fields($model->{$assocData['with']}, $model->{$assocData['with']}->name, $joinFields);
01112                     } else {
01113                         $joinFields = array();
01114                         $joinAssoc = null;
01115                         $joinAlias = $joinTbl;
01116                     }
01117                 }
01118 
01119                 $query = array(
01120                     'conditions' => $assocData['conditions'],
01121                     'limit' => $limit,
01122                     'table' => $this->fullTableName($linkModel),
01123                     'alias' => $alias,
01124                     'fields' => array_merge($this->fields($linkModel, $alias, $assocData['fields']), $joinFields),
01125                     'order' => $assocData['order'],
01126                     'joins' => array(array(
01127                         'table' => $joinTbl,
01128                         'alias' => $joinAssoc,
01129                         'conditions' => array(
01130                             array("{$joinAlias}.{$assocData['foreignKey']}" => '{$__cakeID__$}'),
01131                             array("{$joinAlias}.{$assocData['associationForeignKey']}" => '{$__cakeIdentifier['."{$alias}.{$linkModel->primaryKey}".']__$}')
01132                         ))
01133                     )
01134                 );
01135             break;
01136         }
01137         if (isset($query)) {
01138             return $this->buildStatement($query, $model);
01139         }
01140         return null;
01141     }
01142 
01143     function buildJoinStatement($join) {
01144         $data = array_merge(array(
01145             'type' => null,
01146             'alias' => null,
01147             'table' => 'join_table',
01148             'conditions' => array()
01149         ), $join);
01150 
01151         if (!empty($data['alias'])) {
01152             $data['alias'] = $this->alias . $this->name($data['alias']);
01153         }
01154         if (!empty($data['conditions'])) {
01155             $data['conditions'] = trim($this->conditions($data['conditions'], true, false));
01156         }
01157         return $this->renderJoinStatement($data);
01158     }
01159 
01160     function buildStatement($query, $model) {
01161         $query = array_merge(array('offset' => null, 'joins' => array()), $query);
01162         if (!empty($query['joins'])) {
01163             for ($i = 0; $i < count($query['joins']); $i++) {
01164                 if (is_array($query['joins'][$i])) {
01165                     $query['joins'][$i] = $this->buildJoinStatement($query['joins'][$i]);
01166                 }
01167             }
01168         }
01169         return $this->renderStatement(array(
01170             'conditions' => $this->conditions($query['conditions']),
01171             'fields' => join(', ', $query['fields']),
01172             'table' => $query['table'],
01173             'alias' => $this->alias . $this->name($query['alias']),
01174             'order' => $this->order($query['order']),
01175             'limit' => $this->limit($query['limit'], $query['offset']),
01176             'joins' => join(' ', $query['joins'])
01177         ));
01178     }
01179 
01180     function renderJoinStatement($data) {
01181         extract($data);
01182         return trim("{$type} JOIN {$table} {$alias} ON ({$conditions})");
01183     }
01184 
01185     function renderStatement($data) {
01186         extract($data);
01187         return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order} {$limit}";
01188     }
01189 /**
01190  * Private method
01191  *
01192  * @return array
01193  */
01194     function __mergeConditions($query, $assoc) {
01195         if (!empty($assoc)) {
01196             if (is_array($query)) {
01197                 return array_merge((array)$assoc, $query);
01198             } else {
01199                 if (!empty($query)) {
01200                     $query = array($query);
01201                     if (is_array($assoc)) {
01202                         $query = array_merge($query, $assoc);
01203                     } else {
01204                         $query[] = $assoc;
01205                     }
01206                     return $query;
01207                 } else {
01208                     return $assoc;
01209                 }
01210             }
01211         }
01212         return $query;
01213     }
01214 /**
01215  * Generates and executes an SQL UPDATE statement for given model, fields, and values.
01216  *
01217  * @param Model $model
01218  * @param array $fields
01219  * @param array $values
01220  * @param mixed $conditions
01221  * @return array
01222  */
01223     function update(&$model, $fields = array(), $values = null, $conditions = null) {
01224         $updates = array();
01225 
01226         if ($values == null) {
01227             $combined = $fields;
01228         } else {
01229             $combined = array_combine($fields, $values);
01230         }
01231 
01232         foreach ($combined as $field => $value) {
01233             if ($value === null) {
01234                 $updates[] = $this->name($field) . ' = NULL';
01235             } else {
01236                 $update = $this->name($field) . ' = ';
01237                 if ($conditions == null) {
01238                     $update .= $this->value($value, $model->getColumnType($field));
01239                 } else {
01240                     $update .= $value;
01241                 }
01242                 $updates[] =  $update;
01243             }
01244         }
01245         $conditions = $this->defaultConditions($model, $conditions);
01246 
01247         if ($conditions === false) {
01248             return false;
01249         }
01250 
01251         $fields = join(',', $updates);
01252         $table = $this->fullTableName($model);
01253 
01254         $conditions = $this->conditions($conditions);
01255 
01256         if (!$this->execute("UPDATE {$table} SET {$fields} {$conditions}")) {
01257             $model->onError();
01258             return false;
01259         }
01260         return true;
01261     }
01262 /**
01263  * Generates and executes an SQL DELETE statement for given id on given model.
01264  *
01265  * @param Model $model
01266  * @param mixed $conditions
01267  * @return boolean Success
01268  */
01269     function delete(&$model, $conditions = null) {
01270         $query = $this->defaultConditions($model, $conditions);
01271 
01272         if ($query === false) {
01273             return false;
01274         }
01275 
01276         $table = $this->fullTableName($model);
01277         $conditions = $this->conditions($query);
01278 
01279         if ($this->execute("DELETE FROM {$table} {$conditions}") === false) {
01280             $model->onError();
01281             return false;
01282         }
01283         return true;
01284     }
01285 /**
01286  * Creates a default set of conditions from the model if $conditions is null/empty.
01287  *
01288  * @param object $model
01289  * @param mixed  $conditions
01290  * @return mixed
01291  */
01292     function defaultConditions(&$model, $conditions) {
01293         if (!empty($conditions)) {
01294             return $conditions;
01295         }
01296         if (!$model->exists()) {
01297             return false;
01298         }
01299         return array($model->primaryKey => (array)$model->getID());
01300     }
01301 /**
01302  * Returns a key formatted like a string Model.fieldname(i.e. Post.title, or Country.name)
01303  *
01304  * @param unknown_type $model
01305  * @param unknown_type $key
01306  * @param unknown_type $assoc
01307  * @return string
01308  */
01309     function resolveKey($model, $key, $assoc = null) {
01310         if (empty($assoc)) {
01311             $assoc = $model->alias;
01312         }
01313         if (!strpos('.', $key)) {
01314             return $this->name($model->alias) . '.' . $this->name($key);
01315         }
01316         return $key;
01317     }
01318 /**
01319  * Returns the column type of a given
01320  *
01321  * @param Model $model
01322  * @param string $field
01323  */
01324     function getColumnType(&$model, $field) {
01325         return $model->getColumnType($field);
01326     }
01327 /**
01328  * Private helper method to remove query metadata in given data array.
01329  *
01330  * @param array $data
01331  */
01332     function __scrubQueryData(&$data) {
01333         foreach (array('conditions', 'fields', 'joins', 'order', 'limit', 'offset') as $key) {
01334             if (!isset($data[$key]) || empty($data[$key])) {
01335                 $data[$key] = array();
01336             }
01337         }
01338     }
01339 /**
01340  * Generates the fields list of an SQL query.
01341  *
01342  * @param Model $model
01343  * @param string $alias Alias tablename
01344  * @param mixed $fields
01345  * @param boolean $quote If false, returns fields array unquoted
01346  * @return array
01347  */
01348     function fields(&$model, $alias = null, $fields = array(), $quote = true) {
01349         if (empty($alias)) {
01350             $alias = $model->alias;
01351         }
01352 
01353         if (!is_array($fields)) {
01354             if (!empty($fields)) {
01355                 $depth = 0;
01356                 $offset = 0;
01357                 $buffer = '';
01358                 $results = array();
01359                 $length = strlen($fields);
01360 
01361                 while ($offset <= $length) {
01362                     $tmpOffset = -1;
01363                     $offsets = array(strpos($fields, ',', $offset), strpos($fields, '(', $offset), strpos($fields, ')', $offset));
01364                     for ($i = 0; $i < 3; $i++) {
01365                         if ($offsets[$i] !== false && ($offsets[$i] < $tmpOffset || $tmpOffset == -1)) {
01366                             $tmpOffset = $offsets[$i];
01367                         }
01368                     }
01369                     if ($tmpOffset !== -1) {
01370                         $buffer .= substr($fields, $offset, ($tmpOffset - $offset));
01371                         if ($fields{$tmpOffset} == ',' && $depth == 0) {
01372                             $results[] = $buffer;
01373                             $buffer = '';
01374                         } else {
01375                             $buffer .= $fields{$tmpOffset};
01376                         }
01377                         if ($fields{$tmpOffset} == '(') {
01378                             $depth++;
01379                         }
01380                         if ($fields{$tmpOffset} == ')') {
01381                             $depth--;
01382                         }
01383                         $offset = ++$tmpOffset;
01384                     } else {
01385                         $results[] = $buffer . substr($fields, $offset);
01386                         $offset = $length + 1;
01387                     }
01388                 }
01389                 if (empty($results) && !empty($buffer)) {
01390                     $results[] = $buffer;
01391                 }
01392 
01393                 if (!empty($results)) {
01394                     $fields = array_map('trim', $results);
01395                 } else {
01396                     $fields = array();
01397                 }
01398             }
01399         }
01400         if (empty($fields)) {
01401             $fieldData = $model->loadInfo();
01402             $fields = $fieldData->extract('{n}.name');
01403         } else {
01404             $fields = array_filter($fields);
01405         }
01406 
01407         if (!$quote) {
01408             return $fields;
01409         }
01410         $count = count($fields);
01411 
01412         if ($count >= 1 && !in_array($fields[0], array('*', 'COUNT(*)'))) {
01413             for ($i = 0; $i < $count; $i++) {
01414                 if (!preg_match('/^.+\\(.*\\)/', $fields[$i])) {
01415                     $prepend = '';
01416 
01417                     if (strpos($fields[$i], 'DISTINCT') !== false) {
01418                         $prepend   = 'DISTINCT ';
01419                         $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i]));
01420                     }
01421                     $dot = strpos($fields[$i], '.');
01422 
01423                     if ($dot === false) {
01424                         $fields[$i] = $prepend . $this->name($alias) . '.' . $this->name($fields[$i]);
01425                     } else {
01426                         $comma = strpos($fields[$i], ',');
01427                         if ($comma === false) {
01428                             $build = explode('.', $fields[$i]);
01429                             if (!Set::numeric($build)) {
01430                                 $fields[$i] = $prepend . $this->name($build[0]) . '.' . $this->name($build[1]);
01431                             }
01432                         } else {
01433                             $comma = explode(',', $fields[$i]);
01434                             foreach ($comma as $string) {
01435                                 $build = explode('.', $string);
01436                                 if (!Set::numeric($build)) {
01437                                     $value[] = $prepend . $this->name(trim($build[0])) . '.' . $this->name(trim($build[1]));
01438                                 }
01439                             }
01440                             $fields[$i] = implode(', ', $value);
01441                         }
01442                     }
01443                 } elseif (preg_match('/\(([\.\w]+)\)/', $fields[$i], $field)) {
01444                     if (isset($field[1])) {
01445                         if (strpos($field[1], '.') === false) {
01446                             $field[1] = $this->name($alias) . '.' . $this->name($field[1]);
01447                         } else {
01448                             $field[0] = explode('.', $field[1]);
01449                             if (!Set::numeric($field[0])) {
01450                                 $field[0] = join('.', array_map(array($this, 'name'), $field[0]));
01451                                 $fields[$i] = preg_replace('/\(' . $field[1] . '\)/', '(' . $field[0] . ')', $fields[$i], 1);
01452                             }
01453                         }
01454                     }
01455                 }
01456             }
01457         }
01458         return $fields;
01459     }
01460 /**
01461  * Creates a WHERE clause by parsing given conditions data.
01462  *
01463  * @param mixed $conditions Array or string of conditions
01464  * @return string SQL fragment
01465  */
01466     function conditions($conditions, $quoteValues = true, $where = true) {
01467         $clause = $out = '';
01468         if (is_string($conditions) || empty($conditions) || $conditions === true) {
01469             if (empty($conditions) || trim($conditions) == '' || $conditions === true) {
01470                 if ($where) {
01471                     return ' WHERE 1 = 1';
01472                 }
01473                 return '1 = 1';
01474             }
01475             if (!preg_match('/^WHERE\\x20|^GROUP\\x20BY\\x20|^HAVING\\x20|^ORDER\\x20BY\\x20/i', $conditions, $match)) {
01476                 if ($where) {
01477                     $clause = ' WHERE ';
01478                 }
01479             }
01480             if (trim($conditions) == '') {
01481                 $conditions = ' 1 = 1';
01482             } else {
01483                 $conditions = $this->__quoteFields($conditions);
01484             }
01485             return $clause . $conditions;
01486         } else {
01487             if ($where) {
01488                 $clause = ' WHERE ';
01489             }
01490             if (!empty($conditions)) {
01491                 $out = $this->conditionKeysToString($conditions, $quoteValues);
01492             }
01493             if (empty($out) || empty($conditions)) {
01494                 return $clause . ' 1 = 1';
01495             }
01496             return $clause . join(' AND ', $out);
01497         }
01498     }
01499 /**
01500  * Creates a WHERE clause by parsing given conditions array.  Used by DboSource::conditions().
01501  *
01502  * @param array $conditions Array or string of conditions
01503  * @return string SQL fragment
01504  */
01505     function conditionKeysToString($conditions, $quoteValues = true) {
01506         $c = 0;
01507         $data = $not = null;
01508         $out = array();
01509         $bool = array('and', 'or', 'not', 'and not', 'or not', 'xor', '||', '&&');
01510         $join = ' AND ';
01511 
01512         foreach ($conditions as $key => $value) {
01513             if (is_numeric($key) && empty($value)) {
01514                 continue;
01515             } elseif (is_numeric($key) && is_string($value)) {
01516                 $out[] = $not . $this->__quoteFields($value);
01517             } elseif (in_array(strtolower(trim($key)), $bool)) {
01518                 $join = ' ' . strtoupper($key) . ' ';
01519                 $value = $this->conditionKeysToString($value, $quoteValues);
01520                 if (strpos($join, 'NOT') !== false) {
01521                     if (strtoupper(trim($key)) == 'NOT') {
01522                         $key = 'AND ' . $key;
01523                     }
01524                     $not = 'NOT ';
01525                 } else {
01526                     $not = null;
01527                 }
01528                 $out[] = $not . '((' . join(') ' . strtoupper($key) . ' (', $value) . '))';
01529             } else {
01530                 if (is_string($value) && preg_match('/^\{\$__cakeIdentifier\[(.*)\]__\$}$/', $value, $identifier) && isset($identifier[1])) {
01531                     $data .= $this->name($key) . ' = ' . $this->name($identifier[1]);
01532                 } elseif (is_array($value) && !empty($value)) {
01533                     $keys = array_keys($value);
01534                     if ($keys[0] === 0) {
01535                         $data = $this->name($key) . ' IN (';
01536                         if  (strpos($value[0], '-!') === 0) {
01537                             $value[0] = str_replace('-!', '', $value[0]);
01538                             $data .= $value[0];
01539                             $data .= ')';
01540                         } else {
01541                             if ($quoteValues) {
01542                                 foreach ($value as $valElement) {
01543                                     $data .= $this->value($valElement) . ', ';
01544                                 }
01545                             }
01546                             $data[strlen($data) - 2] = ')';
01547                         }
01548                     } else {
01549                         $ret = $this->conditionKeysToString($value, $quoteValues);
01550                         if (count($ret) > 1) {
01551                             $out[] = '(' . join(') AND (', $ret) . ')';
01552                         } elseif (isset($ret[0])) {
01553                             $out[] = $ret[0];
01554                         }
01555                     }
01556                 } elseif (is_numeric($key) && !empty($value)) {
01557                     $data = $this->__quoteFields($value);
01558                 } elseif ($value === null || (is_array($value) && empty($value))) {
01559                     $data = $this->name($key) . ' IS NULL';
01560                 } elseif ($value === false || $value === true) {
01561                     $data = $this->name($key) . " = " . $this->value($value, 'boolean');
01562                 } elseif ($value === '') {
01563                     $data = $this->name($key) . " = ''";
01564                 } elseif (preg_match('/^([a-z]+\\([a-z0-9]*\\)\\x20+|(?:' . join('\\x20)|(?:', $this->__sqlOps) . '\\x20)|<[>=]?(?![^>]+>)\\x20?|[>=!]{1,3}(?!<)\\x20?)?(.*)/is', $value, $match)) {
01565                     if (preg_match('/(\\x20[\\w]*\\x20)/', $key, $regs)) {
01566                         $clause = $regs['1'];
01567                         $key = preg_replace('/' . $regs['1'] . '/', '', $key);
01568                     }
01569 
01570                     $not = false;
01571                     $mValue = trim($match['1']);
01572                     if (empty($match['1'])) {
01573                         $match['1'] = ' = ';
01574                     } elseif (empty($mValue)) {
01575                         $match['1'] = ' = ';
01576                         $match['2'] = $match['0'];
01577                     } elseif (!isset($match['2'])) {
01578                         $match['1'] = ' = ';
01579                         $match['2'] = $match['0'];
01580                     } elseif (strtolower($mValue) == 'not') {
01581                         $not = $this->conditionKeysToString(array($mValue => array($key => $match[2])), $quoteValues);
01582                     }
01583 
01584                     if ($not) {
01585                         $data = $not[0];
01586                     } elseif (strpos($match['2'], '-!') === 0) {
01587                         $match['2'] = str_replace('-!', '', $match['2']);
01588                         $data = $this->name($key) . ' ' . $match['1'] . ' ' . $match['2'];
01589                     } else {
01590                         if (!empty($match['2']) && $quoteValues) {
01591                             if (!preg_match('/[A-Za-z]+\\([a-z0-9]*\\),?\\x20+/', $match['2'])) {
01592                                 $match['2'] = $this->value($match['2']);
01593                             }
01594                             $match['2'] = str_replace(' AND ', "' AND '", $match['2']);
01595                         }
01596                         $data = $this->__quoteFields($key);
01597                         if ($data === $key) {
01598                             $data = $this->name($key) . ' ' . $match['1'] . ' ' . $match['2'];
01599                         } else {
01600                             $data = $data . ' ' . $match['1'] . ' ' . $match['2'];
01601                         }
01602                     }
01603                 }
01604 
01605                 if ($data != null) {
01606                     $out[] = $data;
01607                     $data = null;
01608                 }
01609             }
01610             $c++;
01611         }
01612         return $out;
01613     }
01614 /**
01615  * Quotes Model.fields
01616  *
01617  * @param string $conditions
01618  * @return string or false if no match
01619  * @access private
01620  */
01621     function __quoteFields($conditions) {
01622         $start = null;
01623         $end  = null;
01624         $original = $conditions;
01625 
01626         if (!empty($this->startQuote)) {
01627             $start = preg_quote($this->startQuote);
01628         }
01629 
01630         if (!empty($this->endQuote)) {
01631             $end = preg_quote($this->endQuote);
01632         }
01633         $conditions = str_replace(array($start, $end), '', $conditions);
01634         preg_match_all('/(?:[\'\"][^\'\"\\\]*(?:\\\.[^\'\"\\\]*)*[\'\"])|([a-z0-9_' . $start . $end . ']*\\.[a-z0-9_' . $start . $end . ']*)/i', $conditions, $replace, PREG_PATTERN_ORDER);
01635 
01636         if (isset($replace['1']['0'])) {
01637             $pregCount = count($replace['1']);
01638 
01639             for ($i = 0; $i < $pregCount; $i++) {
01640                 if (!empty($replace['1'][$i]) && !is_numeric($replace['1'][$i])) {
01641                     $conditions = preg_replace('/\b' . preg_quote($replace['1'][$i]) . '\b/', $this->name($replace['1'][$i]), $conditions);
01642                 }
01643             }
01644             return $conditions;
01645         }
01646         return $original;
01647     }
01648 /**
01649  * Returns a limit statement in the correct format for the particular database.
01650  *
01651  * @param integer $limit Limit of results returned
01652  * @param integer $offset Offset from which to start results
01653  * @return string SQL limit/offset statement
01654  */
01655     function limit($limit, $offset = null) {
01656         if ($limit) {
01657             $rt = '';
01658             if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) {
01659                 $rt = ' LIMIT';
01660             }
01661 
01662             if ($offset) {
01663                 $rt .= ' ' . $offset . ',';
01664             }
01665 
01666             $rt .= ' ' . $limit;
01667             return $rt;
01668         }
01669         return null;
01670     }
01671 /**
01672  * Returns an ORDER BY clause as a string.
01673  *
01674  * @param string $key Field reference, as a key (i.e. Post.title)
01675  * @param string $direction Direction (ASC or DESC)
01676  * @return string ORDER BY clause
01677  */
01678     function order($keys, $direction = 'ASC') {
01679         if (is_string($keys) && strpos($keys, ',') && !preg_match('/\(.+\,.+\)/', $keys)) {
01680             $keys = explode(',', $keys);
01681             array_map('trim', $keys);
01682         }
01683 
01684         if (is_array($keys)) {
01685             foreach ($keys as $key => $val) {
01686                 if (is_numeric($key) && empty($val)) {
01687                     unset ($keys[$key]);
01688                 }
01689             }
01690         }
01691 
01692         if (empty($keys) || (is_array($keys) && count($keys) && isset($keys[0]) && empty($keys[0]))) {
01693             return '';
01694         }
01695 
01696         if (is_array($keys)) {
01697             if (Set::countDim($keys) > 1) {
01698                 $new = array();
01699 
01700                 foreach ($keys as $val) {
01701                     $val = $this->order($val);
01702                     $new[] = $val;
01703                 }
01704 
01705                 $keys = $new;
01706             }
01707 
01708             foreach ($keys as $key => $value) {
01709                 if (is_numeric($key)) {
01710                     $value = ltrim(str_replace('ORDER BY ', '', $this->order($value)));
01711                     $key  = $value;
01712 
01713                     if (!preg_match('/\\x20ASC|\\x20DESC/i', $key)) {
01714                         $value = ' ' . $direction;
01715                     } else {
01716                         $value = '';
01717                     }
01718                 } else {
01719                     $value = ' ' . $value;
01720                 }
01721 
01722                 if (!preg_match('/^.+\\(.*\\)/', $key) && !strpos($key, ',')) {
01723                     $dir   = '';
01724                     $hasDir = preg_match('/\\x20ASC|\\x20DESC/i', $key, $dir);
01725 
01726                     if ($hasDir) {
01727                         $dir = $dir[0];
01728                         $key = preg_replace('/\\x20ASC|\\x20DESC/i', '', $key);
01729                     } else {
01730                         $dir = '';
01731                     }
01732                     $key = trim($this->name(trim($key)) . ' ' . trim($dir));
01733                 }
01734                 $order[] = $this->order($key . $value);
01735             }
01736 
01737             return ' ORDER BY ' . trim(str_replace('ORDER BY', '', join(',', $order)));
01738         } else {
01739             $keys = preg_replace('/ORDER\\x20BY/i', '', $keys);
01740 
01741             if (strpos($keys, '.')) {
01742                 preg_match_all('/([a-zA-Z0-9_]{1,})\\.([a-zA-Z0-9_]{1,})/', $keys, $result,
01743                                     PREG_PATTERN_ORDER);
01744                 $pregCount = count($result['0']);
01745 
01746                 for ($i = 0; $i < $pregCount; $i++) {
01747                     $keys = preg_replace('/' . $result['0'][$i] . '/', $this->name($result['0'][$i]), $keys);
01748                 }
01749 
01750                 if (preg_match('/\\x20ASC|\\x20DESC/i', $keys)) {
01751                     return ' ORDER BY ' . $keys;
01752                 } else {
01753                     return ' ORDER BY ' . $keys . ' ' . $direction;
01754                 }
01755             } elseif (preg_match('/(\\x20ASC|\\x20DESC)/i', $keys, $match)) {
01756                 $direction = $match['1'];
01757                 $keys     = preg_replace('/' . $match['1'] . '/', '', $keys);
01758                 return ' ORDER BY ' . $keys . $direction;
01759             } else {
01760                 $direction = ' ' . $direction;
01761             }
01762             return ' ORDER BY ' . $keys . $direction;
01763         }
01764     }
01765 /**
01766  * Disconnects database, kills the connection and says the connection is closed,
01767  * and if DEBUG is turned on, the log for this object is shown.
01768  *
01769  */
01770     function close() {
01771         if (Configure::read() > 1) {
01772             $this->showLog();
01773         }
01774         $this->disconnect();
01775     }
01776 /**
01777  * Checks if the specified table contains any record matching specified SQL
01778  *
01779  * @param Model $model Model to search
01780  * @param string $sql SQL WHERE clause (condition only, not the "WHERE" part)
01781  * @return boolean True if the table has a matching record, else false
01782  */
01783     function hasAny($model, $sql) {
01784         $sql = $this->conditions($sql);
01785         $out = $this->fetchRow("SELECT COUNT(" . $model->primaryKey . ") " . $this->alias . "count FROM " . $this->fullTableName($model) . ' ' . ($sql ? ' ' . $sql : 'WHERE 1 = 1'));
01786 
01787         if (is_array($out)) {
01788             return $out[0]['count'];
01789         }
01790         return false;
01791     }
01792 /**
01793  * Gets the length of a database-native column description, or null if no length
01794  *
01795  * @param string $real Real database-layer column type (i.e. "varchar(255)")
01796  * @return integer An integer representing the length of the column
01797  */
01798     function length($real) {
01799         $col = str_replace(array(')', 'unsigned'), '', $real);
01800         $limit = null;
01801 
01802         if (strpos($col, '(') !== false) {
01803             list($col, $limit) = explode('(', $col);
01804         }
01805 
01806         if ($limit != null) {
01807             return intval($limit);
01808         }
01809         return null;
01810     }
01811 /**
01812  * Translates between PHP boolean values and Database (faked) boolean values
01813  *
01814  * @param mixed $data Value to be translated
01815  * @return mixed Converted boolean value
01816  */
01817     function boolean($data) {
01818         if ($data === true || $data === false) {
01819             if ($data === true) {
01820                 return 1;
01821             }
01822             return 0;
01823         }
01824 
01825         if (!empty($data)) {
01826             return true;
01827         }
01828         return false;
01829     }
01830 /**
01831  * Destructor. Closes connection to the database.
01832  *
01833  */
01834     function __destruct() {
01835         if ($this->_transactionStarted) {
01836             $null = null;
01837             $this->rollback($null);
01838         }
01839         parent::__destruct();
01840     }
01841 /**
01842  * Inserts multiple values into a join table
01843  *
01844  * @param string $table
01845  * @param string $fields
01846  * @param array $values
01847  */
01848     function insertMulti($table, $fields, $values) {
01849         $values = implode(', ', $values);
01850         $this->query("INSERT INTO {$table} ({$fields}) VALUES {$values}");
01851     }
01852 /**
01853  * Returns an array of the indexes in given datasource name.
01854  *
01855  * @param string $model Name of model to inspect
01856  * @return array Fields in table. Keys are column and unique
01857  */
01858     function index($model) {
01859         return false;
01860     }
01861 /**
01862  * Generate a create syntax from CakeSchema
01863  *
01864  * @param object $schema An instance of a subclass of CakeSchema
01865  * @param string $table Optional.  If specified only the table name given will be generated.
01866  *                      Otherwise, all tables defined in the schema are generated.
01867  * @return string
01868  */
01869     function createSchema($schema, $table = null) {
01870         return false;
01871     }
01872 /**
01873  * Generate a alter syntax from  CakeSchema::compare()
01874  *
01875  * @param unknown_type $schema
01876  * @return unknown
01877  */
01878     function alterSchema($compare, $table = null) {
01879         return false;
01880     }
01881 /**
01882  * Generate a drop syntax from CakeSchema
01883  *
01884  * @param object $schema An instance of a subclass of CakeSchema
01885  * @param string $table Optional.  If specified only the table name given will be generated.
01886  *                      Otherwise, all tables defined in the schema are generated.
01887  * @return string
01888  */
01889     function dropSchema($schema, $table = null) {
01890         return false;
01891     }
01892 /**
01893  * Generate a column schema string
01894  *
01895  * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]),
01896  *                      where options can be 'default', 'length', or 'key'.
01897  * @return string
01898  */
01899     function buildColumn($column) {
01900         return false;
01901     }
01902 /**
01903  * Format indexes for create table
01904  *
01905  * @param array $indexes
01906  * @return string
01907  */
01908     function buildIndex($indexes) {
01909         return false;
01910     }
01911 /**
01912  * Guesses the data type of an array
01913  *
01914  * @param string $value
01915  * @return void
01916  * @access public
01917  */
01918     function introspectType($value) {
01919         if (!is_array($value)) {
01920             if ($value === true || $value === false) {
01921                 return 'boolean';
01922             }
01923             if (is_float($value) && floatval($value) === $value) {
01924                 return 'float';
01925             }
01926             if (is_int($value) && intval($value) === $value) {
01927                 return 'integer';
01928             }
01929             if (is_string($value) && strlen($value) > 255) {
01930                 return 'text';
01931             }
01932             return 'string';
01933         }
01934 
01935         $isAllFloat = $isAllInt = true;
01936         $containsFloat = $containsInt = $containsString = false;
01937         foreach ($value as $key => $valElement) {
01938             $valElement = trim($valElement);
01939             if (!is_float($valElement) && !preg_match('/^[\d]+\.[\d]+$/', $valElement)) {
01940                 $isAllFloat = false;
01941             } else {
01942                 $containsFloat = true;
01943                 continue;
01944             }
01945             if (!is_int($valElement) && !preg_match('/^[\d]+$/', $valElement)) {
01946                 $isAllInt = false;
01947             } else {
01948                 $containsInt = true;
01949                 continue;
01950             }
01951             $containsString = true;
01952         }
01953 
01954         if ($isAllFloat) {
01955             return 'float';
01956         }
01957         if ($isAllInt) {
01958             return 'integer';
01959         }
01960 
01961         if ($containsInt && !$containsString) {
01962             return 'integer';
01963         }
01964         return 'string';
01965     }
01966 }
01967 ?>