Open Journal Systems  3.0.0
 All Classes Namespaces Functions Variables Groups Pages
PKPMetricsDAO.inc.php
1 <?php
19 class PKPMetricsDAO extends DAO {
20 
41  function &getMetrics($metricType, $columns = array(), $filters = array(), $orderBy = array(), $range = null, $nonAdditive = true) {
42  // Return by reference.
43  $nullVar = null;
44 
45  // Canonicalize and validate parameter format.
46  if (is_scalar($metricType)) $metricType = array($metricType);
47  if (is_scalar($columns)) $columns = array($columns);
48  if (!(is_array($filters) && is_array($orderBy))) return $nullVar;
49 
50  // Validate parameter content.
51  foreach ($metricType as $metricTypeElement) {
52  if (!is_string($metricTypeElement)) return $nullVar;
53  }
54  $validColumns = array(
55  STATISTICS_DIMENSION_CONTEXT_ID,
56  STATISTICS_DIMENSION_PKP_SECTION_ID,
57  STATISTICS_DIMENSION_ASSOC_OBJECT_ID,
58  STATISTICS_DIMENSION_ASSOC_OBJECT_TYPE,
59  STATISTICS_DIMENSION_SUBMISSION_ID,
60  STATISTICS_DIMENSION_REPRESENTATION_ID,
61  STATISTICS_DIMENSION_FILE_TYPE,
62  STATISTICS_DIMENSION_ASSOC_TYPE,
63  STATISTICS_DIMENSION_ASSOC_ID,
64  STATISTICS_DIMENSION_COUNTRY,
65  STATISTICS_DIMENSION_REGION,
66  STATISTICS_DIMENSION_CITY,
67  STATISTICS_DIMENSION_MONTH,
68  STATISTICS_DIMENSION_DAY,
69  STATISTICS_DIMENSION_METRIC_TYPE
70  );
71 
72  // If the metric column was defined, remove it. We've already
73  // add that below.
74  $metricKey = array_search(STATISTICS_METRIC, $columns);
75  if ($metricKey !== false) unset($columns[$metricKey]);
76 
77  if (count(array_diff($columns, $validColumns)) > 0) return $nullVar;
78  $validColumns[] = STATISTICS_METRIC;
79  foreach ($filters as $filterColumn => $value) {
80  if (!in_array($filterColumn, $validColumns)) return $nullVar;
81  }
82  $validDirections = array(STATISTICS_ORDER_ASC, STATISTICS_ORDER_DESC);
83  foreach ($orderBy as $orderColumn => $direction) {
84  if (!in_array($orderColumn, $validColumns)) return $nullVar;
85  if (!in_array($direction, $validDirections)) return $nullVar;
86  }
87 
88  // Validate correct use of the (non-additive) metric type dimension. We
89  // either require a filter on a single metric type or the metric type
90  // must be present as a column.
91  if (empty($metricType)) return $nullVar;
92  if (count($metricType) !== 1 && $nonAdditive) {
93  if (!in_array(STATISTICS_DIMENSION_METRIC_TYPE, $columns)) {
94  array_push($columns, STATISTICS_DIMENSION_METRIC_TYPE);
95  }
96  }
97 
98  // Add the metric type as filter.
99  $filters[STATISTICS_DIMENSION_METRIC_TYPE] = $metricType;
100 
101  // Build the select and group by clauses.
102  if (empty($columns)) {
103  $selectClause = 'SELECT SUM(metric) AS metric';
104  $groupByClause = '';
105  } else {
106  $selectedColumns = implode(', ', $columns);
107  $selectClause = "SELECT $selectedColumns, SUM(metric) AS metric";
108  $groupByClause = "GROUP BY $selectedColumns";
109  }
110 
111  // Build the where and having clauses.
112  $params = array();
113  $whereClause = '';
114  $havingClause = '';
115  $isFirst = true;
116  foreach ($filters as $column => $values) {
117  // The filter array contains STATISTICS_* constants for the filtered
118  // hierarchy aggregation level as keys.
119  if ($column === STATISTICS_METRIC) {
120  $havingClause = 'HAVING ';
121  $currentClause =& $havingClause; // Reference required.
122  } else {
123  if ($isFirst && $column) {
124  $whereClause = 'WHERE ';
125  $isFirst = false;
126  } else {
127  $whereClause .= ' AND ';
128  }
129  $currentClause =& $whereClause; // Reference required.
130  }
131 
132  if (is_array($values) && isset($values['from'])) {
133  // Range filter: The value is a hashed array with from/to entries.
134  if (!isset($values['to'])) return $nullVar;
135  $currentClause .= "($column BETWEEN ? AND ?)";
136  $params[] = $values['from'];
137  $params[] = $values['to'];
138  } else {
139  // Element selection filter: The value is a scalar or an
140  // unordered array of one or more hierarchy element IDs.
141  if (is_array($values) && count($values) === 1) {
142  $values = array_pop($values);
143  }
144  if (is_scalar($values)) {
145  $currentClause .= "$column = ?";
146  $params[] = $values;
147  } else {
148  $placeholders = array_pad(array(), count($values), '?');
149  $placeholders = implode(', ', $placeholders);
150  $currentClause .= "$column IN ($placeholders)";
151  foreach ($values as $value) {
152  $params[] = $value;
153  }
154  }
155  }
156 
157  unset($currentClause);
158  }
159 
160  // Replace the current time constant by time values
161  // inside the parameters array.
162  $currentTime = array(
163  STATISTICS_CURRENT_DAY => date('Ymd', time()),
164  STATISTICS_CURRENT_MONTH => date('Ym', time()));
165  foreach ($currentTime as $constant => $time) {
166  $currentTimeKeys = array_keys($params, $constant);
167  foreach ($currentTimeKeys as $key) {
168  $params[$key] = $time;
169  }
170  }
171 
172  // Build the order-by clause.
173  $orderByClause = '';
174  if (count($orderBy) > 0) {
175  $isFirst = true;
176  foreach ($orderBy as $orderColumn => $direction) {
177  if ($isFirst) {
178  $orderByClause = 'ORDER BY ';
179  } else {
180  $orderByClause .= ', ';
181  }
182  $orderByClause .= "$orderColumn $direction";
183  $isFirst = false;
184  }
185  }
186 
187  // Build the report.
188  $sql = "$selectClause FROM metrics $whereClause $groupByClause $havingClause $orderByClause";
189  if (is_a($range, 'DBResultRange')) {
190  if ($range->getCount() > STATISTICS_MAX_ROWS) {
191  $range->setCount(STATISTICS_MAX_ROWS);
192  }
193  $result = $this->retrieveRange($sql, $params, $range);
194  } else {
195  $result = $this->retrieveLimit($sql, $params, STATISTICS_MAX_ROWS);
196  }
197 
198  // Return the report.
199  $returner = $result->GetAll();
200  return $returner;
201  }
202 
212  function getLoadId($assocType, $assocId, $metricType) {
213  $params = array($assocType, $assocId, $metricType);
214  $result = $this->retrieve('SELECT load_id FROM metrics WHERE assoc_type = ? AND assoc_id = ? AND metric_type = ? GROUP BY load_id', $params);
215 
216  $loadIds = array();
217  while (!$result->EOF) {
218  $row = $result->FetchRow();
219  $loadIds[] = $row['load_id'];
220  }
221 
222  return $loadIds;
223  }
224 
231  function hasRecord($metricType) {
232  $result = $this->retrieve('SELECT load_id FROM metrics WHERE metric_type = ? LIMIT 1', array($metricType));
233  $row = $result->GetRowAssoc();
234  if ($row) {
235  return true;
236  } else {
237  return false;
238  }
239  }
240 
246  function purgeLoadBatch($loadId) {
247  $this->update('DELETE FROM metrics WHERE load_id = ?', $loadId); // Not a number.
248  }
249 
256  function purgeRecords($metricType, $toDate) {
257  $this->update('DELETE FROM metrics WHERE metric_type = ? AND day IS NOT NULL AND day <= ?', array($metricType, $toDate));
258  }
259 
266  function insertRecord($record) {
267  $recordToStore = array();
268 
269  // Required dimensions.
270  $requiredDimensions = array('load_id', 'assoc_type', 'assoc_id', 'metric_type');
271  foreach ($requiredDimensions as $requiredDimension) {
272  if (!isset($record[$requiredDimension])) {
273  throw new Exception('Cannot load record: missing dimension "' . $requiredDimension . '".');
274  }
275  $recordToStore[$requiredDimension] = $record[$requiredDimension];
276  }
277  $assocType = $recordToStore['assoc_type'] = (int)$recordToStore['assoc_type'];
278  $assocId = $recordToStore['assoc_id'] = (int)$recordToStore['assoc_id'];
279 
280  list($contextId, $pkpSectionId, $assocObjType,
281  $assocObjId, $submissionId, $representationId) = $this->foreignKeyLookup($assocType, $assocId);
282 
283  $recordToStore['context_id'] = $contextId;
284  $recordToStore['pkp_section_id'] = $pkpSectionId;
285  $recordToStore['assoc_object_type'] = $assocObjType;
286  $recordToStore['assoc_object_id'] = $assocObjId;
287  $recordToStore['submission_id'] = $submissionId;
288  $recordToStore['representation_id'] = $representationId;
289 
290  // File type is optional.
291  if (isset($record['file_type']) && $record['file_type']) $recordToStore['file_type'] = (int)$record['file_type'];
292 
293  // We require either month or day in the time dimension.
294  if (isset($record['day'])) {
295  if (!String::regexp_match('/[0-9]{8}/', $record['day'])) {
296  throw new Exception('Cannot load record: invalid date.');
297  }
298  $recordToStore['day'] = $record['day'];
299  $recordToStore['month'] = substr($record['day'], 0, 6);
300  if (isset($record['month']) && $recordToStore['month'] != $record['month']) {
301  throw new Exception('Cannot load record: invalid month.');
302  }
303  } elseif (isset($record['month'])) {
304  if (!String::regexp_match('/[0-9]{6}/', $record['month'])) {
305  throw new Exception('Cannot load record: invalid month.');
306  }
307  $recordToStore['month'] = $record['month'];
308  } else {
309  throw new Exception('Cannot load record: Missing time dimension.');
310  }
311 
312  // Geolocation is optional.
313  if (isset($record['country_id'])) $recordToStore['country_id'] = (string)$record['country_id'];
314  if (isset($record['region'])) $recordToStore['region'] = (string)$record['region'];
315  if (isset($record['city'])) $recordToStore['city'] = (string)$record['city'];
316 
317  // The metric must be set. If it is 0 we ignore the record.
318  if (!isset($record['metric'])) {
319  throw new Exception('Cannot load record: metric is missing.');
320  }
321  if (!is_numeric($record['metric'])) {
322  throw new Exception('Cannot load record: invalid metric.');
323  }
324  $recordToStore['metric'] = (int) $record['metric'];
325 
326  // Save the record to the database.
327  $fields = implode(', ', array_keys($recordToStore));
328  $placeholders = implode(', ', array_pad(array(), count($recordToStore), '?'));
329  $params = array_values($recordToStore);
330  return $this->update("INSERT INTO metrics ($fields) VALUES ($placeholders)", $params);
331  }
332 
333 
334  //
335  // Protected methods.
336  //
345  protected function foreignKeyLookup($assocType, $assocId) {
346  $contextId = $sectionId = $submissionId = $representationId = null;
347 
348  $isFile = false;
349  $isRepresentation = false;
350 
351  switch($assocType) {
352  case ASSOC_TYPE_SUBMISSION_FILE:
353  $submissionFileDao = DAORegistry::getDAO('SubmissionFileDAO'); /* @var $submissionFileDao SubmissionFileDAO */
354  $submissionFile = $submissionFileDao->getLatestRevision($assocId);
355  if ($submissionFile) {
356  $isFile = true;
357  $submissionId = $submissionFile->getSubmissionId();
358  if ($submissionFile->getAssocType() == ASSOC_TYPE_REPRESENTATION) {
359  $representationId = $submissionFile->getAssocId();
360  } else {
361  throw new Exception('Cannot load record: submission file is not associated with a representation object.');
362  }
363  } else {
364  throw new Exception('Cannot load record: invalid submission file id.');
365  }
366  // Don't break but go on to retrieve the representation.
367  case ASSOC_TYPE_REPRESENTATION:
368  if (!$isFile) $representationId = $assocId;
369  $representationDao = Application::getRepresentationDAO(); /* @var $representationDao RepresentationDAO */
370  $representation = $representationDao->getById($representationId); /* @var $representation Representation */
371  if ($representation) {
372  if (!$isFile) $isRepresentation = true;
373 
374  $contextId = $representation->getContextId();
375  $submissionId = $representation->getSubmissionId();
376  } else {
377  throw new Exception('Cannot load record: invalid representation id.');
378  }
379  // Don't break but go on to retrieve the submission.
380  case ASSOC_TYPE_SUBMISSION:
381  if (!$isFile && !$isRepresentation) $submissionId = $assocId;
382  $submissionDao = Application::getSubmissionDAO(); /* @var $submissionDao SubmissionDAO */
383  $submission = $submissionDao->getById($submissionId);
384  if ($submission) {
385  $contextId = $submission->getContextId();
386  $submissionId = $submission->getId();
387  $sectionId = $submission->getSectionId();
388  } else {
389  throw new Exception('Cannot load record: invalid submission id.');
390  }
391  break;
392  case ASSOC_TYPE_SECTION:
393  $sectionDao = Application::getSectionDAO();
394  $section = $sectionDao->getById($assocId); /* @var $section PKPSection */
395  if ($section) {
396  $sectionId = $section->getId();
397  $contextId = $section->getContextId();
398  } else {
399  throw new Exception('Cannot load record: invalid section id.');
400  }
401  break;
403  $contextDao = Application::getContextDAO(); /* @var $contextDao ContextDAO */
404  $context = $contextDao->getById($assocId);
405  if (!$context) {
406  throw new Exception('Cannot load record: invalid context id.');
407  }
408  $contextId = $assocId;
409  break;
410  }
411 
412  return array($contextId, $sectionId, null, null, $submissionId, $representationId);
413  }
414 }
415 ?>
static & getDAO($name, $dbconn=null)
Operations for retrieving and modifying objects from a database.
Definition: DAO.inc.php:30
static getRepresentationDAO()
& retrieveLimit($sql, $params=false, $numRows=false, $offset=false, $callHooks=true)
Definition: DAO.inc.php:146
& getMetrics($metricType, $columns=array(), $filters=array(), $orderBy=array(), $range=null, $nonAdditive=true)
& retrieve($sql, $params=false, $callHooks=true)
Definition: DAO.inc.php:84
static regexp_match($pattern, $subject)
Definition: String.inc.php:339
& retrieveRange($sql, $params=false, $dbResultRange=null, $callHooks=true)
Definition: DAO.inc.php:174
Class with basic operations for retrieving and adding statistics data.
static getSubmissionDAO()
getLoadId($assocType, $assocId, $metricType)
hasRecord($metricType)
static getContextAssocType()
foreignKeyLookup($assocType, $assocId)
update($sql, $params=false, $callHooks=true, $dieOnError=true)
Definition: DAO.inc.php:208
static getContextDAO()
static getSectionDAO()
purgeRecords($metricType, $toDate)