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 1.3 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 1.3
      • 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

Classes

  • AclBase
  • AclBehavior
  • AclComponent
  • AclNode
  • AclShell
  • Aco
  • AcoAction
  • AjaxHelper
  • ApcEngine
  • ApiShell
  • App
  • AppController
  • AppHelper
  • AppModel
  • Aro
  • AuthComponent
  • BakeShell
  • BakeTask
  • BehaviorCollection
  • Cache
  • CacheEngine
  • CacheHelper
  • CakeErrorController
  • CakeLog
  • CakeRoute
  • CakeSchema
  • CakeSession
  • CakeSocket
  • ClassRegistry
  • Component
  • Configure
  • ConnectionManager
  • ConsoleShell
  • ContainableBehavior
  • Controller
  • ControllerTask
  • CookieComponent
  • DataSource
  • DbAcl
  • DbConfigTask
  • DboMssql
  • DboMysql
  • DboMysqlBase
  • DboMysqli
  • DboOracle
  • DboPostgres
  • DboSource
  • DboSqlite
  • Debugger
  • EmailComponent
  • ErrorHandler
  • ExtractTask
  • File
  • FileEngine
  • FileLog
  • FixtureTask
  • Folder
  • FormHelper
  • Helper
  • HtmlHelper
  • HttpSocket
  • I18n
  • I18nModel
  • I18nShell
  • Inflector
  • IniAcl
  • JavascriptHelper
  • JqueryEngineHelper
  • JsBaseEngineHelper
  • JsHelper
  • L10n
  • MagicDb
  • MagicFileResource
  • MediaView
  • MemcacheEngine
  • Model
  • ModelBehavior
  • ModelTask
  • MootoolsEngineHelper
  • Multibyte
  • NumberHelper
  • Object
  • Overloadable
  • Overloadable2
  • PagesController
  • PaginatorHelper
  • Permission
  • PluginShortRoute
  • PluginTask
  • ProjectTask
  • PrototypeEngineHelper
  • RequestHandlerComponent
  • Router
  • RssHelper
  • Sanitize
  • Scaffold
  • ScaffoldView
  • SchemaShell
  • Security
  • SecurityComponent
  • SessionComponent
  • SessionHelper
  • Set
  • Shell
  • String
  • TemplateTask
  • TestSuiteShell
  • TestTask
  • TextHelper
  • ThemeView
  • TimeHelper
  • TranslateBehavior
  • TreeBehavior
  • Validation
  • View
  • ViewTask
  • XcacheEngine
  • Xml
  • XmlElement
  • XmlHelper
  • XmlManager
  • XmlNode
  • XmlTextNode

Functions

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