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