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