mirror of
https://github.com/kamilwylegala/cakephp2-php8.git
synced 2025-01-19 02:56:15 +00:00
fb46bcceef
Revision: [2316] Added fix for message: Warning: array_diff(): Argument #2 is not an array in Controller::__construct() Revision: [2315] Adding fix for Ticket #517 Revision: [2314] Corrected scaffold throwing errors when irregular words are used for model/table names Revision: [2313] Added patch from Ticket #495. Added missing ?> bracket at end of file Revision: [2312] Corrected model names being set to lower case, When matched to an irregular word in Inflector methods. Added custom inflections.php file to allow application to have custom inflection rules. Correct variable in View::renderCache(). error thrown if time class was used $time variable changed to $timeStart. Revision: [2311] Corrected $db placed in wrong section of the method Revision: [2310] Removing the db attribute from the model, This will help better with persistent objects Revision: [2309] Adding fix for Ticket #490 Revision: [2308] Update to previous commit Revision: [2307] Removing references to Model->db. DataBase connections are now singleton-only. Revision: [2306] Refactoring getDataSource() into loadDataSource() and enumConnectionObjects() Revision: [2305] Reverting changes to this file Revision: [2304] Fixing Ticket #496, getColumnType, and docstrings Revision: [2303] Adding fix for Ticket #510 git-svn-id: https://svn.cakephp.org/repo/trunk/cake@2317 3807eeeb-6ff5-0310-8944-8be069107fe0
1263 lines
No EOL
38 KiB
PHP
1263 lines
No EOL
38 KiB
PHP
<?php
|
|
/* SVN FILE: $Id$ */
|
|
|
|
/**
|
|
* Short description for file.
|
|
*
|
|
* Long description for file
|
|
*
|
|
* PHP versions 4 and 5
|
|
*
|
|
* CakePHP : Rapid Development Framework <http://www.cakephp.org/>
|
|
* Copyright (c) 2006, Cake Software Foundation, Inc.
|
|
* 1785 E. Sahara Avenue, Suite 490-204
|
|
* Las Vegas, Nevada 89104
|
|
*
|
|
* Licensed under The MIT License
|
|
* Redistributions of files must retain the above copyright notice.
|
|
*
|
|
* @filesource
|
|
* @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
|
|
* @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
|
|
* @package cake
|
|
* @subpackage cake.cake.libs.model.datasources
|
|
* @since CakePHP v 0.10.0.1076
|
|
* @version $Revision$
|
|
* @modifiedby $LastChangedBy$
|
|
* @lastmodified $Date$
|
|
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
|
*/
|
|
|
|
/**
|
|
* DboSource
|
|
*
|
|
* Creates DBO-descendant objects from a given db connection configuration
|
|
*
|
|
* @package cake
|
|
* @subpackage cake.cake.libs.model.datasources
|
|
* @since CakePHP v 0.10.0.1076
|
|
*
|
|
*/
|
|
class DboSource extends DataSource
|
|
{
|
|
/**
|
|
* Description string for this Database Data Source.
|
|
*
|
|
* @var unknown_type
|
|
*/
|
|
var $description = "Database Data Source";
|
|
/**
|
|
* Enter description here...
|
|
*
|
|
* @var unknown_type
|
|
*/
|
|
var $__bypass = false;
|
|
/**
|
|
* Enter description here...
|
|
*
|
|
* @var array
|
|
*/
|
|
var $__assocJoins = null;
|
|
/**
|
|
* Enter description here...
|
|
*
|
|
* @var unknown_type
|
|
*/
|
|
var $startQuote = null;
|
|
|
|
/**
|
|
* Enter description here...
|
|
*
|
|
* @var unknown_type
|
|
*/
|
|
var $endQuote = null;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
*/
|
|
function __construct($config = null)
|
|
{
|
|
$this->debug = DEBUG > 0;
|
|
$this->fullDebug = DEBUG > 1;
|
|
parent::__construct($config);
|
|
return $this->connect();
|
|
}
|
|
|
|
/**
|
|
* Prepares a value, or an array of values for database queries by quoting and escaping them.
|
|
*
|
|
* @param mixed $data A value or an array of values to prepare.
|
|
* @return mixed Prepared value or array of values.
|
|
*/
|
|
function value ($data, $column = null)
|
|
{
|
|
if (is_array($data))
|
|
{
|
|
$out = array();
|
|
foreach ($data as $key => $item)
|
|
{
|
|
$out[$key] = $this->value($item);
|
|
}
|
|
return $out;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convenience method for DboSource::listSources().
|
|
*
|
|
* @return array
|
|
*/
|
|
function sources ()
|
|
{
|
|
return array_map('strtolower', $this->listSources());
|
|
}
|
|
|
|
/**
|
|
* Executes given SQL statement.
|
|
*
|
|
* @param string $sql SQL statement
|
|
* @return unknown
|
|
*/
|
|
function rawQuery ($sql)
|
|
{
|
|
$this->took = $this->error = $this->numRows = false;
|
|
return $this->execute($sql);
|
|
}
|
|
|
|
/**
|
|
* Queries the database with given SQL statement, and obtains some metadata about the result
|
|
* (rows affected, timing, any errors, number of rows in resultset). The query is also logged.
|
|
* If DEBUG is set, the log is shown all the time, else it is only shown on errors.
|
|
*
|
|
* @param string $sql
|
|
* @return unknown
|
|
*/
|
|
function execute($sql)
|
|
{
|
|
$t = getMicrotime();
|
|
$this->_result = $this->_execute($sql);
|
|
|
|
$this->affected = $this->lastAffected();
|
|
$this->took = round((getMicrotime() - $t) * 1000, 0);
|
|
$this->error = $this->lastError();
|
|
$this->numRows = $this->lastNumRows($this->_result);
|
|
$this->logQuery($sql);
|
|
|
|
if ($this->error)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return $this->_result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a single row of results from the _last_ SQL query.
|
|
*
|
|
* @param resource $res
|
|
* @return array A single row of results
|
|
*/
|
|
function fetchArray ($assoc=false)
|
|
{
|
|
if ($assoc === false)
|
|
{
|
|
return $this->fetchRow();
|
|
}
|
|
else
|
|
{
|
|
return $this->fetchRow($assoc);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a single row of results for a _given_ SQL query.
|
|
*
|
|
* @param string $sql SQL statement
|
|
* @return array A single row of results
|
|
*/
|
|
function one ($sql)
|
|
{
|
|
if ($this->execute($sql))
|
|
{
|
|
return $this->fetchArray();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns an array of all result rows for a given SQL query.
|
|
* Returns false if no rows matched.
|
|
*
|
|
* @param string $sql SQL statement
|
|
* @return array Array of resultset rows, or false if no rows matched
|
|
*/
|
|
function fetchAll ($sql)
|
|
{
|
|
if($this->execute($sql))
|
|
{
|
|
$out = array();
|
|
while ($item = $this->fetchArray(null, true))
|
|
{
|
|
$out[] = $item;
|
|
}
|
|
return $out;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a single field of the first of query results for a given SQL query, or false if empty.
|
|
*
|
|
* @param string $name Name of the field
|
|
* @param string $sql SQL query
|
|
* @return unknown
|
|
*/
|
|
function field ($name, $sql)
|
|
{
|
|
$data = $this->one($sql);
|
|
if (empty($data[$name]))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return $data[$name];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if it's connected to the database
|
|
*
|
|
* @return boolean True if the database is connected, else false
|
|
*/
|
|
function isConnected()
|
|
{
|
|
return $this->connected;
|
|
}
|
|
|
|
/**
|
|
* Outputs the contents of the queries log.
|
|
*
|
|
* @param boolean $sorted
|
|
*/
|
|
function showLog($sorted=false)
|
|
{
|
|
$log = $sorted?
|
|
sortByKey($this->_queriesLog, 'took', 'desc', SORT_NUMERIC):
|
|
$this->_queriesLog;
|
|
|
|
if($this->_queriesCnt > 1)
|
|
{
|
|
$text = 'queries';
|
|
}
|
|
else
|
|
{
|
|
$text = 'query';
|
|
}
|
|
print("<table border=\"1\">\n<tr><th colspan=\"7\">{$this->_queriesCnt} {$text} took {$this->_queriesTime} ms</th></tr>\n");
|
|
print("<tr><td>Nr</td><td>Query</td><td>Error</td><td>Affected</td><td>Num. rows</td><td>Took (ms)</td></tr>\n");
|
|
|
|
foreach($log as $k => $i)
|
|
{
|
|
print("<tr><td>".($k + 1)."</td><td>{$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");
|
|
}
|
|
|
|
print("</table>\n");
|
|
}
|
|
|
|
/**
|
|
* Log given SQL query.
|
|
*
|
|
* @param string $sql SQL statement
|
|
*/
|
|
function logQuery($sql)
|
|
{
|
|
$this->_queriesCnt++;
|
|
$this->_queriesTime += $this->took;
|
|
|
|
$this->_queriesLog[] = array(
|
|
'query' => $sql,
|
|
'error' => $this->error,
|
|
'affected' => $this->affected,
|
|
'numRows' => $this->numRows,
|
|
'took' => $this->took
|
|
);
|
|
|
|
if (count($this->_queriesLog) > $this->_queriesLogMax)
|
|
{
|
|
array_pop($this->_queriesLog);
|
|
}
|
|
|
|
if ($this->error)
|
|
{
|
|
return false;// shouldn't we be logging errors somehow?
|
|
// TODO: Add hook to error log
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Output information about an SQL query. The SQL statement, number of rows in resultset,
|
|
* and execution time in microseconds. If the query fails, an error is output instead.
|
|
*
|
|
* @param string $sql Query to show information on.
|
|
*/
|
|
function showQuery($sql)
|
|
{
|
|
$error = $this->error;
|
|
|
|
if (strlen($sql) > 200 && !$this->fullDebug)
|
|
{
|
|
$sql = substr($sql, 0, 200) .'[...]';
|
|
}
|
|
|
|
if ($this->debug || $error)
|
|
{
|
|
print("<p style=\"text-align:left\"><b>Query:</b> {$sql} <small>[Aff:{$this->affected} Num:{$this->numRows} Took:{$this->took}ms]</small>");
|
|
if($error)
|
|
{
|
|
print("<br /><span style=\"color:Red;text-align:left\"><b>ERROR:</b> {$this->error}</span>");
|
|
}
|
|
print('</p>');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The "C" in CRUD
|
|
*
|
|
* @param Model $model
|
|
* @param array $fields
|
|
* @param array $values
|
|
* @return boolean Success
|
|
*/
|
|
function create(&$model, $fields = null, $values = null)
|
|
{
|
|
if ($fields == null)
|
|
{
|
|
unset($fields, $values);
|
|
$fields = array_keys($model->data);
|
|
$values = array_values($model->data);
|
|
}
|
|
|
|
foreach ($fields as $field)
|
|
{
|
|
$fieldInsert[] = $this->name($field);
|
|
}
|
|
|
|
$count = 0;
|
|
foreach ($values as $value)
|
|
{
|
|
$valueInsert[] = $this->value($value, $model->getColumnType($fields[$count]));
|
|
$count++;
|
|
}
|
|
|
|
if($this->execute('INSERT INTO '.$model->table.' ('.join(',', $fieldInsert).') VALUES ('.join(',', $valueInsert).')'))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* The "R" in CRUD
|
|
*
|
|
* @param Model $model
|
|
* @param array $queryData
|
|
* @param integer $recursive Number of levels of association
|
|
* @return unknown
|
|
*/
|
|
function read (&$model, $queryData = array(), $recursive = null)
|
|
{
|
|
$this->__scrubQueryData($queryData);
|
|
$null = null;
|
|
$array = array();
|
|
$linkedModels = array();
|
|
$this->__bypass = false;
|
|
$this->__assocJoins = null;
|
|
|
|
if(!is_null($recursive))
|
|
{
|
|
$_recursive = $model->recursive;
|
|
$model->recursive = $recursive;
|
|
}
|
|
|
|
if(!empty($queryData['fields']))
|
|
{
|
|
$this->__bypass = true;
|
|
}
|
|
|
|
if ($model->recursive > 0)
|
|
{
|
|
foreach($model->__associations as $type)
|
|
{
|
|
foreach($model->{$type} as $assoc => $assocData)
|
|
{
|
|
$linkModel =& $model->{$assocData['className']};
|
|
if($model->name == $linkModel->name && $type != 'hasAndBelongsToMany' && $type != 'hasMany')
|
|
{
|
|
if (true === $this->generateSelfAssociationQuery($model, $linkModel, $type, $assoc, $assocData, $queryData, false, $null))
|
|
{
|
|
$linkedModels[] = $type.'/'.$assoc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (true === $this->generateAssociationQuery($model, $linkModel, $type, $assoc, $assocData, $queryData, false, $null))
|
|
{
|
|
$linkedModels[] = $type.'/'.$assoc;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build final query SQL
|
|
$query = $this->generateAssociationQuery($model, $null, null, null, null, $queryData, false, $null);
|
|
$resultSet = $this->fetchAll($query);
|
|
|
|
if ($model->recursive > 0)
|
|
{
|
|
foreach($model->__associations as $type)
|
|
{
|
|
foreach($model->{$type} as $assoc => $assocData)
|
|
{
|
|
$linkModel =& $model->{$assocData['className']};
|
|
if (!in_array($type.'/'.$assoc, $linkedModels))
|
|
{
|
|
$this->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $array, true, $resultSet, $model->recursive);
|
|
}
|
|
elseif($model->recursive > 1 && ($type == 'belongsTo' || $type == 'hasOne'))
|
|
{
|
|
// Do recursive joins on belongsTo and hasOne relationships
|
|
$this->queryAssociation($model, $linkModel, $type, $assoc, $assocData, $array, true, $resultSet, $model->recursive - 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!is_null($recursive))
|
|
{
|
|
$model->recursive = $_recursive;
|
|
}
|
|
return $resultSet;
|
|
}
|
|
|
|
/**
|
|
* Enter description here...
|
|
*
|
|
* @param Model $model
|
|
* @param unknown_type $linkModel
|
|
* @param string $type Association type
|
|
* @param unknown_type $association
|
|
* @param unknown_type $assocData
|
|
* @param unknown_type $queryData
|
|
* @param unknown_type $external
|
|
* @param unknown_type $resultSet
|
|
* @param integer $recursive Number of levels of association
|
|
*/
|
|
function queryAssociation(&$model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive)
|
|
{
|
|
$query = $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet);
|
|
if ($query)
|
|
{
|
|
foreach ($resultSet as $i => $row)
|
|
{
|
|
$q = $this->insertQueryData($query, $resultSet, $association, $assocData, $model, $linkModel, $i);
|
|
$fetch = $this->fetchAll($q);
|
|
|
|
if (!empty($fetch) && is_array($fetch))
|
|
{
|
|
if ($recursive > 0)
|
|
{
|
|
foreach($linkModel->__associations as $type1)
|
|
{
|
|
if ($recursive > 1)
|
|
{
|
|
foreach($linkModel->{$type1} as $assoc1 => $assocData1)
|
|
{
|
|
$deepModel =& $linkModel->{$assocData1['className']};
|
|
if ($deepModel->name != $model->name)
|
|
{
|
|
$this->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$this->__mergeAssociation($resultSet[$i], $fetch, $association, $type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function __mergeAssociation(&$data, $merge, $association, $type)
|
|
{
|
|
if (isset($merge[0]) && !isset($merge[0][$association]))
|
|
{
|
|
$association = Inflector::pluralize($association);
|
|
}
|
|
|
|
if ($type == 'belongsTo' || $type == 'hasOne')
|
|
{
|
|
if (isset($merge[$association]))
|
|
{
|
|
$data[$association] = $merge[$association][0];
|
|
}
|
|
else
|
|
{
|
|
$data[$association] = $merge[0][$association];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach ($merge as $i => $row)
|
|
{
|
|
if (count($row) == 1)
|
|
{
|
|
$data[$association][] = $row[$association];
|
|
}
|
|
else
|
|
{
|
|
$tmp = array_merge($row[$association], $row);
|
|
unset($tmp[$association]);
|
|
$data[$association][] = $tmp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enter description here...
|
|
*
|
|
* @param unknown_type $model
|
|
* @param unknown_type $linkModel
|
|
* @param unknown_type $type
|
|
* @param unknown_type $association
|
|
* @param unknown_type $assocData
|
|
* @param unknown_type $queryData
|
|
* @param unknown_type $external
|
|
* @param unknown_type $resultSet
|
|
* @return unknown
|
|
*/
|
|
function generateSelfAssociationQuery(&$model, &$linkModel, $type, $association = null, $assocData = array(), &$queryData, $external = false, &$resultSet)
|
|
{
|
|
$alias = $association;
|
|
if(!isset($queryData['selfJoin']))
|
|
{
|
|
$queryData['selfJoin'] = array();
|
|
|
|
$sql = 'SELECT ' . join(', ', $this->fields($model, $model->name, $queryData['fields'])). ', ';
|
|
$sql .= join(', ', $this->fields($linkModel, $alias, ''));
|
|
$sql .= ' FROM '.$model->table.' AS ' . $model->name;
|
|
$sql .= ' LEFT JOIN '.$linkModel->table.' AS ' . $alias;
|
|
$sql .= ' ON ';
|
|
$sql .= $this->name($model->name).'.'.$this->name($assocData['foreignKey']);
|
|
$sql .= ' = '.$this->name($alias).'.'.$this->name($linkModel->primaryKey);
|
|
if (!in_array($sql, $queryData['selfJoin']))
|
|
{
|
|
$queryData['selfJoin'][] = $sql;
|
|
return true;
|
|
}
|
|
}
|
|
elseif (isset($linkModel))
|
|
{
|
|
return $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet);
|
|
}
|
|
else
|
|
{
|
|
if(isset($this->__assocJoins))
|
|
{
|
|
$replace = ', ';
|
|
$replace .= join(', ', $this->__assocJoins['fields']);
|
|
$replace .= ' FROM';
|
|
}
|
|
else
|
|
{
|
|
$replace = 'FROM';
|
|
}
|
|
$sql = $queryData['selfJoin'][0];
|
|
$sql .= ' ' . join(' ', $queryData['joins']);
|
|
$sql .= $this->conditions($queryData['conditions']).' '.$this->order($queryData['order']);
|
|
$sql .= ' '.$this->limit($queryData['limit']);
|
|
$result = preg_replace('/FROM/', $replace, $sql);
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enter description here...
|
|
*
|
|
* @param Model $model
|
|
* @param unknown_type $linkModel
|
|
* @param unknown_type $type
|
|
* @param unknown_type $association
|
|
* @param unknown_type $assocData
|
|
* @param unknown_type $queryData
|
|
* @param unknown_type $external
|
|
* @param unknown_type $resultSet
|
|
* @return unknown
|
|
*/
|
|
function generateAssociationQuery(&$model, &$linkModel, $type, $association = null, $assocData = array(), &$queryData, $external = false, &$resultSet)
|
|
{
|
|
$this->__scrubQueryData($queryData);
|
|
$joinedOnSelf = false;
|
|
if ($linkModel == null)
|
|
{
|
|
if(array_key_exists('selfJoin', $queryData))
|
|
{
|
|
return $this->generateSelfAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet);
|
|
}
|
|
else
|
|
{
|
|
if(isset($this->__assocJoins))
|
|
{
|
|
$joinFields = ', ';
|
|
$joinFields .= join(', ', $this->__assocJoins['fields']);
|
|
}
|
|
else
|
|
{
|
|
$joinFields = null;
|
|
}
|
|
// Generates primary query
|
|
$sql = 'SELECT ' . join(', ', $this->fields($model, $model->name, $queryData['fields'])) .$joinFields. ' FROM ';
|
|
$sql .= $this->name($model->table).' AS ';
|
|
$sql .= $this->name($model->name).' ' . join(' ', $queryData['joins']).' ';
|
|
$sql .= $this->conditions($queryData['conditions']).' '.$this->order($queryData['order']);
|
|
$sql .= ' '.$this->limit($queryData['limit']);
|
|
}
|
|
return $sql;
|
|
}
|
|
|
|
$alias = $association;
|
|
if($model->name == $linkModel->name)
|
|
{
|
|
$joinedOnSelf = true;
|
|
}
|
|
|
|
switch ($type)
|
|
{
|
|
case 'hasOne':
|
|
if ($external)
|
|
{
|
|
if (isset($assocData['finderQuery']))
|
|
{
|
|
return $assocData['finderQuery'];
|
|
}
|
|
if(!isset($assocData['fields']))
|
|
{
|
|
$assocData['fields'] = '';
|
|
}
|
|
$sql = 'SELECT '.join(', ', $this->fields($linkModel, $alias, $assocData['fields']));
|
|
$sql .= ' FROM '.$this->name($linkModel->table).' AS '.$alias.' ';
|
|
$conditions = $queryData['conditions'];
|
|
$condition = $model->escapeField($assocData['foreignKey']);
|
|
$condition .= '={$__cakeForeignKey__$}';
|
|
|
|
if (is_array($conditions))
|
|
{
|
|
$conditions[] = $condition;
|
|
}
|
|
else
|
|
{
|
|
$cond = $this->name($alias).'.'.$this->name($assocData['foreignKey']);
|
|
$cond .= '={$__cakeID__$}';
|
|
|
|
if (trim($conditions) != '')
|
|
{
|
|
$conditions .= ' AND ';
|
|
$conditions .= $cond;
|
|
}
|
|
}
|
|
$sql .= $this->conditions($queryData['conditions']) . $this->order($queryData['order']);
|
|
$sql .= $this->limit($queryData['limit']);
|
|
return $sql;
|
|
}
|
|
else
|
|
{
|
|
if(!isset($assocData['fields']))
|
|
{
|
|
$assocData['fields'] = '';
|
|
}
|
|
if($this->__bypass === false)
|
|
{
|
|
$fields = join(', ', $this->fields($linkModel, $alias, $assocData['fields']));
|
|
$this->__assocJoins['fields'][] = $fields;
|
|
}
|
|
else
|
|
{
|
|
$this->__assocJoins = null;
|
|
}
|
|
$sql = ' LEFT JOIN '.$this->name($linkModel->table);
|
|
$sql .= ' AS '.$this->name($alias).' ON '.$this->name($alias).'.';
|
|
$sql .= $this->name($assocData['foreignKey']).'='.$model->escapeField($model->primaryKey);
|
|
$sql .= $this->order($assocData['order']);
|
|
|
|
if (isset($assocData['conditions']) && !empty($assocData['conditions']))
|
|
{
|
|
if(is_array($queryData['conditions']))
|
|
{
|
|
$queryData['conditions'] = array_merge($assocData['conditions'], $queryData['conditions']);
|
|
}
|
|
else
|
|
{
|
|
$queryData['conditions'] = $assocData['conditions'];
|
|
}
|
|
}
|
|
|
|
if (!in_array($sql, $queryData['joins']))
|
|
{
|
|
$queryData['joins'][] = $sql;
|
|
}
|
|
return true;
|
|
}
|
|
break;
|
|
case 'belongsTo':
|
|
if ($external)
|
|
{
|
|
$sql = 'SELECT * FROM '.$this->name($linkModel->table).' AS '.$this->name($alias);
|
|
$conditions = $assocData['conditions'];
|
|
|
|
$condition = $linkModel->escapeField($linkModel->primaryKey);
|
|
$condition .= '={$__cakeForeignKey__$}';
|
|
|
|
if (is_array($conditions))
|
|
{
|
|
$conditions[] = $condition;
|
|
}
|
|
else
|
|
{
|
|
if (trim($conditions) != '')
|
|
{
|
|
$conditions .= ' AND ';
|
|
}
|
|
$conditions .= $condition;
|
|
}
|
|
$sql .= $this->conditions($conditions) . $this->order($assocData['order']);
|
|
if (isset($assocData['limit']))
|
|
{
|
|
$sql .= $this->limit($assocData['limit']);
|
|
}
|
|
return $sql;
|
|
}
|
|
else
|
|
{
|
|
if(!isset($assocData['fields']))
|
|
{
|
|
$assocData['fields'] = '';
|
|
}
|
|
if($this->__bypass === false)
|
|
{
|
|
$fields = join(', ', $this->fields($linkModel, $alias, $assocData['fields']));
|
|
$this->__assocJoins['fields'][] = $fields;
|
|
}
|
|
else
|
|
{
|
|
$this->__assocJoins = null;
|
|
}
|
|
$sql = ' LEFT JOIN '.$this->name($linkModel->table);
|
|
$sql .= ' AS ' . $this->name($alias) . ' ON ';
|
|
$sql .= $this->name($model->name).'.'.$this->name($assocData['foreignKey']);
|
|
$sql .= '='.$this->name($alias).'.'.$this->name($linkModel->primaryKey);
|
|
|
|
if (isset($assocData['conditions']) && !empty($assocData['conditions']))
|
|
{
|
|
if(is_array($queryData['conditions']))
|
|
{
|
|
$queryData['conditions'] = array_merge($assocData['conditions'], $queryData['conditions']);
|
|
}
|
|
else
|
|
{
|
|
$queryData['conditions'] = $assocData['conditions'];
|
|
}
|
|
}
|
|
|
|
if (!in_array($sql, $queryData['joins']))
|
|
{
|
|
$queryData['joins'][] = $sql;
|
|
}
|
|
return true;
|
|
}
|
|
break;
|
|
case 'hasMany':
|
|
if(isset($assocData['finderQuery']) && $assocData['finderQuery'] != null)
|
|
{
|
|
$sql = $assocData['finderQuery'];
|
|
}
|
|
else
|
|
{
|
|
$conditions = $assocData['conditions'];
|
|
$sql = 'SELECT '.join(', ', $this->fields($linkModel, $alias, $assocData['fields']));
|
|
$sql .= ' FROM '.$this->name($linkModel->table).' AS '. $this->name($alias);
|
|
|
|
if (is_array($conditions))
|
|
{
|
|
$conditions[$alias.'.'.$assocData['foreignKey']] = '{$__cakeID__$}';
|
|
}
|
|
else
|
|
{
|
|
$cond = $this->name($alias).'.'.$this->name($assocData['foreignKey']);
|
|
$cond .= '={$__cakeID__$}';
|
|
|
|
if (trim($conditions) != '')
|
|
{
|
|
$conditions .= ' AND ';
|
|
}
|
|
$conditions .= $cond;
|
|
}
|
|
$sql .= $this->conditions($conditions);
|
|
$sql .= $this->order($assocData['order']);
|
|
}
|
|
return $sql;
|
|
break;
|
|
case 'hasAndBelongsToMany':
|
|
if(isset($assocData['finderQuery']) && $assocData['finderQuery'] != null)
|
|
{
|
|
$sql = $assocData['finderQuery'];
|
|
}
|
|
else
|
|
{
|
|
$joinTbl = $this->name($assocData['joinTable']);
|
|
|
|
$sql = 'SELECT '.join(', ', $this->fields($linkModel, $alias, $assocData['fields']));
|
|
$sql .= ' FROM '.$this->name($linkModel->table).' AS '.$this->name($alias);
|
|
$sql .= ' JOIN '.$joinTbl.' ON '.$joinTbl;
|
|
$sql .= '.'.$this->name($assocData['foreignKey']).'={$__cakeID__$}';
|
|
$sql .= ' AND '.$joinTbl.'.'.$this->name($assocData['associationForeignKey']);
|
|
$sql .= ' = '.$this->name($alias).'.'.$this->name($linkModel->primaryKey);
|
|
|
|
$sql .= $this->conditions($assocData['conditions']);
|
|
$sql .= $this->order($assocData['order']);
|
|
}
|
|
return $sql;
|
|
break;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Generates and executes an SQL UPDATE statement for given model, fields, and values.
|
|
*
|
|
* @param Model $model
|
|
* @param array $fields
|
|
* @param array $values
|
|
* @return array
|
|
*/
|
|
function update (&$model, $fields = null, $values = null)
|
|
{
|
|
$updates = array();
|
|
$combined = array_combine($fields, $values);
|
|
foreach ($combined as $field => $value)
|
|
{
|
|
$updates[] = $this->name($field).'='.$this->value($value, $model->getColumnType($field));
|
|
}
|
|
|
|
$sql = 'UPDATE '.$this->name($model->table);
|
|
$sql .= ' SET '.join(',', $updates);
|
|
$sql .= ' WHERE '.$this->name($model->primaryKey).'='.$this->value($model->getID(), $model->getColumnType($model->primaryKey));
|
|
|
|
return $this->execute($sql);
|
|
}
|
|
|
|
/**
|
|
* Generates and executes an SQL DELETE statement for given id on given model.
|
|
*
|
|
* @param Model $model
|
|
* @param mixed $id Primary key id number to remove.
|
|
* @return boolean Success
|
|
*/
|
|
function delete (&$model, $id = null)
|
|
{
|
|
$_id = $model->id;
|
|
if ($id != null)
|
|
{
|
|
$model->id = $id;
|
|
}
|
|
if (!is_array($model->id))
|
|
{
|
|
$model->id = array($model->id);
|
|
}
|
|
foreach ($model->id as $id)
|
|
{
|
|
$result = $this->execute('DELETE FROM '.$this->name($model->table).' WHERE '.$this->name($model->primaryKey).'='.$this->value($id));
|
|
}
|
|
if ($result)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns a key formatted like a string Model.fieldname(i.e. Post.title, or Country.name)
|
|
*
|
|
* @param unknown_type $model
|
|
* @param unknown_type $key
|
|
* @param unknown_type $assoc
|
|
* @return string
|
|
*/
|
|
function resolveKey($model, $key, $assoc = null)
|
|
{
|
|
if ($assoc == null)
|
|
{
|
|
$assoc = $model->name;
|
|
}
|
|
|
|
if (!strpos('.', $key))
|
|
{
|
|
return $this->name($model->table).'.'.$this->name($key);
|
|
}
|
|
return $key;
|
|
}
|
|
|
|
/**
|
|
* Returns the column type of a given
|
|
*
|
|
* @param Model $model
|
|
* @param string $field
|
|
*/
|
|
function getColumnType (&$model, $field)
|
|
{
|
|
return $model->getColumnType($field);
|
|
}
|
|
|
|
/**
|
|
* Private helper method to remove query metadata in given data array.
|
|
*
|
|
* @param array $data
|
|
*/
|
|
function __scrubQueryData(&$data)
|
|
{
|
|
if (!isset($data['conditions']))
|
|
{
|
|
$data['conditions'] = ' 1 = 1 ';
|
|
}
|
|
if (!isset($data['fields']))
|
|
{
|
|
$data['fields'] = '';
|
|
}
|
|
if (!isset($data['joins']))
|
|
{
|
|
$data['joins'] = array();
|
|
}
|
|
if (!isset($data['order']))
|
|
{
|
|
$data['order'] = '';
|
|
}
|
|
if (!isset($data['limit']))
|
|
{
|
|
$data['limit'] = '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates the fields list of an SQL query.
|
|
*
|
|
* @param Model $model
|
|
* @param string $alias Alias tablename
|
|
* @param mixed $fields
|
|
* @return array
|
|
*/
|
|
function fields (&$model, $alias, $fields)
|
|
{
|
|
if (is_array($fields))
|
|
{
|
|
$fields = $fields;
|
|
}
|
|
else
|
|
{
|
|
if ($fields != null)
|
|
{
|
|
if (strpos($fields, ','))
|
|
{
|
|
$fields = explode(',', $fields);
|
|
}
|
|
else
|
|
{
|
|
$fields = array($fields);
|
|
}
|
|
$fields = array_map('trim', $fields);
|
|
}
|
|
else
|
|
{
|
|
foreach ($model->_tableInfo->value as $field)
|
|
{
|
|
$fields[]= $field['name'];
|
|
}
|
|
}
|
|
}
|
|
|
|
$count = count($fields);
|
|
if ($count >= 1 && $fields[0] != '*')
|
|
{
|
|
for ($i = 0; $i < $count; $i++)
|
|
{
|
|
if(!preg_match('/^.+\\(.*\\)/', $fields[$i]))
|
|
{
|
|
$dot = strrpos($fields[$i], '.');
|
|
if ($dot === false)
|
|
{
|
|
$fields[$i] = $this->name($alias).'.'.$this->name($fields[$i]);
|
|
}
|
|
else
|
|
{
|
|
$build = explode('.',$fields[$i]);
|
|
$fields[$i] = $this->name($build[0]).'.'.$this->name($build[1]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $fields;
|
|
}
|
|
|
|
/**
|
|
* Creates a WHERE clause by parsing given conditions data.
|
|
*
|
|
* @param mixed $conditions Array or string of conditions
|
|
* @return string SQL fragment
|
|
*/
|
|
function conditions ($conditions)
|
|
{
|
|
$clause = '';
|
|
if (!is_array($conditions))
|
|
{
|
|
if (!preg_match('/^WHERE\\x20|^GROUP\\x20BY\\x20|^HAVING\\x20|^ORDER\\x20BY\\x20/i', $conditions, $match))
|
|
{
|
|
$clause = ' WHERE ';
|
|
}
|
|
}
|
|
if (is_string($conditions))
|
|
{
|
|
if (trim($conditions) == '')
|
|
{
|
|
$conditions = ' 1 = 1';
|
|
}
|
|
else
|
|
{
|
|
$start = null;
|
|
$end = null;
|
|
if(!empty($this->startQuote))
|
|
{
|
|
$start = '\\\\'.$this->startQuote.'\\\\';
|
|
}
|
|
$end = $this->endQuote;
|
|
if(!empty($this->endQuote))
|
|
{
|
|
$end = '\\\\'.$this->endQuote.'\\\\';
|
|
}
|
|
preg_match_all('/(\'[^\'\\\]*(?:\\\.[^\'\\\]*)*\')|(?P<field>[a-z0-9_'.$start.$end.']*\\.[a-z0-9_'.$start.$end.']*)/i', $conditions, $match, PREG_PATTERN_ORDER);
|
|
|
|
if(isset($match['field'][0]))
|
|
{
|
|
$pregCount = count($match['field']);
|
|
for ($i = 0; $i < $pregCount; $i++)
|
|
{
|
|
if(!empty($match['field'][$i]))
|
|
{
|
|
$conditions = preg_replace('/'.$match['field'][$i].'/', $this->name($match['field'][$i]), $conditions);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $clause.$conditions;
|
|
}
|
|
else
|
|
{
|
|
$clause = ' WHERE ';
|
|
$out = $this->conditionKeysToString($conditions);
|
|
return $clause . ' ('.join(') AND (', $out).')';
|
|
}
|
|
}
|
|
|
|
function conditionKeysToString($conditions)
|
|
{
|
|
$out = array();
|
|
$operator = null;
|
|
$bool = array('and', 'or', 'and not', 'or not', 'xor', '||', '&&');
|
|
|
|
foreach ($conditions as $key => $value)
|
|
{
|
|
if (in_array(low(trim($key)), $bool))
|
|
{
|
|
$out[] = '('.join(') '.$key.' (', $this->conditionKeysToString($value)).')';
|
|
}
|
|
else
|
|
{
|
|
if (is_array($value))
|
|
{
|
|
$data = $key . ' IN (';
|
|
foreach ($value as $valElement)
|
|
{
|
|
$data .= $this->value($valElement) . ', ';
|
|
}
|
|
$data[strlen($data)-2] = ')';
|
|
}
|
|
elseif (is_numeric($key))
|
|
{
|
|
$data = ' '. $value;
|
|
}
|
|
elseif ($value === null)
|
|
{
|
|
$data = $this->name($key).' = NULL';
|
|
}
|
|
elseif ($value === '')
|
|
{
|
|
$data = $this->name($key)." = ''";
|
|
}
|
|
elseif (preg_match('/^(?P<operator>[a-z]*\\([a-z0-9]*\\)\\x20?|like\\x20?|or\\x20?|between\\x20?|regexp\\x20?|[<>=!]{1,3}\\x20?)?(?P<value>.*)/i', $value, $match))
|
|
{
|
|
if (preg_match('/(?P<conditional>\\x20[\\w]*\\x20)/', $key, $regs))
|
|
{
|
|
$clause = $regs['conditional'];
|
|
$key = preg_replace('/'.$regs['conditional'].'/', '', $key);
|
|
}
|
|
if(empty($match['operator']))
|
|
{
|
|
$match['operator'] = ' = ';
|
|
}
|
|
if (strpos($match['value'], '-!') === 0)
|
|
{
|
|
$match['value'] = str_replace('-!', '', $match['value']);
|
|
$data = $this->name($key) . ' '.$match['operator'].' '. $match['value'];
|
|
}
|
|
else
|
|
{
|
|
if($match['value'] != '' && !is_numeric($match['value']))
|
|
{
|
|
$match['value'] = $this->value($match['value']);
|
|
}
|
|
|
|
$data = $this->name($key) . ' '.$match['operator'].' '. $match['value'];
|
|
}
|
|
}
|
|
$out[] = $operator.$data;
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
|
|
/**
|
|
* To be overridden in subclasses.
|
|
*
|
|
*/
|
|
function limit ()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Returns an ORDER BY clause as a string.
|
|
*
|
|
* @param string $key Field reference, as a key (i.e. Post.title)
|
|
* @param string $direction Direction (ASC or DESC)
|
|
* @return string ORDER BY clause
|
|
*/
|
|
function order ($keys, $direction = 'ASC')
|
|
{
|
|
if (empty($keys))
|
|
{
|
|
return '';
|
|
}
|
|
if(is_array($keys))
|
|
{
|
|
foreach($keys as $key => $value)
|
|
{
|
|
if(is_numeric($key))
|
|
{
|
|
$key = $value;
|
|
$value = ' '.$direction;
|
|
}
|
|
else
|
|
{
|
|
$value= ' '.$value;
|
|
}
|
|
$order[] = $this->name($key).$value;
|
|
}
|
|
return ' ORDER BY '.join(',', $order);
|
|
}
|
|
else
|
|
{
|
|
$keys = preg_replace('/ORDER\\x20BY/i', '', $keys);
|
|
if (strpos('.', $keys))
|
|
{
|
|
preg_match_all('/([a-zA-Z0-9_]{1,})\\.([a-zA-Z0-9_]{1,})/', $keys, $result, PREG_PATTERN_ORDER);
|
|
$pregCount = count($result[0]);
|
|
|
|
for ($i = 0; $i < $pregCount; $i++)
|
|
{
|
|
$keys = preg_replace('/'.$result[0][$i].'/', $this->name($result[0][$i]), $keys);
|
|
}
|
|
if (preg_match('/\\x20ASC|\\x20DESC/i', $keys))
|
|
{
|
|
return ' ORDER BY '.$keys;
|
|
}
|
|
else
|
|
{
|
|
return ' ORDER BY '.$keys.' '.$direction;;
|
|
}
|
|
}
|
|
elseif (preg_match('/(?P<direction>\\x20ASC|\\x20DESC)/i', $keys, $match))
|
|
{
|
|
$direction = $match['direction'];
|
|
$keys = preg_replace('/'.$match['direction'].'/', '', $keys);
|
|
return ' ORDER BY '.$keys.$direction;
|
|
}
|
|
else
|
|
{
|
|
$direction = ' '.$direction;
|
|
}
|
|
return ' ORDER BY '.$keys.$direction;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disconnects database, kills the connection and says the connection is closed,
|
|
* and if DEBUG is turned on, the log for this object is shown.
|
|
*
|
|
*/
|
|
function close ()
|
|
{
|
|
if ($this->fullDebug)
|
|
{
|
|
$this->showLog();
|
|
}
|
|
$this->_conn = NULL;
|
|
$this->connected = false;
|
|
}
|
|
|
|
/**
|
|
* Destructor. Closes connection to the database.
|
|
*
|
|
*/
|
|
function __destruct()
|
|
{
|
|
if ($this->__transactionStarted)
|
|
{
|
|
$this->rollback();
|
|
}
|
|
$this->close();
|
|
parent::__destruct();
|
|
}
|
|
|
|
/**
|
|
* Checks if the specified table contains any record matching specified SQL
|
|
*
|
|
* @param string $table Name of table to look in
|
|
* @param string $sql SQL WHERE clause (condition only, not the "WHERE" part)
|
|
* @return boolean True if the table has a matching record, else false
|
|
*/
|
|
function hasAny($table, $sql)
|
|
{
|
|
$out = $this->one("SELECT COUNT(*) AS count FROM {$table}".($sql? " WHERE {$sql}":""));
|
|
return is_array($out)? $out[0]['count']: false;
|
|
}
|
|
}
|
|
?>
|