CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 2.2 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.2
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Packages

  • Cake
    • Cache
      • Engine
    • Configure
    • Console
      • Command
        • Task
    • Controller
      • Component
        • Acl
        • Auth
    • Core
    • Error
    • Event
    • I18n
    • Log
      • Engine
    • Model
      • Behavior
      • Datasource
        • Database
        • Session
      • Validator
    • Network
      • Email
      • Http
    • Routing
      • Filter
      • Route
    • TestSuite
      • Coverage
      • Fixture
      • Reporter
    • Utility
    • View
      • Helper

Classes

  • CakeSession
  • DataSource
  • DboSource
   1: <?php
   2: /**
   3:  * Dbo Source
   4:  *
   5:  * PHP 5
   6:  *
   7:  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
   8:  * Copyright 2005-2012, 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-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14:  * @link          http://cakephp.org CakePHP(tm) Project
  15:  * @package       Cake.Model.Datasource
  16:  * @since         CakePHP(tm) v 0.10.0.1076
  17:  * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
  18:  */
  19: 
  20: App::uses('DataSource', 'Model/Datasource');
  21: App::uses('String', 'Utility');
  22: App::uses('View', 'View');
  23: 
  24: /**
  25:  * DboSource
  26:  *
  27:  * Creates DBO-descendant objects from a given db connection configuration
  28:  *
  29:  * @package       Cake.Model.Datasource
  30:  */
  31: class DboSource extends DataSource {
  32: 
  33: /**
  34:  * Description string for this Database Data Source.
  35:  *
  36:  * @var string
  37:  */
  38:     public $description = "Database Data Source";
  39: 
  40: /**
  41:  * index definition, standard cake, primary, index, unique
  42:  *
  43:  * @var array
  44:  */
  45:     public $index = array('PRI' => 'primary', 'MUL' => 'index', 'UNI' => 'unique');
  46: 
  47: /**
  48:  * Database keyword used to assign aliases to identifiers.
  49:  *
  50:  * @var string
  51:  */
  52:     public $alias = 'AS ';
  53: 
  54: /**
  55:  * Caches result from query parsing operations.  Cached results for both DboSource::name() and
  56:  * DboSource::conditions() will be stored here.  Method caching uses `md5()`. If you have
  57:  * problems with collisions, set DboSource::$cacheMethods to false.
  58:  *
  59:  * @var array
  60:  */
  61:     public static $methodCache = array();
  62: 
  63: /**
  64:  * Whether or not to cache the results of DboSource::name() and DboSource::conditions()
  65:  * into the memory cache.  Set to false to disable the use of the memory cache.
  66:  *
  67:  * @var boolean.
  68:  */
  69:     public $cacheMethods = true;
  70: 
  71: /**
  72:  * Flag to support nested transactions. If it is set to false, you will be able to use
  73:  * the transaction methods (begin/commit/rollback), but just the global transaction will
  74:  * be executed.
  75:  *
  76:  * @var boolean
  77:  */
  78:     public $useNestedTransactions = false;
  79: 
  80: /**
  81:  * Print full query debug info?
  82:  *
  83:  * @var boolean
  84:  */
  85:     public $fullDebug = false;
  86: 
  87: /**
  88:  * String to hold how many rows were affected by the last SQL operation.
  89:  *
  90:  * @var string
  91:  */
  92:     public $affected = null;
  93: 
  94: /**
  95:  * Number of rows in current resultset
  96:  *
  97:  * @var integer
  98:  */
  99:     public $numRows = null;
 100: 
 101: /**
 102:  * Time the last query took
 103:  *
 104:  * @var integer
 105:  */
 106:     public $took = null;
 107: 
 108: /**
 109:  * Result
 110:  *
 111:  * @var array
 112:  */
 113:     protected $_result = null;
 114: 
 115: /**
 116:  * Queries count.
 117:  *
 118:  * @var integer
 119:  */
 120:     protected $_queriesCnt = 0;
 121: 
 122: /**
 123:  * Total duration of all queries.
 124:  *
 125:  * @var integer
 126:  */
 127:     protected $_queriesTime = null;
 128: 
 129: /**
 130:  * Log of queries executed by this DataSource
 131:  *
 132:  * @var array
 133:  */
 134:     protected $_queriesLog = array();
 135: 
 136: /**
 137:  * Maximum number of items in query log
 138:  *
 139:  * This is to prevent query log taking over too much memory.
 140:  *
 141:  * @var integer Maximum number of queries in the queries log.
 142:  */
 143:     protected $_queriesLogMax = 200;
 144: 
 145: /**
 146:  * Caches serialized results of executed queries
 147:  *
 148:  * @var array Cache of results from executed sql queries.
 149:  */
 150:     protected $_queryCache = array();
 151: 
 152: /**
 153:  * A reference to the physical connection of this DataSource
 154:  *
 155:  * @var array
 156:  */
 157:     protected $_connection = null;
 158: 
 159: /**
 160:  * The DataSource configuration key name
 161:  *
 162:  * @var string
 163:  */
 164:     public $configKeyName = null;
 165: 
 166: /**
 167:  * The starting character that this DataSource uses for quoted identifiers.
 168:  *
 169:  * @var string
 170:  */
 171:     public $startQuote = null;
 172: 
 173: /**
 174:  * The ending character that this DataSource uses for quoted identifiers.
 175:  *
 176:  * @var string
 177:  */
 178:     public $endQuote = null;
 179: 
 180: /**
 181:  * The set of valid SQL operations usable in a WHERE statement
 182:  *
 183:  * @var array
 184:  */
 185:     protected $_sqlOps = array('like', 'ilike', 'or', 'not', 'in', 'between', 'regexp', 'similar to');
 186: 
 187: /**
 188:  * Indicates the level of nested transactions
 189:  *
 190:  * @var integer
 191:  */
 192:     protected $_transactionNesting = 0;
 193: 
 194: /**
 195:  * Default fields that are used by the DBO
 196:  *
 197:  * @var array
 198:  */
 199:     protected $_queryDefaults = array(
 200:         'conditions' => array(),
 201:         'fields' => null,
 202:         'table' => null,
 203:         'alias' => null,
 204:         'order' => null,
 205:         'limit' => null,
 206:         'joins' => array(),
 207:         'group' => null,
 208:         'offset' => null
 209:     );
 210: 
 211: /**
 212:  * Separator string for virtualField composition
 213:  *
 214:  * @var string
 215:  */
 216:     public $virtualFieldSeparator = '__';
 217: 
 218: /**
 219:  * List of table engine specific parameters used on table creating
 220:  *
 221:  * @var array
 222:  */
 223:     public $tableParameters = array();
 224: 
 225: /**
 226:  * List of engine specific additional field parameters used on table creating
 227:  *
 228:  * @var array
 229:  */
 230:     public $fieldParameters = array();
 231: 
 232: /**
 233:  * Indicates whether there was a change on the cached results on the methods of this class
 234:  * This will be used for storing in a more persistent cache
 235:  *
 236:  * @var boolean
 237:  */
 238:     protected $_methodCacheChange = false;
 239: 
 240: /**
 241:  * Constructor
 242:  *
 243:  * @param array $config Array of configuration information for the Datasource.
 244:  * @param boolean $autoConnect Whether or not the datasource should automatically connect.
 245:  * @throws MissingConnectionException when a connection cannot be made.
 246:  */
 247:     public function __construct($config = null, $autoConnect = true) {
 248:         if (!isset($config['prefix'])) {
 249:             $config['prefix'] = '';
 250:         }
 251:         parent::__construct($config);
 252:         $this->fullDebug = Configure::read('debug') > 1;
 253:         if (!$this->enabled()) {
 254:             throw new MissingConnectionException(array(
 255:                 'class' => get_class($this),
 256:                 'message' => __d('cake_dev', 'Selected driver is not enabled'),
 257:                 'enabled' => false
 258:             ));
 259:         }
 260:         if ($autoConnect) {
 261:             $this->connect();
 262:         }
 263:     }
 264: 
 265: /**
 266:  * Reconnects to database server with optional new settings
 267:  *
 268:  * @param array $config An array defining the new configuration settings
 269:  * @return boolean True on success, false on failure
 270:  */
 271:     public function reconnect($config = array()) {
 272:         $this->disconnect();
 273:         $this->setConfig($config);
 274:         $this->_sources = null;
 275: 
 276:         return $this->connect();
 277:     }
 278: 
 279: /**
 280:  * Disconnects from database.
 281:  *
 282:  * @return boolean True if the database could be disconnected, else false
 283:  */
 284:     public function disconnect() {
 285:         if ($this->_result instanceof PDOStatement) {
 286:             $this->_result->closeCursor();
 287:         }
 288:         unset($this->_connection);
 289:         $this->connected = false;
 290:         return true;
 291:     }
 292: 
 293: /**
 294:  * Get the underlying connection object.
 295:  *
 296:  * @return PDO
 297:  */
 298:     public function getConnection() {
 299:         return $this->_connection;
 300:     }
 301: 
 302: /**
 303:  * Gets the version string of the database server
 304:  *
 305:  * @return string The database version
 306:  */
 307:     public function getVersion() {
 308:         return $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
 309:     }
 310: 
 311: /**
 312:  * Returns a quoted and escaped string of $data for use in an SQL statement.
 313:  *
 314:  * @param string $data String to be prepared for use in an SQL statement
 315:  * @param string $column The column into which this data will be inserted
 316:  * @return string Quoted and escaped data
 317:  */
 318:     public function value($data, $column = null) {
 319:         if (is_array($data) && !empty($data)) {
 320:             return array_map(
 321:                 array(&$this, 'value'),
 322:                 $data, array_fill(0, count($data), $column)
 323:             );
 324:         } elseif (is_object($data) && isset($data->type, $data->value)) {
 325:             if ($data->type == 'identifier') {
 326:                 return $this->name($data->value);
 327:             } elseif ($data->type == 'expression') {
 328:                 return $data->value;
 329:             }
 330:         } elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
 331:             return $data;
 332:         }
 333: 
 334:         if ($data === null || (is_array($data) && empty($data))) {
 335:             return 'NULL';
 336:         }
 337: 
 338:         if (empty($column)) {
 339:             $column = $this->introspectType($data);
 340:         }
 341: 
 342:         switch ($column) {
 343:             case 'binary':
 344:                 return $this->_connection->quote($data, PDO::PARAM_LOB);
 345:             case 'boolean':
 346:                 return $this->_connection->quote($this->boolean($data, true), PDO::PARAM_BOOL);
 347:             case 'string':
 348:             case 'text':
 349:                 return $this->_connection->quote($data, PDO::PARAM_STR);
 350:             default:
 351:                 if ($data === '') {
 352:                     return 'NULL';
 353:                 }
 354:                 if (is_float($data)) {
 355:                     return str_replace(',', '.', strval($data));
 356:                 }
 357:                 if ((is_int($data) || $data === '0') || (
 358:                     is_numeric($data) && strpos($data, ',') === false &&
 359:                     $data[0] != '0' && strpos($data, 'e') === false)
 360:                 ) {
 361:                     return $data;
 362:                 }
 363:                 return $this->_connection->quote($data);
 364:         }
 365:     }
 366: 
 367: /**
 368:  * Returns an object to represent a database identifier in a query. Expression objects
 369:  * are not sanitized or escaped.
 370:  *
 371:  * @param string $identifier A SQL expression to be used as an identifier
 372:  * @return stdClass An object representing a database identifier to be used in a query
 373:  */
 374:     public function identifier($identifier) {
 375:         $obj = new stdClass();
 376:         $obj->type = 'identifier';
 377:         $obj->value = $identifier;
 378:         return $obj;
 379:     }
 380: 
 381: /**
 382:  * Returns an object to represent a database expression in a query.  Expression objects
 383:  * are not sanitized or escaped.
 384:  *
 385:  * @param string $expression An arbitrary SQL expression to be inserted into a query.
 386:  * @return stdClass An object representing a database expression to be used in a query
 387:  */
 388:     public function expression($expression) {
 389:         $obj = new stdClass();
 390:         $obj->type = 'expression';
 391:         $obj->value = $expression;
 392:         return $obj;
 393:     }
 394: 
 395: /**
 396:  * Executes given SQL statement.
 397:  *
 398:  * @param string $sql SQL statement
 399:  * @param array $params Additional options for the query.
 400:  * @return boolean
 401:  */
 402:     public function rawQuery($sql, $params = array()) {
 403:         $this->took = $this->numRows = false;
 404:         return $this->execute($sql, $params);
 405:     }
 406: 
 407: /**
 408:  * Queries the database with given SQL statement, and obtains some metadata about the result
 409:  * (rows affected, timing, any errors, number of rows in resultset). The query is also logged.
 410:  * If Configure::read('debug') is set, the log is shown all the time, else it is only shown on errors.
 411:  *
 412:  * ### Options
 413:  *
 414:  * - log - Whether or not the query should be logged to the memory log.
 415:  *
 416:  * @param string $sql SQL statement
 417:  * @param array $options
 418:  * @param array $params values to be bound to the query
 419:  * @return mixed Resource or object representing the result set, or false on failure
 420:  */
 421:     public function execute($sql, $options = array(), $params = array()) {
 422:         $options += array('log' => $this->fullDebug);
 423: 
 424:         $t = microtime(true);
 425:         $this->_result = $this->_execute($sql, $params);
 426: 
 427:         if ($options['log']) {
 428:             $this->took = round((microtime(true) - $t) * 1000, 0);
 429:             $this->numRows = $this->affected = $this->lastAffected();
 430:             $this->logQuery($sql, $params);
 431:         }
 432: 
 433:         return $this->_result;
 434:     }
 435: 
 436: /**
 437:  * Executes given SQL statement.
 438:  *
 439:  * @param string $sql SQL statement
 440:  * @param array $params list of params to be bound to query
 441:  * @param array $prepareOptions Options to be used in the prepare statement
 442:  * @return mixed PDOStatement if query executes with no problem, true as the result of a successful, false on error
 443:  * query returning no rows, such as a CREATE statement, false otherwise
 444:  * @throws PDOException
 445:  */
 446:     protected function _execute($sql, $params = array(), $prepareOptions = array()) {
 447:         $sql = trim($sql);
 448:         if (preg_match('/^(?:CREATE|ALTER|DROP)\s+(?:TABLE|INDEX)/i', $sql)) {
 449:             $statements = array_filter(explode(';', $sql));
 450:             if (count($statements) > 1) {
 451:                 $result = array_map(array($this, '_execute'), $statements);
 452:                 return array_search(false, $result) === false;
 453:             }
 454:         }
 455: 
 456:         try {
 457:             $query = $this->_connection->prepare($sql, $prepareOptions);
 458:             $query->setFetchMode(PDO::FETCH_LAZY);
 459:             if (!$query->execute($params)) {
 460:                 $this->_results = $query;
 461:                 $query->closeCursor();
 462:                 return false;
 463:             }
 464:             if (!$query->columnCount()) {
 465:                 $query->closeCursor();
 466:                 if (!$query->rowCount()) {
 467:                     return true;
 468:                 }
 469:             }
 470:             return $query;
 471:         } catch (PDOException $e) {
 472:             if (isset($query->queryString)) {
 473:                 $e->queryString = $query->queryString;
 474:             } else {
 475:                 $e->queryString = $sql;
 476:             }
 477:             throw $e;
 478:         }
 479:     }
 480: 
 481: /**
 482:  * Returns a formatted error message from previous database operation.
 483:  *
 484:  * @param PDOStatement $query the query to extract the error from if any
 485:  * @return string Error message with error number
 486:  */
 487:     public function lastError(PDOStatement $query = null) {
 488:         if ($query) {
 489:             $error = $query->errorInfo();
 490:         } else {
 491:             $error = $this->_connection->errorInfo();
 492:         }
 493:         if (empty($error[2])) {
 494:             return null;
 495:         }
 496:         return $error[1] . ': ' . $error[2];
 497:     }
 498: 
 499: /**
 500:  * Returns number of affected rows in previous database operation. If no previous operation exists,
 501:  * this returns false.
 502:  *
 503:  * @param mixed $source
 504:  * @return integer Number of affected rows
 505:  */
 506:     public function lastAffected($source = null) {
 507:         if ($this->hasResult()) {
 508:             return $this->_result->rowCount();
 509:         }
 510:         return 0;
 511:     }
 512: 
 513: /**
 514:  * Returns number of rows in previous resultset. If no previous resultset exists,
 515:  * this returns false.
 516:  *
 517:  * @param mixed $source Not used
 518:  * @return integer Number of rows in resultset
 519:  */
 520:     public function lastNumRows($source = null) {
 521:         return $this->lastAffected();
 522:     }
 523: 
 524: /**
 525:  * DataSource Query abstraction
 526:  *
 527:  * @return resource Result resource identifier.
 528:  */
 529:     public function query() {
 530:         $args     = func_get_args();
 531:         $fields   = null;
 532:         $order    = null;
 533:         $limit    = null;
 534:         $page     = null;
 535:         $recursive = null;
 536: 
 537:         if (count($args) === 1) {
 538:             return $this->fetchAll($args[0]);
 539:         } elseif (count($args) > 1 && (strpos($args[0], 'findBy') === 0 || strpos($args[0], 'findAllBy') === 0)) {
 540:             $params = $args[1];
 541: 
 542:             if (substr($args[0], 0, 6) === 'findBy') {
 543:                 $all = false;
 544:                 $field = Inflector::underscore(substr($args[0], 6));
 545:             } else {
 546:                 $all = true;
 547:                 $field = Inflector::underscore(substr($args[0], 9));
 548:             }
 549: 
 550:             $or = (strpos($field, '_or_') !== false);
 551:             if ($or) {
 552:                 $field = explode('_or_', $field);
 553:             } else {
 554:                 $field = explode('_and_', $field);
 555:             }
 556:             $off = count($field) - 1;
 557: 
 558:             if (isset($params[1 + $off])) {
 559:                 $fields = $params[1 + $off];
 560:             }
 561: 
 562:             if (isset($params[2 + $off])) {
 563:                 $order = $params[2 + $off];
 564:             }
 565: 
 566:             if (!array_key_exists(0, $params)) {
 567:                 return false;
 568:             }
 569: 
 570:             $c = 0;
 571:             $conditions = array();
 572: 
 573:             foreach ($field as $f) {
 574:                 $conditions[$args[2]->alias . '.' . $f] = $params[$c++];
 575:             }
 576: 
 577:             if ($or) {
 578:                 $conditions = array('OR' => $conditions);
 579:             }
 580: 
 581:             if ($all) {
 582:                 if (isset($params[3 + $off])) {
 583:                     $limit = $params[3 + $off];
 584:                 }
 585: 
 586:                 if (isset($params[4 + $off])) {
 587:                     $page = $params[4 + $off];
 588:                 }
 589: 
 590:                 if (isset($params[5 + $off])) {
 591:                     $recursive = $params[5 + $off];
 592:                 }
 593:                 return $args[2]->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));
 594:             } else {
 595:                 if (isset($params[3 + $off])) {
 596:                     $recursive = $params[3 + $off];
 597:                 }
 598:                 return $args[2]->find('first', compact('conditions', 'fields', 'order', 'recursive'));
 599:             }
 600:         } else {
 601:             if (isset($args[1]) && $args[1] === true) {
 602:                 return $this->fetchAll($args[0], true);
 603:             } elseif (isset($args[1]) && !is_array($args[1]) ) {
 604:                 return $this->fetchAll($args[0], false);
 605:             } elseif (isset($args[1]) && is_array($args[1])) {
 606:                 if (isset($args[2])) {
 607:                     $cache = $args[2];
 608:                 } else {
 609:                     $cache = true;
 610:                 }
 611:                 return $this->fetchAll($args[0], $args[1], array('cache' => $cache));
 612:             }
 613:         }
 614:     }
 615: 
 616: /**
 617:  * Returns a row from current resultset as an array
 618:  *
 619:  * @param string $sql Some SQL to be executed.
 620:  * @return array The fetched row as an array
 621:  */
 622:     public function fetchRow($sql = null) {
 623:         if (is_string($sql) && strlen($sql) > 5 && !$this->execute($sql)) {
 624:             return null;
 625:         }
 626: 
 627:         if ($this->hasResult()) {
 628:             $this->resultSet($this->_result);
 629:             $resultRow = $this->fetchResult();
 630:             if (isset($resultRow[0])) {
 631:                 $this->fetchVirtualField($resultRow);
 632:             }
 633:             return $resultRow;
 634:         } else {
 635:             return null;
 636:         }
 637:     }
 638: 
 639: /**
 640:  * Returns an array of all result rows for a given SQL query.
 641:  * Returns false if no rows matched.
 642:  *
 643:  *
 644:  * ### Options
 645:  *
 646:  * - `cache` - Returns the cached version of the query, if exists and stores the result in cache.
 647:  *   This is a non-persistent cache, and only lasts for a single request. This option
 648:  *   defaults to true. If you are directly calling this method, you can disable caching
 649:  *   by setting $options to `false`
 650:  *
 651:  * @param string $sql SQL statement
 652:  * @param array $params parameters to be bound as values for the SQL statement
 653:  * @param array $options additional options for the query.
 654:  * @return array Array of resultset rows, or false if no rows matched
 655:  */
 656:     public function fetchAll($sql, $params = array(), $options = array()) {
 657:         if (is_string($options)) {
 658:             $options = array('modelName' => $options);
 659:         }
 660:         if (is_bool($params)) {
 661:             $options['cache'] = $params;
 662:             $params = array();
 663:         }
 664:         $options += array('cache' => true);
 665:         $cache = $options['cache'];
 666:         if ($cache && ($cached = $this->getQueryCache($sql, $params)) !== false) {
 667:             return $cached;
 668:         }
 669:         if ($result = $this->execute($sql, array(), $params)) {
 670:             $out = array();
 671: 
 672:             if ($this->hasResult()) {
 673:                 $first = $this->fetchRow();
 674:                 if ($first != null) {
 675:                     $out[] = $first;
 676:                 }
 677:                 while ($item = $this->fetchResult()) {
 678:                     if (isset($item[0])) {
 679:                         $this->fetchVirtualField($item);
 680:                     }
 681:                     $out[] = $item;
 682:                 }
 683:             }
 684: 
 685:             if (!is_bool($result) && $cache) {
 686:                 $this->_writeQueryCache($sql, $out, $params);
 687:             }
 688: 
 689:             if (empty($out) && is_bool($this->_result)) {
 690:                 return $this->_result;
 691:             }
 692:             return $out;
 693:         }
 694:         return false;
 695:     }
 696: 
 697: /**
 698:  * Fetches the next row from the current result set
 699:  *
 700:  * @return boolean
 701:  */
 702:     public function fetchResult() {
 703:         return false;
 704:     }
 705: 
 706: /**
 707:  * Modifies $result array to place virtual fields in model entry where they belongs to
 708:  *
 709:  * @param array $result Reference to the fetched row
 710:  * @return void
 711:  */
 712:     public function fetchVirtualField(&$result) {
 713:         if (isset($result[0]) && is_array($result[0])) {
 714:             foreach ($result[0] as $field => $value) {
 715:                 if (strpos($field, $this->virtualFieldSeparator) === false) {
 716:                     continue;
 717:                 }
 718:                 list($alias, $virtual) = explode($this->virtualFieldSeparator, $field);
 719: 
 720:                 if (!ClassRegistry::isKeySet($alias)) {
 721:                     return;
 722:                 }
 723:                 $model = ClassRegistry::getObject($alias);
 724:                 if ($model->isVirtualField($virtual)) {
 725:                     $result[$alias][$virtual] = $value;
 726:                     unset($result[0][$field]);
 727:                 }
 728:             }
 729:             if (empty($result[0])) {
 730:                 unset($result[0]);
 731:             }
 732:         }
 733:     }
 734: 
 735: /**
 736:  * Returns a single field of the first of query results for a given SQL query, or false if empty.
 737:  *
 738:  * @param string $name Name of the field
 739:  * @param string $sql SQL query
 740:  * @return mixed Value of field read.
 741:  */
 742:     public function field($name, $sql) {
 743:         $data = $this->fetchRow($sql);
 744:         if (empty($data[$name])) {
 745:             return false;
 746:         }
 747:         return $data[$name];
 748:     }
 749: 
 750: /**
 751:  * Empties the method caches.
 752:  * These caches are used by DboSource::name() and DboSource::conditions()
 753:  *
 754:  * @return void
 755:  */
 756:     public function flushMethodCache() {
 757:         $this->_methodCacheChange = true;
 758:         self::$methodCache = array();
 759:     }
 760: 
 761: /**
 762:  * Cache a value into the methodCaches.  Will respect the value of DboSource::$cacheMethods.
 763:  * Will retrieve a value from the cache if $value is null.
 764:  *
 765:  * If caching is disabled and a write is attempted, the $value will be returned.
 766:  * A read will either return the value or null.
 767:  *
 768:  * @param string $method Name of the method being cached.
 769:  * @param string $key The key name for the cache operation.
 770:  * @param mixed $value The value to cache into memory.
 771:  * @return mixed Either null on failure, or the value if its set.
 772:  */
 773:     public function cacheMethod($method, $key, $value = null) {
 774:         if ($this->cacheMethods === false) {
 775:             return $value;
 776:         }
 777:         if (empty(self::$methodCache)) {
 778:             self::$methodCache = Cache::read('method_cache', '_cake_core_');
 779:         }
 780:         if ($value === null) {
 781:             return (isset(self::$methodCache[$method][$key])) ? self::$methodCache[$method][$key] : null;
 782:         }
 783:         $this->_methodCacheChange = true;
 784:         return self::$methodCache[$method][$key] = $value;
 785:     }
 786: 
 787: /**
 788:  * Returns a quoted name of $data for use in an SQL statement.
 789:  * Strips fields out of SQL functions before quoting.
 790:  *
 791:  * Results of this method are stored in a memory cache.  This improves performance, but
 792:  * because the method uses a hashing algorithm it can have collisions.
 793:  * Setting DboSource::$cacheMethods to false will disable the memory cache.
 794:  *
 795:  * @param mixed $data Either a string with a column to quote. An array of columns to quote or an
 796:  *   object from DboSource::expression() or DboSource::identifier()
 797:  * @return string SQL field
 798:  */
 799:     public function name($data) {
 800:         if (is_object($data) && isset($data->type)) {
 801:             return $data->value;
 802:         }
 803:         if ($data === '*') {
 804:             return '*';
 805:         }
 806:         if (is_array($data)) {
 807:             foreach ($data as $i => $dataItem) {
 808:                 $data[$i] = $this->name($dataItem);
 809:             }
 810:             return $data;
 811:         }
 812:         $cacheKey = md5($this->startQuote . $data . $this->endQuote);
 813:         if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) {
 814:             return $return;
 815:         }
 816:         $data = trim($data);
 817:         if (preg_match('/^[\w-]+(?:\.[^ \*]*)*$/', $data)) { // string, string.string
 818:             if (strpos($data, '.') === false) { // string
 819:                 return $this->cacheMethod(__FUNCTION__, $cacheKey, $this->startQuote . $data . $this->endQuote);
 820:             }
 821:             $items = explode('.', $data);
 822:             return $this->cacheMethod(__FUNCTION__, $cacheKey,
 823:                 $this->startQuote . implode($this->endQuote . '.' . $this->startQuote, $items) . $this->endQuote
 824:             );
 825:         }
 826:         if (preg_match('/^[\w-]+\.\*$/', $data)) { // string.*
 827:             return $this->cacheMethod(__FUNCTION__, $cacheKey,
 828:                 $this->startQuote . str_replace('.*', $this->endQuote . '.*', $data)
 829:             );
 830:         }
 831:         if (preg_match('/^([\w-]+)\((.*)\)$/', $data, $matches)) { // Functions
 832:             return $this->cacheMethod(__FUNCTION__, $cacheKey,
 833:                 $matches[1] . '(' . $this->name($matches[2]) . ')'
 834:             );
 835:         }
 836:         if (
 837:             preg_match('/^([\w-]+(\.[\w-]+|\(.*\))*)\s+' . preg_quote($this->alias) . '\s*([\w-]+)$/i', $data, $matches
 838:         )) {
 839:             return $this->cacheMethod(
 840:                 __FUNCTION__, $cacheKey,
 841:                 preg_replace(
 842:                     '/\s{2,}/', ' ', $this->name($matches[1]) . ' ' . $this->alias . ' ' . $this->name($matches[3])
 843:                 )
 844:             );
 845:         }
 846:         if (preg_match('/^[\w-_\s]*[\w-_]+/', $data)) {
 847:             return $this->cacheMethod(__FUNCTION__, $cacheKey, $this->startQuote . $data . $this->endQuote);
 848:         }
 849:         return $this->cacheMethod(__FUNCTION__, $cacheKey, $data);
 850:     }
 851: 
 852: /**
 853:  * Checks if the source is connected to the database.
 854:  *
 855:  * @return boolean True if the database is connected, else false
 856:  */
 857:     public function isConnected() {
 858:         return $this->connected;
 859:     }
 860: 
 861: /**
 862:  * Checks if the result is valid
 863:  *
 864:  * @return boolean True if the result is valid else false
 865:  */
 866:     public function hasResult() {
 867:         return is_a($this->_result, 'PDOStatement');
 868:     }
 869: 
 870: /**
 871:  * Get the query log as an array.
 872:  *
 873:  * @param boolean $sorted Get the queries sorted by time taken, defaults to false.
 874:  * @param boolean $clear If True the existing log will cleared.
 875:  * @return array Array of queries run as an array
 876:  */
 877:     public function getLog($sorted = false, $clear = true) {
 878:         if ($sorted) {
 879:             $log = sortByKey($this->_queriesLog, 'took', 'desc', SORT_NUMERIC);
 880:         } else {
 881:             $log = $this->_queriesLog;
 882:         }
 883:         if ($clear) {
 884:             $this->_queriesLog = array();
 885:         }
 886:         return array('log' => $log, 'count' => $this->_queriesCnt, 'time' => $this->_queriesTime);
 887:     }
 888: 
 889: /**
 890:  * Outputs the contents of the queries log. If in a non-CLI environment the sql_log element
 891:  * will be rendered and output.  If in a CLI environment, a plain text log is generated.
 892:  *
 893:  * @param boolean $sorted Get the queries sorted by time taken, defaults to false.
 894:  * @return void
 895:  */
 896:     public function showLog($sorted = false) {
 897:         $log = $this->getLog($sorted, false);
 898:         if (empty($log['log'])) {
 899:             return;
 900:         }
 901:         if (PHP_SAPI != 'cli') {
 902:             $controller = null;
 903:             $View = new View($controller, false);
 904:             $View->set('logs', array($this->configKeyName => $log));
 905:             echo $View->element('sql_dump', array('_forced_from_dbo_' => true));
 906:         } else {
 907:             foreach ($log['log'] as $k => $i) {
 908:                 print (($k + 1) . ". {$i['query']}\n");
 909:             }
 910:         }
 911:     }
 912: 
 913: /**
 914:  * Log given SQL query.
 915:  *
 916:  * @param string $sql SQL statement
 917:  * @param array $params Values binded to the query (prepared statements)
 918:  * @return void
 919:  */
 920:     public function logQuery($sql, $params = array()) {
 921:         $this->_queriesCnt++;
 922:         $this->_queriesTime += $this->took;
 923:         $this->_queriesLog[] = array(
 924:             'query' => $sql,
 925:             'params' => $params,
 926:             'affected' => $this->affected,
 927:             'numRows' => $this->numRows,
 928:             'took' => $this->took
 929:         );
 930:         if (count($this->_queriesLog) > $this->_queriesLogMax) {
 931:             array_shift($this->_queriesLog);
 932:         }
 933:     }
 934: 
 935: /**
 936:  * Gets full table name including prefix
 937:  *
 938:  * @param Model|string $model Either a Model object or a string table name.
 939:  * @param boolean $quote Whether you want the table name quoted.
 940:  * @param boolean $schema Whether you want the schema name included.
 941:  * @return string Full quoted table name
 942:  */
 943:     public function fullTableName($model, $quote = true, $schema = true) {
 944:         if (is_object($model)) {
 945:             $schemaName = $model->schemaName;
 946:             $table = $model->tablePrefix . $model->table;
 947:         } elseif (!empty($this->config['prefix']) && strpos($model, $this->config['prefix']) !== 0) {
 948:             $table = $this->config['prefix'] . strval($model);
 949:         } else {
 950:             $table = strval($model);
 951:         }
 952:         if ($schema && !isset($schemaName)) {
 953:             $schemaName = $this->getSchemaName();
 954:         }
 955: 
 956:         if ($quote) {
 957:             if ($schema && !empty($schemaName)) {
 958:                 if (false == strstr($table, '.')) {
 959:                     return $this->name($schemaName) . '.' . $this->name($table);
 960:                 }
 961:             }
 962:             return $this->name($table);
 963:         }
 964:         if ($schema && !empty($schemaName)) {
 965:             if (false == strstr($table, '.')) {
 966:                 return $schemaName . '.' . $table;
 967:             }
 968:         }
 969:         return $table;
 970:     }
 971: 
 972: /**
 973:  * The "C" in CRUD
 974:  *
 975:  * Creates new records in the database.
 976:  *
 977:  * @param Model $model Model object that the record is for.
 978:  * @param array $fields An array of field names to insert. If null, $model->data will be
 979:  *   used to generate field names.
 980:  * @param array $values An array of values with keys matching the fields. If null, $model->data will
 981:  *   be used to generate values.
 982:  * @return boolean Success
 983:  */
 984:     public function create(Model $model, $fields = null, $values = null) {
 985:         $id = null;
 986: 
 987:         if ($fields == null) {
 988:             unset($fields, $values);
 989:             $fields = array_keys($model->data);
 990:             $values = array_values($model->data);
 991:         }
 992:         $count = count($fields);
 993: 
 994:         for ($i = 0; $i < $count; $i++) {
 995:             $valueInsert[] = $this->value($values[$i], $model->getColumnType($fields[$i]));
 996:             $fieldInsert[] = $this->name($fields[$i]);
 997:             if ($fields[$i] == $model->primaryKey) {
 998:                 $id = $values[$i];
 999:             }
1000:         }
1001:         $query = array(
1002:             'table' => $this->fullTableName($model),
1003:             'fields' => implode(', ', $fieldInsert),
1004:             'values' => implode(', ', $valueInsert)
1005:         );
1006: 
1007:         if ($this->execute($this->renderStatement('create', $query))) {
1008:             if (empty($id)) {
1009:                 $id = $this->lastInsertId($this->fullTableName($model, false, false), $model->primaryKey);
1010:             }
1011:             $model->setInsertID($id);
1012:             $model->id = $id;
1013:             return true;
1014:         }
1015:         $model->onError();
1016:         return false;
1017:     }
1018: 
1019: /**
1020:  * The "R" in CRUD
1021:  *
1022:  * Reads record(s) from the database.
1023:  *
1024:  * @param Model $model A Model object that the query is for.
1025:  * @param array $queryData An array of queryData information containing keys similar to Model::find()
1026:  * @param integer $recursive Number of levels of association
1027:  * @return mixed boolean false on error/failure.  An array of results on success.
1028:  */
1029:     public function read(Model $model, $queryData = array(), $recursive = null) {
1030:         $queryData = $this->_scrubQueryData($queryData);
1031: 
1032:         $null = null;
1033:         $array = array('callbacks' => $queryData['callbacks']);
1034:         $linkedModels = array();
1035:         $bypass = false;
1036: 
1037:         if ($recursive === null && isset($queryData['recursive'])) {
1038:             $recursive = $queryData['recursive'];
1039:         }
1040: 
1041:         if (!is_null($recursive)) {
1042:             $_recursive = $model->recursive;
1043:             $model->recursive = $recursive;
1044:         }
1045: 
1046:         if (!empty($queryData['fields'])) {
1047:             $bypass = true;
1048:             $queryData['fields'] = $this->fields($model, null, $queryData['fields']);
1049:         } else {
1050:             $queryData['fields'] = $this->fields($model);
1051:         }
1052: 
1053:         $_associations = $model->associations();
1054: 
1055:         if ($model->recursive == -1) {
1056:             $_associations = array();
1057:         } elseif ($model->recursive == 0) {
1058:             unset($_associations[2], $_associations[3]);
1059:         }
1060: 
1061:         foreach ($_associations as $type) {
1062:             foreach ($model->{$type} as $assoc => $assocData) {
1063:                 $linkModel = $model->{$assoc};
1064:                 $external = isset($assocData['external']);
1065: 
1066:                 $linkModel->getDataSource();
1067:                 if ($model->useDbConfig === $linkModel->useDbConfig) {
1068:                     if ($bypass) {
1069:                         $assocData['fields'] = false;
1070:                     }
1071:                     if (true === $this->generateAssociationQuery($model, $linkModel, $type, $assoc, $assocData, $queryData, $external, $null)) {
1072:                         $linkedModels[$type . '/' . $assoc] = true;
1073:                     }
1074:                 }
1075:             }
1076:         }
1077: 
1078:         $query = trim($this->generateAssociationQuery($model, null, null, null, null, $queryData, false, $null));
1079: 
1080:         $resultSet = $this->fetchAll($query, $model->cacheQueries);
1081: 
1082:         if ($resultSet === false) {
1083:             $model->onError();
1084:             return false;
1085:         }
1086: 
1087:         $filtered = array();
1088: 
1089:         if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
1090:             $filtered = $this->_filterResults($resultSet, $model);
1091:         }
1092: 
1093:         if ($model->recursive > -1) {
1094:             $joined = array();
1095:             if (isset($queryData['joins'][0]['alias'])) {
1096:                 $joined[$model->alias] = (array)Hash::extract($queryData['joins'], '{n}.alias');
1097:             }
1098:             foreach ($_associations as $type) {
1099:                 foreach ($model->{$type} as $assoc => $assocData) {
1100:                     $linkModel = $model->{$assoc};
1101: 
1102:                     if (!isset($linkedModels[$type . '/' . $assoc])) {
1103:                         if ($model->useDbConfig === $linkModel->useDbConfig) {
1104:                             $db = $this;
1105:                         } else {
1106:                             $db = ConnectionManager::getDataSource($linkModel->useDbConfig);
1107:                         }
1108:                     } elseif ($model->recursive > 1 && ($type === 'belongsTo' || $type === 'hasOne')) {
1109:                         $db = $this;
1110:                     }
1111: 
1112:                     if (isset($db) && method_exists($db, 'queryAssociation')) {
1113:                         $stack = array($assoc);
1114:                         $stack['_joined'] = $joined;
1115:                         $db->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $array, true, $resultSet, $model->recursive - 1, $stack);
1116:                         unset($db);
1117: 
1118:                         if ($type === 'hasMany') {
1119:                             $filtered[] = $assoc;
1120:                         }
1121:                     }
1122:                 }
1123:             }
1124:             if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
1125:                 $this->_filterResults($resultSet, $model, $filtered);
1126:             }
1127:         }
1128: 
1129:         if (!is_null($recursive)) {
1130:             $model->recursive = $_recursive;
1131:         }
1132:         return $resultSet;
1133:     }
1134: 
1135: /**
1136:  * Passes association results thru afterFind filters of corresponding model
1137:  *
1138:  * @param array $results Reference of resultset to be filtered
1139:  * @param Model $model Instance of model to operate against
1140:  * @param array $filtered List of classes already filtered, to be skipped
1141:  * @return array Array of results that have been filtered through $model->afterFind
1142:  */
1143:     protected function _filterResults(&$results, Model $model, $filtered = array()) {
1144:         $current = reset($results);
1145:         if (!is_array($current)) {
1146:             return array();
1147:         }
1148:         $keys = array_diff(array_keys($current), $filtered, array($model->alias));
1149:         $filtering = array();
1150:         foreach ($keys as $className) {
1151:             if (!isset($model->{$className}) || !is_object($model->{$className})) {
1152:                 continue;
1153:             }
1154:             $linkedModel = $model->{$className};
1155:             $filtering[] = $className;
1156:             foreach ($results as $key => &$result) {
1157:                 $data = $linkedModel->afterFind(array(array($className => $result[$className])), false);
1158:                 if (isset($data[0][$className])) {
1159:                     $result[$className] = $data[0][$className];
1160:                 } else {
1161:                     unset($results[$key]);
1162:                 }
1163:             }
1164:         }
1165:         return $filtering;
1166:     }
1167: 
1168: /**
1169:  * Queries associations.  Used to fetch results on recursive models.
1170:  *
1171:  * @param Model $model Primary Model object
1172:  * @param Model $linkModel Linked model that
1173:  * @param string $type Association type, one of the model association types ie. hasMany
1174:  * @param string $association
1175:  * @param array $assocData
1176:  * @param array $queryData
1177:  * @param boolean $external Whether or not the association query is on an external datasource.
1178:  * @param array $resultSet Existing results
1179:  * @param integer $recursive Number of levels of association
1180:  * @param array $stack
1181:  * @return mixed
1182:  * @throws CakeException when results cannot be created.
1183:  */
1184:     public function queryAssociation(Model $model, &$linkModel, $type, $association, $assocData, &$queryData, $external, &$resultSet, $recursive, $stack) {
1185:         if (isset($stack['_joined'])) {
1186:             $joined = $stack['_joined'];
1187:             unset($stack['_joined']);
1188:         }
1189: 
1190:         if ($query = $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet)) {
1191:             if (!is_array($resultSet)) {
1192:                 throw new CakeException(__d('cake_dev', 'Error in Model %s', get_class($model)));
1193:             }
1194:             if ($type === 'hasMany' && empty($assocData['limit']) && !empty($assocData['foreignKey'])) {
1195:                 $ins = $fetch = array();
1196:                 foreach ($resultSet as &$result) {
1197:                     if ($in = $this->insertQueryData('{$__cakeID__$}', $result, $association, $assocData, $model, $linkModel, $stack)) {
1198:                         $ins[] = $in;
1199:                     }
1200:                 }
1201: 
1202:                 if (!empty($ins)) {
1203:                     $ins = array_unique($ins);
1204:                     $fetch = $this->fetchAssociated($model, $query, $ins);
1205:                 }
1206: 
1207:                 if (!empty($fetch) && is_array($fetch)) {
1208:                     if ($recursive > 0) {
1209:                         foreach ($linkModel->associations() as $type1) {
1210:                             foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
1211:                                 $deepModel = $linkModel->{$assoc1};
1212:                                 $tmpStack = $stack;
1213:                                 $tmpStack[] = $assoc1;
1214: 
1215:                                 if ($linkModel->useDbConfig === $deepModel->useDbConfig) {
1216:                                     $db = $this;
1217:                                 } else {
1218:                                     $db = ConnectionManager::getDataSource($deepModel->useDbConfig);
1219:                                 }
1220:                                 $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
1221:                             }
1222:                         }
1223:                     }
1224:                 }
1225:                 if ($queryData['callbacks'] === true || $queryData['callbacks'] === 'after') {
1226:                     $this->_filterResults($fetch, $model);
1227:                 }
1228:                 return $this->_mergeHasMany($resultSet, $fetch, $association, $model, $linkModel);
1229:             } elseif ($type === 'hasAndBelongsToMany') {
1230:                 $ins = $fetch = array();
1231:                 foreach ($resultSet as &$result) {
1232:                     if ($in = $this->insertQueryData('{$__cakeID__$}', $result, $association, $assocData, $model, $linkModel, $stack)) {
1233:                         $ins[] = $in;
1234:                     }
1235:                 }
1236:                 if (!empty($ins)) {
1237:                     $ins = array_unique($ins);
1238:                     if (count($ins) > 1) {
1239:                         $query = str_replace('{$__cakeID__$}', '(' . implode(', ', $ins) . ')', $query);
1240:                         $query = str_replace('= (', 'IN (', $query);
1241:                     } else {
1242:                         $query = str_replace('{$__cakeID__$}', $ins[0], $query);
1243:                     }
1244:                     $query = str_replace(' WHERE 1 = 1', '', $query);
1245:                 }
1246: 
1247:                 $foreignKey = $model->hasAndBelongsToMany[$association]['foreignKey'];
1248:                 $joinKeys = array($foreignKey, $model->hasAndBelongsToMany[$association]['associationForeignKey']);
1249:                 list($with, $habtmFields) = $model->joinModel($model->hasAndBelongsToMany[$association]['with'], $joinKeys);
1250:                 $habtmFieldsCount = count($habtmFields);
1251:                 $q = $this->insertQueryData($query, null, $association, $assocData, $model, $linkModel, $stack);
1252: 
1253:                 if ($q !== false) {
1254:                     $fetch = $this->fetchAll($q, $model->cacheQueries);
1255:                 } else {
1256:                     $fetch = null;
1257:                 }
1258:             }
1259: 
1260:             $modelAlias = $model->alias;
1261:             $modelPK = $model->primaryKey;
1262:             foreach ($resultSet as &$row) {
1263:                 if ($type !== 'hasAndBelongsToMany') {
1264:                     $q = $this->insertQueryData($query, $row, $association, $assocData, $model, $linkModel, $stack);
1265:                     $fetch = null;
1266:                     if ($q !== false) {
1267:                         $joinedData = array();
1268:                         if (($type === 'belongsTo' || $type === 'hasOne') && isset($row[$linkModel->alias], $joined[$model->alias]) && in_array($linkModel->alias, $joined[$model->alias])) {
1269:                             $joinedData = Hash::filter($row[$linkModel->alias]);
1270:                             if (!empty($joinedData)) {
1271:                                 $fetch[0] = array($linkModel->alias => $row[$linkModel->alias]);
1272:                             }
1273:                         } else {
1274:                             $fetch = $this->fetchAll($q, $model->cacheQueries);
1275:                         }
1276:                     }
1277:                 }
1278:                 $selfJoin = $linkModel->name === $model->name;
1279: 
1280:                 if (!empty($fetch) && is_array($fetch)) {
1281:                     if ($recursive > 0) {
1282:                         foreach ($linkModel->associations() as $type1) {
1283:                             foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
1284:                                 $deepModel = $linkModel->{$assoc1};
1285: 
1286:                                 if ($type1 === 'belongsTo' || ($deepModel->alias === $modelAlias && $type === 'belongsTo') || ($deepModel->alias !== $modelAlias)) {
1287:                                     $tmpStack = $stack;
1288:                                     $tmpStack[] = $assoc1;
1289:                                     if ($linkModel->useDbConfig == $deepModel->useDbConfig) {
1290:                                         $db = $this;
1291:                                     } else {
1292:                                         $db = ConnectionManager::getDataSource($deepModel->useDbConfig);
1293:                                     }
1294:                                     $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
1295:                                 }
1296:                             }
1297:                         }
1298:                     }
1299:                     if ($type === 'hasAndBelongsToMany') {
1300:                         $merge = array();
1301: 
1302:                         foreach ($fetch as $data) {
1303:                             if (isset($data[$with]) && $data[$with][$foreignKey] === $row[$modelAlias][$modelPK]) {
1304:                                 if ($habtmFieldsCount <= 2) {
1305:                                     unset($data[$with]);
1306:                                 }
1307:                                 $merge[] = $data;
1308:                             }
1309:                         }
1310:                         if (empty($merge) && !isset($row[$association])) {
1311:                             $row[$association] = $merge;
1312:                         } else {
1313:                             $this->_mergeAssociation($row, $merge, $association, $type);
1314:                         }
1315:                     } else {
1316:                         $this->_mergeAssociation($row, $fetch, $association, $type, $selfJoin);
1317:                     }
1318:                     if (isset($row[$association])) {
1319:                         $row[$association] = $linkModel->afterFind($row[$association], false);
1320:                     }
1321:                 } else {
1322:                     $tempArray[0][$association] = false;
1323:                     $this->_mergeAssociation($row, $tempArray, $association, $type, $selfJoin);
1324:                 }
1325:             }
1326:         }
1327:     }
1328: 
1329: /**
1330:  * A more efficient way to fetch associations.  Woohoo!
1331:  *
1332:  * @param Model $model Primary model object
1333:  * @param string $query Association query
1334:  * @param array $ids Array of IDs of associated records
1335:  * @return array Association results
1336:  */
1337:     public function fetchAssociated(Model $model, $query, $ids) {
1338:         $query = str_replace('{$__cakeID__$}', implode(', ', $ids), $query);
1339:         if (count($ids) > 1) {
1340:             $query = str_replace('= (', 'IN (', $query);
1341:         }
1342:         return $this->fetchAll($query, $model->cacheQueries);
1343:     }
1344: 
1345: /**
1346:  * mergeHasMany - Merge the results of hasMany relations.
1347:  *
1348:  *
1349:  * @param array $resultSet Data to merge into
1350:  * @param array $merge Data to merge
1351:  * @param string $association Name of Model being Merged
1352:  * @param Model $model Model being merged onto
1353:  * @param Model $linkModel Model being merged
1354:  * @return void
1355:  */
1356:     protected function _mergeHasMany(&$resultSet, $merge, $association, $model, $linkModel) {
1357:         $modelAlias = $model->alias;
1358:         $modelPK = $model->primaryKey;
1359:         $modelFK = $model->hasMany[$association]['foreignKey'];
1360:         foreach ($resultSet as &$result) {
1361:             if (!isset($result[$modelAlias])) {
1362:                 continue;
1363:             }
1364:             $merged = array();
1365:             foreach ($merge as $data) {
1366:                 if ($result[$modelAlias][$modelPK] === $data[$association][$modelFK]) {
1367:                     if (count($data) > 1) {
1368:                         $data = array_merge($data[$association], $data);
1369:                         unset($data[$association]);
1370:                         foreach ($data as $key => $name) {
1371:                             if (is_numeric($key)) {
1372:                                 $data[$association][] = $name;
1373:                                 unset($data[$key]);
1374:                             }
1375:                         }
1376:                         $merged[] = $data;
1377:                     } else {
1378:                         $merged[] = $data[$association];
1379:                     }
1380:                 }
1381:             }
1382:             $result = Hash::mergeDiff($result, array($association => $merged));
1383:         }
1384:     }
1385: 
1386: /**
1387:  * Merge association of merge into data
1388:  *
1389:  * @param array $data
1390:  * @param array $merge
1391:  * @param string $association
1392:  * @param string $type
1393:  * @param boolean $selfJoin
1394:  * @return void
1395:  */
1396:     protected function _mergeAssociation(&$data, &$merge, $association, $type, $selfJoin = false) {
1397:         if (isset($merge[0]) && !isset($merge[0][$association])) {
1398:             $association = Inflector::pluralize($association);
1399:         }
1400: 
1401:         if ($type === 'belongsTo' || $type === 'hasOne') {
1402:             if (isset($merge[$association])) {
1403:                 $data[$association] = $merge[$association][0];
1404:             } else {
1405:                 if (count($merge[0][$association]) > 1) {
1406:                     foreach ($merge[0] as $assoc => $data2) {
1407:                         if ($assoc !== $association) {
1408:                             $merge[0][$association][$assoc] = $data2;
1409:                         }
1410:                     }
1411:                 }
1412:                 if (!isset($data[$association])) {
1413:                     if ($merge[0][$association] != null) {
1414:                         $data[$association] = $merge[0][$association];
1415:                     } else {
1416:                         $data[$association] = array();
1417:                     }
1418:                 } else {
1419:                     if (is_array($merge[0][$association])) {
1420:                         foreach ($data[$association] as $k => $v) {
1421:                             if (!is_array($v)) {
1422:                                 $dataAssocTmp[$k] = $v;
1423:                             }
1424:                         }
1425: 
1426:                         foreach ($merge[0][$association] as $k => $v) {
1427:                             if (!is_array($v)) {
1428:                                 $mergeAssocTmp[$k] = $v;
1429:                             }
1430:                         }
1431:                         $dataKeys = array_keys($data);
1432:                         $mergeKeys = array_keys($merge[0]);
1433: 
1434:                         if ($mergeKeys[0] === $dataKeys[0] || $mergeKeys === $dataKeys) {
1435:                             $data[$association][$association] = $merge[0][$association];
1436:                         } else {
1437:                             $diff = Hash::diff($dataAssocTmp, $mergeAssocTmp);
1438:                             $data[$association] = array_merge($merge[0][$association], $diff);
1439:                         }
1440:                     } elseif ($selfJoin && array_key_exists($association, $merge[0])) {
1441:                         $data[$association] = array_merge($data[$association], array($association => array()));
1442:                     }
1443:                 }
1444:             }
1445:         } else {
1446:             if (isset($merge[0][$association]) && $merge[0][$association] === false) {
1447:                 if (!isset($data[$association])) {
1448:                     $data[$association] = array();
1449:                 }
1450:             } else {
1451:                 foreach ($merge as $row) {
1452:                     $insert = array();
1453:                     if (count($row) === 1) {
1454:                         $insert = $row[$association];
1455:                     } elseif (isset($row[$association])) {
1456:                         $insert = array_merge($row[$association], $row);
1457:                         unset($insert[$association]);
1458:                     }
1459: 
1460:                     if (empty($data[$association]) || (isset($data[$association]) && !in_array($insert, $data[$association], true))) {
1461:                         $data[$association][] = $insert;
1462:                     }
1463:                 }
1464:             }
1465:         }
1466:     }
1467: 
1468: /**
1469:  * Generates an array representing a query or part of a query from a single model or two associated models
1470:  *
1471:  * @param Model $model
1472:  * @param Model $linkModel
1473:  * @param string $type
1474:  * @param string $association
1475:  * @param array $assocData
1476:  * @param array $queryData
1477:  * @param boolean $external
1478:  * @param array $resultSet
1479:  * @return mixed
1480:  */
1481:     public function generateAssociationQuery(Model $model, $linkModel, $type, $association, $assocData, &$queryData, $external, &$resultSet) {
1482:         $queryData = $this->_scrubQueryData($queryData);
1483:         $assocData = $this->_scrubQueryData($assocData);
1484:         $modelAlias = $model->alias;
1485: 
1486:         if (empty($queryData['fields'])) {
1487:             $queryData['fields'] = $this->fields($model, $modelAlias);
1488:         } elseif (!empty($model->hasMany) && $model->recursive > -1) {
1489:             $assocFields = $this->fields($model, $modelAlias, array("{$modelAlias}.{$model->primaryKey}"));
1490:             $passedFields = $queryData['fields'];
1491:             if (count($passedFields) === 1) {
1492:                 if (strpos($passedFields[0], $assocFields[0]) === false && !preg_match('/^[a-z]+\(/i', $passedFields[0])) {
1493:                     $queryData['fields'] = array_merge($passedFields, $assocFields);
1494:                 } else {
1495:                     $queryData['fields'] = $passedFields;
1496:                 }
1497:             } else {
1498:                 $queryData['fields'] = array_merge($passedFields, $assocFields);
1499:             }
1500:             unset($assocFields, $passedFields);
1501:         }
1502: 
1503:         if ($linkModel === null) {
1504:             return $this->buildStatement(
1505:                 array(
1506:                     'fields' => array_unique($queryData['fields']),
1507:                     'table' => $this->fullTableName($model),
1508:                     'alias' => $modelAlias,
1509:                     'limit' => $queryData['limit'],
1510:                     'offset' => $queryData['offset'],
1511:                     'joins' => $queryData['joins'],
1512:                     'conditions' => $queryData['conditions'],
1513:                     'order' => $queryData['order'],
1514:                     'group' => $queryData['group']
1515:                 ),
1516:                 $model
1517:             );
1518:         }
1519:         if ($external && !empty($assocData['finderQuery'])) {
1520:             return $assocData['finderQuery'];
1521:         }
1522: 
1523:         $self = $model->name === $linkModel->name;
1524:         $fields = array();
1525: 
1526:         if ($external || (in_array($type, array('hasOne', 'belongsTo')) && $assocData['fields'] !== false)) {
1527:             $fields = $this->fields($linkModel, $association, $assocData['fields']);
1528:         }
1529:         if (empty($assocData['offset']) && !empty($assocData['page'])) {
1530:             $assocData['offset'] = ($assocData['page'] - 1) * $assocData['limit'];
1531:         }
1532:         $assocData['limit'] = $this->limit($assocData['limit'], $assocData['offset']);
1533: 
1534:         switch ($type) {
1535:             case 'hasOne':
1536:             case 'belongsTo':
1537:                 $conditions = $this->_mergeConditions(
1538:                     $assocData['conditions'],
1539:                     $this->getConstraint($type, $model, $linkModel, $association, array_merge($assocData, compact('external', 'self')))
1540:                 );
1541: 
1542:                 if (!$self && $external) {
1543:                     foreach ($conditions as $key => $condition) {
1544:                         if (is_numeric($key) && strpos($condition, $modelAlias . '.') !== false) {
1545:                             unset($conditions[$key]);
1546:                         }
1547:                     }
1548:                 }
1549: 
1550:                 if ($external) {
1551:                     $query = array_merge($assocData, array(
1552:                         'conditions' => $conditions,
1553:                         'table' => $this->fullTableName($linkModel),
1554:                         'fields' => $fields,
1555:                         'alias' => $association,
1556:                         'group' => null
1557:                     ));
1558:                     $query += array('order' => $assocData['order'], 'limit' => $assocData['limit']);
1559:                 } else {
1560:                     $join = array(
1561:                         'table' => $linkModel,
1562:                         'alias' => $association,
1563:                         'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT',
1564:                         'conditions' => trim($this->conditions($conditions, true, false, $model))
1565:                     );
1566:                     $queryData['fields'] = array_merge($queryData['fields'], $fields);
1567: 
1568:                     if (!empty($assocData['order'])) {
1569:                         $queryData['order'][] = $assocData['order'];
1570:                     }
1571:                     if (!in_array($join, $queryData['joins'])) {
1572:                         $queryData['joins'][] = $join;
1573:                     }
1574:                     return true;
1575:                 }
1576:             break;
1577:             case 'hasMany':
1578:                 $assocData['fields'] = $this->fields($linkModel, $association, $assocData['fields']);
1579:                 if (!empty($assocData['foreignKey'])) {
1580:                     $assocData['fields'] = array_merge($assocData['fields'], $this->fields($linkModel, $association, array("{$association}.{$assocData['foreignKey']}")));
1581:                 }
1582:                 $query = array(
1583:                     'conditions' => $this->_mergeConditions($this->getConstraint('hasMany', $model, $linkModel, $association, $assocData), $assocData['conditions']),
1584:                     'fields' => array_unique($assocData['fields']),
1585:                     'table' => $this->fullTableName($linkModel),
1586:                     'alias' => $association,
1587:                     'order' => $assocData['order'],
1588:                     'limit' => $assocData['limit'],
1589:                     'group' => null
1590:                 );
1591:             break;
1592:             case 'hasAndBelongsToMany':
1593:                 $joinFields = array();
1594:                 $joinAssoc = null;
1595: 
1596:                 if (isset($assocData['with']) && !empty($assocData['with'])) {
1597:                     $joinKeys = array($assocData['foreignKey'], $assocData['associationForeignKey']);
1598:                     list($with, $joinFields) = $model->joinModel($assocData['with'], $joinKeys);
1599: 
1600:                     $joinTbl = $model->{$with};
1601:                     $joinAlias = $joinTbl;
1602: 
1603:                     if (is_array($joinFields) && !empty($joinFields)) {
1604:                         $joinAssoc = $joinAlias = $model->{$with}->alias;
1605:                         $joinFields = $this->fields($model->{$with}, $joinAlias, $joinFields);
1606:                     } else {
1607:                         $joinFields = array();
1608:                     }
1609:                 } else {
1610:                     $joinTbl = $assocData['joinTable'];
1611:                     $joinAlias = $this->fullTableName($assocData['joinTable']);
1612:                 }
1613:                 $query = array(
1614:                     'conditions' => $assocData['conditions'],
1615:                     'limit' => $assocData['limit'],
1616:                     'table' => $this->fullTableName($linkModel),
1617:                     'alias' => $association,
1618:                     'fields' => array_merge($this->fields($linkModel, $association, $assocData['fields']), $joinFields),
1619:                     'order' => $assocData['order'],
1620:                     'group' => null,
1621:                     'joins' => array(array(
1622:                         'table' => $joinTbl,
1623:                         'alias' => $joinAssoc,
1624:                         'conditions' => $this->getConstraint('hasAndBelongsToMany', $model, $linkModel, $joinAlias, $assocData, $association)
1625:                     ))
1626:                 );
1627:             break;
1628:         }
1629:         if (isset($query)) {
1630:             return $this->buildStatement($query, $model);
1631:         }
1632:         return null;
1633:     }
1634: 
1635: /**
1636:  * Returns a conditions array for the constraint between two models
1637:  *
1638:  * @param string $type Association type
1639:  * @param Model $model Model object
1640:  * @param string $linkModel
1641:  * @param string $alias
1642:  * @param array $assoc
1643:  * @param string $alias2
1644:  * @return array Conditions array defining the constraint between $model and $association
1645:  */
1646:     public function getConstraint($type, $model, $linkModel, $alias, $assoc, $alias2 = null) {
1647:         $assoc += array('external' => false, 'self' => false);
1648: 
1649:         if (empty($assoc['foreignKey'])) {
1650:             return array();
1651:         }
1652: 
1653:         switch (true) {
1654:             case ($assoc['external'] && $type === 'hasOne'):
1655:                 return array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}');
1656:             case ($assoc['external'] && $type === 'belongsTo'):
1657:                 return array("{$alias}.{$linkModel->primaryKey}" => '{$__cakeForeignKey__$}');
1658:             case (!$assoc['external'] && $type === 'hasOne'):
1659:                 return array("{$alias}.{$assoc['foreignKey']}" => $this->identifier("{$model->alias}.{$model->primaryKey}"));
1660:             case (!$assoc['external'] && $type === 'belongsTo'):
1661:                 return array("{$model->alias}.{$assoc['foreignKey']}" => $this->identifier("{$alias}.{$linkModel->primaryKey}"));
1662:             case ($type === 'hasMany'):
1663:                 return array("{$alias}.{$assoc['foreignKey']}" => array('{$__cakeID__$}'));
1664:             case ($type === 'hasAndBelongsToMany'):
1665:                 return array(
1666:                     array("{$alias}.{$assoc['foreignKey']}" => '{$__cakeID__$}'),
1667:                     array("{$alias}.{$assoc['associationForeignKey']}" => $this->identifier("{$alias2}.{$linkModel->primaryKey}"))
1668:                 );
1669:         }
1670:         return array();
1671:     }
1672: 
1673: /**
1674:  * Builds and generates a JOIN statement from an array.  Handles final clean-up before conversion.
1675:  *
1676:  * @param array $join An array defining a JOIN statement in a query
1677:  * @return string An SQL JOIN statement to be used in a query
1678:  * @see DboSource::renderJoinStatement()
1679:  * @see DboSource::buildStatement()
1680:  */
1681:     public function buildJoinStatement($join) {
1682:         $data = array_merge(array(
1683:             'type' => null,
1684:             'alias' => null,
1685:             'table' => 'join_table',
1686:             'conditions' => array()
1687:         ), $join);
1688: 
1689:         if (!empty($data['alias'])) {
1690:             $data['alias'] = $this->alias . $this->name($data['alias']);
1691:         }
1692:         if (!empty($data['conditions'])) {
1693:             $data['conditions'] = trim($this->conditions($data['conditions'], true, false));
1694:         }
1695:         if (!empty($data['table'])) {
1696:             $schema = !(is_string($data['table']) && strpos($data['table'], '(') === 0);
1697:             $data['table'] = $this->fullTableName($data['table'], true, $schema);
1698:         }
1699:         return $this->renderJoinStatement($data);
1700:     }
1701: 
1702: /**
1703:  * Builds and generates an SQL statement from an array.  Handles final clean-up before conversion.
1704:  *
1705:  * @param array $query An array defining an SQL query
1706:  * @param Model $model The model object which initiated the query
1707:  * @return string An executable SQL statement
1708:  * @see DboSource::renderStatement()
1709:  */
1710:     public function buildStatement($query, $model) {
1711:         $query = array_merge($this->_queryDefaults, $query);
1712:         if (!empty($query['joins'])) {
1713:             $count = count($query['joins']);
1714:             for ($i = 0; $i < $count; $i++) {
1715:                 if (is_array($query['joins'][$i])) {
1716:                     $query['joins'][$i] = $this->buildJoinStatement($query['joins'][$i]);
1717:                 }
1718:             }
1719:         }
1720:         return $this->renderStatement('select', array(
1721:             'conditions' => $this->conditions($query['conditions'], true, true, $model),
1722:             'fields' => implode(', ', $query['fields']),
1723:             'table' => $query['table'],
1724:             'alias' => $this->alias . $this->name($query['alias']),
1725:             'order' => $this->order($query['order'], 'ASC', $model),
1726:             'limit' => $this->limit($query['limit'], $query['offset']),
1727:             'joins' => implode(' ', $query['joins']),
1728:             'group' => $this->group($query['group'], $model)
1729:         ));
1730:     }
1731: 
1732: /**
1733:  * Renders a final SQL JOIN statement
1734:  *
1735:  * @param array $data
1736:  * @return string
1737:  */
1738:     public function renderJoinStatement($data) {
1739:         extract($data);
1740:         return trim("{$type} JOIN {$table} {$alias} ON ({$conditions})");
1741:     }
1742: 
1743: /**
1744:  * Renders a final SQL statement by putting together the component parts in the correct order
1745:  *
1746:  * @param string $type type of query being run.  e.g select, create, update, delete, schema, alter.
1747:  * @param array $data Array of data to insert into the query.
1748:  * @return string Rendered SQL expression to be run.
1749:  */
1750:     public function renderStatement($type, $data) {
1751:         extract($data);
1752:         $aliases = null;
1753: 
1754:         switch (strtolower($type)) {
1755:             case 'select':
1756:                 return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}";
1757:             case 'create':
1758:                 return "INSERT INTO {$table} ({$fields}) VALUES ({$values})";
1759:             case 'update':
1760:                 if (!empty($alias)) {
1761:                     $aliases = "{$this->alias}{$alias} {$joins} ";
1762:                 }
1763:                 return "UPDATE {$table} {$aliases}SET {$fields} {$conditions}";
1764:             case 'delete':
1765:                 if (!empty($alias)) {
1766:                     $aliases = "{$this->alias}{$alias} {$joins} ";
1767:                 }
1768:                 return "DELETE {$alias} FROM {$table} {$aliases}{$conditions}";
1769:             case 'schema':
1770:                 foreach (array('columns', 'indexes', 'tableParameters') as $var) {
1771:                     if (is_array(${$var})) {
1772:                         ${$var} = "\t" . join(",\n\t", array_filter(${$var}));
1773:                     } else {
1774:                         ${$var} = '';
1775:                     }
1776:                 }
1777:                 if (trim($indexes) !== '') {
1778:                     $columns .= ',';
1779:                 }
1780:                 return "CREATE TABLE {$table} (\n{$columns}{$indexes}) {$tableParameters};";
1781:             case 'alter':
1782:                 return;
1783:         }
1784:     }
1785: 
1786: /**
1787:  * Merges a mixed set of string/array conditions
1788:  *
1789:  * @param mixed $query
1790:  * @param mixed $assoc
1791:  * @return array
1792:  */
1793:     protected function _mergeConditions($query, $assoc) {
1794:         if (empty($assoc)) {
1795:             return $query;
1796:         }
1797: 
1798:         if (is_array($query)) {
1799:             return array_merge((array)$assoc, $query);
1800:         }
1801: 
1802:         if (!empty($query)) {
1803:             $query = array($query);
1804:             if (is_array($assoc)) {
1805:                 $query = array_merge($query, $assoc);
1806:             } else {
1807:                 $query[] = $assoc;
1808:             }
1809:             return $query;
1810:         }
1811: 
1812:         return $assoc;
1813:     }
1814: 
1815: /**
1816:  * Generates and executes an SQL UPDATE statement for given model, fields, and values.
1817:  * For databases that do not support aliases in UPDATE queries.
1818:  *
1819:  * @param Model $model
1820:  * @param array $fields
1821:  * @param array $values
1822:  * @param mixed $conditions
1823:  * @return boolean Success
1824:  */
1825:     public function update(Model $model, $fields = array(), $values = null, $conditions = null) {
1826:         if ($values == null) {
1827:             $combined = $fields;
1828:         } else {
1829:             $combined = array_combine($fields, $values);
1830:         }
1831: 
1832:         $fields = implode(', ', $this->_prepareUpdateFields($model, $combined, empty($conditions)));
1833: 
1834:         $alias = $joins = null;
1835:         $table = $this->fullTableName($model);
1836:         $conditions = $this->_matchRecords($model, $conditions);
1837: 
1838:         if ($conditions === false) {
1839:             return false;
1840:         }
1841:         $query = compact('table', 'alias', 'joins', 'fields', 'conditions');
1842: 
1843:         if (!$this->execute($this->renderStatement('update', $query))) {
1844:             $model->onError();
1845:             return false;
1846:         }
1847:         return true;
1848:     }
1849: 
1850: /**
1851:  * Quotes and prepares fields and values for an SQL UPDATE statement
1852:  *
1853:  * @param Model $model
1854:  * @param array $fields
1855:  * @param boolean $quoteValues If values should be quoted, or treated as SQL snippets
1856:  * @param boolean $alias Include the model alias in the field name
1857:  * @return array Fields and values, quoted and prepared
1858:  */
1859:     protected function _prepareUpdateFields(Model $model, $fields, $quoteValues = true, $alias = false) {
1860:         $quotedAlias = $this->startQuote . $model->alias . $this->endQuote;
1861: 
1862:         $updates = array();
1863:         foreach ($fields as $field => $value) {
1864:             if ($alias && strpos($field, '.') === false) {
1865:                 $quoted = $model->escapeField($field);
1866:             } elseif (!$alias && strpos($field, '.') !== false) {
1867:                 $quoted = $this->name(str_replace($quotedAlias . '.', '', str_replace(
1868:                     $model->alias . '.', '', $field
1869:                 )));
1870:             } else {
1871:                 $quoted = $this->name($field);
1872:             }
1873: 
1874:             if ($value === null) {
1875:                 $updates[] = $quoted . ' = NULL';
1876:                 continue;
1877:             }
1878:             $update = $quoted . ' = ';
1879: 
1880:             if ($quoteValues) {
1881:                 $update .= $this->value($value, $model->getColumnType($field));
1882:             } elseif ($model->getColumnType($field) == 'boolean' && (is_int($value) || is_bool($value))) {
1883:                 $update .= $this->boolean($value, true);
1884:             } elseif (!$alias) {
1885:                 $update .= str_replace($quotedAlias . '.', '', str_replace(
1886:                     $model->alias . '.', '', $value
1887:                 ));
1888:             } else {
1889:                 $update .= $value;
1890:             }
1891:             $updates[] = $update;
1892:         }
1893:         return $updates;
1894:     }
1895: 
1896: /**
1897:  * Generates and executes an SQL DELETE statement.
1898:  * For databases that do not support aliases in UPDATE queries.
1899:  *
1900:  * @param Model $model
1901:  * @param mixed $conditions
1902:  * @return boolean Success
1903:  */
1904:     public function delete(Model $model, $conditions = null) {
1905:         $alias = $joins = null;
1906:         $table = $this->fullTableName($model);
1907:         $conditions = $this->_matchRecords($model, $conditions);
1908: 
1909:         if ($conditions === false) {
1910:             return false;
1911:         }
1912: 
1913:         if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) {
1914:             $model->onError();
1915:             return false;
1916:         }
1917:         return true;
1918:     }
1919: 
1920: /**
1921:  * Gets a list of record IDs for the given conditions.  Used for multi-record updates and deletes
1922:  * in databases that do not support aliases in UPDATE/DELETE queries.
1923:  *
1924:  * @param Model $model
1925:  * @param mixed $conditions
1926:  * @return array List of record IDs
1927:  */
1928:     protected function _matchRecords(Model $model, $conditions = null) {
1929:         if ($conditions === true) {
1930:             $conditions = $this->conditions(true);
1931:         } elseif ($conditions === null) {
1932:             $conditions = $this->conditions($this->defaultConditions($model, $conditions, false), true, true, $model);
1933:         } else {
1934:             $noJoin = true;
1935:             foreach ($conditions as $field => $value) {
1936:                 $originalField = $field;
1937:                 if (strpos($field, '.') !== false) {
1938:                     list($alias, $field) = explode('.', $field);
1939:                     $field = ltrim($field, $this->startQuote);
1940:                     $field = rtrim($field, $this->endQuote);
1941:                 }
1942:                 if (!$model->hasField($field)) {
1943:                     $noJoin = false;
1944:                     break;
1945:                 }
1946:                 if ($field !== $originalField) {
1947:                     $conditions[$field] = $value;
1948:                     unset($conditions[$originalField]);
1949:                 }
1950:             }
1951:             if ($noJoin === true) {
1952:                 return $this->conditions($conditions);
1953:             }
1954:             $idList = $model->find('all', array(
1955:                 'fields' => "{$model->alias}.{$model->primaryKey}",
1956:                 'conditions' => $conditions
1957:             ));
1958: 
1959:             if (empty($idList)) {
1960:                 return false;
1961:             }
1962:             $conditions = $this->conditions(array(
1963:                 $model->primaryKey => Hash::extract($idList, "{n}.{$model->alias}.{$model->primaryKey}")
1964:             ));
1965:         }
1966:         return $conditions;
1967:     }
1968: 
1969: /**
1970:  * Returns an array of SQL JOIN fragments from a model's associations
1971:  *
1972:  * @param Model $model
1973:  * @return array
1974:  */
1975:     protected function _getJoins(Model $model) {
1976:         $join = array();
1977:         $joins = array_merge($model->getAssociated('hasOne'), $model->getAssociated('belongsTo'));
1978: 
1979:         foreach ($joins as $assoc) {
1980:             if (isset($model->{$assoc}) && $model->useDbConfig == $model->{$assoc}->useDbConfig && $model->{$assoc}->getDataSource()) {
1981:                 $assocData = $model->getAssociated($assoc);
1982:                 $join[] = $this->buildJoinStatement(array(
1983:                     'table' => $model->{$assoc},
1984:                     'alias' => $assoc,
1985:                     'type' => isset($assocData['type']) ? $assocData['type'] : 'LEFT',
1986:                     'conditions' => trim($this->conditions(
1987:                         $this->_mergeConditions($assocData['conditions'], $this->getConstraint($assocData['association'], $model, $model->{$assoc}, $assoc, $assocData)),
1988:                         true, false, $model
1989:                     ))
1990:                 ));
1991:             }
1992:         }
1993:         return $join;
1994:     }
1995: 
1996: /**
1997:  * Returns an SQL calculation, i.e. COUNT() or MAX()
1998:  *
1999:  * @param Model $model
2000:  * @param string $func Lowercase name of SQL function, i.e. 'count' or 'max'
2001:  * @param array $params Function parameters (any values must be quoted manually)
2002:  * @return string An SQL calculation function
2003:  */
2004:     public function calculate(Model $model, $func, $params = array()) {
2005:         $params = (array)$params;
2006: 
2007:         switch (strtolower($func)) {
2008:             case 'count':
2009:                 if (!isset($params[0])) {
2010:                     $params[0] = '*';
2011:                 }
2012:                 if (!isset($params[1])) {
2013:                     $params[1] = 'count';
2014:                 }
2015:                 if (is_object($model) && $model->isVirtualField($params[0])) {
2016:                     $arg = $this->_quoteFields($model->getVirtualField($params[0]));
2017:                 } else {
2018:                     $arg = $this->name($params[0]);
2019:                 }
2020:                 return 'COUNT(' . $arg . ') AS ' . $this->name($params[1]);
2021:             case 'max':
2022:             case 'min':
2023:                 if (!isset($params[1])) {
2024:                     $params[1] = $params[0];
2025:                 }
2026:                 if (is_object($model) && $model->isVirtualField($params[0])) {
2027:                     $arg = $this->_quoteFields($model->getVirtualField($params[0]));
2028:                 } else {
2029:                     $arg = $this->name($params[0]);
2030:                 }
2031:                 return strtoupper($func) . '(' . $arg . ') AS ' . $this->name($params[1]);
2032:         }
2033:     }
2034: 
2035: /**
2036:  * Deletes all the records in a table and resets the count of the auto-incrementing
2037:  * primary key, where applicable.
2038:  *
2039:  * @param Model|string $table A string or model class representing the table to be truncated
2040:  * @return boolean  SQL TRUNCATE TABLE statement, false if not applicable.
2041:  */
2042:     public function truncate($table) {
2043:         return $this->execute('TRUNCATE TABLE ' . $this->fullTableName($table));
2044:     }
2045: 
2046: /**
2047:  * Check if the server support nested transactions
2048:  *
2049:  * @return boolean
2050:  */
2051:     public function nestedTransactionSupported() {
2052:         return false;
2053:     }
2054: 
2055: /**
2056:  * Begin a transaction
2057:  *
2058:  * @return boolean True on success, false on fail
2059:  * (i.e. if the database/model does not support transactions,
2060:  * or a transaction has not started).
2061:  */
2062:     public function begin() {
2063:         if ($this->_transactionStarted) {
2064:             if ($this->nestedTransactionSupported()) {
2065:                 return $this->_beginNested();
2066:             }
2067:             $this->_transactionNesting++;
2068:             return $this->_transactionStarted;
2069:         }
2070: 
2071:         $this->_transactionNesting = 0;
2072:         if ($this->fullDebug) {
2073:             $this->logQuery('BEGIN');
2074:         }
2075:         return $this->_transactionStarted = $this->_connection->beginTransaction();
2076:     }
2077: 
2078: /**
2079:  * Begin a nested transaction
2080:  *
2081:  * @return boolean
2082:  */
2083:     protected function _beginNested() {
2084:         $query = 'SAVEPOINT LEVEL' . ++$this->_transactionNesting;
2085:         if ($this->fullDebug) {
2086:             $this->logQuery($query);
2087:         }
2088:         $this->_connection->exec($query);
2089:         return true;
2090:     }
2091: 
2092: /**
2093:  * Commit a transaction
2094:  *
2095:  * @return boolean True on success, false on fail
2096:  * (i.e. if the database/model does not support transactions,
2097:  * or a transaction has not started).
2098:  */
2099:     public function commit() {
2100:         if (!$this->_transactionStarted) {
2101:             return false;
2102:         }
2103: 
2104:         if ($this->_transactionNesting === 0) {
2105:             if ($this->fullDebug) {
2106:                 $this->logQuery('COMMIT');
2107:             }
2108:             $this->_transactionStarted = false;
2109:             return $this->_connection->commit();
2110:         }
2111: 
2112:         if ($this->nestedTransactionSupported()) {
2113:             return $this->_commitNested();
2114:         }
2115: 
2116:         $this->_transactionNesting--;
2117:         return true;
2118:     }
2119: 
2120: /**
2121:  * Commit a nested transaction
2122:  *
2123:  * @return boolean
2124:  */
2125:     protected function _commitNested() {
2126:         $query = 'RELEASE SAVEPOINT LEVEL' . $this->_transactionNesting--;
2127:         if ($this->fullDebug) {
2128:             $this->logQuery($query);
2129:         }
2130:         $this->_connection->exec($query);
2131:         return true;
2132:     }
2133: 
2134: /**
2135:  * Rollback a transaction
2136:  *
2137:  * @return boolean True on success, false on fail
2138:  * (i.e. if the database/model does not support transactions,
2139:  * or a transaction has not started).
2140:  */
2141:     public function rollback() {
2142:         if (!$this->_transactionStarted) {
2143:             return false;
2144:         }
2145: 
2146:         if ($this->_transactionNesting === 0) {
2147:             if ($this->fullDebug) {
2148:                 $this->logQuery('ROLLBACK');
2149:             }
2150:             $this->_transactionStarted = false;
2151:             return $this->_connection->rollBack();
2152:         }
2153: 
2154:         if ($this->nestedTransactionSupported()) {
2155:             return $this->_rollbackNested();
2156:         }
2157: 
2158:         $this->_transactionNesting--;
2159:         return true;
2160:     }
2161: 
2162: /**
2163:  * Rollback a nested transaction
2164:  *
2165:  * @return boolean
2166:  */
2167:     protected function _rollbackNested() {
2168:         $query = 'ROLLBACK TO SAVEPOINT LEVEL' . $this->_transactionNesting--;
2169:         if ($this->fullDebug) {
2170:             $this->logQuery($query);
2171:         }
2172:         $this->_connection->exec($query);
2173:         return true;
2174:     }
2175: 
2176: /**
2177:  * Returns the ID generated from the previous INSERT operation.
2178:  *
2179:  * @param mixed $source
2180:  * @return mixed
2181:  */
2182:     public function lastInsertId($source = null) {
2183:         return $this->_connection->lastInsertId();
2184:     }
2185: 
2186: /**
2187:  * Creates a default set of conditions from the model if $conditions is null/empty.
2188:  * If conditions are supplied then they will be returned.  If a model doesn't exist and no conditions
2189:  * were provided either null or false will be returned based on what was input.
2190:  *
2191:  * @param Model $model
2192:  * @param string|array|boolean $conditions Array of conditions, conditions string, null or false. If an array of conditions,
2193:  *   or string conditions those conditions will be returned.  With other values the model's existence will be checked.
2194:  *   If the model doesn't exist a null or false will be returned depending on the input value.
2195:  * @param boolean $useAlias Use model aliases rather than table names when generating conditions
2196:  * @return mixed Either null, false, $conditions or an array of default conditions to use.
2197:  * @see DboSource::update()
2198:  * @see DboSource::conditions()
2199:  */
2200:     public function defaultConditions(Model $model, $conditions, $useAlias = true) {
2201:         if (!empty($conditions)) {
2202:             return $conditions;
2203:         }
2204:         $exists = $model->exists();
2205:         if (!$exists && $conditions !== null) {
2206:             return false;
2207:         } elseif (!$exists) {
2208:             return null;
2209:         }
2210:         $alias = $model->alias;
2211: 
2212:         if (!$useAlias) {
2213:             $alias = $this->fullTableName($model, false);
2214:         }
2215:         return array("{$alias}.{$model->primaryKey}" => $model->getID());
2216:     }
2217: 
2218: /**
2219:  * Returns a key formatted like a string Model.fieldname(i.e. Post.title, or Country.name)
2220:  *
2221:  * @param Model $model
2222:  * @param string $key
2223:  * @param string $assoc
2224:  * @return string
2225:  */
2226:     public function resolveKey(Model $model, $key, $assoc = null) {
2227:         if (strpos('.', $key) !== false) {
2228:             return $this->name($model->alias) . '.' . $this->name($key);
2229:         }
2230:         return $key;
2231:     }
2232: 
2233: /**
2234:  * Private helper method to remove query metadata in given data array.
2235:  *
2236:  * @param array $data
2237:  * @return array
2238:  */
2239:     protected function _scrubQueryData($data) {
2240:         static $base = null;
2241:         if ($base === null) {
2242:             $base = array_fill_keys(array('conditions', 'fields', 'joins', 'order', 'limit', 'offset', 'group'), array());
2243:             $base['callbacks'] = null;
2244:         }
2245:         return (array)$data + $base;
2246:     }
2247: 
2248: /**
2249:  * Converts model virtual fields into sql expressions to be fetched later
2250:  *
2251:  * @param Model $model
2252:  * @param string $alias Alias table name
2253:  * @param array $fields virtual fields to be used on query
2254:  * @return array
2255:  */
2256:     protected function _constructVirtualFields(Model $model, $alias, $fields) {
2257:         $virtual = array();
2258:         foreach ($fields as $field) {
2259:             $virtualField = $this->name($alias . $this->virtualFieldSeparator . $field);
2260:             $expression = $this->_quoteFields($model->getVirtualField($field));
2261:             $virtual[] = '(' . $expression . ") {$this->alias} {$virtualField}";
2262:         }
2263:         return $virtual;
2264:     }
2265: 
2266: /**
2267:  * Generates the fields list of an SQL query.
2268:  *
2269:  * @param Model $model
2270:  * @param string $alias Alias table name
2271:  * @param mixed $fields
2272:  * @param boolean $quote If false, returns fields array unquoted
2273:  * @return array
2274:  */
2275:     public function fields(Model $model, $alias = null, $fields = array(), $quote = true) {
2276:         if (empty($alias)) {
2277:             $alias = $model->alias;
2278:         }
2279:         $virtualFields = $model->getVirtualField();
2280:         $cacheKey = array(
2281:             $alias,
2282:             get_class($model),
2283:             $model->alias,
2284:             $virtualFields,
2285:             $fields,
2286:             $quote,
2287:             ConnectionManager::getSourceName($this),
2288:             $model->table
2289:         );
2290:         $cacheKey = md5(serialize($cacheKey));
2291:         if ($return = $this->cacheMethod(__FUNCTION__, $cacheKey)) {
2292:             return $return;
2293:         }
2294:         $allFields = empty($fields);
2295:         if ($allFields) {
2296:             $fields = array_keys($model->schema());
2297:         } elseif (!is_array($fields)) {
2298:             $fields = String::tokenize($fields);
2299:         }
2300:         $fields = array_values(array_filter($fields));
2301:         $allFields = $allFields || in_array('*', $fields) || in_array($model->alias . '.*', $fields);
2302: 
2303:         $virtual = array();
2304:         if (!empty($virtualFields)) {
2305:             $virtualKeys = array_keys($virtualFields);
2306:             foreach ($virtualKeys as $field) {
2307:                 $virtualKeys[] = $model->alias . '.' . $field;
2308:             }
2309:             $virtual = ($allFields) ? $virtualKeys : array_intersect($virtualKeys, $fields);
2310:             foreach ($virtual as $i => $field) {
2311:                 if (strpos($field, '.') !== false) {
2312:                     $virtual[$i] = str_replace($model->alias . '.', '', $field);
2313:                 }
2314:                 $fields = array_diff($fields, array($field));
2315:             }
2316:             $fields = array_values($fields);
2317:         }
2318:         if (!$quote) {
2319:             if (!empty($virtual)) {
2320:                 $fields = array_merge($fields, $this->_constructVirtualFields($model, $alias, $virtual));
2321:             }
2322:             return $fields;
2323:         }
2324:         $count = count($fields);
2325: 
2326:         if ($count >= 1 && !in_array($fields[0], array('*', 'COUNT(*)'))) {
2327:             for ($i = 0; $i < $count; $i++) {
2328:                 if (is_string($fields[$i]) && in_array($fields[$i], $virtual)) {
2329:                     unset($fields[$i]);
2330:                     continue;
2331:                 }
2332:                 if (is_object($fields[$i]) && isset($fields[$i]->type) && $fields[$i]->type === 'expression') {
2333:                     $fields[$i] = $fields[$i]->value;
2334:                 } elseif (preg_match('/^\(.*\)\s' . $this->alias . '.*/i', $fields[$i])) {
2335:                     continue;
2336:                 } elseif (!preg_match('/^.+\\(.*\\)/', $fields[$i])) {
2337:                     $prepend = '';
2338: 
2339:                     if (strpos($fields[$i], 'DISTINCT') !== false) {
2340:                         $prepend = 'DISTINCT ';
2341:                         $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i]));
2342:                     }
2343:                     $dot = strpos($fields[$i], '.');
2344: 
2345:                     if ($dot === false) {
2346:                         $prefix = !(
2347:                             strpos($fields[$i], ' ') !== false ||
2348:                             strpos($fields[$i], '(') !== false
2349:                         );
2350:                         $fields[$i] = $this->name(($prefix ? $alias . '.' : '') . $fields[$i]);
2351:                     } else {
2352:                         if (strpos($fields[$i], ',') === false) {
2353:                             $build = explode('.', $fields[$i]);
2354:                             if (!Hash::numeric($build)) {
2355:                                 $fields[$i] = $this->name(implode('.', $build));
2356:                             }
2357:                         }
2358:                     }
2359:                     $fields[$i] = $prepend . $fields[$i];
2360:                 } elseif (preg_match('/\(([\.\w]+)\)/', $fields[$i], $field)) {
2361:                     if (isset($field[1])) {
2362:                         if (strpos($field[1], '.') === false) {
2363:                             $field[1] = $this->name($alias . '.' . $field[1]);
2364:                         } else {
2365:                             $field[0] = explode('.', $field[1]);
2366:                             if (!Hash::numeric($field[0])) {
2367:                                 $field[0] = implode('.', array_map(array(&$this, 'name'), $field[0]));
2368:                                 $fields[$i] = preg_replace('/\(' . $field[1] . '\)/', '(' . $field[0] . ')', $fields[$i], 1);
2369:                             }
2370:                         }
2371:                     }
2372:                 }
2373:             }
2374:         }
2375:         if (!empty($virtual)) {
2376:             $fields = array_merge($fields, $this->_constructVirtualFields($model, $alias, $virtual));
2377:         }
2378:         return $this->cacheMethod(__FUNCTION__, $cacheKey, array_unique($fields));
2379:     }
2380: 
2381: /**
2382:  * Creates a WHERE clause by parsing given conditions data.  If an array or string
2383:  * conditions are provided those conditions will be parsed and quoted.  If a boolean
2384:  * is given it will be integer cast as condition.  Null will return 1 = 1.
2385:  *
2386:  * Results of this method are stored in a memory cache.  This improves performance, but
2387:  * because the method uses a hashing algorithm it can have collisions.
2388:  * Setting DboSource::$cacheMethods to false will disable the memory cache.
2389:  *
2390:  * @param mixed $conditions Array or string of conditions, or any value.
2391:  * @param boolean $quoteValues If true, values should be quoted
2392:  * @param boolean $where If true, "WHERE " will be prepended to the return value
2393:  * @param Model $model A reference to the Model instance making the query
2394:  * @return string SQL fragment
2395:  */
2396:     public function conditions($conditions, $quoteValues = true, $where = true, $model = null) {
2397:         $clause = $out = '';
2398: 
2399:         if ($where) {
2400:             $clause = ' WHERE ';
2401:         }
2402: 
2403:         if (is_array($conditions) && !empty($conditions)) {
2404:             $out = $this->conditionKeysToString($conditions, $quoteValues, $model);
2405: 
2406:             if (empty($out)) {
2407:                 return $clause . ' 1 = 1';
2408:             }
2409:             return $clause . implode(' AND ', $out);
2410:         }
2411:         if (is_bool($conditions)) {
2412:             return $clause . (int)$conditions . ' = 1';
2413:         }
2414: 
2415:         if (empty($conditions) || trim($conditions) === '') {
2416:             return $clause . '1 = 1';
2417:         }
2418:         $clauses = '/^WHERE\\x20|^GROUP\\x20BY\\x20|^HAVING\\x20|^ORDER\\x20BY\\x20/i';
2419: 
2420:         if (preg_match($clauses, $conditions)) {
2421:             $clause = '';
2422:         }
2423:         $conditions = $this->_quoteFields($conditions);
2424:         return $clause . $conditions;
2425:     }
2426: 
2427: /**
2428:  * Creates a WHERE clause by parsing given conditions array.  Used by DboSource::conditions().
2429:  *
2430:  * @param array $conditions Array or string of conditions
2431:  * @param boolean $quoteValues If true, values should be quoted
2432:  * @param Model $model A reference to the Model instance making the query
2433:  * @return string SQL fragment
2434:  */
2435:     public function conditionKeysToString($conditions, $quoteValues = true, $model = null) {
2436:         $out = array();
2437:         $data = $columnType = null;
2438:         $bool = array('and', 'or', 'not', 'and not', 'or not', 'xor', '||', '&&');
2439: 
2440:         foreach ($conditions as $key => $value) {
2441:             $join = ' AND ';
2442:             $not = null;
2443: 
2444:             if (is_array($value)) {
2445:                 $valueInsert = (
2446:                     !empty($value) &&
2447:                     (substr_count($key, '?') === count($value) || substr_count($key, ':') === count($value))
2448:                 );
2449:             }
2450: 
2451:             if (is_numeric($key) && empty($value)) {
2452:                 continue;
2453:             } elseif (is_numeric($key) && is_string($value)) {
2454:                 $out[] = $not . $this->_quoteFields($value);
2455:             } elseif ((is_numeric($key) && is_array($value)) || in_array(strtolower(trim($key)), $bool)) {
2456:                 if (in_array(strtolower(trim($key)), $bool)) {
2457:                     $join = ' ' . strtoupper($key) . ' ';
2458:                 } else {
2459:                     $key = $join;
2460:                 }
2461:                 $value = $this->conditionKeysToString($value, $quoteValues, $model);
2462: 
2463:                 if (strpos($join, 'NOT') !== false) {
2464:                     if (strtoupper(trim($key)) === 'NOT') {
2465:                         $key = 'AND ' . trim($key);
2466:                     }
2467:                     $not = 'NOT ';
2468:                 }
2469: 
2470:                 if (empty($value)) {
2471:                     continue;
2472:                 }
2473: 
2474:                 if (empty($value[1])) {
2475:                     if ($not) {
2476:                         $out[] = $not . '(' . $value[0] . ')';
2477:                     } else {
2478:                         $out[] = $value[0];
2479:                     }
2480:                 } else {
2481:                     $out[] = '(' . $not . '(' . implode(') ' . strtoupper($key) . ' (', $value) . '))';
2482:                 }
2483:             } else {
2484:                 if (is_object($value) && isset($value->type)) {
2485:                     if ($value->type === 'identifier') {
2486:                         $data .= $this->name($key) . ' = ' . $this->name($value->value);
2487:                     } elseif ($value->type === 'expression') {
2488:                         if (is_numeric($key)) {
2489:                             $data .= $value->value;
2490:                         } else {
2491:                             $data .= $this->name($key) . ' = ' . $value->value;
2492:                         }
2493:                     }
2494:                 } elseif (is_array($value) && !empty($value) && !$valueInsert) {
2495:                     $keys = array_keys($value);
2496:                     if ($keys === array_values($keys)) {
2497:                         $count = count($value);
2498:                         if ($count === 1 && !preg_match("/\s+NOT$/", $key)) {
2499:                             $data = $this->_quoteFields($key) . ' = (';
2500:                             if ($quoteValues) {
2501:                                 if (is_object($model)) {
2502:                                     $columnType = $model->getColumnType($key);
2503:                                 }
2504:                                 $data .= implode(', ', $this->value($value, $columnType));
2505:                             }
2506:                             $data .= ')';
2507:                         } else {
2508:                             $data = $this->_parseKey($model, $key, $value);
2509:                         }
2510:                     } else {
2511:                         $ret = $this->conditionKeysToString($value, $quoteValues, $model);
2512:                         if (count($ret) > 1) {
2513:                             $data = '(' . implode(') AND (', $ret) . ')';
2514:                         } elseif (isset($ret[0])) {
2515:                             $data = $ret[0];
2516:                         }
2517:                     }
2518:                 } elseif (is_numeric($key) && !empty($value)) {
2519:                     $data = $this->_quoteFields($value);
2520:                 } else {
2521:                     $data = $this->_parseKey($model, trim($key), $value);
2522:                 }
2523: 
2524:                 if ($data != null) {
2525:                     $out[] = $data;
2526:                     $data = null;
2527:                 }
2528:             }
2529:         }
2530:         return $out;
2531:     }
2532: 
2533: /**
2534:  * Extracts a Model.field identifier and an SQL condition operator from a string, formats
2535:  * and inserts values, and composes them into an SQL snippet.
2536:  *
2537:  * @param Model $model Model object initiating the query
2538:  * @param string $key An SQL key snippet containing a field and optional SQL operator
2539:  * @param mixed $value The value(s) to be inserted in the string
2540:  * @return string
2541:  */
2542:     protected function _parseKey($model, $key, $value) {
2543:         $operatorMatch = '/^(((' . implode(')|(', $this->_sqlOps);
2544:         $operatorMatch .= ')\\x20?)|<[>=]?(?![^>]+>)\\x20?|[>=!]{1,3}(?!<)\\x20?)/is';
2545:         $bound = (strpos($key, '?') !== false || (is_array($value) && strpos($key, ':') !== false));
2546: 
2547:         if (strpos($key, ' ') === false) {
2548:             $operator = '=';
2549:         } else {
2550:             list($key, $operator) = explode(' ', trim($key), 2);
2551: 
2552:             if (!preg_match($operatorMatch, trim($operator)) && strpos($operator, ' ') !== false) {
2553:                 $key = $key . ' ' . $operator;
2554:                 $split = strrpos($key, ' ');
2555:                 $operator = substr($key, $split);
2556:                 $key = substr($key, 0, $split);
2557:             }
2558:         }
2559: 
2560:         $virtual = false;
2561:         if (is_object($model) && $model->isVirtualField($key)) {
2562:             $key = $this->_quoteFields($model->getVirtualField($key));
2563:             $virtual = true;
2564:         }
2565: 
2566:         $type = is_object($model) ? $model->getColumnType($key) : null;
2567:         $null = $value === null || (is_array($value) && empty($value));
2568: 
2569:         if (strtolower($operator) === 'not') {
2570:             $data = $this->conditionKeysToString(
2571:                 array($operator => array($key => $value)), true, $model
2572:             );
2573:             return $data[0];
2574:         }
2575: 
2576:         $value = $this->value($value, $type);
2577: 
2578:         if (!$virtual && $key !== '?') {
2579:             $isKey = (
2580:                 strpos($key, '(') !== false ||
2581:                 strpos($key, ')') !== false ||
2582:                 strpos($key, '|') !== false
2583:             );
2584:             $key = $isKey ? $this->_quoteFields($key) : $this->name($key);
2585:         }
2586: 
2587:         if ($bound) {
2588:             return String::insert($key . ' ' . trim($operator), $value);
2589:         }
2590: 
2591:         if (!preg_match($operatorMatch, trim($operator))) {
2592:             $operator .= ' =';
2593:         }
2594:         $operator = trim($operator);
2595: 
2596:         if (is_array($value)) {
2597:             $value = implode(', ', $value);
2598: 
2599:             switch ($operator) {
2600:                 case '=':
2601:                     $operator = 'IN';
2602:                 break;
2603:                 case '!=':
2604:                 case '<>':
2605:                     $operator = 'NOT IN';
2606:                 break;
2607:             }
2608:             $value = "({$value})";
2609:         } elseif ($null || $value === 'NULL') {
2610:             switch ($operator) {
2611:                 case '=':
2612:                     $operator = 'IS';
2613:                 break;
2614:                 case '!=':
2615:                 case '<>':
2616:                     $operator = 'IS NOT';
2617:                 break;
2618:             }
2619:         }
2620:         if ($virtual) {
2621:             return "({$key}) {$operator} {$value}";
2622:         }
2623:         return "{$key} {$operator} {$value}";
2624:     }
2625: 
2626: /**
2627:  * Quotes Model.fields
2628:  *
2629:  * @param string $conditions
2630:  * @return string or false if no match
2631:  */
2632:     protected function _quoteFields($conditions) {
2633:         $start = $end = null;
2634:         $original = $conditions;
2635: 
2636:         if (!empty($this->startQuote)) {
2637:             $start = preg_quote($this->startQuote);
2638:         }
2639:         if (!empty($this->endQuote)) {
2640:             $end = preg_quote($this->endQuote);
2641:         }
2642:         $conditions = str_replace(array($start, $end), '', $conditions);
2643:         $conditions = preg_replace_callback(
2644:             '/(?:[\'\"][^\'\"\\\]*(?:\\\.[^\'\"\\\]*)*[\'\"])|([a-z0-9_][a-z0-9\\-_]*\\.[a-z0-9_][a-z0-9_\\-]*)/i',
2645:             array(&$this, '_quoteMatchedField'),
2646:             $conditions
2647:         );
2648:         if ($conditions !== null) {
2649:             return $conditions;
2650:         }
2651:         return $original;
2652:     }
2653: 
2654: /**
2655:  * Auxiliary function to quote matches `Model.fields` from a preg_replace_callback call
2656:  *
2657:  * @param string $match matched string
2658:  * @return string quoted string
2659:  */
2660:     protected function _quoteMatchedField($match) {
2661:         if (is_numeric($match[0])) {
2662:             return $match[0];
2663:         }
2664:         return $this->name($match[0]);
2665:     }
2666: 
2667: /**
2668:  * Returns a limit statement in the correct format for the particular database.
2669:  *
2670:  * @param integer $limit Limit of results returned
2671:  * @param integer $offset Offset from which to start results
2672:  * @return string SQL limit/offset statement
2673:  */
2674:     public function limit($limit, $offset = null) {
2675:         if ($limit) {
2676:             $rt = '';
2677:             if (!strpos(strtolower($limit), 'limit')) {
2678:                 $rt = ' LIMIT';
2679:             }
2680: 
2681:             if ($offset) {
2682:                 $rt .= ' ' . $offset . ',';
2683:             }
2684: 
2685:             $rt .= ' ' . $limit;
2686:             return $rt;
2687:         }
2688:         return null;
2689:     }
2690: 
2691: /**
2692:  * Returns an ORDER BY clause as a string.
2693:  *
2694:  * @param array|string $keys Field reference, as a key (i.e. Post.title)
2695:  * @param string $direction Direction (ASC or DESC)
2696:  * @param Model $model model reference (used to look for virtual field)
2697:  * @return string ORDER BY clause
2698:  */
2699:     public function order($keys, $direction = 'ASC', $model = null) {
2700:         if (!is_array($keys)) {
2701:             $keys = array($keys);
2702:         }
2703:         $keys = array_filter($keys);
2704:         $result = array();
2705:         while (!empty($keys)) {
2706:             list($key, $dir) = each($keys);
2707:             array_shift($keys);
2708: 
2709:             if (is_numeric($key)) {
2710:                 $key = $dir;
2711:                 $dir = $direction;
2712:             }
2713: 
2714:             if (is_string($key) && strpos($key, ',') !== false && !preg_match('/\(.+\,.+\)/', $key)) {
2715:                 $key = array_map('trim', explode(',', $key));
2716:             }
2717:             if (is_array($key)) {
2718:                 //Flatten the array
2719:                 $key = array_reverse($key, true);
2720:                 foreach ($key as $k => $v) {
2721:                     if (is_numeric($k)) {
2722:                         array_unshift($keys, $v);
2723:                     } else {
2724:                         $keys = array($k => $v) + $keys;
2725:                     }
2726:                 }
2727:                 continue;
2728:             } elseif (is_object($key) && isset($key->type) && $key->type === 'expression') {
2729:                 $result[] = $key->value;
2730:                 continue;
2731:             }
2732: 
2733:             if (preg_match('/\\x20(ASC|DESC).*/i', $key, $_dir)) {
2734:                 $dir = $_dir[0];
2735:                 $key = preg_replace('/\\x20(ASC|DESC).*/i', '', $key);
2736:             }
2737: 
2738:             $key = trim($key);
2739: 
2740:             if (is_object($model) && $model->isVirtualField($key)) {
2741:                 $key = '(' . $this->_quoteFields($model->getVirtualField($key)) . ')';
2742:             }
2743:             list($alias, $field) = pluginSplit($key);
2744:             if (is_object($model) && $alias !== $model->alias && is_object($model->{$alias}) && $model->{$alias}->isVirtualField($key)) {
2745:                 $key = '(' . $this->_quoteFields($model->{$alias}->getVirtualField($key)) . ')';
2746:             }
2747: 
2748:             if (strpos($key, '.')) {
2749:                 $key = preg_replace_callback('/([a-zA-Z0-9_-]{1,})\\.([a-zA-Z0-9_-]{1,})/', array(&$this, '_quoteMatchedField'), $key);
2750:             }
2751:             if (!preg_match('/\s/', $key) && strpos($key, '.') === false) {
2752:                 $key = $this->name($key);
2753:             }
2754:             $key .= ' ' . trim($dir);
2755:             $result[] = $key;
2756:         }
2757:         if (!empty($result)) {
2758:             return ' ORDER BY ' . implode(', ', $result);
2759:         }
2760:         return '';
2761:     }
2762: 
2763: /**
2764:  * Create a GROUP BY SQL clause
2765:  *
2766:  * @param string $group Group By Condition
2767:  * @param Model $model
2768:  * @return string string condition or null
2769:  */
2770:     public function group($group, $model = null) {
2771:         if ($group) {
2772:             if (!is_array($group)) {
2773:                 $group = array($group);
2774:             }
2775:             foreach ($group as $index => $key) {
2776:                 if (is_object($model) && $model->isVirtualField($key)) {
2777:                     $group[$index] = '(' . $model->getVirtualField($key) . ')';
2778:                 }
2779:             }
2780:             $group = implode(', ', $group);
2781:             return ' GROUP BY ' . $this->_quoteFields($group);
2782:         }
2783:         return null;
2784:     }
2785: 
2786: /**
2787:  * Disconnects database, kills the connection and says the connection is closed.
2788:  *
2789:  * @return void
2790:  */
2791:     public function close() {
2792:         $this->disconnect();
2793:     }
2794: 
2795: /**
2796:  * Checks if the specified table contains any record matching specified SQL
2797:  *
2798:  * @param Model $Model Model to search
2799:  * @param string $sql SQL WHERE clause (condition only, not the "WHERE" part)
2800:  * @return boolean True if the table has a matching record, else false
2801:  */
2802:     public function hasAny(Model $Model, $sql) {
2803:         $sql = $this->conditions($sql);
2804:         $table = $this->fullTableName($Model);
2805:         $alias = $this->alias . $this->name($Model->alias);
2806:         $where = $sql ? "{$sql}" : ' WHERE 1 = 1';
2807:         $id = $Model->escapeField();
2808: 
2809:         $out = $this->fetchRow("SELECT COUNT({$id}) {$this->alias}count FROM {$table} {$alias}{$where}");
2810: 
2811:         if (is_array($out)) {
2812:             return $out[0]['count'];
2813:         }
2814:         return false;
2815:     }
2816: 
2817: /**
2818:  * Gets the length of a database-native column description, or null if no length
2819:  *
2820:  * @param string $real Real database-layer column type (i.e. "varchar(255)")
2821:  * @return mixed An integer or string representing the length of the column, or null for unknown length.
2822:  */
2823:     public function length($real) {
2824:         if (!preg_match_all('/([\w\s]+)(?:\((\d+)(?:,(\d+))?\))?(\sunsigned)?(\szerofill)?/', $real, $result)) {
2825:             $col = str_replace(array(')', 'unsigned'), '', $real);
2826:             $limit = null;
2827: 
2828:             if (strpos($col, '(') !== false) {
2829:                 list($col, $limit) = explode('(', $col);
2830:             }
2831:             if ($limit !== null) {
2832:                 return intval($limit);
2833:             }
2834:             return null;
2835:         }
2836: 
2837:         $types = array(
2838:             'int' => 1, 'tinyint' => 1, 'smallint' => 1, 'mediumint' => 1, 'integer' => 1, 'bigint' => 1
2839:         );
2840: 
2841:         list($real, $type, $length, $offset, $sign, $zerofill) = $result;
2842:         $typeArr = $type;
2843:         $type = $type[0];
2844:         $length = $length[0];
2845:         $offset = $offset[0];
2846: 
2847:         $isFloat = in_array($type, array('dec', 'decimal', 'float', 'numeric', 'double'));
2848:         if ($isFloat && $offset) {
2849:             return $length . ',' . $offset;
2850:         }
2851: 
2852:         if (($real[0] == $type) && (count($real) === 1)) {
2853:             return null;
2854:         }
2855: 
2856:         if (isset($types[$type])) {
2857:             $length += $types[$type];
2858:             if (!empty($sign)) {
2859:                 $length--;
2860:             }
2861:         } elseif (in_array($type, array('enum', 'set'))) {
2862:             $length = 0;
2863:             foreach ($typeArr as $key => $enumValue) {
2864:                 if ($key === 0) {
2865:                     continue;
2866:                 }
2867:                 $tmpLength = strlen($enumValue);
2868:                 if ($tmpLength > $length) {
2869:                     $length = $tmpLength;
2870:                 }
2871:             }
2872:         }
2873:         return intval($length);
2874:     }
2875: 
2876: /**
2877:  * Translates between PHP boolean values and Database (faked) boolean values
2878:  *
2879:  * @param mixed $data Value to be translated
2880:  * @param boolean $quote
2881:  * @return string|boolean Converted boolean value
2882:  */
2883:     public function boolean($data, $quote = false) {
2884:         if ($quote) {
2885:             return !empty($data) ? '1' : '0';
2886:         }
2887:         return !empty($data);
2888:     }
2889: 
2890: /**
2891:  * Inserts multiple values into a table
2892:  *
2893:  * @param string $table The table being inserted into.
2894:  * @param array $fields The array of field/column names being inserted.
2895:  * @param array $values The array of values to insert.  The values should
2896:  *   be an array of rows.  Each row should have values keyed by the column name.
2897:  *   Each row must have the values in the same order as $fields.
2898:  * @return boolean
2899:  */
2900:     public function insertMulti($table, $fields, $values) {
2901:         $table = $this->fullTableName($table);
2902:         $holder = implode(',', array_fill(0, count($fields), '?'));
2903:         $fields = implode(', ', array_map(array(&$this, 'name'), $fields));
2904: 
2905:         $pdoMap = array(
2906:             'integer' => PDO::PARAM_INT,
2907:             'float' => PDO::PARAM_STR,
2908:             'boolean' => PDO::PARAM_BOOL,
2909:             'string' => PDO::PARAM_STR,
2910:             'text' => PDO::PARAM_STR
2911:         );
2912:         $columnMap = array();
2913: 
2914:         $sql = "INSERT INTO {$table} ({$fields}) VALUES ({$holder})";
2915:         $statement = $this->_connection->prepare($sql);
2916:         $this->begin();
2917: 
2918:         foreach ($values[key($values)] as $key => $val) {
2919:             $type = $this->introspectType($val);
2920:             $columnMap[$key] = $pdoMap[$type];
2921:         }
2922: 
2923:         foreach ($values as $value) {
2924:             $i = 1;
2925:             foreach ($value as $col => $val) {
2926:                 $statement->bindValue($i, $val, $columnMap[$col]);
2927:                 $i += 1;
2928:             }
2929:             $statement->execute();
2930:             $statement->closeCursor();
2931: 
2932:             if ($this->fullDebug) {
2933:                 $this->logQuery($sql, $value);
2934:             }
2935:         }
2936:         return $this->commit();
2937:     }
2938: 
2939: /**
2940:  * Returns an array of the indexes in given datasource name.
2941:  *
2942:  * @param string $model Name of model to inspect
2943:  * @return array Fields in table. Keys are column and unique
2944:  */
2945:     public function index($model) {
2946:         return false;
2947:     }
2948: 
2949: /**
2950:  * Generate a database-native schema for the given Schema object
2951:  *
2952:  * @param Model $schema An instance of a subclass of CakeSchema
2953:  * @param string $tableName Optional.  If specified only the table name given will be generated.
2954:  *   Otherwise, all tables defined in the schema are generated.
2955:  * @return string
2956:  */
2957:     public function createSchema($schema, $tableName = null) {
2958:         if (!is_a($schema, 'CakeSchema')) {
2959:             trigger_error(__d('cake_dev', 'Invalid schema object'), E_USER_WARNING);
2960:             return null;
2961:         }
2962:         $out = '';
2963: 
2964:         foreach ($schema->tables as $curTable => $columns) {
2965:             if (!$tableName || $tableName == $curTable) {
2966:                 $cols = $colList = $indexes = $tableParameters = array();
2967:                 $primary = null;
2968:                 $table = $this->fullTableName($curTable);
2969: 
2970:                 $primaryCount = 0;
2971:                 foreach ($columns as $col) {
2972:                     if (isset($col['key']) && $col['key'] === 'primary') {
2973:                         $primaryCount++;
2974:                     }
2975:                 }
2976: 
2977:                 foreach ($columns as $name => $col) {
2978:                     if (is_string($col)) {
2979:                         $col = array('type' => $col);
2980:                     }
2981:                     $isPrimary = isset($col['key']) && $col['key'] === 'primary';
2982:                     // Multi-column primary keys are not supported.
2983:                     if ($isPrimary && $primaryCount > 1) {
2984:                         unset($col['key']);
2985:                         $isPrimary = false;
2986:                     }
2987:                     if ($isPrimary) {
2988:                         $primary = $name;
2989:                     }
2990:                     if ($name !== 'indexes' && $name !== 'tableParameters') {
2991:                         $col['name'] = $name;
2992:                         if (!isset($col['type'])) {
2993:                             $col['type'] = 'string';
2994:                         }
2995:                         $cols[] = $this->buildColumn($col);
2996:                     } elseif ($name === 'indexes') {
2997:                         $indexes = array_merge($indexes, $this->buildIndex($col, $table));
2998:                     } elseif ($name === 'tableParameters') {
2999:                         $tableParameters = array_merge($tableParameters, $this->buildTableParameters($col, $table));
3000:                     }
3001:                 }
3002:                 if (!isset($columns['indexes']['PRIMARY']) && !empty($primary)) {
3003:                     $col = array('PRIMARY' => array('column' => $primary, 'unique' => 1));
3004:                     $indexes = array_merge($indexes, $this->buildIndex($col, $table));
3005:                 }
3006:                 $columns = $cols;
3007:                 $out .= $this->renderStatement('schema', compact('table', 'columns', 'indexes', 'tableParameters')) . "\n\n";
3008:             }
3009:         }
3010:         return $out;
3011:     }
3012: 
3013: /**
3014:  * Generate a alter syntax from CakeSchema::compare()
3015:  *
3016:  * @param mixed $compare
3017:  * @param string $table
3018:  * @return boolean
3019:  */
3020:     public function alterSchema($compare, $table = null) {
3021:         return false;
3022:     }
3023: 
3024: /**
3025:  * Generate a "drop table" statement for the given Schema object
3026:  *
3027:  * @param CakeSchema $schema An instance of a subclass of CakeSchema
3028:  * @param string $table Optional.  If specified only the table name given will be generated.
3029:  *   Otherwise, all tables defined in the schema are generated.
3030:  * @return string
3031:  */
3032:     public function dropSchema(CakeSchema $schema, $table = null) {
3033:         $out = '';
3034: 
3035:         foreach ($schema->tables as $curTable => $columns) {
3036:             if (!$table || $table == $curTable) {
3037:                 $out .= 'DROP TABLE ' . $this->fullTableName($curTable) . ";\n";
3038:             }
3039:         }
3040:         return $out;
3041:     }
3042: 
3043: /**
3044:  * Generate a database-native column schema string
3045:  *
3046:  * @param array $column An array structured like the following: array('name' => 'value', 'type' => 'value'[, options]),
3047:  *   where options can be 'default', 'length', or 'key'.
3048:  * @return string
3049:  */
3050:     public function buildColumn($column) {
3051:         $name = $type = null;
3052:         extract(array_merge(array('null' => true), $column));
3053: 
3054:         if (empty($name) || empty($type)) {
3055:             trigger_error(__d('cake_dev', 'Column name or type not defined in schema'), E_USER_WARNING);
3056:             return null;
3057:         }
3058: 
3059:         if (!isset($this->columns[$type])) {
3060:             trigger_error(__d('cake_dev', 'Column type %s does not exist', $type), E_USER_WARNING);
3061:             return null;
3062:         }
3063: 
3064:         $real = $this->columns[$type];
3065:         $out = $this->name($name) . ' ' . $real['name'];
3066: 
3067:         if (isset($column['length'])) {
3068:             $length = $column['length'];
3069:         } elseif (isset($column['limit'])) {
3070:             $length = $column['limit'];
3071:         } elseif (isset($real['length'])) {
3072:             $length = $real['length'];
3073:         } elseif (isset($real['limit'])) {
3074:             $length = $real['limit'];
3075:         }
3076:         if (isset($length)) {
3077:             $out .= '(' . $length . ')';
3078:         }
3079: 
3080:         if (($column['type'] === 'integer' || $column['type'] === 'float') && isset($column['default']) && $column['default'] === '') {
3081:             $column['default'] = null;
3082:         }
3083:         $out = $this->_buildFieldParameters($out, $column, 'beforeDefault');
3084: 
3085:         if (isset($column['key']) && $column['key'] === 'primary' && $type === 'integer') {
3086:             $out .= ' ' . $this->columns['primary_key']['name'];
3087:         } elseif (isset($column['key']) && $column['key'] === 'primary') {
3088:             $out .= ' NOT NULL';
3089:         } elseif (isset($column['default']) && isset($column['null']) && $column['null'] === false) {
3090:             $out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL';
3091:         } elseif (isset($column['default'])) {
3092:             $out .= ' DEFAULT ' . $this->value($column['default'], $type);
3093:         } elseif ($type !== 'timestamp' && !empty($column['null'])) {
3094:             $out .= ' DEFAULT NULL';
3095:         } elseif ($type === 'timestamp' && !empty($column['null'])) {
3096:             $out .= ' NULL';
3097:         } elseif (isset($column['null']) && $column['null'] === false) {
3098:             $out .= ' NOT NULL';
3099:         }
3100:         if ($type === 'timestamp' && isset($column['default']) && strtolower($column['default']) === 'current_timestamp') {
3101:             $out = str_replace(array("'CURRENT_TIMESTAMP'", "'current_timestamp'"), 'CURRENT_TIMESTAMP', $out);
3102:         }
3103:         return $this->_buildFieldParameters($out, $column, 'afterDefault');
3104:     }
3105: 
3106: /**
3107:  * Build the field parameters, in a position
3108:  *
3109:  * @param string $columnString The partially built column string
3110:  * @param array $columnData The array of column data.
3111:  * @param string $position The position type to use. 'beforeDefault' or 'afterDefault' are common
3112:  * @return string a built column with the field parameters added.
3113:  */
3114:     protected function _buildFieldParameters($columnString, $columnData, $position) {
3115:         foreach ($this->fieldParameters as $paramName => $value) {
3116:             if (isset($columnData[$paramName]) && $value['position'] == $position) {
3117:                 if (isset($value['options']) && !in_array($columnData[$paramName], $value['options'])) {
3118:                     continue;
3119:                 }
3120:                 $val = $columnData[$paramName];
3121:                 if ($value['quote']) {
3122:                     $val = $this->value($val);
3123:                 }
3124:                 $columnString .= ' ' . $value['value'] . $value['join'] . $val;
3125:             }
3126:         }
3127:         return $columnString;
3128:     }
3129: 
3130: /**
3131:  * Format indexes for create table
3132:  *
3133:  * @param array $indexes
3134:  * @param string $table
3135:  * @return array
3136:  */
3137:     public function buildIndex($indexes, $table = null) {
3138:         $join = array();
3139:         foreach ($indexes as $name => $value) {
3140:             $out = '';
3141:             if ($name === 'PRIMARY') {
3142:                 $out .= 'PRIMARY ';
3143:                 $name = null;
3144:             } else {
3145:                 if (!empty($value['unique'])) {
3146:                     $out .= 'UNIQUE ';
3147:                 }
3148:                 $name = $this->startQuote . $name . $this->endQuote;
3149:             }
3150:             if (is_array($value['column'])) {
3151:                 $out .= 'KEY ' . $name . ' (' . implode(', ', array_map(array(&$this, 'name'), $value['column'])) . ')';
3152:             } else {
3153:                 $out .= 'KEY ' . $name . ' (' . $this->name($value['column']) . ')';
3154:             }
3155:             $join[] = $out;
3156:         }
3157:         return $join;
3158:     }
3159: 
3160: /**
3161:  * Read additional table parameters
3162:  *
3163:  * @param string $name
3164:  * @return array
3165:  */
3166:     public function readTableParameters($name) {
3167:         $parameters = array();
3168:         if (method_exists($this, 'listDetailedSources')) {
3169:             $currentTableDetails = $this->listDetailedSources($name);
3170:             foreach ($this->tableParameters as $paramName => $parameter) {
3171:                 if (!empty($parameter['column']) && !empty($currentTableDetails[$parameter['column']])) {
3172:                     $parameters[$paramName] = $currentTableDetails[$parameter['column']];
3173:                 }
3174:             }
3175:         }
3176:         return $parameters;
3177:     }
3178: 
3179: /**
3180:  * Format parameters for create table
3181:  *
3182:  * @param array $parameters
3183:  * @param string $table
3184:  * @return array
3185:  */
3186:     public function buildTableParameters($parameters, $table = null) {
3187:         $result = array();
3188:         foreach ($parameters as $name => $value) {
3189:             if (isset($this->tableParameters[$name])) {
3190:                 if ($this->tableParameters[$name]['quote']) {
3191:                     $value = $this->value($value);
3192:                 }
3193:                 $result[] = $this->tableParameters[$name]['value'] . $this->tableParameters[$name]['join'] . $value;
3194:             }
3195:         }
3196:         return $result;
3197:     }
3198: 
3199: /**
3200:  * Guesses the data type of an array
3201:  *
3202:  * @param string $value
3203:  * @return void
3204:  */
3205:     public function introspectType($value) {
3206:         if (!is_array($value)) {
3207:             if (is_bool($value)) {
3208:                 return 'boolean';
3209:             }
3210:             if (is_float($value) && floatval($value) === $value) {
3211:                 return 'float';
3212:             }
3213:             if (is_int($value) && intval($value) === $value) {
3214:                 return 'integer';
3215:             }
3216:             if (is_string($value) && strlen($value) > 255) {
3217:                 return 'text';
3218:             }
3219:             return 'string';
3220:         }
3221: 
3222:         $isAllFloat = $isAllInt = true;
3223:         $containsFloat = $containsInt = $containsString = false;
3224:         foreach ($value as $valElement) {
3225:             $valElement = trim($valElement);
3226:             if (!is_float($valElement) && !preg_match('/^[\d]+\.[\d]+$/', $valElement)) {
3227:                 $isAllFloat = false;
3228:             } else {
3229:                 $containsFloat = true;
3230:                 continue;
3231:             }
3232:             if (!is_int($valElement) && !preg_match('/^[\d]+$/', $valElement)) {
3233:                 $isAllInt = false;
3234:             } else {
3235:                 $containsInt = true;
3236:                 continue;
3237:             }
3238:             $containsString = true;
3239:         }
3240: 
3241:         if ($isAllFloat) {
3242:             return 'float';
3243:         }
3244:         if ($isAllInt) {
3245:             return 'integer';
3246:         }
3247: 
3248:         if ($containsInt && !$containsString) {
3249:             return 'integer';
3250:         }
3251:         return 'string';
3252:     }
3253: 
3254: /**
3255:  * Writes a new key for the in memory sql query cache
3256:  *
3257:  * @param string $sql SQL query
3258:  * @param mixed $data result of $sql query
3259:  * @param array $params query params bound as values
3260:  * @return void
3261:  */
3262:     protected function _writeQueryCache($sql, $data, $params = array()) {
3263:         if (preg_match('/^\s*select/i', $sql)) {
3264:             $this->_queryCache[$sql][serialize($params)] = $data;
3265:         }
3266:     }
3267: 
3268: /**
3269:  * Returns the result for a sql query if it is already cached
3270:  *
3271:  * @param string $sql SQL query
3272:  * @param array $params query params bound as values
3273:  * @return mixed results for query if it is cached, false otherwise
3274:  */
3275:     public function getQueryCache($sql, $params = array()) {
3276:         if (isset($this->_queryCache[$sql]) && preg_match('/^\s*select/i', $sql)) {
3277:             $serialized = serialize($params);
3278:             if (isset($this->_queryCache[$sql][$serialized])) {
3279:                 return $this->_queryCache[$sql][$serialized];
3280:             }
3281:         }
3282:         return false;
3283:     }
3284: 
3285: /**
3286:  * Used for storing in cache the results of the in-memory methodCache
3287:  *
3288:  */
3289:     public function __destruct() {
3290:         if ($this->_methodCacheChange) {
3291:             Cache::write('method_cache', self::$methodCache, '_cake_core_');
3292:         }
3293:     }
3294: 
3295: }
3296: 
OpenHub
Rackspace
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Reporting Security Issues
  • Privacy Policy
  • Logos & Trademarks
  • Community
  • Get Involved
  • Issues (GitHub)
  • Bakery
  • Featured Resources
  • Training
  • Meetups
  • My CakePHP
  • CakeFest
  • Newsletter
  • Linkedin
  • YouTube
  • Facebook
  • Twitter
  • Mastodon
  • Help & Support
  • Forum
  • Stack Overflow
  • Slack
  • Paid Support

Generated using CakePHP API Docs