PageRenderTime 36ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/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
  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', 'order', 'limit', 'offset', 'group') as $key) {
  1547. if (!isset($data[$key]) || empty($data[$key])) {
  1548. $data[$key] = array();
  1549. }
  1550. }
  1551. return $data;
  1552. }
  1553. /**
  1554. * Generates the fields list of an SQL query.
  1555. *
  1556. * @param Model $model
  1557. * @param string $alias Alias tablename
  1558. * @param mixed $fields
  1559. * @param boolean $quote If false, returns fields array unquoted
  1560. * @return array
  1561. */
  1562. function fields(&$model, $alias = null, $fields = array(), $quote = true) {
  1563. if (empty($alias)) {
  1564. $alias = $model->alias;
  1565. }
  1566. if (empty($fields)) {
  1567. $fields = array_keys($model->schema());
  1568. } elseif (!is_array($fields)) {
  1569. $fields = String::tokenize($fields);
  1570. }
  1571. $fields = array_values(array_filter($fields));
  1572. if (!$quote) {
  1573. return $fields;
  1574. }
  1575. $count = count($fields);
  1576. if ($count >= 1 && !in_array($fields[0], array('*', 'COUNT(*)'))) {
  1577. for ($i = 0; $i < $count; $i++) {
  1578. if (!preg_match('/^.+\\(.*\\)/', $fields[$i])) {
  1579. $prepend = '';
  1580. if (strpos($fields[$i], 'DISTINCT') !== false) {
  1581. $prepend = 'DISTINCT ';
  1582. $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i]));
  1583. }
  1584. $dot = strpos($fields[$i], '.');
  1585. if ($dot === false) {
  1586. $prefix = !(
  1587. strpos($fields[$i], ' ') !== false ||
  1588. strpos($fields[$i], '(') !== false
  1589. );
  1590. $fields[$i] = $this->name(($prefix ? $alias . '.' : '') . $fields[$i]);
  1591. } else {
  1592. $value = array();
  1593. $comma = strpos($fields[$i], ',');
  1594. if ($comma === false) {
  1595. $build = explode('.', $fields[$i]);
  1596. if (!Set::numeric($build)) {
  1597. $fields[$i] = $this->name($build[0] . '.' . $build[1]);
  1598. }
  1599. $comma = String::tokenize($fields[$i]);
  1600. foreach ($comma as $string) {
  1601. if (preg_match('/^[0-9]+\.[0-9]+$/', $string)) {
  1602. $value[] = $string;
  1603. } else {
  1604. $build = explode('.', $string);
  1605. $value[] = $this->name(trim($build[0]) . '.' . trim($build[1]));
  1606. }
  1607. }
  1608. $fields[$i] = implode(', ', $value);
  1609. }
  1610. }
  1611. $fields[$i] = $prepend . $fields[$i];
  1612. } elseif (preg_match('/\(([\.\w]+)\)/', $fields[$i], $field)) {
  1613. if (isset($field[1])) {
  1614. if (strpos($field[1], '.') === false) {
  1615. $field[1] = $this->name($alias . '.' . $field[1]);
  1616. } else {
  1617. $field[0] = explode('.', $field[1]);
  1618. if (!Set::numeric($field[0])) {
  1619. $field[0] = join('.', array_map(array($this, 'name'), $field[0]));
  1620. $fields[$i] = preg_replace('/\(' . $field[1] . '\)/', '(' . $field[0] . ')', $fields[$i], 1);
  1621. }
  1622. }
  1623. }
  1624. }
  1625. }
  1626. }
  1627. return array_unique($fields);
  1628. }
  1629. /**
  1630. * Creates a WHERE clause by parsing given conditions data.
  1631. *
  1632. * @param mixed $conditions Array or string of conditions
  1633. * @param boolean $quoteValues If true, values should be quoted
  1634. * @param boolean $where If true, "WHERE " will be prepended to the return value
  1635. * @param Model $model A reference to the Model instance making the query
  1636. * @return string SQL fragment
  1637. */
  1638. function conditions($conditions, $quoteValues = true, $where = true, $model = null) {
  1639. $clause = $out = '';
  1640. if ($where) {
  1641. $clause = ' WHERE ';
  1642. }
  1643. if (is_array($conditions) && !empty($conditions)) {
  1644. $out = $this->conditionKeysToString($conditions, $quoteValues, $model);
  1645. if (empty($out)) {
  1646. return $clause . ' 1 = 1';
  1647. }
  1648. return $clause . join(' AND ', $out);
  1649. }
  1650. if (empty($conditions) || trim($conditions) == '' || $conditions === true) {
  1651. return $clause . '1 = 1';
  1652. }
  1653. $clauses = '/^WHERE\\x20|^GROUP\\x20BY\\x20|^HAVING\\x20|^ORDER\\x20BY\\x20/i';
  1654. if (preg_match($clauses, $conditions, $match)) {
  1655. $clause = '';
  1656. }
  1657. if (trim($conditions) == '') {
  1658. $conditions = ' 1 = 1';
  1659. } else {
  1660. $conditions = $this->__quoteFields($conditions);
  1661. }
  1662. return $clause . $conditions;
  1663. }
  1664. /**
  1665. * Creates a WHERE clause by parsing given conditions array. Used by DboSource::conditions().
  1666. *
  1667. * @param array $conditions Array or string of conditions
  1668. * @param boolean $quoteValues If true, values should be quoted
  1669. * @param Model $model A reference to the Model instance making the query
  1670. * @return string SQL fragment
  1671. */
  1672. function conditionKeysToString($conditions, $quoteValues = true, $model = null) {
  1673. $c = 0;
  1674. $out = array();
  1675. $data = $columnType = null;
  1676. $bool = array('and', 'or', 'not', 'and not', 'or not', 'xor', '||', '&&');
  1677. foreach ($conditions as $key => $value) {
  1678. $join = ' AND ';
  1679. $not = null;
  1680. if (is_array($value)) {
  1681. $valueInsert = (
  1682. !empty($value) &&
  1683. (substr_count($key, '?') == count($value) || substr_count($key, ':') == count($value))
  1684. );
  1685. }
  1686. if (is_numeric($key) && empty($value)) {
  1687. continue;
  1688. } elseif (is_numeric($key) && is_string($value)) {
  1689. $out[] = $not . $this->__quoteFields($value);
  1690. } elseif ((is_numeric($key) && is_array($value)) || in_array(strtolower(trim($key)), $bool)) {
  1691. if (in_array(strtolower(trim($key)), $bool)) {
  1692. $join = ' ' . strtoupper($key) . ' ';
  1693. } else {
  1694. $key = $join;
  1695. }
  1696. $value = $this->conditionKeysToString($value, $quoteValues, $model);
  1697. if (strpos($join, 'NOT') !== false) {
  1698. if (strtoupper(trim($key)) == 'NOT') {
  1699. $key = 'AND ' . trim($key);
  1700. }
  1701. $not = 'NOT ';
  1702. }
  1703. if (empty($value[1])) {
  1704. if ($not) {
  1705. $out[] = $not . '(' . $value[0] . ')';
  1706. } else {
  1707. $out[] = $value[0] ;
  1708. }
  1709. } else {
  1710. $out[] = '(' . $not . '(' . join(') ' . strtoupper($key) . ' (', $value) . '))';
  1711. }
  1712. } else {
  1713. if (is_object($value) && isset($value->type)) {
  1714. if ($value->type == 'identifier') {
  1715. $data .= $this->name($key) . ' = ' . $this->name($value->value);
  1716. } elseif ($value->type == 'expression') {
  1717. if (is_numeric($key)) {
  1718. $data .= $value->value;
  1719. } else {
  1720. $data .= $this->name($key) . ' = ' . $value->value;
  1721. }
  1722. }
  1723. } elseif (is_array($value) && !empty($value) && !$valueInsert) {
  1724. $keys = array_keys($value);
  1725. if (array_keys($value) === array_values(array_keys($value))) {
  1726. $count = count($value);
  1727. if ($count === 1) {
  1728. $data = $this->name($key) . ' = (';
  1729. } else {
  1730. $data = $this->name($key) . ' IN (';
  1731. }
  1732. if ($quoteValues || strpos($value[0], '-!') !== 0) {
  1733. if (is_object($model)) {
  1734. $columnType = $model->getColumnType($key);
  1735. }
  1736. $data .= join(', ', $this->value($value, $columnType));
  1737. }
  1738. $data .= ')';
  1739. } else {
  1740. $ret = $this->conditionKeysToString($value, $quoteValues, $model);
  1741. if (count($ret) > 1) {
  1742. $data = '(' . join(') AND (', $ret) . ')';
  1743. } elseif (isset($ret[0])) {
  1744. $data = $ret[0];
  1745. }
  1746. }
  1747. } elseif (is_numeric($key) && !empty($value)) {
  1748. $data = $this->__quoteFields($value);
  1749. } else {
  1750. $data = $this->__parseKey($model, trim($key), $value);
  1751. }
  1752. if ($data != null) {
  1753. if (preg_match('/^\(\(\((.+)\)\)\)$/', $data)) {
  1754. $data = substr($data, 1, strlen($data) - 2);
  1755. }
  1756. $out[] = $data;
  1757. $data = null;
  1758. }
  1759. }
  1760. $c++;
  1761. }
  1762. return $out;
  1763. }
  1764. /**
  1765. * Extracts a Model.field identifier and an SQL condition operator from a string, formats
  1766. * and inserts values, and composes them into an SQL snippet.
  1767. *
  1768. * @param Model $model Model object initiating the query
  1769. * @param string $key An SQL key snippet containing a field and optional SQL operator
  1770. * @param mixed $value The value(s) to be inserted in the string
  1771. * @return string
  1772. * @access private
  1773. */
  1774. function __parseKey($model, $key, $value) {
  1775. $operatorMatch = '/^((' . join(')|(', $this->__sqlOps);
  1776. $operatorMatch .= '\\x20)|<[>=]?(?![^>]+>)\\x20?|[>=!]{1,3}(?!<)\\x20?)/is';
  1777. $bound = (strpos($key, '?') !== false || (is_array($value) && strpos($key, ':') !== false));
  1778. if (!strpos($key, ' ')) {
  1779. $operator = '=';
  1780. } else {
  1781. list($key, $operator) = explode(' ', trim($key), 2);
  1782. if (!preg_match($operatorMatch, trim($operator)) && strpos($operator, ' ') !== false) {
  1783. $key = $key . ' ' . $operator;
  1784. $split = strrpos($key, ' ');
  1785. $operator = substr($key, $split);
  1786. $key = substr($key, 0, $split);
  1787. }
  1788. }
  1789. $type = (is_object($model) ? $model->getColumnType($key) : null);
  1790. $null = ($value === null || (is_array($value) && empty($value)));
  1791. if (strtolower($operator) === 'not') {
  1792. $data = $this->conditionKeysToString(
  1793. array($operator => array($key => $value)), true, $model
  1794. );
  1795. return $data[0];
  1796. }
  1797. $value = $this->value($value, $type);
  1798. if ($key !== '?') {
  1799. $isKey = (strpos($key, '(') !== false || strpos($key, ')') !== false);
  1800. $key = $isKey ? $this->__quoteFields($key) : $this->name($key);
  1801. }
  1802. if ($bound) {
  1803. return String::insert($key . ' ' . trim($operator), $value);
  1804. }
  1805. if (!preg_match($operatorMatch, trim($operator))) {
  1806. $operator .= ' =';
  1807. }
  1808. $operator = trim($operator);
  1809. if (is_array($value)) {
  1810. $value = join(', ', $value);
  1811. switch ($operator) {
  1812. case '=':
  1813. $operator = 'IN';
  1814. break;
  1815. case '!=':
  1816. case '<>':
  1817. $operator = 'NOT IN';
  1818. break;
  1819. }
  1820. $value = "({$value})";
  1821. } elseif ($null) {
  1822. switch ($operator) {
  1823. case '=':
  1824. $operator = 'IS';
  1825. break;
  1826. case '!=':
  1827. case '<>':
  1828. $operator = 'IS NOT';
  1829. break;
  1830. }
  1831. }
  1832. return "{$key} {$operator} {$value}";
  1833. }
  1834. /**
  1835. * Quotes Model.fields
  1836. *
  1837. * @param string $conditions
  1838. * @return string or false if no match
  1839. * @access private
  1840. */
  1841. function __quoteFields($conditions) {
  1842. $start = $end = null;
  1843. $original = $conditions;
  1844. if (!empty($this->startQuote)) {
  1845. $start = preg_quote($this->startQuote);
  1846. }
  1847. if (!empty($this->endQuote)) {
  1848. $end = preg_quote($this->endQuote);
  1849. }
  1850. $conditions = str_replace(array($start, $end), '', $conditions);
  1851. preg_match_all('/(?:[\'\"][^\'\"\\\]*(?:\\\.[^\'\"\\\]*)*[\'\"])|([a-z0-9_' . $start . $end . ']*\\.[a-z0-9_' . $start . $end . ']*)/i', $conditions, $replace, PREG_PATTERN_ORDER);
  1852. if (isset($replace['1']['0'])) {
  1853. $pregCount = count($replace['1']);
  1854. for ($i = 0; $i < $pregCount; $i++) {
  1855. if (!empty($replace['1'][$i]) && !is_numeric($replace['1'][$i])) {
  1856. $conditions = preg_replace('/\b' . preg_quote($replace['1'][$i]) . '\b/', $this->name($replace['1'][$i]), $conditions);
  1857. }
  1858. }
  1859. return $conditions;
  1860. }
  1861. return $original;
  1862. }
  1863. /**
  1864. * Returns a limit statement in the correct format for the particular database.
  1865. *
  1866. * @param integer $limit Limit of results returned
  1867. * @param integer $offset Offset from which to start results
  1868. * @return string SQL limit/offset statement
  1869. */
  1870. function limit($limit, $offset = null) {
  1871. if ($limit) {
  1872. $rt = '';
  1873. if (!strpos(strtolower($limit), 'limit') || strpos(strtolower($limit), 'limit') === 0) {
  1874. $rt = ' LIMIT';
  1875. }
  1876. if ($offset) {
  1877. $rt .= ' ' . $offset . ',';
  1878. }
  1879. $rt .= ' ' . $limit;
  1880. return $rt;
  1881. }
  1882. return null;
  1883. }
  1884. /**
  1885. * Returns an ORDER BY clause as a string.
  1886. *
  1887. * @param string $key Field reference, as a key (i.e. Post.title)
  1888. * @param string $direction Direction (ASC or DESC)
  1889. * @return string ORDER BY clause
  1890. */
  1891. function order($keys, $direction = 'ASC') {
  1892. if (is_string($keys) && strpos($keys, ',') && !preg_match('/\(.+\,.+\)/', $keys)) {
  1893. $keys = array_map('trim', explode(',', $keys));
  1894. }
  1895. if (is_array($keys)) {
  1896. $keys = array_filter($keys);
  1897. }
  1898. if (empty($keys) || (is_array($keys) && count($keys) && isset($keys[0]) && empty($keys[0]))) {
  1899. return '';
  1900. }
  1901. if (is_array($keys)) {
  1902. $keys = (Set::countDim($keys) > 1) ? array_map(array(&$this, 'order'), $keys) : $keys;
  1903. foreach ($keys as $key => $value) {
  1904. if (is_numeric($key)) {
  1905. $key = $value = ltrim(str_replace('ORDER BY ', '', $this->order($value)));
  1906. $value = (!preg_match('/\\x20ASC|\\x20DESC/i', $key) ? ' ' . $direction : '');
  1907. } else {
  1908. $value = ' ' . $value;
  1909. }
  1910. if (!preg_match('/^.+\\(.*\\)/', $key) && !strpos($key, ',')) {
  1911. if (preg_match('/\\x20ASC|\\x20DESC/i', $key, $dir)) {
  1912. $dir = $dir[0];
  1913. $key = preg_replace('/\\x20ASC|\\x20DESC/i', '', $key);
  1914. } else {
  1915. $dir = '';
  1916. }
  1917. $key = trim($key);
  1918. if (!preg_match('/\s/', $key)) {
  1919. $key = $this->name($key);
  1920. }
  1921. $key .= ' ' . trim($dir);
  1922. }
  1923. $order[] = $this->order($key . $value);
  1924. }
  1925. return ' ORDER BY ' . trim(str_replace('ORDER BY', '', join(',', $order)));
  1926. }
  1927. $keys = preg_replace('/ORDER\\x20BY/i', '', $keys);
  1928. if (strpos($keys, '.')) {
  1929. preg_match_all('/([a-zA-Z0-9_]{1,})\\.([a-zA-Z0-9_]{1,})/', $keys, $result, PREG_PATTERN_ORDER);
  1930. $pregCount = count($result[0]);
  1931. for ($i = 0; $i < $pregCount; $i++) {
  1932. if (!is_numeric($result[0][$i])) {
  1933. $keys = preg_replace('/' . $result[0][$i] . '/', $this->name($result[0][$i]), $keys);
  1934. }
  1935. }
  1936. $result = ' ORDER BY ' . $keys;
  1937. return $result . (!preg_match('/\\x20ASC|\\x20DESC/i', $keys) ? ' ' . $direction : '');
  1938. } elseif (preg_match('/(\\x20ASC|\\x20DESC)/i', $keys, $match)) {
  1939. $direction = $match[1];
  1940. return ' ORDER BY ' . preg_replace('/' . $match[1] . '/', '', $keys) . $direction;
  1941. }
  1942. return ' ORDER BY ' . $keys . ' ' . $direction;
  1943. }
  1944. /**
  1945. * Create a GROUP BY SQL clause
  1946. *
  1947. * @param string $group Group By Condition
  1948. * @return mixed string condition or null
  1949. */
  1950. function group($group) {
  1951. if ($group) {
  1952. if (is_array($group)) {
  1953. $group = join(', ', $group);
  1954. }
  1955. return ' GROUP BY ' . $this->__quoteFields($group);
  1956. }
  1957. return null;
  1958. }
  1959. /**
  1960. * Disconnects database, kills the connection and says the connection is closed,
  1961. * and if DEBUG is turned on, the log for this object is shown.
  1962. *
  1963. */
  1964. function close() {
  1965. if (Configure::read() > 1) {
  1966. $this->showLog();
  1967. }
  1968. $this->disconnect();
  1969. }
  1970. /**
  1971. * Checks if the specified table contains any record matching specified SQL
  1972. *
  1973. * @param Model $model Model to search
  1974. * @param string $sql SQL WHERE clause (condition only, not the "WHERE" part)
  1975. * @return boolean True if the table has a matching record, else false
  1976. */
  1977. function hasAny(&$Model, $sql) {
  1978. $sql = $this->conditions($sql);
  1979. $table = $this->fullTableName($Model);
  1980. $alias = $this->alias . $this->name($Model->alias);
  1981. $where = $sql ? "{$sql}" : ' WHERE 1 = 1';
  1982. $id = $Model->escapeField();
  1983. $out = $this->fetchRow("SELECT COUNT({$id}) {$this->alias}count FROM {$table} {$alias}{$where}");
  1984. if (is_array($out)) {
  1985. return $out[0]['count'];
  1986. }
  1987. return false;
  1988. }
  1989. /**
  1990. * Gets the length of a database-native column description, or null if no length
  1991. *
  1992. * @param string $real Real database-layer column type (i.e. "varchar(255)")
  1993. * @return mixed An integer or string representing the length of the column
  1994. */
  1995. function length($real) {
  1996. if (!preg_match_all('/([\w\s]+)(?:\((\d+)(?:,(\d+))?\))?(\sunsigned)?(\szerofill)?/', $real, $result)) {
  1997. trigger_error(__('FIXME: Can\'t parse field: ' . $real, true), E_USER_WARNING);
  1998. $col = str_replace(array(')', 'unsigned'), '', $real);
  1999. $limit = null;
  2000. if (strpos($col, '(') !== false) {
  2001. list($col, $limit) = explode('(', $col);
  2002. }
  2003. if ($limit != null) {
  2004. return intval($limit);
  2005. }
  2006. return null;
  2007. }
  2008. $types = array(
  2009. 'int' => 1, 'tinyint' => 1, 'smallint' => 1, 'mediumint' => 1, 'integer' => 1, 'bigint' => 1
  2010. );
  2011. list($real, $type, $length, $offset, $sign, $zerofill) = $result;
  2012. $typeArr = $type;
  2013. $type = $type[0];
  2014. $length = $length[0];
  2015. $offset = $offset[0];
  2016. $isFloat = in_array($type, array('dec', 'decimal', 'float', 'numeric', 'double'));
  2017. if ($isFloat && $offset) {
  2018. return $length.','.$offset;
  2019. }
  2020. if (($real[0] == $type) && (count($real) == 1)) {
  2021. return null;
  2022. }
  2023. if (isset($types[$type])) {
  2024. $length += $types[$type];
  2025. if (!empty($sign)) {
  2026. $length--;
  2027. }
  2028. } elseif (in_array($type, array('enum', 'set'))) {
  2029. $length = 0;
  2030. foreach ($typeArr as $key => $enumValue) {
  2031. if ($key == 0) {
  2032. continue;
  2033. }
  2034. $tmpLength = strlen($enumValue);
  2035. if ($tmpLength > $length) {
  2036. $length = $tmpLength;
  2037. }
  2038. }
  2039. }
  2040. return intval($length);
  2041. }
  2042. /**
  2043. * Translates between PHP boolean values and Database (faked) boolean values
  2044. *
  2045. * @param mixed $data Value to be translated
  2046. * @return mixed Converted boolean value
  2047. */
  2048. function boolean($data) {
  2049. if ($data === true || $data === false) {
  2050. if ($data === true) {
  2051. return 1;
  2052. }
  2053. return 0;
  2054. } else {
  2055. return !empty($data);
  2056. }
  2057. }
  2058. /**
  2059. * Inserts multiple values into a table
  2060. *
  2061. * @param string $table
  2062. * @param string $fields
  2063. * @param array $values
  2064. * @access protected
  2065. */
  2066. function insertMulti($table, $fields, $values) {
  2067. $table = $this->fullTableName($table);
  2068. if (is_array($fields)) {
  2069. $fields = join(', ', array_map(array(&$this, 'name'), $fields));
  2070. }
  2071. $count = count($values);
  2072. for ($x = 0; $x < $count; $x++) {
  2073. $this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}");
  2074. }
  2075. }
  2076. /**
  2077. * Returns an array of the indexes in given datasource name.
  2078. *
  2079. * @param string $model Name of model to inspect
  2080. * @return array Fields in table. Keys are column and unique
  2081. */
  2082. function index($model) {
  2083. return false;
  2084. }
  2085. /**
  2086. * Generate a database-native schema for the given Schema object
  2087. *
  2088. * @param object $schema An instance of a subclass of CakeSchema
  2089. * @param string $tableName Optional. If specified only the table name given will be generated.
  2090. * Otherwise, all tables defined in the schema are generated.
  2091. * @return string
  2092. */
  2093. function createSchema($schema, $tableName = null) {
  2094. if (!is_a($schema, 'CakeSchema')) {
  2095. trigger_error(__('Invalid schema object', true), E_USER_WARNING);
  2096. return null;
  2097. }
  2098. $out = '';
  2099. foreach ($schema->tables as $curTable => $columns) {
  2100. if (!$tableName || $tableName == $curTable) {
  2101. $cols = $colList = $indexes = array();
  2102. $primary = null;
  2103. $table = $this->fullTableName($curTable);
  2104. foreach ($columns as $name => $col) {
  2105. if (is_string($col)) {
  2106. $col = array('type' => $col);
  2107. }
  2108. if (isset($col['key']) && $col['key'] == 'primary') {
  2109. $primary = $name;
  2110. }
  2111. if ($name !== 'indexes') {
  2112. $col['name'] = $name;
  2113. if (!isset($col['type'])) {
  2114. $col['type'] = 'string';
  2115. }
  2116. $cols[] = $this->buildColumn($col);
  2117. } else {
  2118. $indexes = array_merge($indexes, $this->buildIndex($col, $table));
  2119. }
  2120. }
  2121. if (empty($indexes) && !empty($primary)) {
  2122. $col = array('PRIMARY' => array('column' => $primary, 'unique' => 1));
  2123. $indexes = array_merge($indexes, $this->buildIndex($col, $table));
  2124. }
  2125. $columns = $cols;
  2126. $out .= $this->renderStatement('schema', compact('table', 'columns', 'indexes')) . "\n\n";
  2127. }
  2128. }
  2129. return $out;
  2130. }
  2131. /**
  2132. * Generate a alter syntax from CakeSchema::compare()
  2133. *
  2134. * @param unknown_type $schema
  2135. * @return unknown
  2136. */
  2137. function alterSchema($compare, $table = null) {
  2138. return false;
  2139. }
  2140. /**
  2141. * Generate a "drop table" statement for the given Schema object
  2142. *
  2143. * @param object $schema An instance of a subclass of CakeSchema
  2144. * @param string $table Optional. If specified only the table name given will be generated.
  2145. * Otherwise, all tables defined in the schema are generated.
  2146. * @return string
  2147. */
  2148. function dropSchema($schema, $table = null) {
  2149. if (!is_a($schema, 'CakeSchema')) {
  2150. trigger_error(__('Invalid schema object', true), E_USER_WARNING);
  2151. return null;
  2152. }
  2153. $out = '';
  2154. foreach ($schema->tables as $curTable => $columns) {
  2155. if (!$table || $table == $curTable) {
  2156. $out .= 'DROP TABLE ' . $this->fullTableName($curTable) . ";\n";
  2157. }
  2158. }
  2159. return $out;
  2160. }
  2161. /**
  2162. * Generate a database-native column schema string
  2163. *
  2164. * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]),
  2165. * where options can be 'default', 'length', or 'key'.
  2166. * @return string
  2167. */
  2168. function buildColumn($column) {
  2169. $name = $type = null;
  2170. extract(array_merge(array('null' => true), $column));
  2171. if (empty($name) || empty($type)) {
  2172. trigger_error('Column name or type not defined in schema', E_USER_WARNING);
  2173. return null;
  2174. }
  2175. if (!isset($this->columns[$type])) {
  2176. trigger_error("Column type {$type} does not exist", E_USER_WARNING);
  2177. return null;
  2178. }
  2179. $real = $this->columns[$type];
  2180. $out = $this->name($name) . ' ' . $real['name'];
  2181. if (isset($real['limit']) || isset($real['length']) || isset($column['limit']) || isset($column['length'])) {
  2182. if (isset($column['length'])) {
  2183. $length = $column['length'];
  2184. } elseif (isset($column['limit'])) {
  2185. $length = $column['limit'];
  2186. } elseif (isset($real['length'])) {
  2187. $length = $real['length'];
  2188. } else {
  2189. $length = $real['limit'];
  2190. }
  2191. $out .= '(' . $length . ')';
  2192. }
  2193. if (($column['type'] == 'integer' || $column['type'] == 'float' ) && isset($column['default']) && $column['default'] === '') {
  2194. $column['default'] = null;
  2195. }
  2196. if (isset($column['key']) && $column['key'] == 'primary' && $type == 'integer') {
  2197. $out .= ' ' . $this->columns['primary_key']['name'];
  2198. } elseif (isset($column['key']) && $column['key'] == 'primary') {
  2199. $out .= ' NOT NULL';
  2200. } elseif (isset($column['default']) && isset($column['null']) && $column['null'] == false) {
  2201. $out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL';
  2202. } elseif (isset($column['default'])) {
  2203. $out .= ' DEFAULT ' . $this->value($column['default'], $type);
  2204. } elseif (isset($column['null']) && $column['null'] == true) {
  2205. $out .= ' DEFAULT NULL';
  2206. } elseif (isset($column['null']) && $column['null'] == false) {
  2207. $out .= ' NOT NULL';
  2208. }
  2209. return $out;
  2210. }
  2211. /**
  2212. * Format indexes for create table
  2213. *
  2214. * @param array $indexes
  2215. * @param string $table
  2216. * @return array
  2217. */
  2218. function buildIndex($indexes, $table = null) {
  2219. $join = array();
  2220. foreach ($indexes as $name => $value) {
  2221. $out = '';
  2222. if ($name == 'PRIMARY') {
  2223. $out .= 'PRIMARY ';
  2224. $name = null;
  2225. } else {
  2226. if (!empty($value['unique'])) {
  2227. $out .= 'UNIQUE ';
  2228. }
  2229. }
  2230. if (is_array($value['column'])) {
  2231. $out .= 'KEY '. $name .' (' . join(', ', array_map(array(&$this, 'name'), $value['column'])) . ')';
  2232. } else {
  2233. $out .= 'KEY '. $name .' (' . $this->name($value['column']) . ')';
  2234. }
  2235. $join[] = $out;
  2236. }
  2237. return $join;
  2238. }
  2239. /**
  2240. * Guesses the data type of an array
  2241. *
  2242. * @param string $value
  2243. * @return void
  2244. * @access public
  2245. */
  2246. function introspectType($value) {
  2247. if (!is_array($value)) {
  2248. if ($value === true || $value === false) {
  2249. return 'boolean';
  2250. }
  2251. if (is_float($value) && floatval($value) === $value) {
  2252. return 'float';
  2253. }
  2254. if (is_int($value) && intval($value) === $value) {
  2255. return 'integer';
  2256. }
  2257. if (is_string($value) && strlen($value) > 255) {
  2258. return 'text';
  2259. }
  2260. return 'string';
  2261. }
  2262. $isAllFloat = $isAllInt = true;
  2263. $containsFloat = $containsInt = $containsString = false;
  2264. foreach ($value as $key => $valElement) {
  2265. $valElement = trim($valElement);
  2266. if (!is_float($valElement) && !preg_match('/^[\d]+\.[\d]+$/', $valElement)) {
  2267. $isAllFloat = false;
  2268. } else {
  2269. $containsFloat = true;
  2270. continue;
  2271. }
  2272. if (!is_int($valElement) && !preg_match('/^[\d]+$/', $valElement)) {
  2273. $isAllInt = false;
  2274. } else {
  2275. $containsInt = true;
  2276. continue;
  2277. }
  2278. $containsString = true;
  2279. }
  2280. if ($isAllFloat) {
  2281. return 'float';
  2282. }
  2283. if ($isAllInt) {
  2284. return 'integer';
  2285. }
  2286. if ($containsInt && !$containsString) {
  2287. return 'integer';
  2288. }
  2289. return 'string';
  2290. }
  2291. }
  2292. ?>