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

/lib/pkp/classes/db/DAO.inc.php

https://github.com/lib-uoguelph-ca/ocs
PHP | 553 lines | 325 code | 48 blank | 180 comment | 59 complexity | 77e962de6d3952b566927435691dbc44 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /**
  3. * @defgroup db
  4. */
  5. /**
  6. * @file classes/db/DAO.inc.php
  7. *
  8. * Copyright (c) 2000-2012 John Willinsky
  9. * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING.
  10. *
  11. * @class DAO
  12. * @ingroup db
  13. * @see DAORegistry
  14. *
  15. * @brief Operations for retrieving and modifying objects from a database.
  16. */
  17. // $Id$
  18. import('db.DBConnection');
  19. import('db.DAOResultFactory');
  20. import('core.DataObject');
  21. define('SORT_DIRECTION_ASC', 0x00001);
  22. define('SORT_DIRECTION_DESC', 0x00002);
  23. class DAO {
  24. /** The database connection object */
  25. var $_dataSource;
  26. /**
  27. * Constructor.
  28. * Initialize the database connection.
  29. */
  30. function DAO($dataSource = null, $callHooks = true) {
  31. if ($callHooks === true && checkPhpVersion('4.3.0')) {
  32. $trace = debug_backtrace();
  33. // Call hooks based on the calling entity, assuming
  34. // this method is only called by a subclass. Results
  35. // in hook calls named e.g. "sessiondao::_Constructor"
  36. if (HookRegistry::call(strtolower($trace[1]['class']) . '::_Constructor', array(&$this, &$dataSource))) {
  37. return;
  38. }
  39. }
  40. if (!isset($dataSource)) {
  41. $this->_dataSource =& DBConnection::getConn();
  42. } else {
  43. $this->_dataSource = $dataSource;
  44. }
  45. }
  46. /**
  47. * Execute a SELECT SQL statement.
  48. * @param $sql string the SQL statement
  49. * @param $params array parameters for the SQL statement
  50. * @return ADORecordSet
  51. */
  52. function &retrieve($sql, $params = false, $callHooks = true) {
  53. if ($callHooks === true && checkPhpVersion('4.3.0')) {
  54. $trace = debug_backtrace();
  55. // Call hooks based on the calling entity, assuming
  56. // this method is only called by a subclass. Results
  57. // in hook calls named e.g. "sessiondao::_getsession"
  58. // (always lower case).
  59. $value = null;
  60. if (HookRegistry::call(strtolower($trace[1]['class'] . '::_' . $trace[1]['function']), array(&$sql, &$params, &$value))) {
  61. return $value;
  62. }
  63. }
  64. $start = Core::microtime();
  65. $result =& $this->_dataSource->execute($sql, $params !== false && !is_array($params) ? array($params) : $params);
  66. DBConnection::logQuery($sql, $start, $params);
  67. if ($this->_dataSource->errorNo()) {
  68. // FIXME Handle errors more elegantly.
  69. fatalError('DB Error: ' . $this->_dataSource->errorMsg());
  70. }
  71. return $result;
  72. }
  73. /**
  74. * Execute a cached SELECT SQL statement.
  75. * @param $sql string the SQL statement
  76. * @param $params array parameters for the SQL statement
  77. * @return ADORecordSet
  78. */
  79. function &retrieveCached($sql, $params = false, $secsToCache = 3600, $callHooks = true) {
  80. if ($callHooks === true && checkPhpVersion('4.3.0')) {
  81. $trace = debug_backtrace();
  82. // Call hooks based on the calling entity, assuming
  83. // this method is only called by a subclass. Results
  84. // in hook calls named e.g. "sessiondao::_getsession"
  85. // (all lowercase).
  86. $value = null;
  87. if (HookRegistry::call(strtolower($trace[1]['class'] . '::_' . $trace[1]['function']), array(&$sql, &$params, &$secsToCache, &$value))) {
  88. return $value;
  89. }
  90. }
  91. $this->setCacheDir();
  92. $start = Core::microtime();
  93. $result =& $this->_dataSource->CacheExecute($secsToCache, $sql, $params !== false && !is_array($params) ? array($params) : $params);
  94. DBConnection::logQuery($sql, $start, $params);
  95. if ($this->_dataSource->errorNo()) {
  96. // FIXME Handle errors more elegantly.
  97. fatalError('DB Error: ' . $this->_dataSource->errorMsg());
  98. }
  99. return $result;
  100. }
  101. /**
  102. * Execute a SELECT SQL statement with LIMIT on the rows returned.
  103. * @param $sql string the SQL statement
  104. * @param $params array parameters for the SQL statement
  105. * @param $numRows int maximum number of rows to return in the result set
  106. * @param $offset int row offset in the result set
  107. * @return ADORecordSet
  108. */
  109. function &retrieveLimit($sql, $params = false, $numRows = false, $offset = false, $callHooks = true) {
  110. if ($callHooks === true && checkPhpVersion('4.3.0')) {
  111. $trace = debug_backtrace();
  112. // Call hooks based on the calling entity, assuming
  113. // this method is only called by a subclass. Results
  114. // in hook calls named e.g. "sessiondao::_getsession"
  115. // (all lowercase).
  116. $value = null;
  117. if (HookRegistry::call(strtolower($trace[1]['class'] . '::_' . $trace[1]['function']), array(&$sql, &$params, &$numRows, &$offset, &$value))) {
  118. return $value;
  119. }
  120. }
  121. $start = Core::microtime();
  122. $result =& $this->_dataSource->selectLimit($sql, $numRows === false ? -1 : $numRows, $offset === false ? -1 : $offset, $params !== false && !is_array($params) ? array($params) : $params);
  123. DBConnection::logQuery($sql, $start, $params);
  124. if ($this->_dataSource->errorNo()) {
  125. fatalError('DB Error: ' . $this->_dataSource->errorMsg());
  126. }
  127. return $result;
  128. }
  129. /**
  130. * Execute a SELECT SQL statment, returning rows in the range supplied.
  131. * @param $sql string the SQL statement
  132. * @param $params array parameters for the SQL statement
  133. * @param $dbResultRange object the DBResultRange object describing the desired range
  134. */
  135. function &retrieveRange($sql, $params = false, $dbResultRange = null, $callHooks = true) {
  136. if ($callHooks === true && checkPhpVersion('4.3.0')) {
  137. $trace = debug_backtrace();
  138. // Call hooks based on the calling entity, assuming
  139. // this method is only called by a subclass. Results
  140. // in hook calls named e.g. "sessiondao::_getsession"
  141. $value = null;
  142. if (HookRegistry::call(strtolower($trace[1]['class'] . '::_' . $trace[1]['function']), array(&$sql, &$params, &$dbResultRange, &$value))) {
  143. return $value;
  144. }
  145. }
  146. if (isset($dbResultRange) && $dbResultRange->isValid()) {
  147. $start = Core::microtime();
  148. $result =& $this->_dataSource->PageExecute($sql, $dbResultRange->getCount(), $dbResultRange->getPage(), $params);
  149. DBConnection::logQuery($sql, $start, $params);
  150. if ($this->_dataSource->errorNo()) {
  151. fatalError('DB Error: ' . $this->_dataSource->errorMsg());
  152. }
  153. }
  154. else {
  155. $result =& $this->retrieve($sql, $params, false);
  156. }
  157. return $result;
  158. }
  159. /**
  160. * Execute an INSERT, UPDATE, or DELETE SQL statement.
  161. * @param $sql the SQL statement the execute
  162. * @param $params an array of parameters for the SQL statement
  163. * @param $callHooks boolean Whether or not to call hooks
  164. * @param $dieOnError boolean Whether or not to die if an error occurs
  165. * @return boolean
  166. */
  167. function update($sql, $params = false, $callHooks = true, $dieOnError = true) {
  168. if ($callHooks === true && checkPhpVersion('4.3.0')) {
  169. $trace = debug_backtrace();
  170. // Call hooks based on the calling entity, assuming
  171. // this method is only called by a subclass. Results
  172. // in hook calls named e.g. "sessiondao::_updateobject"
  173. // (all lowercase)
  174. $value = null;
  175. if (HookRegistry::call(strtolower($trace[1]['class'] . '::_' . $trace[1]['function']), array(&$sql, &$params, &$value))) {
  176. return $value;
  177. }
  178. }
  179. $start = Core::microtime();
  180. $this->_dataSource->execute($sql, $params !== false && !is_array($params) ? array($params) : $params);
  181. DBConnection::logQuery($sql, $start, $params);
  182. if ($dieOnError && $this->_dataSource->errorNo()) {
  183. fatalError('DB Error: ' . $this->_dataSource->errorMsg());
  184. }
  185. return $this->_dataSource->errorNo() == 0 ? true : false;
  186. }
  187. /**
  188. * Insert a row in a table, replacing an existing row if necessary.
  189. * @param $table string
  190. * @param $arrFields array Associative array of colName => value
  191. * @param $keyCols array Array of column names that are keys
  192. */
  193. function replace($table, $arrFields, $keyCols) {
  194. $arrFields = array_map(array($this->_dataSource, 'qstr'), $arrFields);
  195. $this->_dataSource->Replace($table, $arrFields, $keyCols, false);
  196. }
  197. /**
  198. * Return the last ID inserted in an autonumbered field.
  199. * @param $table string table name
  200. * @param $id string the ID/key column in the table
  201. * @return int
  202. */
  203. function getInsertId($table = '', $id = '', $callHooks = true) {
  204. return $this->_dataSource->po_insert_id($table, $id);
  205. }
  206. /**
  207. * Configure the caching directory for database results
  208. * NOTE: This is implemented as a GLOBAL setting and cannot
  209. * be set on a per-connection basis.
  210. */
  211. function setCacheDir() {
  212. static $cacheDir;
  213. if (!isset($cacheDir)) {
  214. global $ADODB_CACHE_DIR;
  215. $cacheDir = CacheManager::getFileCachePath() . '/_db';
  216. $ADODB_CACHE_DIR = $cacheDir;
  217. }
  218. }
  219. /**
  220. * Flush the system cache.
  221. */
  222. function flushCache() {
  223. $this->setCacheDir();
  224. $this->_dataSource->CacheFlush();
  225. }
  226. /**
  227. * Return datetime formatted for DB insertion.
  228. * @param $dt int/string *nix timestamp or ISO datetime string
  229. * @return string
  230. */
  231. function datetimeToDB($dt) {
  232. return $this->_dataSource->DBTimeStamp($dt);
  233. }
  234. /**
  235. * Return date formatted for DB insertion.
  236. * @param $d int/string *nix timestamp or ISO date string
  237. * @return string
  238. */
  239. function dateToDB($d) {
  240. return $this->_dataSource->DBDate($d);
  241. }
  242. /**
  243. * Return datetime from DB as ISO datetime string.
  244. * @param $dt string datetime from DB
  245. * @return string
  246. */
  247. function datetimeFromDB($dt) {
  248. if ($dt === null) return null;
  249. return $this->_dataSource->UserTimeStamp($dt, 'Y-m-d H:i:s');
  250. }
  251. /**
  252. * Return date from DB as ISO date string.
  253. * @param $d string date from DB
  254. * @return string
  255. */
  256. function dateFromDB($d) {
  257. if ($d === null) return null;
  258. return $this->_dataSource->UserDate($d, 'Y-m-d');
  259. }
  260. /**
  261. * Convert a stored type from the database
  262. * @param $value string Value from DB
  263. * @param $type string Type from DB
  264. * @return mixed
  265. */
  266. function convertFromDB($value, $type) {
  267. switch ($type) {
  268. case 'bool':
  269. $value = (bool) $value;
  270. break;
  271. case 'int':
  272. $value = (int) $value;
  273. break;
  274. case 'float':
  275. $value = (float) $value;
  276. break;
  277. case 'object':
  278. $value = unserialize($value);
  279. break;
  280. case 'date':
  281. if ($value !== null) $value = strtotime($value);
  282. break;
  283. case 'string':
  284. default:
  285. // Nothing required.
  286. break;
  287. }
  288. return $value;
  289. }
  290. /**
  291. * Get the type of a value to be stored in the database
  292. * @param $value string
  293. * @return string
  294. */
  295. function getType($value) {
  296. switch (gettype($value)) {
  297. case 'boolean':
  298. case 'bool':
  299. return 'bool';
  300. case 'integer':
  301. case 'int':
  302. return 'int';
  303. case 'double':
  304. case 'float':
  305. return 'float';
  306. case 'array':
  307. case 'object':
  308. return 'object';
  309. case 'string':
  310. default:
  311. return 'string';
  312. }
  313. }
  314. /**
  315. * Convert a PHP variable into a string to be stored in the DB
  316. * @param $value mixed
  317. * @param $type string
  318. * @return string
  319. */
  320. function convertToDB($value, &$type) {
  321. if ($type == null) {
  322. $type = $this->getType($value);
  323. }
  324. switch ($type) {
  325. case 'object':
  326. $value = serialize($value);
  327. break;
  328. case 'bool':
  329. $value = $value ? 1 : 0;
  330. break;
  331. case 'date':
  332. if ($value !== null) {
  333. if (!is_numeric($value)) $value = strtotime($value);
  334. $value = strftime('%Y-%m-%d %H:%M:%S', $value);
  335. }
  336. break;
  337. default:
  338. }
  339. return $value;
  340. }
  341. function nullOrInt($value) {
  342. return (empty($value)?null:(int) $value);
  343. }
  344. function getAdditionalFieldNames() {
  345. $returner = array();
  346. if (checkPhpVersion('4.3.0')) {
  347. $trace = debug_backtrace();
  348. // Call hooks based on the calling entity, assuming
  349. // this method is only called by a subclass. Results
  350. // in hook calls named e.g. "sessiondao::getAdditionalFieldNames"
  351. // (class names lowercase)
  352. HookRegistry::call(strtolower($trace[2]['class']) . '::getAdditionalFieldNames', array(&$this, &$returner));
  353. }
  354. return $returner;
  355. }
  356. function getLocaleFieldNames() {
  357. $returner = array();
  358. if (checkPhpVersion('4.3.0')) {
  359. $trace = debug_backtrace();
  360. // Call hooks based on the calling entity, assuming
  361. // this method is only called by a subclass. Results
  362. // in hook calls named e.g. "sessiondao::getLocaleFieldNames"
  363. // (class names lowercase)
  364. HookRegistry::call(strtolower($trace[2]['class']) . '::getLocaleFieldNames', array(&$this, &$returner));
  365. }
  366. return $returner;
  367. }
  368. /**
  369. * Update the settings table of a data object.
  370. * @param $tableName string
  371. * @param $dataObject DataObject
  372. * @param $idArray array
  373. */
  374. function updateDataObjectSettings($tableName, &$dataObject, $idArray) {
  375. // Initialize variables
  376. $idFields = array_keys($idArray);
  377. $idFields[] = 'locale';
  378. $idFields[] = 'setting_name';
  379. // Build a data structure that we can process efficiently.
  380. $translated = $metadata = 1;
  381. $settings = !$metadata;
  382. $settingFields = array(
  383. // Translated data
  384. $translated => array(
  385. $settings => $this->getLocaleFieldNames(),
  386. $metadata => $dataObject->getLocaleMetadataFieldNames()
  387. ),
  388. // Shared data
  389. !$translated => array(
  390. $settings => $this->getAdditionalFieldNames(),
  391. $metadata => $dataObject->getAdditionalMetadataFieldNames()
  392. )
  393. );
  394. // Loop over all fields and update them in the settings table
  395. $updateArray = $idArray;
  396. $noLocale = 0;
  397. $staleMetadataSettings = array();
  398. foreach ($settingFields as $isTranslated => $fieldTypes) {
  399. foreach ($fieldTypes as $isMetadata => $fieldNames) {
  400. foreach ($fieldNames as $fieldName) {
  401. // Now we have the following control data:
  402. // - $isTranslated: true for translated data, false data shared between locales
  403. // - $isMetadata: true for metadata fields, false for normal settings
  404. // - $fieldName: the field in the data object to be updated
  405. if ($dataObject->hasData($fieldName)) {
  406. if ($isTranslated) {
  407. // Translated data comes in as an array
  408. // with the locale as the key.
  409. $values = $dataObject->getData($fieldName);
  410. if (!is_array($values)) {
  411. // Inconsistent data: should have been an array
  412. assert(false);
  413. continue;
  414. }
  415. } else {
  416. // Transform shared data into an array so that
  417. // we can handle them the same way as translated data.
  418. $values = array(
  419. $noLocale => $dataObject->getData($fieldName)
  420. );
  421. }
  422. // Loop over the values and update them in the database
  423. foreach ($values as $locale => $value) {
  424. $updateArray['locale'] = ($locale === $noLocale ? '' : $locale);
  425. $updateArray['setting_name'] = $fieldName;
  426. $updateArray['setting_type'] = null;
  427. // Convert the data value and implicitly set the setting type.
  428. $updateArray['setting_value'] = $this->convertToDB($value, $updateArray['setting_type']);
  429. $this->replace($tableName, $updateArray, $idFields);
  430. }
  431. } else {
  432. // Meta-data fields are maintained "sparsly". Only set fields will be
  433. // recorded in the settings table. Fields that are not explicity set
  434. // in the data object will be deleted.
  435. if ($isMetadata) $staleMetadataSettings[] = $fieldName;
  436. }
  437. }
  438. }
  439. }
  440. // Remove stale meta-data
  441. if (count($staleMetadataSettings)) {
  442. $removeWhere = '';
  443. $removeParams = array();
  444. foreach ($idArray as $idField => $idValue) {
  445. if (!empty($removeWhere)) $removeWhere .= ' AND ';
  446. $removeWhere .= $idField.' = ?';
  447. $removeParams[] = $idValue;
  448. }
  449. $removeWhere .= rtrim(' AND setting_name IN ( '.str_repeat('? ,', count($staleMetadataSettings)), ',').')';
  450. $removeParams = array_merge($removeParams, $staleMetadataSettings);
  451. $removeSql = 'DELETE FROM '.$tableName.' WHERE '.$removeWhere;
  452. $this->update($removeSql, $removeParams);
  453. }
  454. }
  455. function getDataObjectSettings($tableName, $idFieldName, $idFieldValue, &$dataObject) {
  456. if ($idFieldName !== null) {
  457. $sql = "SELECT * FROM $tableName WHERE $idFieldName = ?";
  458. $params = array($idFieldValue);
  459. } else {
  460. $sql = "SELECT * FROM $tableName";
  461. $params = false;
  462. }
  463. $start = Core::microtime();
  464. $result =& $this->retrieve($sql, $params);
  465. while (!$result->EOF) {
  466. $row =& $result->getRowAssoc(false);
  467. $dataObject->setData(
  468. $row['setting_name'],
  469. $this->convertFromDB(
  470. $row['setting_value'],
  471. $row['setting_type']
  472. ),
  473. empty($row['locale'])?null:$row['locale']
  474. );
  475. unset($row);
  476. $result->MoveNext();
  477. }
  478. $result->Close();
  479. unset($result);
  480. }
  481. /**
  482. * Get the driver for this connection.
  483. * @return string
  484. */
  485. function getDriver() {
  486. $conn =& DBConnection::getInstance();
  487. return $conn->getDriver();
  488. }
  489. /**
  490. * Get the driver for this connection.
  491. * @param $direction int
  492. * @return string
  493. */
  494. function getDirectionMapping($direction) {
  495. switch ($direction) {
  496. case SORT_DIRECTION_ASC:
  497. return 'ASC';
  498. case SORT_DIRECTION_DESC:
  499. return 'DESC';
  500. default:
  501. return 'ASC';
  502. }
  503. }
  504. }
  505. ?>