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