16 import(
'lib.pkp.classes.plugins.GenericPlugin');
36 parent::__construct();
42 switch ($applicationName) {
44 import(
'plugins.generic.usageStats.OJSUsageStatsReportPlugin');
47 import(
'plugins.generic.usageStats.OMPUsageStatsReportPlugin');
50 import(
'plugins.generic.usageStats.OPSUsageStatsReportPlugin');
67 switch ($applicationName) {
69 $this->
import(
'OJSUsageStatsReportPlugin');
72 $this->
import(
'OMPUsageStatsReportPlugin');
75 $this->
import(
'OPSUsageStatsReportPlugin');
89 function register($category, $path, $mainContextId =
null) {
90 $success = parent::register($category, $path, $mainContextId);
94 if ($this->
getEnabled($mainContextId) && $success) {
96 $this->_dataPrivacyOn = $this->
getSetting(CONTEXT_ID_NONE,
'dataPrivacyOption');
97 $this->_saltpath = $this->
getSetting(CONTEXT_ID_NONE,
'saltFilepath');
99 if (!$this->_saltpath) $this->_saltpath =
Config::getVar(
'usageStats',
'salt_filepath');
101 $this->_optedOut =
$request->getCookieVar(
'usageStats-opt-out');
102 if ($this->_optedOut) {
104 $request->setCookieVar(
'usageStats-opt-out',
true, time() + 60*60*24*365);
107 if ($this->_dataPrivacyOn) {
108 $this->
import(
'UsageStatsOptoutBlockPlugin');
119 if ($this->
getSetting(CONTEXT_ID_NONE,
'createLogFiles')) {
140 switch($applicationName) {
170 return __(
'plugins.generic.usageStats.displayName');
177 return __(
'plugins.generic.usageStats.description');
191 return $this->
getPluginPath() . DIRECTORY_SEPARATOR .
'settings.xml';
198 $this->
import(
'UsageStatsMigration');
206 $this->
import(
'UsageStatsSettingsForm');
207 switch(
$request->getUserVar(
'verb')) {
210 $settingsForm->initData();
214 $settingsForm->readInputData();
215 if ($settingsForm->validate()) {
216 $settingsForm->execute();
218 $notificationManager->createTrivialNotification(
220 NOTIFICATION_TYPE_SUCCESS,
221 array(
'contents' => __(
'plugins.generic.usageStats.settings.saved'))
227 return parent::manage($args,
$request);
239 import(
'lib.pkp.classes.linkAction.request.AjaxModal');
245 $router->url(
$request,
null,
null,
'manage',
null, array(
'verb' =>
'settings',
'plugin' => $this->
getName(),
'category' =>
'generic')),
248 __(
'manager.plugins.settings'),
273 if ($page !==
'usageStats')
return;
275 $availableOps = array(
'privacyInformation');
277 if (!in_array(
$op, $availableOps))
return;
279 define(
'HANDLER_CLASS',
'UsageStatsHandler');
280 define(
'USAGESTATS_PLUGIN_NAME', $this->
getName());
281 $handlerFile =& $args[2];
295 $taskFilesPath =& $args[0];
296 $taskFilesPath[] = $this->
getPluginPath() . DIRECTORY_SEPARATOR .
'scheduledTasksAutoStage.xml';
307 if (!file_exists($saltpath)) {
310 if (is_writable($saltpath)) {
322 $hooks = array(
'FileManager::downloadFileFinished');
325 switch ($applicationName) {
327 $hooks[] =
'HtmlArticleGalleyPlugin::articleDownloadFinished';
330 $hooks[] =
'HtmlMonographFilePlugin::monographDownloadFinished';
333 $hooks[] =
'HtmlArticleGalleyPlugin::articleDownloadFinished';
353 $hookName = $args[0];
354 $usageEvent = $args[1];
357 if ($this->_optedOut)
return false;
361 $downloadSuccess = $args[2];
362 if ($downloadSuccess && !connection_aborted()) {
363 $this->_currentUsageEvent[
'downloadSuccess'] =
true;
368 if ($usageEvent && !$usageEvent[
'downloadSuccess']) {
370 $this->_currentUsageEvent = $usageEvent;
390 $this->
import(
'GeoLocationTool');
394 if (
$tool->isPresent()) {
406 import(
'lib.pkp.classes.file.PrivateFileManager');
409 return realpath($fileMgr->getBasePath()) . DIRECTORY_SEPARATOR .
'usageStats';
425 return 'usage_events_' . date(
"Ymd") .
'.log';
440 $templateMgr->addJavaScript(
442 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.0.1/Chart.js',
444 'contexts' => $contexts,
451 $script_data =
'var pkpUsageStats = pkpUsageStats || {};';
452 $script_data .=
'pkpUsageStats.locale = pkpUsageStats.locale || {};';
453 $script_data .=
'pkpUsageStats.locale.months = ' . json_encode(explode(
' ', __(
'plugins.generic.usageStats.monthInitials'))) .
';';
454 $script_data .=
'pkpUsageStats.config = pkpUsageStats.config || {};';
455 $script_data .=
'pkpUsageStats.config.chartType = ' . json_encode($chartType) .
';';
456 $templateMgr->addJavaScript(
457 'pkpUsageStatsConfig',
461 'contexts' => $contexts,
466 $baseImportPath =
$request->getBaseUrl() . DIRECTORY_SEPARATOR . $this->
getPluginPath() . DIRECTORY_SEPARATOR;
467 $templateMgr->addJavaScript(
468 'usageStatsFrontend',
469 $baseImportPath .
'js/UsageStatsFrontendHandler.js',
471 'contexts' => $contexts,
488 $script_data =
'var pkpUsageStats = pkpUsageStats || {};';
489 $script_data .=
'pkpUsageStats.data = pkpUsageStats.data || {};';
490 $script_data .=
'pkpUsageStats.data.' . $pubObjectType .
' = pkpUsageStats.data.' . $pubObjectType .
' || {};';
491 $namespace = $pubObjectType .
'[' . $pubObjectId .
']';
492 $script_data .=
'pkpUsageStats.data.' . $namespace .
' = ' . json_encode($data) .
';';
497 $templateMgr->addJavaScript(
502 'contexts' => $contexts,
516 $smarty->assign($args);
531 $smarty =& $params[1];
532 $output =& $params[2];
534 $context = $smarty->getTemplateVars(
'currentContext');
536 $contextDisplaySettingExists = $pluginSettingsDao->settingExists($context->getId(), $this->getName(),
'displayStatistics');
537 $contextDisplaySetting = $this->
getSetting($context->getId(),
'displayStatistics');
538 $siteDisplaySetting = $this->
getSetting(CONTEXT_ID_NONE,
'displayStatistics');
539 if (($contextDisplaySettingExists && $contextDisplaySetting) ||
540 (!$contextDisplaySettingExists && $siteDisplaySetting)) {
542 $pubObject = $smarty->getTemplateVars(
'article');
543 assert(is_a($pubObject,
'Submission'));
544 $pubObjectId = $pubObject->getId();
545 $pubObjectType =
'Submission';
549 'pubObjectType' => $pubObjectType,
550 'pubObjectId' => $pubObjectId,
552 'outputFrontend.tpl',
573 $smarty =& $params[1];
574 $output =& $params[2];
576 $context = $smarty->getTemplateVars(
'currentContext');
578 $contextDisplaySettingExists = $pluginSettingsDao->settingExists($context->getId(), $this->getName(),
'displayStatistics');
579 $contextDisplaySetting = $this->
getSetting($context->getId(),
'displayStatistics');
580 $siteDisplaySetting = $this->
getSetting(CONTEXT_ID_NONE,
'displayStatistics');
581 if (($contextDisplaySettingExists && $contextDisplaySetting) ||
582 (!$contextDisplaySettingExists && $siteDisplaySetting)) {
584 $pubObject = $smarty->getTemplateVars(
'publication');
585 assert(is_a($pubObject,
'Publication'));
586 $pubObjectId = $pubObject->getId();
587 $pubObjectType =
'Publication';
591 'pubObjectType' => $pubObjectType,
592 'pubObjectId' => $pubObjectId,
594 'outputFrontend.tpl',
615 $smarty =& $params[1];
616 $output =& $params[2];
618 $context = $smarty->getTemplateVars(
'currentContext');
620 $contextDisplaySettingExists = $pluginSettingsDao->settingExists($context->getId(), $this->getName(),
'displayStatistics');
621 $contextDisplaySetting = $this->
getSetting($context->getId(),
'displayStatistics');
622 $siteDisplaySetting = $this->
getSetting(CONTEXT_ID_NONE,
'displayStatistics');
623 if (($contextDisplaySettingExists && $contextDisplaySetting) ||
624 (!$contextDisplaySettingExists && $siteDisplaySetting)) {
626 $pubObject = $smarty->getTemplateVars(
'preprint');
627 assert(is_a($pubObject,
'Submission'));
628 $pubObjectId = $pubObject->getId();
629 $pubObjectType =
'Submission';
633 'pubObjectType' => $pubObjectType,
634 'pubObjectId' => $pubObjectId,
636 'outputFrontend.tpl',
659 if ($this->_dataPrivacyOn) {
663 $currentDate = date(
"Ymd");
664 $saltFilenameLastModified = date(
"Ymd", filemtime($saltFilename));
665 $file = fopen($saltFilename,
'r');
666 $salt = trim(fread($file,filesize($saltFilename)));
668 if (empty($salt) || ($currentDate != $saltFilenameLastModified)) {
669 if(function_exists(
'mcrypt_create_iv')) {
670 $newSalt = bin2hex(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM|MCRYPT_RAND));
671 } elseif (function_exists(
'openssl_random_pseudo_bytes')){
672 $newSalt = bin2hex(openssl_random_pseudo_bytes(16, $cstrong));
673 } elseif (file_exists(
'/dev/urandom')){
674 $newSalt = bin2hex(file_get_contents(
'/dev/urandom',
false,
null, 0, 16));
676 $newSalt = mt_rand();
678 $file = fopen($saltFilename,
'wb');
679 if (flock($file, LOCK_EX)) {
680 fwrite($file, $newSalt);
681 flock($file, LOCK_UN);
691 if ($this->_dataPrivacyOn) {
692 if (!isset($salt))
return false;
694 $hashedIp = $this->
_hashIp($usageEvent[
'ip'], $salt);
696 if ($hashedIp ===
false)
return false;
697 $desiredParams = array($hashedIp);
699 $desiredParams = array($usageEvent[
'ip']);
702 if (isset($usageEvent[
'classification'])) {
703 $desiredParams[] = $usageEvent[
'classification'];
705 $desiredParams[] =
'-';
708 if (!$this->_dataPrivacyOn && isset($usageEvent[
'user'])) {
709 $desiredParams[] = $usageEvent[
'user']->getId();
711 $desiredParams[] =
'-';
714 $desiredParams = array_merge($desiredParams,
715 array(
'"' . $usageEvent[
'time'] .
'"', $usageEvent[
'canonicalUrl'],
717 '"' . $usageEvent[
'userAgent'] .
'"'));
719 $usageLogEntry = implode(
' ', $desiredParams) . PHP_EOL;
721 import(
'lib.pkp.classes.file.PrivateFileManager');
729 if (!$fileMgr->fileExists($usageEventFilesPath,
'dir')) {
730 $success = $fileMgr->mkdirtree($usageEventFilesPath);
738 $filePath = $usageEventFilesPath . DIRECTORY_SEPARATOR . $filename;
739 $fp = fopen($filePath,
'ab');
740 if (flock($fp, LOCK_EX)) {
741 fwrite($fp, $usageLogEntry);
762 if(function_exists(
'mhash')) {
763 return bin2hex(mhash(MHASH_SHA256, $ip.$salt));
765 assert(function_exists(
'hash'));
766 if (!function_exists(
'hash'))
return false;
767 return hash(
'sha256', $ip.$salt);
777 $cache =
CacheManager::getManager()->getCache(
'downloadStats', $pubObjectId, array($this,
'_downloadStatsCacheMiss'));
778 if (time() - $cache->getCacheTime() > 60 * 60 * 24) {
782 $statsReports = $cache->get($pubObjectId);
784 $currentYear = date(
"Y");
785 $months = range(1, 12);
786 $statsByFormat = $statsByMonth = $years = array();
788 foreach ($statsReports as $statsReport) {
789 $month = (int) substr($statsReport[STATISTICS_DIMENSION_MONTH], -2);
790 $year = (int) substr($statsReport[STATISTICS_DIMENSION_MONTH], 0, 4);
791 $metric = $statsReport[STATISTICS_METRIC];
794 $years[$year] =
null;
796 $representationId = $statsReport[STATISTICS_DIMENSION_REPRESENTATION_ID];
800 if (!array_key_exists($representationId, $statsByFormat)) {
802 $representation = $representationDao->getById($representationId);
803 if (empty($representation)) {
806 $statsByFormat[$representationId] = array(
808 'label' => $representation->getLocalizedName(),
809 'color' => $this->_getColor($representationId),
814 if (!array_key_exists($year, $statsByFormat[$representationId][
'data'])) {
815 $statsByFormat[$representationId][
'data'][$year] = array_fill_keys($months, 0);
817 $statsByFormat[$representationId][
'data'][$year][$month] = $metric;
818 $statsByFormat[$representationId][
'total'] += $metric;
821 if (!array_key_exists($year, $statsByMonth)) {
822 $statsByMonth[$year] = array_fill_keys($months, 0);
824 $statsByMonth[$year][$month] += $metric;
825 $totalDownloads += $metric;
829 $datasetId =
'allDownloads';
830 $statsByMonth = array($datasetId => array(
831 'data' => $statsByMonth,
832 'label' => __(
'common.allDownloads'),
833 'color' => $this->_getColor(REALLY_BIG_NUMBER),
834 'total' => $totalDownloads
838 return array($statsByFormat, $statsByMonth, array_keys($years));
855 $allDownloadStats = array();
856 foreach($stats as $dataset) {
857 if (array_key_exists(
'allDownloads', $dataset)) {
858 $allDownloadStats = $dataset[
'allDownloads'];
862 return $allDownloadStats;
873 STATISTICS_DIMENSION_SUBMISSION_ID => $pubObjectId,
874 STATISTICS_DIMENSION_ASSOC_TYPE => ASSOC_TYPE_SUBMISSION_FILE
876 $orderBy = array(STATISTICS_DIMENSION_MONTH => STATISTICS_ORDER_ASC);
881 $statsReports =
$application->getMetrics(current($reportPlugin->getMetricTypes()), array(STATISTICS_DIMENSION_MONTH, STATISTICS_DIMENSION_REPRESENTATION_ID), $filter, $orderBy);
882 $cache->setEntireCache(array($pubObjectId => $statsReports));
883 return $statsReports;
892 function _getColor($num) {
893 $hash = md5(
'color' . $num * 2);
894 return hexdec(substr($hash, 0, 2)) .
',' . hexdec(substr($hash, 2, 2)) .
',' . hexdec(substr($hash, 4, 2));
906 if ($context && $pluginSettingsDao->settingExists($context->getId(), $this->getName(), $name)) {
907 return $this->
getSetting($context->getId(), $name);
909 return $this->
getSetting(CONTEXT_ID_NONE, $name);