PageRenderTime 80ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/cake/libs/model/datasources/dbo_source.php

https://github.com/hardsshah/bookmarks
PHP | 2450 lines | 1742 code | 158 blank | 550 comment | 555 complexity | 5ee7ac6cf1149b020d25564bdf9469f3 MD5 | raw file

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

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

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