Open Journal Systems  3.3.0
DAO.inc.php
1 <?php
2 
23 import('lib.pkp.classes.db.DBConnection');
24 import('lib.pkp.classes.db.DAOResultFactory');
25 import('lib.pkp.classes.db.DBResultRange');
26 import('lib.pkp.classes.core.DataObject');
27 
28 define('SORT_DIRECTION_ASC', 0x00001);
29 define('SORT_DIRECTION_DESC', 0x00002);
30 
31 class DAO {
34 
39  function getDataSource() {
40  return $this->_dataSource;
41  }
42 
47  function setDataSource($dataSource) {
48  $this->_dataSource = $dataSource;
49  }
50 
54  function concat() {
55  $args = func_get_args();
56  return call_user_func_array(array($this->getDataSource(), 'Concat'), $args);
57  }
58 
63  function __construct($dataSource = null, $callHooks = true) {
64  if ($callHooks === true) {
65  // Call hooks based on the object name. Results
66  // in hook calls named e.g. "sessiondao::_Constructor"
67  if (HookRegistry::call(strtolower_codesafe(get_class($this)) . '::_Constructor', array($this, &$dataSource))) {
68  return;
69  }
70  }
71 
72  if (!isset($dataSource)) {
74  } else {
75  $this->setDataSource($dataSource);
76  }
77  }
78 
85  function &retrieve($sql, $params = false, $callHooks = true) {
86  if ($callHooks === true) {
87  $trace = debug_backtrace();
88  // Call hooks based on the calling entity, assuming
89  // this method is only called by a subclass. Results
90  // in hook calls named e.g. "sessiondao::_getsession"
91  // (always lower case).
92  $value = null;
93  if (HookRegistry::call(strtolower_codesafe($trace[1]['class'] . '::_' . $trace[1]['function']), array(&$sql, &$params, &$value))) {
94  return $value;
95  }
96  }
97 
98  $start = Core::microtime();
99  $dataSource = $this->getDataSource();
100  $result = $dataSource->execute($sql, $params !== false && !is_array($params) ? array($params) : $params);
101  if ($dataSource->errorNo()) {
102  // FIXME Handle errors more elegantly.
103  $this->handleError($dataSource, $sql);
104  }
105 
106  return $result;
107  }
108 
115  function &retrieveCached($sql, $params = false, $secsToCache = 3600, $callHooks = true) {
116  if ($callHooks === true) {
117  $trace = debug_backtrace();
118  // Call hooks based on the calling entity, assuming
119  // this method is only called by a subclass. Results
120  // in hook calls named e.g. "sessiondao::_getsession"
121  // (all lowercase).
122  $value = null;
123  if (HookRegistry::call(strtolower_codesafe($trace[1]['class'] . '::_' . $trace[1]['function']), array(&$sql, &$params, &$secsToCache, &$value))) {
124  return $value;
125  }
126  }
127 
128  $this->setCacheDir();
129 
130  $start = Core::microtime();
131  $dataSource = $this->getDataSource();
132  $result = $dataSource->CacheExecute($secsToCache, $sql, $params !== false && !is_array($params) ? array($params) : $params);
133  if ($dataSource->errorNo()) {
134  // FIXME Handle errors more elegantly.
135  $this->handleError($dataSource, $sql);
136  }
137  return $result;
138  }
139 
148  function &retrieveLimit($sql, $params = false, $numRows = false, $offset = false, $callHooks = true) {
149  if ($callHooks === true) {
150  $trace = debug_backtrace();
151  // Call hooks based on the calling entity, assuming
152  // this method is only called by a subclass. Results
153  // in hook calls named e.g. "sessiondao::_getsession"
154  // (all lowercase).
155  $value = null;
156  if (HookRegistry::call(strtolower_codesafe($trace[1]['class'] . '::_' . $trace[1]['function']), array(&$sql, &$params, &$numRows, &$offset, &$value))) {
157  return $value;
158  }
159  }
160 
161  $start = Core::microtime();
162  $dataSource = $this->getDataSource();
163  $result = $dataSource->selectLimit($sql, $numRows === false ? -1 : $numRows, $offset === false ? -1 : $offset, $params !== false && !is_array($params) ? array($params) : $params);
164  if ($dataSource->errorNo()) {
165  $this->handleError($dataSource, $sql);
166  }
167  return $result;
168  }
169 
176  function &retrieveRange($sql, $params = false, $dbResultRange = null, $callHooks = true) {
177  if ($callHooks === true) {
178  $trace = debug_backtrace();
179  // Call hooks based on the calling entity, assuming
180  // this method is only called by a subclass. Results
181  // in hook calls named e.g. "sessiondao::_getsession"
182  $value = null;
183  if (HookRegistry::call(strtolower_codesafe($trace[1]['class'] . '::_' . $trace[1]['function']), array(&$sql, &$params, &$dbResultRange, &$value))) {
184  return $value;
185  }
186  }
187 
188  if (isset($dbResultRange) && $dbResultRange->isValid()) {
189  $start = Core::microtime();
190  $dataSource = $this->getDataSource();
191  if (is_null($dbResultRange->getOffset())) {
192  $result = $dataSource->PageExecute($sql, $dbResultRange->getCount(), $dbResultRange->getPage(), $params);
193  } else {
194  $result = $dataSource->SelectLimit($sql, $dbResultRange->getCount(), $dbResultRange->getOffset(), $params);
195  }
196  if ($dataSource->errorNo()) {
197  $this->handleError($dataSource, $sql);
198  }
199  }
200  else {
201  $result = $this->retrieve($sql, $params, false);
202  }
203  return $result;
204  }
205 
214  function update($sql, $params = false, $callHooks = true, $dieOnError = true) {
215  if ($callHooks === true) {
216  $trace = debug_backtrace();
217  // Call hooks based on the calling entity, assuming
218  // this method is only called by a subclass. Results
219  // in hook calls named e.g. "sessiondao::_updateobject"
220  // (all lowercase)
221  $value = null;
222  if (HookRegistry::call(strtolower_codesafe($trace[1]['class'] . '::_' . $trace[1]['function']), array(&$sql, &$params, &$value))) {
223  return $value;
224  }
225  }
226 
227  $start = Core::microtime();
228  $dataSource = $this->getDataSource();
229  $dataSource->execute($sql, $params !== false && !is_array($params) ? array($params) : $params);
230  if ($dieOnError && $dataSource->errorNo()) {
231  $this->handleError($dataSource, $sql);
232  }
233  return $dataSource->errorNo() == 0 ? true : false;
234  }
235 
243  function replace($table, $arrFields, $keyCols) {
244  $dataSource = $this->getDataSource();
245  $arrFields = array_map(array($dataSource, 'qstr'), $arrFields);
246  return $dataSource->Replace($table, $arrFields, $keyCols, false);
247  }
248 
255  protected function _getInsertId($table = '', $id = '') {
256  $dataSource = $this->getDataSource();
257  return $dataSource->po_insert_id($table, $id);
258  }
259 
264  function getAffectedRows() {
265  $dataSource = $this->getDataSource();
266  return $dataSource->Affected_Rows();
267  }
268 
274  function setCacheDir() {
275  static $cacheDir;
276  if (!isset($cacheDir)) {
277  global $ADODB_CACHE_DIR;
278 
279  $cacheDir = CacheManager::getFileCachePath() . '/_db';
280 
281  $ADODB_CACHE_DIR = $cacheDir;
282  }
283  }
284 
288  function flushCache() {
289  $this->setCacheDir();
290  $dataSource = $this->getDataSource();
291  $dataSource->CacheFlush();
292  }
293 
299  function datetimeToDB($dt) {
300  $dataSource = $this->getDataSource();
301  return $dataSource->DBTimeStamp($dt);
302  }
303 
309  function dateToDB($d) {
310  $dataSource = $this->getDataSource();
311  return $dataSource->DBDate($d);
312  }
313 
319  function datetimeFromDB($dt) {
320  if ($dt === null) return null;
321  $dataSource = $this->getDataSource();
322  return $dataSource->UserTimeStamp($dt, 'Y-m-d H:i:s');
323  }
329  function dateFromDB($d) {
330  if ($d === null) return null;
331  $dataSource = $this->getDataSource();
332  return $dataSource->UserDate($d, 'Y-m-d');
333  }
334 
341  function convertFromDB($value, $type) {
342  switch ($type) {
343  case 'bool':
344  case 'boolean':
345  $value = (bool) $value;
346  break;
347  case 'int':
348  case 'integer':
349  $value = (int) $value;
350  break;
351  case 'float':
352  case 'number':
353  $value = (float) $value;
354  break;
355  case 'object':
356  case 'array':
357  $value = unserialize($value);
358  break;
359  case 'date':
360  if ($value !== null) $value = strtotime($value);
361  break;
362  case 'string':
363  default:
364  // Nothing required.
365  break;
366  }
367  return $value;
368  }
369 
375  function getType($value) {
376  switch (gettype($value)) {
377  case 'boolean':
378  case 'bool':
379  return 'bool';
380  case 'integer':
381  case 'int':
382  return 'int';
383  case 'double':
384  case 'float':
385  return 'float';
386  case 'array':
387  case 'object':
388  return 'object';
389  case 'string':
390  default:
391  return 'string';
392  }
393  }
394 
401  function convertToDB($value, &$type) {
402  if ($type == null) {
403  $type = $this->getType($value);
404  }
405 
406  switch ($type) {
407  case 'object':
408  case 'array':
409  $value = serialize($value);
410  break;
411  case 'bool':
412  case 'boolean':
413  // Cast to boolean, ensuring that string
414  // "false" evaluates to boolean false
415  $value = ($value && $value !== 'false') ? 1 : 0;
416  break;
417  case 'int':
418  case 'integer':
419  $value = (int) $value;
420  break;
421  case 'float':
422  case 'number':
423  $value = (float) $value;
424  break;
425  case 'date':
426  if ($value !== null) {
427  if (!is_numeric($value)) $value = strtotime($value);
428  $value = strftime('%Y-%m-%d %H:%M:%S', $value);
429  }
430  break;
431  case 'string':
432  default:
433  // do nothing.
434  }
435 
436  return $value;
437  }
438 
444  function nullOrInt($value) {
445  return (empty($value)?null:(int) $value);
446  }
447 
455  $returner = array();
456  // Call hooks based on the calling entity, assuming
457  // this method is only called by a subclass. Results
458  // in hook calls named e.g. "sessiondao::getAdditionalFieldNames"
459  // (class names lowercase)
460  HookRegistry::call(strtolower_codesafe(get_class($this)) . '::getAdditionalFieldNames', array($this, &$returner));
461 
462  return $returner;
463  }
464 
471  function getLocaleFieldNames() {
472  $returner = array();
473  // Call hooks based on the calling entity, assuming
474  // this method is only called by a subclass. Results
475  // in hook calls named e.g. "sessiondao::getLocaleFieldNames"
476  // (class names lowercase)
477  HookRegistry::call(strtolower_codesafe(get_class($this)) . '::getLocaleFieldNames', array($this, &$returner));
478 
479  return $returner;
480  }
481 
488  function updateDataObjectSettings($tableName, $dataObject, $idArray) {
489  // Initialize variables
490  $idFields = array_keys($idArray);
491  $idFields[] = 'locale';
492  $idFields[] = 'setting_name';
493 
494  // Build a data structure that we can process efficiently.
495  $translated = $metadata = 1;
496  $settings = !$metadata;
497  $settingFields = array(
498  // Translated data
499  $translated => array(
500  $settings => $this->getLocaleFieldNames(),
501  $metadata => $dataObject->getLocaleMetadataFieldNames()
502  ),
503  // Shared data
504  !$translated => array(
505  $settings => $this->getAdditionalFieldNames(),
506  $metadata => $dataObject->getAdditionalMetadataFieldNames()
507  )
508  );
509 
510  // Loop over all fields and update them in the settings table
511  $updateArray = $idArray;
512  $noLocale = 0;
513  $staleSettings = array();
514 
515  foreach ($settingFields as $isTranslated => $fieldTypes) {
516  foreach ($fieldTypes as $isMetadata => $fieldNames) {
517  foreach ($fieldNames as $fieldName) {
518  // Now we have the following control data:
519  // - $isTranslated: true for translated data, false data shared between locales
520  // - $isMetadata: true for metadata fields, false for normal settings
521  // - $fieldName: the field in the data object to be updated
522  if ($dataObject->hasData($fieldName)) {
523  if ($isTranslated) {
524  // Translated data comes in as an array
525  // with the locale as the key.
526  $values = $dataObject->getData($fieldName);
527  if (!is_array($values)) {
528  // Inconsistent data: should have been an array
529  assert(false);
530  continue;
531  }
532  } else {
533  // Transform shared data into an array so that
534  // we can handle them the same way as translated data.
535  $values = array(
536  $noLocale => $dataObject->getData($fieldName)
537  );
538  }
539 
540  // Loop over the values and update them in the database
541  foreach ($values as $locale => $value) {
542  $updateArray['locale'] = ($locale === $noLocale ? '' : $locale);
543  $updateArray['setting_name'] = $fieldName;
544  $updateArray['setting_type'] = null;
545  // Convert the data value and implicitly set the setting type.
546  $updateArray['setting_value'] = $this->convertToDB($value, $updateArray['setting_type']);
547  $this->replace($tableName, $updateArray, $idFields);
548  }
549  } else {
550  // Data is maintained "sparsely". Only set fields will be
551  // recorded in the settings table. Fields that are not explicity set
552  // in the data object will be deleted.
553  $staleSettings[] = $fieldName;
554  }
555  }
556  }
557  }
558 
559  // Remove stale data
560  if (count($staleSettings)) {
561  $removeWhere = '';
562  $removeParams = array();
563  foreach ($idArray as $idField => $idValue) {
564  if (!empty($removeWhere)) $removeWhere .= ' AND ';
565  $removeWhere .= $idField.' = ?';
566  $removeParams[] = $idValue;
567  }
568  $removeWhere .= rtrim(' AND setting_name IN ( '.str_repeat('? ,', count($staleSettings)), ',').')';
569  $removeParams = array_merge($removeParams, $staleSettings);
570  $removeSql = 'DELETE FROM '.$tableName.' WHERE '.$removeWhere;
571  $this->update($removeSql, $removeParams);
572  }
573  }
574 
582  function getDataObjectSettings($tableName, $idFieldName, $idFieldValue, $dataObject) {
583  if ($idFieldName !== null) {
584  $sql = "SELECT * FROM $tableName WHERE $idFieldName = ?";
585  $params = array($idFieldValue);
586  } else {
587  $sql = "SELECT * FROM $tableName";
588  $params = false;
589  }
590  $result = $this->retrieve($sql, $params);
591  while (!$result->EOF) {
592  $row = $result->getRowAssoc(false);
593  $dataObject->setData(
594  $row['setting_name'],
595  $this->convertFromDB(
596  $row['setting_value'],
597  $row['setting_type']
598  ),
599  empty($row['locale'])?null:$row['locale']
600  );
601  $result->MoveNext();
602  }
603  $result->Close();
604 
605  }
606 
611  function getDriver() {
612  $conn = DBConnection::getInstance();
613  return $conn->getDriver();
614  }
615 
621  function getDirectionMapping($direction) {
622  switch ($direction) {
623  case SORT_DIRECTION_ASC:
624  return 'ASC';
625  case SORT_DIRECTION_DESC:
626  return 'DESC';
627  default:
628  return 'ASC';
629  }
630  }
631 
647  static function getDataChangedEvent($elementId = null, $parentElementId = null, $content = '') {
648  // Create the event data.
649  $eventData = null;
650  if ($elementId) {
651  $eventData = array($elementId);
652  if ($parentElementId) {
653  $eventData['parentElementId'] = $parentElementId;
654  }
655  }
656 
657  // Create and render the JSON message with the
658  // event to be triggered on the client side.
659  import('lib.pkp.classes.core.JSONMessage');
660  $json = new JSONMessage(true, $content);
661  $json->setEvent('dataChanged', $eventData);
662  return $json;
663  }
664 
676  function formatDateToDB($date, $defaultNumWeeks = null, $acceptPastDate = true) {
677  $today = getDate();
678  $todayTimestamp = mktime(0, 0, 0, $today['mon'], $today['mday'], $today['year']);
679  if ($date != null) {
680  $dateParts = explode('-', $date);
681 
682  // If we don't accept past dates...
683  if (!$acceptPastDate && $todayTimestamp > strtotime($date)) {
684  // ... return today.
685  return date('Y-m-d H:i:s', $todayTimestamp);
686  } else {
687  // Return the passed date.
688  return date('Y-m-d H:i:s', mktime(0, 0, 0, $dateParts[1], $dateParts[2], $dateParts[0]));
689  }
690  } elseif (isset($defaultNumWeeks)) {
691  // Add the equivalent of $numWeeks weeks, measured in seconds, to $todaysTimestamp.
692  $numWeeks = max((int) $defaultNumWeeks, 2);
693  $newDueDateTimestamp = $todayTimestamp + ($numWeeks * 7 * 24 * 60 * 60);
694  return date('Y-m-d H:i:s', $newDueDateTimestamp);
695  } else {
696  // Either the date or the defaultNumWeeks must be set
697  assert(false);
698  return null;
699  }
700  }
701 
702  function handleError($dataSource, $sql) {
703  throw new Exception('DB Error: ' . $dataSource->errorMsg() . ' Query: ' . $sql);
704  }
705 }
DAO\getDriver
getDriver()
Definition: DAO.inc.php:611
DAO\handleError
handleError($dataSource, $sql)
Definition: DAO.inc.php:702
CacheManager\getFileCachePath
static getFileCachePath()
Definition: CacheManager.inc.php:112
DAO\retrieveRange
& retrieveRange($sql, $params=false, $dbResultRange=null, $callHooks=true)
Definition: DAO.inc.php:176
DAO\getAdditionalFieldNames
getAdditionalFieldNames()
Definition: DAO.inc.php:454
DAO\concat
concat()
Definition: DAO.inc.php:54
DAO\getDirectionMapping
getDirectionMapping($direction)
Definition: DAO.inc.php:621
DAO\getLocaleFieldNames
getLocaleFieldNames()
Definition: DAO.inc.php:471
DAO\convertToDB
convertToDB($value, &$type)
Definition: DAO.inc.php:401
DAO\getAffectedRows
getAffectedRows()
Definition: DAO.inc.php:264
DAO\dateToDB
dateToDB($d)
Definition: DAO.inc.php:309
DAO\__construct
__construct($dataSource=null, $callHooks=true)
Definition: DAO.inc.php:63
Core\microtime
static microtime()
Definition: Core.inc.php:71
DAO\retrieve
& retrieve($sql, $params=false, $callHooks=true)
Definition: DAO.inc.php:85
DAO\convertFromDB
convertFromDB($value, $type)
Definition: DAO.inc.php:341
DAO\setDataSource
setDataSource($dataSource)
Definition: DAO.inc.php:47
DAO\$_dataSource
$_dataSource
Definition: DAO.inc.php:33
DAO\setCacheDir
setCacheDir()
Definition: DAO.inc.php:274
DAO\datetimeFromDB
datetimeFromDB($dt)
Definition: DAO.inc.php:319
DAO\getDataChangedEvent
static getDataChangedEvent($elementId=null, $parentElementId=null, $content='')
Definition: DAO.inc.php:647
JSONMessage
Class to represent a JSON (Javascript Object Notation) message.
Definition: JSONMessage.inc.php:18
DBConnection\getConn
static & getConn()
Definition: DBConnection.inc.php:257
DAO\update
update($sql, $params=false, $callHooks=true, $dieOnError=true)
Definition: DAO.inc.php:214
DAO\datetimeToDB
datetimeToDB($dt)
Definition: DAO.inc.php:299
DAO\dateFromDB
dateFromDB($d)
Definition: DAO.inc.php:329
DAO\_getInsertId
_getInsertId($table='', $id='')
Definition: DAO.inc.php:255
DAO\nullOrInt
nullOrInt($value)
Definition: DAO.inc.php:444
DAO\getDataObjectSettings
getDataObjectSettings($tableName, $idFieldName, $idFieldValue, $dataObject)
Definition: DAO.inc.php:582
strtolower_codesafe
strtolower_codesafe($str)
Definition: functions.inc.php:280
DAO\getType
getType($value)
Definition: DAO.inc.php:375
DAO\replace
replace($table, $arrFields, $keyCols)
Definition: DAO.inc.php:243
DAO\formatDateToDB
formatDateToDB($date, $defaultNumWeeks=null, $acceptPastDate=true)
Definition: DAO.inc.php:676
DAO\retrieveCached
& retrieveCached($sql, $params=false, $secsToCache=3600, $callHooks=true)
Definition: DAO.inc.php:115
DAO\flushCache
flushCache()
Definition: DAO.inc.php:288
DAO\updateDataObjectSettings
updateDataObjectSettings($tableName, $dataObject, $idArray)
Definition: DAO.inc.php:488
DAO\getDataSource
getDataSource()
Definition: DAO.inc.php:39
DAO\retrieveLimit
& retrieveLimit($sql, $params=false, $numRows=false, $offset=false, $callHooks=true)
Definition: DAO.inc.php:148
HookRegistry\call
static call($hookName, $args=null)
Definition: HookRegistry.inc.php:86
DBConnection\getInstance
static getInstance($setInstance=null)
Definition: DBConnection.inc.php:241
DAO
Operations for retrieving and modifying objects from a database.
Definition: DAO.inc.php:31