PageRenderTime 157ms CodeModel.GetById 47ms app.highlight 84ms RepoModel.GetById 1ms app.codeStats 1ms

/code/ryzom/tools/server/www/webtt/cake/libs/model/datasources/dbo_source.php

https://bitbucket.org/SirCotare/ryzom
PHP | 2925 lines | 2076 code | 209 blank | 640 comment | 491 complexity | 2d9fd69e908013b7fb9fc11cfd341864 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/**
   3 * Dbo Source
   4 *
   5 * PHP versions 4 and 5
   6 *
   7 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
   8 * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
   9 *
  10 * Licensed under The MIT License
  11 * Redistributions of files must retain the above copyright notice.
  12 *
  13 * @copyright     Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14 * @link          http://cakephp.org CakePHP(tm) Project
  15 * @package       cake
  16 * @subpackage    cake.cake.libs.model.datasources
  17 * @since         CakePHP(tm) v 0.10.0.1076
  18 * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
  19 */
  20App::import('Core', array('Set', 'String'));
  21
  22/**
  23 * DboSource
  24 *
  25 * Creates DBO-descendant objects from a given db connection configuration
  26 *
  27 * @package       cake
  28 * @subpackage    cake.cake.libs.model.datasources
  29 */
  30class DboSource extends DataSource {
  31
  32/**
  33 * Description string for this Database Data Source.
  34 *
  35 * @var string
  36 * @access public
  37 */
  38	var $description = "Database Data Source";
  39
  40/**
  41 * index definition, standard cake, primary, index, unique
  42 *
  43 * @var array
  44 */
  45	var $index = array('PRI' => 'primary', 'MUL' => 'index', 'UNI' => 'unique');
  46
  47/**
  48 * Database keyword used to assign aliases to identifiers.
  49 *
  50 * @var string
  51 * @access public
  52 */
  53	var $alias = 'AS ';
  54
  55/**
  56 * Caches result from query parsing operations.  Cached results for both DboSource::name() and
  57 * DboSource::conditions() will be stored here.  Method caching uses `crc32()` which is
  58 * fast but can collisions more easily than other hashing algorithms.  If you have problems
  59 * with collisions, set DboSource::$cacheMethods to false.
  60 *
  61 * @var array
  62 * @access public
  63 */
  64	var $methodCache = array();
  65
  66/**
  67 * Whether or not to cache the results of DboSource::name() and DboSource::conditions()
  68 * into the memory cache.  Set to false to disable the use of the memory cache.
  69 *
  70 * @var boolean.
  71 * @access public
  72 */
  73	var $cacheMethods = true ;
  74
  75/**
  76 * Bypass automatic adding of joined fields/associations.
  77 *
  78 * @var boolean
  79 * @access private
  80 */
  81	var $__bypass = false;
  82
  83/**
  84 * The set of valid SQL operations usable in a WHERE statement
  85 *
  86 * @var array
  87 * @access private
  88 */
  89	var $__sqlOps = array('like', 'ilike', 'or', 'not', 'in', 'between', 'regexp', 'similar to');
  90
  91/**
  92 * Index of basic SQL commands
  93 *
  94 * @var array
  95 * @access protected
  96 */
  97	var $_commands = array(
  98		'begin' => 'BEGIN',
  99		'commit' => 'COMMIT',
 100		'rollback' => 'ROLLBACK'
 101	);
 102
 103/**
 104 * Separator string for virtualField composition
 105 *
 106 * @var string
 107 */
 108	var $virtualFieldSeparator = '__';
 109
 110/**
 111 * List of table engine specific parameters used on table creating
 112 *
 113 * @var array
 114 * @access public
 115 */
 116	var $tableParameters = array();
 117
 118/**
 119 * List of engine specific additional field parameters used on table creating
 120 *
 121 * @var array
 122 * @access public
 123 */
 124	var $fieldParameters = array();
 125
 126/**
 127 * Constructor
 128 *
 129 * @param array $config Array of configuration information for the Datasource.
 130 * @param boolean $autoConnect Whether or not the datasource should automatically connect.
 131 * @access public
 132 */
 133	function __construct($config = null, $autoConnect = true) {
 134		if (!isset($config['prefix'])) {
 135			$config['prefix'] = '';
 136		}
 137		parent::__construct($config);
 138		$this->fullDebug = Configure::read() > 1;
 139		if (!$this->enabled()) {
 140			return false;
 141		}
 142		if ($autoConnect) {
 143			return $this->connect();
 144		} else {
 145			return true;
 146		}
 147	}
 148
 149/**
 150 * Reconnects to database server with optional new settings
 151 *
 152 * @param array $config An array defining the new configuration settings
 153 * @return boolean True on success, false on failure
 154 * @access public
 155 */
 156	function reconnect($config = array()) {
 157		$this->disconnect();
 158		$this->setConfig($config);
 159		$this->_sources = null;
 160
 161		return $this->connect();
 162	}
 163
 164/**
 165 * Prepares a value, or an array of values for database queries by quoting and escaping them.
 166 *
 167 * @param mixed $data A value or an array of values to prepare.
 168 * @param string $column The column into which this data will be inserted
 169 * @param boolean $read Value to be used in READ or WRITE context
 170 * @return mixed Prepared value or array of values.
 171 * @access public
 172 */
 173	function value($data, $column = null, $read = true) {
 174		if (is_array($data) && !empty($data)) {
 175			return array_map(
 176				array(&$this, 'value'),
 177				$data, array_fill(0, count($data), $column), array_fill(0, count($data), $read)
 178			);
 179		} elseif (is_object($data) && isset($data->type)) {
 180			if ($data->type == 'identifier') {
 181				return $this->name($data->value);
 182			} elseif ($data->type == 'expression') {
 183				return $data->value;
 184			}
 185		} elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
 186			return $data;
 187		} else {
 188			return null;
 189		}
 190	}
 191
 192/**
 193 * Returns an object to represent a database identifier in a query
 194 *
 195 * @param string $identifier
 196 * @return object An object representing a database identifier to be used in a query
 197 * @access public
 198 */
 199	function identifier($identifier) {
 200		$obj = new stdClass();
 201		$obj->type = 'identifier';
 202		$obj->value = $identifier;
 203		return $obj;
 204	}
 205
 206/**
 207 * Returns an object to represent a database expression in a query
 208 *
 209 * @param string $expression
 210 * @return object An object representing a database expression to be used in a query
 211 * @access public
 212 */
 213	function expression($expression) {
 214		$obj = new stdClass();
 215		$obj->type = 'expression';
 216		$obj->value = $expression;
 217		return $obj;
 218	}
 219
 220/**
 221 * Executes given SQL statement.
 222 *
 223 * @param string $sql SQL statement
 224 * @return boolean
 225 * @access public
 226 */
 227	function rawQuery($sql) {
 228		$this->took = $this->error = $this->numRows = false;
 229		return $this->execute($sql);
 230	}
 231
 232/**
 233 * Queries the database with given SQL statement, and obtains some metadata about the result
 234 * (rows affected, timing, any errors, number of rows in resultset). The query is also logged.
 235 * If Configure::read('debug') is set, the log is shown all the time, else it is only shown on errors.
 236 *
 237 * ### Options
 238 *
 239 * - stats - Collect meta data stats for this query. Stats include time take, rows affected,
 240 *   any errors, and number of rows returned. Defaults to `true`.
 241 * - log - Whether or not the query should be logged to the memory log.
 242 *
 243 * @param string $sql
 244 * @param array $options
 245 * @return mixed Resource or object representing the result set, or false on failure
 246 * @access public
 247 */
 248	function execute($sql, $options = array()) {
 249		$defaults = array('stats' => true, 'log' => $this->fullDebug);
 250		$options = array_merge($defaults, $options);
 251
 252		$t = getMicrotime();
 253		$this->_result = $this->_execute($sql);
 254		if ($options['stats']) {
 255			$this->took = round((getMicrotime() - $t) * 1000, 0);
 256			$this->affected = $this->lastAffected();
 257			$this->error = $this->lastError();
 258			$this->numRows = $this->lastNumRows();
 259		}
 260
 261		if ($options['log']) {
 262			$this->logQuery($sql);
 263		}
 264
 265		if ($this->error) {
 266			$this->showQuery($sql);
 267			return false;
 268		}
 269		return $this->_result;
 270	}
 271
 272/**
 273 * DataSource Query abstraction
 274 *
 275 * @return resource Result resource identifier.
 276 * @access public
 277 */
 278	function query() {
 279		$args	  = func_get_args();
 280		$fields	  = null;
 281		$order	  = null;
 282		$limit	  = null;
 283		$page	  = null;
 284		$recursive = null;
 285
 286		if (count($args) == 1) {
 287			return $this->fetchAll($args[0]);
 288
 289		} elseif (count($args) > 1 && (strpos(strtolower($args[0]), 'findby') === 0 || strpos(strtolower($args[0]), 'findallby') === 0)) {
 290			$params = $args[1];
 291
 292			if (strpos(strtolower($args[0]), 'findby') === 0) {
 293				$all  = false;
 294				$field = Inflector::underscore(preg_replace('/^findBy/i', '', $args[0]));
 295			} else {
 296				$all  = true;
 297				$field = Inflector::underscore(preg_replace('/^findAllBy/i', '', $args[0]));
 298			}
 299
 300			$or = (strpos($field, '_or_') !== false);
 301			if ($or) {
 302				$field = explode('_or_', $field);
 303			} else {
 304				$field = explode('_and_', $field);
 305			}
 306			$off = count($field) - 1;
 307
 308			if (isset($params[1 + $off])) {
 309				$fields = $params[1 + $off];
 310			}
 311
 312			if (isset($params[2 + $off])) {
 313				$order = $params[2 + $off];
 314			}
 315
 316			if (!array_key_exists(0, $params)) {
 317				return false;
 318			}
 319
 320			$c = 0;
 321			$conditions = array();
 322
 323			foreach ($field as $f) {
 324				$conditions[$args[2]->alias . '.' . $f] = $params[$c];
 325				$c++;
 326			}
 327
 328			if ($or) {
 329				$conditions = array('OR' => $conditions);
 330			}
 331
 332			if ($all) {
 333				if (isset($params[3 + $off])) {
 334					$limit = $params[3 + $off];
 335				}
 336
 337				if (isset($params[4 + $off])) {
 338					$page = $params[4 + $off];
 339				}
 340
 341				if (isset($params[5 + $off])) {
 342					$recursive = $params[5 + $off];
 343				}
 344				return $args[2]->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));
 345			} else {
 346				if (isset($params[3 + $off])) {
 347					$recursive = $params[3 + $off];
 348				}
 349				return $args[2]->find('first', compact('conditions', 'fields', 'order', 'recursive'));
 350			}
 351		} else {
 352			if (isset($args[1]) && $args[1] === true) {
 353				return $this->fetchAll($args[0], true);
 354			} else if (isset($args[1]) && !is_array($args[1]) ) {
 355				return $this->fetchAll($args[0], false);
 356			} else if (isset($args[1]) && is_array($args[1])) {
 357				$offset = 0;
 358				if (isset($args[2])) {
 359					$cache = $args[2];
 360				} else {
 361					$cache = true;
 362				}
 363				$args[1] = array_map(array(&$this, 'value'), $args[1]);
 364				return $this->fetchAll(String::insert($args[0], $args[1]), $cache);
 365			}
 366		}
 367	}
 368
 369/**
 370 * Returns a row from current resultset as an array
 371 *
 372 * @return array The fetched row as an array
 373 * @access public
 374 */
 375	function fetchRow($sql = null) {
 376		if (!empty($sql) && is_string($sql) && strlen($sql) > 5) {
 377			if (!$this->execute($sql)) {
 378				return null;
 379			}
 380		}
 381
 382		if ($this->hasResult()) {
 383			$this->resultSet($this->_result);
 384			$resultRow = $this->fetchResult();
 385			if (!empty($resultRow)) {
 386				$this->fetchVirtualField($resultRow);
 387			}
 388			return $resultRow;
 389		} else {
 390			return null;
 391		}
 392	}
 393
 394/**
 395 * Returns an array of all result rows for a given SQL query.
 396 * Returns false if no rows matched.
 397 *
 398 * @param string $sql SQL statement
 399 * @param boolean $cache Enables returning/storing cached query results
 400 * @return array Array of resultset rows, or false if no rows matched
 401 * @access public
 402 */
 403	function fetchAll($sql, $cache = true, $modelName = null) {
 404		if ($cache && isset($this->_queryCache[$sql])) {
 405			if (preg_match('/^\s*select/i', $sql)) {
 406				return $this->_queryCache[$sql];
 407			}
 408		}
 409
 410		if ($this->execute($sql)) {
 411			$out = array();
 412
 413			$first = $this->fetchRow();
 414			if ($first != null) {
 415				$out[] = $first;
 416			}
 417			while ($this->hasResult() && $item = $this->fetchResult()) {
 418				$this->fetchVirtualField($item);
 419				$out[] = $item;
 420			}
 421
 422			if ($cache) {
 423				if (strpos(trim(strtolower($sql)), 'select') !== false) {
 424					$this->_queryCache[$sql] = $out;
 425				}
 426			}
 427			if (empty($out) && is_bool($this->_result)) {
 428				return $this->_result;
 429			}
 430			return $out;
 431		} else {
 432			return false;
 433		}
 434	}
 435
 436/**
 437 * Modifies $result array to place virtual fields in model entry where they belongs to
 438 *
 439 * @param array $resut REference to the fetched row
 440 * @return void
 441 */
 442	function fetchVirtualField(&$result) {
 443		if (isset($result[0]) && is_array($result[0])) {
 444			foreach ($result[0] as $field => $value) {
 445				if (strpos($field, $this->virtualFieldSeparator) === false) {
 446					continue;
 447				}
 448				list($alias, $virtual) = explode($this->virtualFieldSeparator, $field);
 449
 450				if (!ClassRegistry::isKeySet($alias)) {
 451					return;
 452				}
 453				$model = ClassRegistry::getObject($alias);
 454				if ($model->isVirtualField($virtual)) {
 455					$result[$alias][$virtual] = $value;
 456					unset($result[0][$field]);
 457				}
 458			}
 459			if (empty($result[0])) {
 460				unset($result[0]);
 461			}
 462		}
 463	}
 464
 465/**
 466 * Returns a single field of the first of query results for a given SQL query, or false if empty.
 467 *
 468 * @param string $name Name of the field
 469 * @param string $sql SQL query
 470 * @return mixed Value of field read.
 471 * @access public
 472 */
 473	function field($name, $sql) {
 474		$data = $this->fetchRow($sql);
 475		if (!isset($data[$name]) || empty($data[$name])) {
 476			return false;
 477		} else {
 478			return $data[$name];
 479		}
 480	}
 481
 482/**
 483 * Empties the method caches.
 484 * These caches are used by DboSource::name() and DboSource::conditions()
 485 *
 486 * @return void
 487 */
 488	function flushMethodCache() {
 489		$this->methodCache = array();
 490	}
 491
 492/**
 493 * Cache a value into the methodCaches.  Will respect the value of DboSource::$cacheMethods.
 494 * Will retrieve a value from the cache if $value is null.
 495 *
 496 * If caching is disabled and a write is attempted, the $value will be returned.
 497 * A read will either return the value or null.
 498 *
 499 * @param string $method Name of the method being cached.
 500 * @param string $key The keyname for the cache operation.
 501 * @param mixed $value The value to cache into memory.
 502 * @return mixed Either null on failure, or the value if its set.
 503 */
 504	function cacheMethod($method, $key, $value = null) {
 505		if ($this->cacheMethods === false) {
 506			return $value;
 507		}
 508		if ($value === null) {
 509			return (isset($this->methodCache[$method][$key])) ? $this->methodCache[$method][$key] : null;
 510		}
 511		return $this->methodCache[$method][$key] = $value;
 512	}
 513
 514/**
 515 * Returns a quoted name of $data for use in an SQL statement.
 516 * Strips fields out of SQL functions before quoting.
 517 *
 518 * Results of this method are stored in a memory cache.  This improves performance, but
 519 * because the method uses a simple hashing algorithm it can infrequently have collisions.
 520 * Setting DboSource::$cacheMethods to false will disable the memory cache.
 521 *
 522 * @param mixed $data Either a string with a column to quote. An array of columns to quote or an
 523 *   object from DboSource::expression() or DboSource::identifier()
 524 * @return string SQL field
 525 * @access public
 526 */
 527	function name($data) {
 528		if (is_object($data) && isset($data->type)) {
 529			return $data->value;
 530		}
 531		if ($data === '*') {
 532			return '*';
 533		}
 534		if (is_array($data)) {
 535			foreach ($data as $i => $dataItem) {
 536				$data[$i] = $this->name($dataItem);
 537			}
 538			return $data;
 539		}
 540		$cacheKey = crc32($this->startQuote.$data.$this->endQuote);
 541		if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) {
 542			return $return;
 543		}
 544		$data = trim($data);
 545		if (preg_match('/^[\w-]+(?:\.[^ \*]*)*$/', $data)) { // string, string.string
 546			if (strpos($data, '.') === false) { // string
 547				return $this->cacheMethod(__FUNCTION__, $cacheKey, $this->startQuote . $data . $this->endQuote);
 548			}
 549			$items = explode('.', $data);
 550			return $this->cacheMethod(__FUNCTION__, $cacheKey,
 551				$this->startQuote . implode($this->endQuote . '.' . $this->startQuote, $items) . $this->endQuote
 552			);
 553		}
 554		if (preg_match('/^[\w-]+\.\*$/', $data)) { // string.*
 555			return $this->cacheMethod(__FUNCTION__, $cacheKey,
 556				$this->startQuote . str_replace('.*', $this->endQuote . '.*', $data)
 557			);
 558		}
 559		if (preg_match('/^([\w-]+)\((.*)\)$/', $data, $matches)) { // Functions
 560			return $this->cacheMethod(__FUNCTION__, $cacheKey,
 561				 $matches[1] . '(' . $this->name($matches[2]) . ')'
 562			);
 563		}
 564		if (
 565			preg_match('/^([\w-]+(\.[\w-]+|\(.*\))*)\s+' . preg_quote($this->alias) . '\s*([\w-]+)$/i', $data, $matches
 566		)) {
 567			return $this->cacheMethod(
 568				__FUNCTION__, $cacheKey,
 569				preg_replace(
 570					'/\s{2,}/', ' ', $this->name($matches[1]) . ' ' . $this->alias . ' ' . $this->name($matches[3])
 571				)
 572			);
 573		}
 574		if (preg_match('/^[\w-_\s]*[\w-_]+/', $data)) {
 575			return $this->cacheMethod(__FUNCTION__, $cacheKey, $this->startQuote . $data . $this->endQuote);
 576		}
 577		return $this->cacheMethod(__FUNCTION__, $cacheKey, $data);
 578	}
 579
 580/**
 581 * Checks if the source is connected to the database.
 582 *
 583 * @return boolean True if the database is connected, else false
 584 * @access public
 585 */
 586	function isConnected() {
 587		return $this->connected;
 588	}
 589
 590/**
 591 * Checks if the result is valid
 592 *
 593 * @return boolean True if the result is valid else false
 594 * @access public
 595 */
 596	function hasResult() {
 597		return is_resource($this->_result);
 598	}
 599
 600/**
 601 * Get the query log as an array.
 602 *
 603 * @param boolean $sorted Get the queries sorted by time taken, defaults to false.
 604 * @return array Array of queries run as an array
 605 * @access public
 606 */
 607	function getLog($sorted = false, $clear = true) {
 608		if ($sorted) {
 609			$log = sortByKey($this->_queriesLog, 'took', 'desc', SORT_NUMERIC);
 610		} else {
 611			$log = $this->_queriesLog;
 612		}
 613		if ($clear) {
 614			$this->_queriesLog = array();
 615		}
 616		return array('log' => $log, 'count' => $this->_queriesCnt, 'time' => $this->_queriesTime);
 617	}
 618
 619/**
 620 * Outputs the contents of the queries log. If in a non-CLI environment the sql_log element
 621 * will be rendered and output.  If in a CLI environment, a plain text log is generated.
 622 *
 623 * @param boolean $sorted Get the queries sorted by time taken, defaults to false.
 624 * @return void
 625 */
 626	function showLog($sorted = false) {
 627		$log = $this->getLog($sorted, false);
 628		if (empty($log['log'])) {
 629			return;
 630		}
 631		if (PHP_SAPI != 'cli') {
 632			App::import('Core', 'View');
 633			$controller = null;
 634			$View =& new View($controller, false);
 635			$View->set('logs', array($this->configKeyName => $log));
 636			echo $View->element('sql_dump', array('_forced_from_dbo_' => true));
 637		} else {
 638			foreach ($log['log'] as $k => $i) {
 639				print (($k + 1) . ". {$i['query']} {$i['error']}\n");
 640			}
 641		}
 642	}
 643
 644/**
 645 * Log given SQL query.
 646 *
 647 * @param string $sql SQL statement
 648 * @todo: Add hook to log errors instead of returning false
 649 * @access public
 650 */
 651	function logQuery($sql) {
 652		$this->_queriesCnt++;
 653		$this->_queriesTime += $this->took;
 654		$this->_queriesLog[] = array(
 655			'query' => $sql,
 656			'error'		=> $this->error,
 657			'affected'	=> $this->affected,
 658			'numRows'	=> $this->numRows,
 659			'took'		=> $this->took
 660		);
 661		if (count($this->_queriesLog) > $this->_queriesLogMax) {
 662			array_pop($this->_queriesLog);
 663		}
 664		if ($this->error) {
 665			return false;
 666		}
 667	}
 668
 669/**
 670 * Output information about an SQL query. The SQL statement, number of rows in resultset,
 671 * and execution time in microseconds. If the query fails, an error is output instead.
 672 *
 673 * @param string $sql Query to show information on.
 674 * @access public
 675 */
 676	function showQuery($sql) {
 677		$error = $this->error;
 678		if (strlen($sql) > 200 && !$this->fullDebug && Configure::read() > 1) {
 679			$sql = substr($sql, 0, 200) . '[...]';
 680		}
 681		if (Configure::read() > 0) {
 682			$out = null;
 683			if ($error) {
 684				trigger_error('<span style="color:Red;text-align:left"><b>' . __('SQL Error:', true) . "</b> {$this->error}</span>", E_USER_WARNING);
 685			} else {
 686				$out = ('<small>[' . sprintf(__('Aff:%s Num:%s Took:%sms', true), $this->affected, $this->numRows, $this->took) . ']</small>');
 687			}
 688			pr(sprintf('<p style="text-align:left"><b>' . __('Query:', true) . '</b> %s %s</p>', $sql, $out));
 689		}
 690	}
 691
 692/**
 693 * Gets full table name including prefix
 694 *
 695 * @param mixed $model Either a Model object or a string table name.
 696 * @param boolean $quote Whether you want the table name quoted.
 697 * @return string Full quoted table name
 698 * @access public
 699 */
 700	function fullTableName($model, $quote = true) {
 701		if (is_object($model)) {
 702			$table = $model->tablePrefix . $model->table;
 703		} elseif (isset($this->config['prefix'])) {
 704			$table = $this->config['prefix'] . strval($model);
 705		} else {
 706			$table = strval($model);
 707		}
 708		if ($quote) {
 709			return $this->name($table);
 710		}
 711		return $table;
 712	}
 713
 714/**
 715 * The "C" in CRUD
 716 *
 717 * Creates new records in the database.
 718 *
 719 * @param Model $model Model object that the record is for.
 720 * @param array $fields An array of field names to insert. If null, $model->data will be
 721 *   used to generate field names.
 722 * @param array $values An array of values with keys matching the fields. If null, $model->data will
 723 *   be used to generate values.
 724 * @return boolean Success
 725 * @access public
 726 */
 727	function create(&$model, $fields = null, $values = null) {
 728		$id = null;
 729
 730		if ($fields == null) {
 731			unset($fields, $values);
 732			$fields = array_keys($model->data);
 733			$values = array_values($model->data);
 734		}
 735		$count = count($fields);
 736
 737		for ($i = 0; $i < $count; $i++) {
 738			$valueInsert[] = $this->value($values[$i], $model->getColumnType($fields[$i]), false);
 739		}
 740		for ($i = 0; $i < $count; $i++) {
 741			$fieldInsert[] = $this->name($fields[$i]);
 742			if ($fields[$i] == $model->primaryKey) {
 743				$id = $values[$i];
 744			}
 745		}
 746		$query = array(
 747			'table' => $this->fullTableName($model),
 748			'fields' => implode(', ', $fieldInsert),
 749			'values' => implode(', ', $valueInsert)
 750		);
 751
 752		if ($this->execute($this->renderStatement('create', $query))) {
 753			if (empty($id)) {
 754				$id = $this->lastInsertId($this->fullTableName($model, false), $model->primaryKey);
 755			}
 756			$model->setInsertID($id);
 757			$model->id = $id;
 758			return true;
 759		} else {
 760			$model->onError();
 761			return false;
 762		}
 763	}
 764
 765/**
 766 * The "R" in CRUD
 767 *
 768 * Reads record(s) from the database.
 769 *
 770 * @param Model $model A Model object that the query is for.
 771 * @param array $queryData An array of queryData information containing keys similar to Model::find()
 772 * @param integer $recursive Number of levels of association
 773 * @return mixed boolean false on error/failure.  An array of results on success.
 774 */
 775	function read(&$model, $queryData = array(), $recursive = null) {
 776		$queryData = $this->__scrubQueryData($queryData);
 777
 778		$null = null;
 779		$array = array();
 780		$linkedModels = array();
 781		$this->__bypass = false;
 782		$this->__booleans = array();
 783
 784		if ($recursive === null && isset($queryData['recursive'])) {
 785			$recursive = $queryData['recursive'];
 786		}
 787
 788		if (!is_null($recursive)) {
 789			$_recursive = $model->recursive;
 790			$model->recursive = $recursive;
 791		}
 792
 793		if (!empty($queryData['fields'])) {
 794			$this->__bypass = true;
 795			$queryData['fields'] = $this->fields($model, null, $queryData['fields']);
 796		} else {
 797			$queryData['fields'] = $this->fields($model);
 798		}
 799
 800		$_associations = $model->__associations;
 801
 802		if ($model->recursive == -1) {
 803			$_associations = array();
 804		} else if ($model->recursive == 0) {
 805			unset($_associations[2], $_associations[3]);
 806		}
 807
 808		foreach ($_associations as $type) {
 809			foreach ($model->{$type} as $assoc => $assocData) {
 810				$linkModel =& $model->{$assoc};
 811				$external = isset($assocData['external']);
 812
 813				if ($model->useDbConfig == $linkModel->useDbConfig) {
 814					if (true === $this->generateAssociationQuery($model, $linkModel, $type, $assoc, $assocData, $queryData, $external, $null)) {
 815						$linkedModels[$type . '/' . $assoc] = true;
 816					}
 817				}
 818			}
 819		}
 820
 821		$query = $this->generateAssociationQuery($model, $null, null, null, null, $queryData, false, $null);
 822
 823		$resultSet = $this->fetchAll($query, $model->cacheQueries, $model->alias);
 824
 825		if ($resultSet === false) {
 826			$model->onError();
 827			return false;
 828		}
 829
 830		$filtered = $this->__filterResults($resultSet, $model);
 831
 832		if ($model->recursive > -1) {
 833			foreach ($_associations as $type) {
 834				foreach ($model->{$type} as $assoc => $assocData) {
 835					$linkModel =& $model->{$assoc};
 836
 837					if (empty($linkedModels[$type . '/' . $assoc])) {
 838						if ($model->useDbConfig == $linkModel->useDbConfig) {
 839							$db =& $this;
 840						} else {
 841							$db =& ConnectionManager::getDataSource($linkModel->useDbConfig);
 842						}
 843					} elseif ($model->recursive > 1 && ($type == 'belongsTo' || $type == 'hasOne')) {
 844						$db =& $this;
 845					}
 846
 847					if (isset($db) && method_exists($db, 'queryAssociation')) {
 848						$stack = array($assoc);
 849						$db->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $array, true, $resultSet, $model->recursive - 1, $stack);
 850						unset($db);
 851
 852						if ($type === 'hasMany') {
 853							$filtered []= $assoc;
 854						}
 855					}
 856				}
 857			}
 858			$this->__filterResults($resultSet, $model, $filtered);
 859		}
 860
 861		if (!is_null($recursive)) {
 862			$model->recursive = $_recursive;
 863		}
 864		return $resultSet;
 865	}
 866
 867/**
 868 * Passes association results thru afterFind filters of corresponding model
 869 *
 870 * @param array $results Reference of resultset to be filtered
 871 * @param object $model Instance of model to operate against
 872 * @param array $filtered List of classes already filtered, to be skipped
 873 * @return array Array of results that have been filtered through $model->afterFind
 874 * @access private
 875 */
 876	function __filterResults(&$results, &$model, $filtered = array()) {
 877		$filtering = array();
 878		$count = count($results);
 879
 880		for ($i = 0; $i < $count; $i++) {
 881			if (is_array($results[$i])) {
 882				$classNames = array_keys($results[$i]);
 883				$count2 = count($classNames);
 884
 885				for ($j = 0; $j < $count2; $j++) {
 886					$className = $classNames[$j];
 887					if ($model->alias != $className && !in_array($className, $filtered)) {
 888						if (!in_array($className, $filtering)) {
 889							$filtering[] = $className;
 890						}
 891
 892						if (isset($model->{$className}) && is_object($model->{$className})) {
 893							$data = $model->{$className}->afterFind(array(array($className => $results[$i][$className])), false);
 894						}
 895						if (isset($data[0][$className])) {
 896							$results[$i][$className] = $data[0][$className];
 897						}
 898					}
 899				}
 900			}
 901		}
 902		return $filtering;
 903	}
 904
 905/**
 906 * Queries associations.  Used to fetch results on recursive models.
 907 *
 908 * @param Model $model Primary Model object
 909 * @param Model $linkModel Linked model that
 910 * @param string $type Association type, one of the model association types ie. hasMany
 911 * @param unknown_type $association
 912 * @param unknown_type $assocData
 913 * @param array $queryData
 914 * @param boolean $external Whether or not the association query is on an external datasource.
 915 * @param array $resultSet Existing results
 916 * @param integer $recursive Number of levels of association
 917 * @param array $stack
 918 */
 919	function queryAssociation(&$model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive, $stack) {
 920		if ($query = $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet)) {
 921			if (!isset($resultSet) || !is_array($resultSet)) {
 922				if (Configure::read() > 0) {
 923					echo '<div style = "font: Verdana bold 12px; color: #FF0000">' . sprintf(__('SQL Error in model %s:', true), $model->alias) . ' ';
 924					if (isset($this->error) && $this->error != null) {
 925						echo $this->error;
 926					}
 927					echo '</div>';
 928				}
 929				return null;
 930			}
 931			$count = count($resultSet);
 932
 933			if ($type === 'hasMany' && empty($assocData['limit']) && !empty($assocData['foreignKey'])) {
 934				$ins = $fetch = array();
 935				for ($i = 0; $i < $count; $i++) {
 936					if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) {
 937						$ins[] = $in;
 938					}
 939				}
 940
 941				if (!empty($ins)) {
 942					$ins = array_unique($ins);
 943					$fetch = $this->fetchAssociated($model, $query, $ins);
 944				}
 945
 946				if (!empty($fetch) && is_array($fetch)) {
 947					if ($recursive > 0) {
 948						foreach ($linkModel->__associations as $type1) {
 949							foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
 950								$deepModel =& $linkModel->{$assoc1};
 951								$tmpStack = $stack;
 952								$tmpStack[] = $assoc1;
 953
 954								if ($linkModel->useDbConfig === $deepModel->useDbConfig) {
 955									$db =& $this;
 956								} else {
 957									$db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
 958								}
 959								$db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
 960							}
 961						}
 962					}
 963				}
 964				$this->__filterResults($fetch, $model);
 965				return $this->__mergeHasMany($resultSet, $fetch, $association, $model, $linkModel, $recursive);
 966			} elseif ($type === 'hasAndBelongsToMany') {
 967				$ins = $fetch = array();
 968				for ($i = 0; $i < $count; $i++) {
 969					if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) {
 970						$ins[] = $in;
 971					}
 972				}
 973				if (!empty($ins)) {
 974					$ins = array_unique($ins);
 975					if (count($ins) > 1) {
 976						$query = str_replace('{$__cakeID__$}', '(' .implode(', ', $ins) .')', $query);
 977						$query = str_replace('= (', 'IN (', $query);
 978					} else {
 979						$query = str_replace('{$__cakeID__$}',$ins[0], $query);
 980					}
 981
 982					$query = str_replace(' WHERE 1 = 1', '', $query);
 983				}
 984
 985				$foreignKey = $model->hasAndBelongsToMany[$association]['foreignKey'];
 986				$joinKeys = array($foreignKey, $model->hasAndBelongsToMany[$association]['associationForeignKey']);
 987				list($with, $habtmFields) = $model->joinModel($model->hasAndBelongsToMany[$association]['with'], $joinKeys);
 988				$habtmFieldsCount = count($habtmFields);
 989				$q = $this->insertQueryData($query, null, $association, $assocData, $model, $linkModel, $stack);
 990
 991				if ($q != false) {
 992					$fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias);
 993				} else {
 994					$fetch = null;
 995				}
 996			}
 997
 998			for ($i = 0; $i < $count; $i++) {
 999				$row =& $resultSet[$i];
1000
1001				if ($type !== 'hasAndBelongsToMany') {
1002					$q = $this->insertQueryData($query, $resultSet[$i], $association, $assocData, $model, $linkModel, $stack);
1003					if ($q != false) {
1004						$fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias);
1005					} else {
1006						$fetch = null;
1007					}
1008				}
1009				$selfJoin = false;
1010
1011				if ($linkModel->name === $model->name) {
1012					$selfJoin = true;
1013				}
1014
1015				if (!empty($fetch) && is_array($fetch)) {
1016					if ($recursive > 0) {
1017						foreach ($linkModel->__associations as $type1) {
1018							foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
1019								$deepModel =& $linkModel->{$assoc1};
1020
1021								if (($type1 === 'belongsTo') || ($deepModel->alias === $model->alias && $type === 'belongsTo') || ($deepModel->alias != $model->alias)) {
1022									$tmpStack = $stack;
1023									$tmpStack[] = $assoc1;
1024									if ($linkModel->useDbConfig == $deepModel->useDbConfig) {
1025										$db =& $this;
1026									} else {
1027										$db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
1028									}
1029									$db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
1030								}
1031							}
1032						}
1033					}
1034					if ($type == 'hasAndBelongsToMany') {
1035						$uniqueIds = $merge = array();
1036
1037						foreach ($fetch as $j => $data) {
1038							if (
1039								(isset($data[$with]) && $data[$with][$foreignKey] === $row[$model->alias][$model->primaryKey])
1040							) {
1041								if ($habtmFieldsCount <= 2) {
1042									unset($data[$with]);
1043								}
1044								$merge[] = $data;
1045							}
1046						}
1047						if (empty($merge) && !isset($row[$association])) {
1048							$row[$association] = $merge;
1049						} else {
1050							$this->__mergeAssociation($resultSet[$i], $merge, $association, $type);
1051						}
1052					} else {
1053						$this->__mergeAssociation($resultSet[$i], $fetch, $association, $type, $selfJoin);
1054					}
1055					if (isset($resultSet[$i][$association])) {
1056						$resultSet[$i][$association] = $linkModel->afterFind($resultSet[$i][$association], false);
1057					}
1058				} else {
1059					$tempArray[0][$association] = false;
1060					$this->__mergeAssociation($resultSet[$i], $tempArray, $association, $type, $selfJoin);
1061				}
1062			}
1063		}
1064	}
1065
1066/**
1067 * A more efficient way to fetch associations.	Woohoo!
1068 *
1069 * @param model $model Primary model object
1070 * @param string $query Association query
1071 * @param array $ids Array of IDs of associated records
1072 * @return array Association results
1073 * @access public
1074 */
1075	function fetchAssociated($model, $query, $ids) {
1076		$query = str_replace('{$__cakeID__$}', implode(', ', $ids), $query);
1077		if (count($ids) > 1) {
1078			$query = str_replace('= (', 'IN (', $query);
1079		}
1080		return $this->fetchAll($query, $model->cacheQueries, $model->alias);
1081	}
1082
1083/**
1084 * mergeHasMany - Merge the results of hasMany relations.
1085 *
1086 *
1087 * @param array $resultSet Data to merge into
1088 * @param array $merge Data to merge
1089 * @param string $association Name of Model being Merged
1090 * @param object $model Model being merged onto
1091 * @param object $linkModel Model being merged
1092 * @return void
1093 */
1094	function __mergeHasMany(&$resultSet, $merge, $association, &$model, &$linkModel) {
1095		foreach ($resultSet as $i => $value) {
1096			$count = 0;
1097			$merged[$association] = array();
1098			foreach ($merge as $j => $data) {
1099				if (isset($value[$model->alias]) && $value[$model->alias][$model->primaryKey] === $data[$association][$model->hasMany[$association]['foreignKey']]) {
1100					if (count($data) > 1) {
1101						$data = array_merge($data[$association], $data);
1102						unset($data[$association]);
1103						foreach ($data as $key => $name) {
1104							if (is_numeric($key)) {
1105								$data[$association][] = $name;
1106								unset($data[$key]);
1107							}
1108						}
1109						$merged[$association][] = $data;
1110					} else {
1111						$merged[$association][] = $data[$association];
1112					}
1113				}
1114				$count++;
1115			}
1116			if (isset($value[$model->alias])) {
1117				$resultSet[$i] = Set::pushDiff($resultSet[$i], $merged);
1118				unset($merged);
1119			}
1120		}
1121	}
1122
1123/**
1124 * Enter description here...
1125 *
1126 * @param unknown_type $data
1127 * @param unknown_type $merge
1128 * @param unknown_type $association
1129 * @param unknown_type $type
1130 * @param boolean $selfJoin
1131 * @access private
1132 */
1133	function __mergeAssociation(&$data, $merge, $association, $type, $selfJoin = false) {
1134		if (isset($merge[0]) && !isset($merge[0][$association])) {
1135			$association = Inflector::pluralize($association);
1136		}
1137
1138		if ($type == 'belongsTo' || $type == 'hasOne') {
1139			if (isset($merge[$association])) {
1140				$data[$association] = $merge[$association][0];
1141			} else {
1142				if (count($merge[0][$association]) > 1) {
1143					foreach ($merge[0] as $assoc => $data2) {
1144						if ($assoc != $association) {
1145							$merge[0][$association][$assoc] = $data2;
1146						}
1147					}
1148				}
1149				if (!isset($data[$association])) {
1150					if ($merge[0][$association] != null) {
1151						$data[$association] = $merge[0][$association];
1152					} else {
1153						$data[$association] = array();
1154					}
1155				} else {
1156					if (is_array($merge[0][$association])) {
1157						foreach ($data[$association] as $k => $v) {
1158							if (!is_array($v)) {
1159								$dataAssocTmp[$k] = $v;
1160							}
1161						}
1162
1163						foreach ($merge[0][$association] as $k => $v) {
1164							if (!is_array($v)) {
1165								$mergeAssocTmp[$k] = $v;
1166							}
1167						}
1168						$dataKeys = array_keys($data);
1169						$mergeKeys = array_keys($merge[0]);
1170
1171						if ($mergeKeys[0] === $dataKeys[0] || $mergeKeys === $dataKeys) {
1172							$data[$association][$association] = $merge[0][$association];
1173						} else {
1174							$diff = Set::diff($dataAssocTmp, $mergeAssocTmp);
1175							$data[$association] = array_merge($merge[0][$association], $diff);
1176						}
1177					} elseif ($selfJoin && array_key_exists($association, $merge[0])) {
1178						$data[$association] = array_merge($data[$association], array($association => array()));
1179					}
1180				}
1181			}
1182		} else {
1183			if (isset($merge[0][$association]) && $merge[0][$association] === false) {
1184				if (!isset($data[$association])) {
1185					$data[$association] = array();
1186				}
1187			} else {
1188				foreach ($merge as $i => $row) {
1189					if (count($row) == 1) {
1190						if (empty($data[$association]) || (isset($data[$association]) && !in_array($row[$association], $data[$association]))) {
1191							$data[$association][] = $row[$association];
1192						}
1193					} else if (!empty($row)) {
1194						$tmp = array_merge($row[$association], $row);
1195						unset($tmp[$association]);
1196						$data[$association][] = $tmp;
1197					}
1198				}
1199			}
1200		}
1201	}
1202
1203/**
1204 * Generates an array representing a query or part of a query from a single model or two associated models
1205 *
1206 * @param Model $model
1207 * @param Model $linkModel
1208 * @param string $type
1209 * @param string $association
1210 * @param array $assocData
1211 * @param array $queryData
1212 * @param boolean $external
1213 * @param array $resultSet
1214 * @return mixed
1215 * @access public
1216 */
1217	function generateAssociationQuery(&$model, &$linkModel, $type, $association = null, $assocData = array(), &$queryData, $external = false, &$resultSet) {
1218		$queryData = $this->__scrubQueryData($queryData);
1219		$assocData = $this->__scrubQueryData($assocData);
1220
1221		if (empty($queryData['fields'])) {
1222			$queryData['fields'] = $this->fields($model, $model->alias);
1223		} elseif (!empty($model->hasMany) && $model->recursive > -1) {
1224			$assocFields = $this->fields($model, $model->alias, array("{$model->alias}.{$model->primaryKey}"));
1225			$passedFields = $this->fields($model, $model->alias, $queryData['fields']);
1226			if (count($passedFields) === 1) {
1227				$match = strpos($passedFields[0], $assocFields[0]);
1228				$match1 = (bool)preg_match('/^[a-z]+\(/i', $passedFields[0]);
1229
1230				if ($match === false && $match1 === false) {
1231					$queryData['fields'] = array_merge($passedFields, $assocFields);
1232				} else {
1233					$queryData['fields'] = $passedFields;
1234				}
1235			} else {
1236				$queryData['fields'] = array_merge($passedFields, $assocFields);
1237			}
1238			unset($assocFields, $passedFields);
1239		}
1240
1241		if ($linkModel == null) {
1242			return $this->buildStatement(
1243				array(
1244					'fields' => array_unique($queryData['fields']),
1245					'table' => $this->fullTableName($model),
1246					'alias' => $model->alias,
1247					'limit' => $queryData['limit'],
1248					'offset' => $queryData['offset'],
1249					'joins' => $queryData['joins'],
1250					'conditions' => $queryData['conditions'],
1251					'order' => $queryData['order'],
1252					'group' => $queryData['group']
1253				),
1254				$model
1255			);
1256		}
1257		if ($external && !empty($assocData['finderQuery'])) {
1258			return $assocData['finderQuery'];
1259		}
1260
1261		$alias = $association;
1262		$self = ($model->name == $linkModel->name);
1263		$fields = array();
1264
1265		if ((!$external && in_array($type, array('hasOne', 'belongsTo')) && $this->__bypass === false) || $external) {
1266			$fields = $this->fields($linkModel, $alias, $assocData['fields']);
1267		}
1268		if (empty($assocData['offset']) && !empty($assocData['page'])) {
1269			$assocData['offset'] = ($assocData['page'] - 1) * $assocData['limit'];
1270		}
1271		$assocData['limit'] = $this->limit($assocData['limit'], $assocData['offset']);
1272
1273		switch ($type) {
1274			case 'hasOne':
1275			case 'belongsTo':
1276				$conditions = $this->__mergeConditions(
1277					$assocData['conditions'],
1278					$this->getConstraint($type, $model, $linkModel, $alias, array_merge($assocData, compact('external', 'self')))
1279				);
1280
1281				if (!$self && $external) {
1282					foreach ($conditions as $key => $condition) {
1283						if (is_numeric($key) && strpos($condition, $model->alias . '.') !== false) {
1284							unset($conditions[$key]);
1285						}
1286					}
1287				}
1288
1289				if ($external) {
1290					$query = array_merge($assocData, array(
1291						'conditions' => $conditions,
1292						'table' => $this->fullTableName($linkModel),
1293						'fields' => $fields,
1294						'alias' => $alias,
1295						'group' => null
1296					));
1297					$query = array_merge(array('order' => $assocData['order'], 'limit' => $assocData['limit']), $query);
1298				} else {
1299					$join = array(
1300						'table' => $this->fullTableName($linkModel),
1301						'alias' => $alias,
1302						'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT',
1303						'conditions' => trim($this->conditions($conditions, true, false, $model))
1304					);
1305					$queryData['fields'] = array_merge($queryData['fields'], $fields);
1306
1307					if (!empty($assocData['order'])) {
1308						$queryData['order'][] = $assocData['order'];
1309					}
1310					if (!in_array($join, $queryData['joins'])) {
1311						$queryData['joins'][] = $join;
1312					}
1313					return true;
1314				}
1315			break;
1316			case 'hasMany':
1317				$assocData['fields'] = $this->fields($linkModel, $alias, $assocData['fields']);
1318				if (!empty($assocData['foreignKey'])) {
1319					$assocData['fields'] = array_merge($assocData['fields'], $this->fields($linkModel, $alias, array("{$alias}.{$assocData['foreignKey']}")));
1320				}
1321				$query = array(
1322					'conditions' => $this->__mergeConditions($this->getConstraint('hasMany', $model, $linkModel, $alias, $assocData), $assocData['conditions']),
1323					'fields' => array_unique($assocData['fields']),
1324					'table' => $this->fullTableName($linkModel),
1325					'alias' => $alias,
1326					'order' => $assocData['order'],
1327					'limit' => $assocData['limit'],
1328					'group' => null
1329				);
1330			break;
1331			case 'hasAndBelongsToMany':
1332				$joinFields = array();
1333				$joinAssoc = null;
1334
1335				if (isset($assocData['with']) && !empty($assocData['with'])) {
1336					$joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']);
1337					list($with, $joinFields) = $model->joinModel($assocData['with'], $joinKeys);
1338
1339					$joinTbl = $this->fullTableName($model->{$with});
1340					$joinAlias = $joinTbl;
1341
1342					if (is_array($joinFields) && !empty($joinFields)) {
1343						$joinFields = $this->fields($model->{$with}, $model->{$with}->alias, $joinFields);
1344						$joinAssoc = $joinAlias = $model->{$with}->alias;
1345					} else {
1346						$joinFields = array();
1347					}
1348				} else {
1349					$joinTbl = $this->fullTableName($assocData['joinTable']);
1350					$joinAlias = $joinTbl;
1351				}
1352				$query = array(
1353					'conditions' => $assocData['conditions'],
1354					'limit' => $assocData['limit'],
1355					'table' => $this->fullTableName($linkModel),
1356					'alias' => $alias,
1357					'fields' => array_merge($this->fields($linkModel, $alias, $assocData['fields']), $joinFields),
1358					'order' => $assocData['order'],
1359					'group' => null,
1360					'joins' => array(array(
1361						'table' => $joinTbl,
1362						'alias' => $joinAssoc,
1363						'conditions' => $this->getConstraint('hasAndBelongsToMany', $model, $linkModel, $joinAlias, $assocData, $alias)
1364					))
1365				);
1366			break;
1367		}
1368		if (isset($query)) {
1369			return $this->buildStatement($query, $model);
1370		}
1371		return null;
1372	}
1373
1374/**
1375 * Returns a conditions array for the constraint between two models
1376 *
1377 * @param string $type Association type
1378 * @param object $model Model object
1379 * @param array $association Association array
1380 * @return array Conditions array defining the constraint between $model and $association
1381 * @access public
1382 */
1383	function getConstraint($type, $model, $linkModel, $alias, $assoc, $alias2 = null) {
1384		$assoc = array_merge(array('external' => false, 'self' => false), $assoc);
1385
1386		if (array_key_exists('foreignKey', $assoc) && empty($assoc['foreignKey'])) {
1387			return array();
1388		}
1389
1390		switch (true) {
1391			case ($assoc['external'] && $type == 'hasOne'):
1392				return array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}');
1393			break;
1394			case ($assoc['external'] && $type == 'belongsTo'):
1395				return array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}');
1396			break;
1397			case (!$assoc['external'] && $type == 'hasOne'):
1398				return array("{$alias}.{$assoc['foreignKey']}" => $this->identifier("{$model->alias}.{$model->primaryKey}"));
1399			break;
1400			case (!$assoc['external'] && $type == 'belongsTo'):
1401				return array("{$model->alias}.{$assoc['foreignKey']}" => $this->identifier("{$alias}.{$linkModel->primaryKey}"));
1402			break;
1403			case ($type == 'hasMany'):
1404				return array("{$alias}.{$assoc['foreignKey']}" => array('{$__cakeID__$}'));
1405			break;
1406			case ($type == 'hasAndBelongsToMany'):
1407				return array(
1408					array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'),
1409					array("{$alias}.{$assoc['associationForeignKey']}" => $this->identifier("{$alias2}.{$linkModel->primaryKey}"))
1410				);
1411			break;
1412		}
1413		return array();
1414	}
1415
1416/**
1417 * Builds and generates a JOIN statement from an array.	 Handles final clean-up before conversion.
1418 *
1419 * @param array $join An array defining a JOIN statement in a query
1420 * @return string An SQL JOIN statement to be used in a query
1421 * @access public
1422 * @see DboSource::renderJoinStatement()
1423 * @see DboSource::buildStatement()
1424 */
1425	function buildJoinStatement($join) {
1426		$data = array_merge(array(
1427			'type' => null,
1428			'alias' => null,
1429			'table' => 'join_table',
1430			'conditions' => array()
1431		), $join);
1432
1433		if (!empty($data['alias'])) {
1434			$data['alias'] = $this->alias . $this->name($data['alias']);
1435		}
1436		if (!empty($data['conditions'])) {
1437			$data['conditions'] = trim($this->conditions($data['conditions'], true, false));
1438		}
1439		return $this->renderJoinStatement($data);
1440	}
1441
1442/**
1443 * Builds and generates an SQL statement from an array.	 Handles final clean-up before conversion.
1444 *
1445 * @param array $query An array defining an SQL query
1446 * @param object $model The model object which initiated the query
1447 * @return string An executable SQL statement
1448 * @access public
1449 * @see DboSource::renderStatement()
1450 */
1451	function buildStatement($query, &$model) {
1452		$query = array_merge(array('offset' => null, 'joins' => array()), $query);
1453		if (!empty($query['joins'])) {
1454			$count = count($query['joins']);
1455			for ($i = 0; $i < $count; $i++) {
1456				if (is_array($query['joins'][$i])) {
1457					$query['joins'][$i] = $this->buildJoinStatement($query['joins'][$i]);
1458				}
1459			}
1460		}
1461		return $this->renderStatement('select', array(
1462			'conditions' => $this->conditions($query['conditions'], true, true, $model),
1463			'fields' => implode(', ', $query['fields']),
1464			'table' => $query['table'],
1465			'alias' => $this->alias . $this->name($query['alias']),
1466			'order' => $this->order($query['order'], 'ASC', $model),
1467			'limit' => $this->limit($query['limit'], $query['offset']),
1468			'joins' => implode(' ', $query['joins']),
1469			'group' => $this->group($query['group'], $model)
1470		));
1471	}
1472
1473/**
1474 * Renders a final SQL JOIN statement
1475 *
1476 * @param array $data
1477 * @return string
1478 * @access public
1479 */
1480	function renderJoinStatement($data) {
1481		extract($data);
1482		return trim("{$type} JOIN {$table} {$alias} ON ({$conditions})");
1483	}
1484
1485/**
1486 * Renders a final SQL statement by putting together the component parts in the correct order
1487 *
1488 * @param string $type type of query being run.  e.g select, create, update, delete, schema, alter.
1489 * @param array $data Array of data to insert into the query.
1490 * @return string Rendered SQL expression to be run.
1491 * @access public
1492 */
1493	function renderStatement($type, $data) {
1494		extract($data);
1495		$aliases = null;
1496
1497		switch (strtolower($type)) {
1498			case 'select':
1499				return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}";
1500			break;
1501			case 'create':
1502				return "INSERT INTO {$table} ({$fields}) VALUES ({$values})";
1503			break;
1504			case 'update':
1505				if (!empty($alias)) {
1506					$aliases = "{$this->alias}{$alias} {$joins} ";
1507				}
1508				return "UPDATE {$table} {$aliases}SET {$fields} {$conditions}";
1509			break;
1510			case 'delete':
1511				if (!empty($alias)) {
1512					$aliases = "{$this->alias}{$alias} {$joins} ";
1513				}
1514				return "DELETE {$alias} FROM {$table} {$aliases}{$conditions}";
1515			break;
1516			case 'schema':
1517				foreach (array('columns', 'indexes', 'tableParameters') as $var) {
1518					if (is_array(${$var})) {
1519						${$var} = "\t" . join(",\n\t", array_filter(${$var}));
1520					} else {
1521						${$var} = '';
1522					}
1523				}
1524				if (trim($indexes) != '') {
1525					$columns .= ',';
1526				}
1527				return "CREATE TABLE {$table} (\n{$columns}{$indexes}){$tableParameters};";
1528			break;
1529			case 'alter':
1530			break;
1531		}
1532	}
1533
1534/**
1535 * Merges a mixed set of string/array conditions
1536 *
1537 * @return array
1538 * @access private
1539 */
1540	function __mergeConditions($query, $assoc) {
1541		if (empty($assoc)) {
1542			return $query;
1543		}
1544
1545		if (is_array($query)) {
1546			return array_merge((array)$assoc, $query);
1547		}
1548
1549		if (!empty($query)) {
1550			$query = array($query);
1551			if (is_array($assoc)) {
1552				$query = array_merge($query, $assoc);
1553			} else {
1554				$query[] = $assoc;
1555			}
1556			return $query;
1557		}
1558
1559		return $assoc;
1560	}
1561
1562/**
1563 * Generates and executes an SQL UPDATE statement for given model, fields, and values.
1564 * For databases that do not support aliases in UPDATE queries.
1565 *
1566 * @param Model $model
1567 * @param array $fields
1568 * @param array $values
1569 * @param mixed $conditions
1570 * @return boolean Success
1571 * @access public
1572 */
1573	function update(&$model, $fields = array(), $values = null, $conditions = null) {
1574		if ($values == null) {
1575			$combined = $fields;
1576		} else {
1577			$combined = array_combine($fields, $values);
1578		}
1579
1580		$fields = implode(', ', $this->_prepareUpdateFields($model, $combined, empty($conditions)));
1581
1582		$alias = $joins = null;
1583		$table = $this->fullTableName($model);
1584		$conditions = $this->_matchRecords($model, $conditions);
1585
1586		if ($conditions === false) {
1587			return false;
1588		}
1589		$query = compact('table', 'alias', 'joins', 'fields', 'conditions');
1590
1591		if (!$this->execute($this->renderStatement('update', $query))) {
1592			$model->onError();
1593			return false;
1594		}
1595		return true;
1596	}
1597
1598/**
1599 * Quotes and prepares fields and values for an SQL UPDATE statement
1600 *
1601 * @param Model $model
1602 * @param array $fields
1603 * @param boolean $quoteValues If values should be quoted, or treated as SQL snippets
1604 * @param boolean $alias Include the model alias in the field name
1605 * @return array Fields and values, quoted and preparted
1606 * @access protected
1607 */
1608	function _prepareUpdateFields(&$model, $fields, $quoteValues = true, $alias = false) {
1609		$quotedAlias = $this->startQuote . $model->alias . $this->endQuote;
1610
1611		$updates = array();
1612		foreach ($fields as $field => $value) {
1613			if ($alias && strpos($field, '.') === false) {
1614				$quoted = $model->escapeField($field);
1615			} elseif (!$alias && strpos($field, '.') !== false) {
1616				$quoted = $this->name(str_replace($quotedAlias . '.', '', str_replace(
1617					$model->alias . '.', '', $field
1618				)));
1619			} else {
1620				$quoted = $this->name($field);
1621			}
1622
1623			if ($value === null) {
1624				$updates[] = $quoted . ' = NULL';
1625				continue;
1626			}
1627			$update = $quoted . ' = ';
1628
1629			if ($quoteValues) {
1630				$update .= $this->value($value, $model->getColumnType($field), false);
1631			} elseif (!$alias) {
1632				$update .= str_replace($quotedAlias . '.', '', str_replace(
1633					$model->alias . '.', '', $value
1634				));
1635			} else {
1636				$update .= $value;
1637			}
1638			$updates[] =  $update;
1639		}
1640		return $updates;
1641	}
1642
1643/**
1644 * Generates and executes an SQL DELETE statement.
1645 * For databases that do not support aliases in UPDATE queries.
1646 *
1647 * @param Model $model
1648 * @param mixed $conditions
1649 * @return boolean Success
1650 * @access public
1651 */
1652	function delete(&$model, $conditions = null) {
1653		$alias = $joins = null;
1654		$table = $this->fullTableName($model);
1655		$conditions = $this->_matchRecords($model, $conditions);
1656
1657		if ($conditions === false) {
1658			return false;
1659		}
1660
1661		if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) {
1662			$model->onError();
1663			return false;
1664		}
1665		return true;
1666	}
1667
1668/**
1669 * Gets a list of record IDs f…

Large files files are truncated, but you can click here to view the full file