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.0 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.0
      • 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
        • Auth
    • Core
    • Error
    • I18n
    • Log
      • Engine
    • Model
      • Behavior
      • Datasource
        • Database
        • Session
    • Network
      • Email
      • Http
    • Routing
      • Route
    • TestSuite
      • Coverage
      • Fixture
      • Reporter
    • Utility
    • View
      • Helper

Classes

  • CakeSession
  • DataSource
  • DboSource

Interfaces

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