Open Journal Systems  2.4.3
 All Classes Namespaces Functions Variables Groups Pages
MetricsDAO.inc.php
1 <?php
19 class MetricsDAO extends DAO {
20 
38  function &getMetrics($metricType, $columns = array(), $filters = array(), $orderBy = array(), $range = null) {
39  // Return by reference.
40  $nullVar = null;
41 
42  // Canonicalize and validate parameter format.
43  if (is_scalar($metricType)) $metricType = array($metricType);
44  if (is_scalar($columns)) $columns = array($columns);
45  if (!(is_array($filters) && is_array($orderBy))) return $nullVar;
46 
47  // Validate parameter content.
48  foreach ($metricType as $metricTypeElement) {
49  if (!is_string($metricTypeElement)) return $nullVar;
50  }
51  $validColumns = array(
52  STATISTICS_DIMENSION_CONTEXT_ID, STATISTICS_DIMENSION_ISSUE_ID,
53  STATISTICS_DIMENSION_SUBMISSION_ID, STATISTICS_DIMENSION_COUNTRY,
54  STATISTICS_DIMENSION_REGION, STATISTICS_DIMENSION_CITY,
55  STATISTICS_DIMENSION_ASSOC_TYPE, STATISTICS_DIMENSION_ASSOC_ID,
56  STATISTICS_DIMENSION_MONTH, STATISTICS_DIMENSION_DAY,
57  STATISTICS_DIMENSION_FILE_TYPE, STATISTICS_DIMENSION_METRIC_TYPE
58  );
59 
60  if (count(array_diff($columns, $validColumns)) > 0) return $nullVar;
61  $validColumns[] = STATISTICS_METRIC;
62  foreach ($filters as $filterColumn => $value) {
63  if (!in_array($filterColumn, $validColumns)) return $nullVar;
64  }
65  $validDirections = array(STATISTICS_ORDER_ASC, STATISTICS_ORDER_DESC);
66  foreach ($orderBy as $orderColumn => $direction) {
67  if (!in_array($orderColumn, $validColumns)) return $nullVar;
68  if (!in_array($direction, $validDirections)) return $nullVar;
69  }
70 
71  // Validate correct use of the (non-additive) metric type dimension. We
72  // either require a filter on a single metric type or the metric type
73  // must be present as a column.
74  if (empty($metricType)) return $nullVar;
75  if (count($metricType) !== 1) {
76  if (!in_array(STATISTICS_DIMENSION_METRIC_TYPE, $columns)) {
77  array_push($columns, STATISTICS_DIMENSION_METRIC_TYPE);
78  }
79  }
80 
81  // Add the metric type as filter.
82  $filters[STATISTICS_DIMENSION_METRIC_TYPE] = $metricType;
83 
84  // Build the select and group by clauses.
85  if (empty($columns)) {
86  $selectClause = 'SELECT SUM(metric) AS metric';
87  $groupByClause = '';
88  } else {
89  $selectedColumns = implode(', ', $columns);
90  $selectClause = "SELECT $selectedColumns, SUM(metric) AS metric";
91  $groupByClause = "GROUP BY $selectedColumns";
92  }
93 
94  // Build the where and having clauses.
95  $params = array();
96  $whereClause = '';
97  $havingClause = '';
98  $isFirst = true;
99  foreach ($filters as $column => $values) {
100  // The filter array contains STATISTICS_* constants for the filtered
101  // hierarchy aggregation level as keys.
102  if ($column === STATISTICS_METRIC) {
103  $havingClause = 'HAVING ';
104  $currentClause =& $havingClause; // Reference required.
105  } else {
106  if ($isFirst && $column) {
107  $whereClause = 'WHERE ';
108  $isFirst = false;
109  } else {
110  $whereClause .= ' AND ';
111  }
112  $currentClause =& $whereClause; // Reference required.
113  }
114 
115  if (is_array($values) && isset($values['from'])) {
116  // Range filter: The value is a hashed array with from/to entries.
117  if (!isset($values['to'])) return $nullVar;
118  $currentClause .= "($column BETWEEN ? AND ?)";
119  $params[] = $values['from'];
120  $params[] = $values['to'];
121  } else {
122  // Element selection filter: The value is a scalar or an
123  // unordered array of one or more hierarchy element IDs.
124  if (is_array($values) && count($values) === 1) {
125  $values = array_pop($values);
126  }
127  if (is_scalar($values)) {
128  $currentClause .= "$column = ?";
129  $params[] = $values;
130  } else {
131  $placeholders = array_pad(array(), count($values), '?');
132  $placeholders = implode(', ', $placeholders);
133  $currentClause .= "$column IN ($placeholders)";
134  foreach ($values as $value) {
135  $params[] = $value;
136  }
137  }
138  }
139 
140  unset($currentClause);
141  }
142 
143  // Replace the current time constant by time values
144  // inside the parameters array.
145  $currentTime = array(
146  STATISTICS_CURRENT_DAY => date('Ymd', time()),
147  STATISTICS_CURRENT_MONTH => date('Ym', time()));
148  foreach ($currentTime as $constant => $time) {
149  $currentTimeKeys = array_keys($params, $constant);
150  foreach ($currentTimeKeys as $key) {
151  $params[$key] = $time;
152  }
153  }
154 
155  // Build the order-by clause.
156  $orderByClause = '';
157  if (count($orderBy) > 0) {
158  $isFirst = true;
159  foreach ($orderBy as $orderColumn => $direction) {
160  if ($isFirst) {
161  $orderByClause = 'ORDER BY ';
162  } else {
163  $orderByClause .= ', ';
164  }
165  $orderByClause .= "$orderColumn $direction";
166  $isFirst = false;
167  }
168  }
169 
170  // Build the report.
171  $sql = "$selectClause FROM metrics $whereClause $groupByClause $havingClause $orderByClause";
172  if (is_a($range, 'DBResultRange')) {
173  if ($range->getCount() > STATISTICS_MAX_ROWS) {
174  $range->setCount(STATISTICS_MAX_ROWS);
175  }
176  $result =& $this->retrieveRange($sql, $params, $range);
177  } else {
178  $result =& $this->retrieveLimit($sql, $params, STATISTICS_MAX_ROWS);
179  }
180 
181  // Return the report.
182  return $result->GetAll();
183  }
184 
194  function getLoadId($assocType, $assocId, $metricType) {
195  $params = array($assocType, $assocId, $metricType);
196  $result = $this->retrieve('SELECT load_id FROM metrics WHERE assoc_type = ? AND assoc_id = ? AND metric_type = ? GROUP BY load_id', $params);
197 
198  $loadIds = array();
199  while (!$result->EOF) {
200  $row = $result->FetchRow();
201  $loadIds[] = $row['load_id'];
202  }
203 
204  return $loadIds;
205  }
206 
213  function hasRecord($metricType) {
214  $result = $this->retrieve('SELECT load_id FROM metrics WHERE metric_type = ? LIMIT 1', array($metricType));
215  $row =& $result->GetRowAssoc();
216  if ($row) {
217  return true;
218  } else {
219  return false;
220  }
221  }
222 
228  function purgeLoadBatch($loadId) {
229  $this->update('DELETE FROM metrics WHERE load_id = ?', $loadId); // Not a number.
230  }
231 
238  function purgeRecords($metricType, $toDate) {
239  $this->update('DELETE FROM metrics WHERE metric_type = ? AND day IS NOT NULL AND day <= ?', array($metricType, $toDate));
240  }
241 
248  function insertRecord(&$record, &$errorMsg) {
249  $recordToStore = array();
250 
251  // Required dimensions.
252  $requiredDimensions = array('load_id', 'assoc_type', 'assoc_id', 'metric_type');
253  foreach ($requiredDimensions as $requiredDimension) {
254  if (!isset($record[$requiredDimension])) {
255  $errorMsg = 'Cannot load record: missing dimension "' . $requiredDimension . '".';
256  return false;
257  }
258  $recordToStore[$requiredDimension] = $record[$requiredDimension];
259  }
260  $recordToStore['assoc_type'] = (int)$recordToStore['assoc_type'];
261  $recordToStore['assoc_id'] = (int)$recordToStore['assoc_id'];
262 
263  // Foreign key lookup for the publication object dimension.
264  $isArticleFile = false;
265  switch($recordToStore['assoc_type']) {
266  case ASSOC_TYPE_GALLEY:
267  case ASSOC_TYPE_SUPP_FILE:
268  if ($recordToStore['assoc_type'] == ASSOC_TYPE_GALLEY) {
269  $galleyDao =& DAORegistry::getDAO('ArticleGalleyDAO'); /* @var $galleyDao ArticleGalleyDAO */
270  $articleFile =& $galleyDao->getGalley($recordToStore['assoc_id']);
271  if (!is_a($articleFile, 'ArticleGalley')) {
272  $errorMsg = 'Cannot load record: invalid galley id.';
273  return false;
274  }
275  } else {
276  $suppFileDao =& DAORegistry::getDAO('SuppFileDAO'); /* @var $suppFileDao SuppFileDAO */
277  $articleFile =& $suppFileDao->getSuppFile($recordToStore['assoc_id']);
278  if (!is_a($articleFile, 'SuppFile')) {
279  $errorMsg = 'Cannot load record: invalid supplementary file id.';
280  return false;
281  }
282  }
283  $articleId = $articleFile->getSubmissionId();
284  $isArticleFile = true;
285  // Don't break but go on to retrieve the article.
286 
287  case ASSOC_TYPE_ARTICLE:
288  if (!$isArticleFile) $articleId = $recordToStore['assoc_id'];
289  $publishedArticleDao =& DAORegistry::getDAO('PublishedArticleDAO'); /* @var $publishedArticleDao PublishedArticleDAO */
290  $article =& $publishedArticleDao->getPublishedArticleByArticleId($articleId, null, true);
291  if (is_a($article, 'PublishedArticle')) {
292  $issueId = $article->getIssueId();
293  } else {
294  $issueId = null;
295  $articleDao =& DAORegistry::getDAO('ArticleDAO'); /* @var $articleDao ArticleDAO */
296  $article =& $articleDao->getArticle($articleId, null, true);
297  }
298  if (!is_a($article, 'Article')) {
299  $errorMsg = 'Cannot load record: invalid article id.';
300  return false;
301  }
302  $journalId = $article->getJournalId();
303  break;
304 
305  case ASSOC_TYPE_ISSUE_GALLEY:
306  $articleId = null;
307  $issueGalleyDao =& DAORegistry::getDAO('IssueGalleyDAO'); /* @var $issueGalleyDao IssueGalleyDAO */
308  $issueGalley =& $issueGalleyDao->getGalley($recordToStore['assoc_id']);
309  if (!is_a($issueGalley, 'IssueGalley')) {
310  $errorMsg = 'Cannot load record: invalid issue galley id.';
311  return false;
312  }
313  $issueId = $issueGalley->getIssueId();
314  $issueDao =& DAORegistry::getDAO('IssueDAO'); /* @var $issueDao IssueDAO */
315  $issue =& $issueDao->getById($issueId, null, true);
316  if (!is_a($issue, 'Issue')) {
317  $errorMsg = 'Cannot load record: issue galley without issue.';
318  return false;
319  }
320  $journalId = $issue->getJournalId();
321  break;
322 
323  case ASSOC_TYPE_ISSUE:
324  $articleId = null;
325  $issueId = $recordToStore['assoc_id'];
326  $issueDao =& DAORegistry::getDAO('IssueDAO');
327  $issue =& $issueDao->getIssueByPubId('publisher-id', $issueId, null, true); /* @var $issue Issue */
328  if (!$issue) {
329  $issue =& $issueDao->getIssueById($issueId, null, true);
330  }
331  if (!is_a($issue, 'Issue')) {
332  $errorMsg = 'Cannot load record: invalid issue id.';
333  return false;
334  }
335  $journalId = $issue->getJournalId();
336  break;
337  case ASSOC_TYPE_JOURNAL:
338  $articleId = $issueId = null;
339  $journalDao =& DAORegistry::getDAO('JournalDAO'); /* @var $journalDao JournalDAO */
340  $journal =& $journalDao->getById($recordToStore['assoc_id']);
341  if (!$journal) {
342  $errorMsg = 'Cannot load record: invalid journal id.';
343  return false;
344  }
345  $journalId = $recordToStore['assoc_id'];
346  break;
347  default:
348  $errorMsg = 'Cannot load record: invalid association type.';
349  return false;
350  }
351  $recordToStore['context_id'] = $journalId;
352  $recordToStore['issue_id'] = $issueId;
353  $recordToStore['submission_id'] = $articleId;
354 
355  // We require either month or day in the time dimension.
356  if (isset($record['day'])) {
357  if (!String::regexp_match('/[0-9]{8}/', $record['day'])) {
358  $errorMsg = 'Cannot load record: invalid date.';
359  return false;
360  }
361  $recordToStore['day'] = $record['day'];
362  $recordToStore['month'] = substr($record['day'], 0, 6);
363  if (isset($record['month']) && $recordToStore['month'] != $record['month']) {
364  $errorMsg = 'Cannot load record: invalid month.';
365  return false;
366  }
367  } elseif (isset($record['month'])) {
368  if (!String::regexp_match('/[0-9]{6}/', $record['month'])) {
369  $errorMsg = 'Cannot load record: invalid month.';
370  return false;
371  }
372  $recordToStore['month'] = $record['month'];
373  } else {
374  $errorMsg = 'Cannot load record: Missing time dimension.';
375  return false;
376  }
377 
378  // File type is optional.
379  if (isset($record['file_type']) && $record['file_type']) $recordToStore['file_type'] = (int)$record['file_type'];
380 
381  // Geolocation is optional.
382  if (isset($record['country_id'])) $recordToStore['country_id'] = (string)$record['country_id'];
383  if (isset($record['region'])) $recordToStore['region'] = (string)$record['region'];
384  if (isset($record['city'])) $recordToStore['city'] = (string)$record['city'];
385 
386  // The metric must be set. If it is 0 we ignore the record.
387  if (!isset($record['metric'])) {
388  $errorMsg = 'Cannot load record: metric is missing.';
389  return false;
390  }
391  if (!is_numeric($record['metric'])) {
392  $errorMsg = 'Cannot load record: invalid metric.';
393  return false;
394  }
395  $recordToStore['metric'] = (int) $record['metric'];
396 
397  // Save the record to the database.
398  $fields = implode(', ', array_keys($recordToStore));
399  $placeholders = implode(', ', array_pad(array(), count($recordToStore), '?'));
400  $params = array_values($recordToStore);
401  return $this->update("INSERT INTO metrics ($fields) VALUES ($placeholders)", $params);
402  }
403 }
404 
405 ?>
regexp_match($pattern, $subject)
Definition: String.inc.php:325
Operations for retrieving and modifying objects from a database.
Definition: DAO.inc.php:29
& retrieveLimit($sql, $params=false, $numRows=false, $offset=false, $callHooks=true)
Definition: DAO.inc.php:147
getLoadId($assocType, $assocId, $metricType)
& retrieve($sql, $params=false, $callHooks=true)
Definition: DAO.inc.php:83
hasRecord($metricType)
purgeLoadBatch($loadId)
& retrieveRange($sql, $params=false, $dbResultRange=null, $callHooks=true)
Definition: DAO.inc.php:176
insertRecord(&$record, &$errorMsg)
Operations for retrieving and adding statistics data.
purgeRecords($metricType, $toDate)
& getDAO($name, $dbconn=null)
& getMetrics($metricType, $columns=array(), $filters=array(), $orderBy=array(), $range=null)
update($sql, $params=false, $callHooks=true, $dieOnError=true)
Definition: DAO.inc.php:211