first commit
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/services/ContextService.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ContextService
|
||||
*
|
||||
* @ingroup services
|
||||
*
|
||||
* @brief Extends the base context service class with app-specific
|
||||
* requirements.
|
||||
*/
|
||||
|
||||
namespace APP\services;
|
||||
|
||||
use APP\article\ArticleTombstoneManager;
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\file\PublicFileManager;
|
||||
use APP\subscription\IndividualSubscriptionDAO;
|
||||
use APP\subscription\InstitutionalSubscriptionDAO;
|
||||
use APP\subscription\SubscriptionTypeDAO;
|
||||
use PKP\config\Config;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\file\TemporaryFileManager;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\submission\GenreDAO;
|
||||
|
||||
class ContextService extends \PKP\services\PKPContextService
|
||||
{
|
||||
/** @copydoc \PKP\services\PKPContextService::$contextsFileDirName */
|
||||
public $contextsFileDirName = 'journals';
|
||||
|
||||
/**
|
||||
* Initialize hooks for extending PKPContextService
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->installFileDirs = [
|
||||
Config::getVar('files', 'files_dir') . '/%s/%d',
|
||||
Config::getVar('files', 'files_dir') . '/%s/%d/articles',
|
||||
Config::getVar('files', 'files_dir') . '/%s/%d/issues',
|
||||
Config::getVar('files', 'public_files_dir') . '/%s/%d',
|
||||
];
|
||||
|
||||
Hook::add('Context::add', [$this, 'afterAddContext']);
|
||||
Hook::add('Context::edit', [$this, 'afterEditContext']);
|
||||
Hook::add('Context::delete::before', [$this, 'beforeDeleteContext']);
|
||||
Hook::add('Context::delete', [$this, 'afterDeleteContext']);
|
||||
Hook::add('Context::validate', [$this, 'validateContext']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take additional actions after a new context has been added
|
||||
*
|
||||
* @param string $hookName
|
||||
* @param array $args [
|
||||
*
|
||||
* @option Journal The new context
|
||||
* @option Request
|
||||
* ]
|
||||
*/
|
||||
public function afterAddContext($hookName, $args)
|
||||
{
|
||||
$context = $args[0];
|
||||
|
||||
// Create a default section
|
||||
$section = Repo::section()->newDataObject();
|
||||
$section->setTitle(__('section.default.title'), $context->getPrimaryLocale());
|
||||
$section->setAbbrev(__('section.default.abbrev'), $context->getPrimaryLocale());
|
||||
$section->setMetaIndexed(true);
|
||||
$section->setMetaReviewed(true);
|
||||
$section->setPolicy(__('section.default.policy'), $context->getPrimaryLocale());
|
||||
$section->setEditorRestricted(false);
|
||||
$section->setHideTitle(false);
|
||||
$section->setContextId($context->getId());
|
||||
Repo::section()->add($section);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update journal-specific settings when a context is edited
|
||||
*
|
||||
* @param string $hookName
|
||||
* @param array $args [
|
||||
*
|
||||
* @option Journal The new context
|
||||
* @option Journal The current context
|
||||
* @option array The params to edit
|
||||
* @option Request
|
||||
* ]
|
||||
*/
|
||||
public function afterEditContext($hookName, $args)
|
||||
{
|
||||
$newContext = $args[0];
|
||||
$currentContext = $args[1];
|
||||
$params = $args[2];
|
||||
$request = $args[3];
|
||||
|
||||
// Move an uploaded journal thumbnail and set the updated data
|
||||
if (!empty($params['journalThumbnail'])) {
|
||||
$supportedLocales = $newContext->getSupportedFormLocales();
|
||||
foreach ($supportedLocales as $localeKey) {
|
||||
if (!array_key_exists($localeKey, $params['journalThumbnail'])) {
|
||||
continue;
|
||||
}
|
||||
$localeValue = $this->_saveFileParam(
|
||||
$newContext,
|
||||
$params['journalThumbnail'][$localeKey],
|
||||
'journalThumbnail',
|
||||
$request->getUser()->getId(),
|
||||
$localeKey,
|
||||
true
|
||||
);
|
||||
$newContext->setData('journalThumbnail', $localeValue, $localeKey);
|
||||
}
|
||||
}
|
||||
|
||||
// If the context is enabled or disabled, create or delete
|
||||
// tombstones for all published submissions
|
||||
if ($newContext->getData('enabled') !== $currentContext->getData('enabled')) {
|
||||
$articleTombstoneManager = new ArticleTombstoneManager();
|
||||
if ($newContext->getData('enabled')) {
|
||||
$articleTombstoneManager->deleteTombstonesByContextId($newContext->getId());
|
||||
} else {
|
||||
$articleTombstoneManager->insertTombstonesByContext($newContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform actions before a context has been deleted
|
||||
*
|
||||
* This should only be used in cases where you need the context to still exist
|
||||
* in the database to complete the actions. Otherwise, use
|
||||
* ContextService::afterDeleteContext().
|
||||
*
|
||||
* @param string $hookName
|
||||
* @param array $args [
|
||||
*
|
||||
* @option Context The new context
|
||||
* @option Request
|
||||
* ]
|
||||
*/
|
||||
public function beforeDeleteContext($hookName, $args)
|
||||
{
|
||||
$context = $args[0];
|
||||
|
||||
// Create tombstones for all published submissions
|
||||
$articleTombstoneManager = new ArticleTombstoneManager();
|
||||
$articleTombstoneManager->insertTombstonesByContext($context);
|
||||
/** @var GenreDAO */
|
||||
$genreDao = DAORegistry::getDAO('GenreDAO');
|
||||
$genreDao->deleteByContextId($context->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Take additional actions after a context has been deleted
|
||||
*
|
||||
* @param string $hookName
|
||||
* @param array $args [
|
||||
*
|
||||
* @option Journal The new context
|
||||
* @option Request
|
||||
* ]
|
||||
*/
|
||||
public function afterDeleteContext($hookName, $args)
|
||||
{
|
||||
$context = $args[0];
|
||||
|
||||
Repo::section()->deleteByContextId($context->getId());
|
||||
|
||||
Repo::issue()->deleteByContextId($context->getId());
|
||||
/** @var IndividualSubscriptionDAO */
|
||||
$subscriptionDao = DAORegistry::getDAO('IndividualSubscriptionDAO');
|
||||
$subscriptionDao->deleteByJournalId($context->getId());
|
||||
/** @var InstitutionalSubscriptionDAO */
|
||||
$subscriptionDao = DAORegistry::getDAO('InstitutionalSubscriptionDAO');
|
||||
$subscriptionDao->deleteByJournalId($context->getId());
|
||||
/** @var SubscriptionTypeDAO */
|
||||
$subscriptionTypeDao = DAORegistry::getDAO('SubscriptionTypeDAO');
|
||||
$subscriptionTypeDao->deleteByJournal($context->getId());
|
||||
|
||||
Repo::submission()->deleteByContextId($context->getId());
|
||||
|
||||
$publicFileManager = new PublicFileManager();
|
||||
$publicFileManager->rmtree($publicFileManager->getContextFilesPath($context->getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make additional validation checks
|
||||
*
|
||||
* @param string $hookName
|
||||
* @param array $args [
|
||||
*
|
||||
* @option Journal The new context
|
||||
* @option Request
|
||||
* ]
|
||||
*/
|
||||
public function validateContext($hookName, $args)
|
||||
{
|
||||
$errors = & $args[0];
|
||||
$props = $args[2];
|
||||
$allowedLocales = $args[3];
|
||||
|
||||
if (!isset($props['journalThumbnail'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If a journal thumbnail is passed, check that the temporary file exists
|
||||
// and the current user owns it
|
||||
$user = Application::get()->getRequest()->getUser();
|
||||
$userId = $user ? $user->getId() : null;
|
||||
$temporaryFileManager = new TemporaryFileManager();
|
||||
if (isset($props['journalThumbnail']) && empty($errors['journalThumbnail'])) {
|
||||
foreach ($allowedLocales as $localeKey) {
|
||||
if (empty($props['journalThumbnail'][$localeKey]) || empty($props['journalThumbnail'][$localeKey]['temporaryFileId'])) {
|
||||
continue;
|
||||
}
|
||||
if (!$temporaryFileManager->getFile($props['journalThumbnail'][$localeKey]['temporaryFileId'], $userId)) {
|
||||
if (!is_array($errors['journalThumbnail'])) {
|
||||
$errors['journalThumbnail'] = [];
|
||||
}
|
||||
$errors['journalThumbnail'][$localeKey] = [__('common.noTemporaryFile')];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/services/NavigationMenuService.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class NavigationMenuService
|
||||
*
|
||||
* @ingroup services
|
||||
*
|
||||
* @brief Helper class that encapsulates NavigationMenu business logic
|
||||
*/
|
||||
|
||||
namespace APP\services;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\template\TemplateManager;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\security\Validation;
|
||||
|
||||
class NavigationMenuService extends \PKP\services\PKPNavigationMenuService
|
||||
{
|
||||
// Types for all ojs default navigationMenuItems
|
||||
public const NMI_TYPE_SUBSCRIPTIONS = 'NMI_TYPE_SUBSCRIPTIONS';
|
||||
public const NMI_TYPE_MY_SUBSCRIPTIONS = 'NMI_TYPE_MY_SUBSCRIPTIONS';
|
||||
public const NMI_TYPE_CURRENT = 'NMI_TYPE_CURRENT';
|
||||
public const NMI_TYPE_ARCHIVES = 'NMI_TYPE_ARCHIVES';
|
||||
|
||||
/**
|
||||
* Initialize hooks for extending PKPNavigationMenuService
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
Hook::add('NavigationMenus::itemTypes', [$this, 'getMenuItemTypesCallback']);
|
||||
Hook::add('NavigationMenus::displaySettings', [$this, 'getDisplayStatusCallback']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all default navigationMenuItemTypes.
|
||||
*
|
||||
* @param string $hookName
|
||||
* @param array $args of arguments passed
|
||||
*/
|
||||
public function getMenuItemTypesCallback($hookName, $args)
|
||||
{
|
||||
$types = & $args[0];
|
||||
|
||||
$ojsTypes = [
|
||||
self::NMI_TYPE_CURRENT => [
|
||||
'title' => __('editor.issues.currentIssue'),
|
||||
'description' => __('manager.navigationMenus.current.description'),
|
||||
],
|
||||
self::NMI_TYPE_ARCHIVES => [
|
||||
'title' => __('navigation.archives'),
|
||||
'description' => __('manager.navigationMenus.archives.description'),
|
||||
],
|
||||
self::NMI_TYPE_SUBSCRIPTIONS => [
|
||||
'title' => __('navigation.subscriptions'),
|
||||
'description' => __('manager.navigationMenus.subscriptions.description'),
|
||||
'conditionalWarning' => __('manager.navigationMenus.subscriptions.conditionalWarning'),
|
||||
],
|
||||
self::NMI_TYPE_MY_SUBSCRIPTIONS => [
|
||||
'title' => __('user.subscriptions.mySubscriptions'),
|
||||
'description' => __('manager.navigationMenus.mySubscriptions.description'),
|
||||
'conditionalWarning' => __('manager.navigationMenus.mySubscriptions.conditionalWarning'),
|
||||
],
|
||||
];
|
||||
|
||||
$types = array_merge($types, $ojsTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for display menu item functionality
|
||||
*
|
||||
* @param string $hookName
|
||||
* @param array $args of arguments passed
|
||||
*/
|
||||
public function getDisplayStatusCallback($hookName, $args)
|
||||
{
|
||||
$navigationMenuItem = & $args[0];
|
||||
|
||||
$request = Application::get()->getRequest();
|
||||
$dispatcher = $request->getDispatcher();
|
||||
$templateMgr = TemplateManager::getManager(Application::get()->getRequest());
|
||||
|
||||
$isUserLoggedIn = Validation::isLoggedIn();
|
||||
$isUserLoggedInAs = Validation::loggedInAs();
|
||||
$context = $request->getContext();
|
||||
|
||||
$this->transformNavMenuItemTitle($templateMgr, $navigationMenuItem);
|
||||
|
||||
$menuItemType = $navigationMenuItem->getType();
|
||||
|
||||
// Conditionally hide some items
|
||||
switch ($menuItemType) {
|
||||
case self::NMI_TYPE_CURRENT:
|
||||
case self::NMI_TYPE_ARCHIVES:
|
||||
$navigationMenuItem->setIsDisplayed($context && $context->getData('publishingMode') != \APP\journal\Journal::PUBLISHING_MODE_NONE);
|
||||
break;
|
||||
case self::NMI_TYPE_SUBSCRIPTIONS:
|
||||
if ($context) {
|
||||
$paymentManager = Application::getPaymentManager($context);
|
||||
$navigationMenuItem->setIsDisplayed($context->getData('paymentsEnabled') && $paymentManager->isConfigured());
|
||||
}
|
||||
break;
|
||||
case self::NMI_TYPE_MY_SUBSCRIPTIONS:
|
||||
if ($context) {
|
||||
$paymentManager = Application::getPaymentManager($context);
|
||||
$navigationMenuItem->setIsDisplayed(Validation::isLoggedIn() && $context->getData('paymentsEnabled') && $paymentManager->isConfigured() && $context->getData('publishingMode') == \APP\journal\Journal::PUBLISHING_MODE_SUBSCRIPTION);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ($navigationMenuItem->getIsDisplayed()) {
|
||||
// Set the URL
|
||||
switch ($menuItemType) {
|
||||
case self::NMI_TYPE_CURRENT:
|
||||
$navigationMenuItem->setUrl($dispatcher->url(
|
||||
$request,
|
||||
Application::ROUTE_PAGE,
|
||||
null,
|
||||
'issue',
|
||||
'current',
|
||||
null
|
||||
));
|
||||
break;
|
||||
case self::NMI_TYPE_ARCHIVES:
|
||||
$navigationMenuItem->setUrl($dispatcher->url(
|
||||
$request,
|
||||
Application::ROUTE_PAGE,
|
||||
null,
|
||||
'issue',
|
||||
'archive',
|
||||
null
|
||||
));
|
||||
break;
|
||||
case self::NMI_TYPE_SUBSCRIPTIONS:
|
||||
$navigationMenuItem->setUrl($dispatcher->url(
|
||||
$request,
|
||||
Application::ROUTE_PAGE,
|
||||
null,
|
||||
'about',
|
||||
'subscriptions',
|
||||
null
|
||||
));
|
||||
break;
|
||||
case self::NMI_TYPE_MY_SUBSCRIPTIONS:
|
||||
$navigationMenuItem->setUrl($dispatcher->url(
|
||||
$request,
|
||||
Application::ROUTE_PAGE,
|
||||
null,
|
||||
'user',
|
||||
'subscriptions',
|
||||
null
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
foreach ([
|
||||
'NMI_TYPE_SUBSCRIPTIONS',
|
||||
'NMI_TYPE_MY_SUBSCRIPTIONS',
|
||||
'NMI_TYPE_CURRENT',
|
||||
'NMI_TYPE_ARCHIVES',
|
||||
] as $constantName) {
|
||||
define($constantName, constant('\APP\services\NavigationMenuService::' . $constantName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/services/OJSServiceProvider.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class OJSServiceProvider
|
||||
*
|
||||
* @ingroup services
|
||||
*
|
||||
* @brief Utility class to package all OJS services
|
||||
*/
|
||||
|
||||
namespace APP\services;
|
||||
|
||||
use Pimple\Container;
|
||||
use PKP\services\PKPFileService;
|
||||
use PKP\services\PKPSchemaService;
|
||||
use PKP\services\PKPSiteService;
|
||||
use PKP\services\PKPStatsContextService;
|
||||
use PKP\services\PKPStatsGeoService;
|
||||
use PKP\services\PKPStatsSushiService;
|
||||
|
||||
class OJSServiceProvider implements \Pimple\ServiceProviderInterface
|
||||
{
|
||||
/**
|
||||
* Registers services
|
||||
*
|
||||
*/
|
||||
public function register(Container $pimple)
|
||||
{
|
||||
// File service
|
||||
$pimple['file'] = function () {
|
||||
return new PKPFileService();
|
||||
};
|
||||
|
||||
// NavigationMenus service
|
||||
$pimple['navigationMenu'] = function () {
|
||||
return new NavigationMenuService();
|
||||
};
|
||||
// Context service
|
||||
$pimple['context'] = function () {
|
||||
return new ContextService();
|
||||
};
|
||||
|
||||
// Site service
|
||||
$pimple['site'] = function () {
|
||||
return new PKPSiteService();
|
||||
};
|
||||
|
||||
// Schema service
|
||||
$pimple['schema'] = function () {
|
||||
return new PKPSchemaService();
|
||||
};
|
||||
|
||||
// Context statistics service
|
||||
$pimple['contextStats'] = function () {
|
||||
return new PKPStatsContextService();
|
||||
};
|
||||
|
||||
// Publication statistics service
|
||||
$pimple['publicationStats'] = function () {
|
||||
return new StatsPublicationService();
|
||||
};
|
||||
|
||||
// Issue statistics service
|
||||
$pimple['issueStats'] = function () {
|
||||
return new StatsIssueService();
|
||||
};
|
||||
|
||||
// Geo statistics service
|
||||
$pimple['geoStats'] = function () {
|
||||
return new PKPStatsGeoService();
|
||||
};
|
||||
|
||||
// SUSHI statistics service
|
||||
$pimple['sushiStats'] = function () {
|
||||
return new PKPStatsSushiService();
|
||||
};
|
||||
|
||||
// Editorial statistics service
|
||||
$pimple['editorialStats'] = function () {
|
||||
return new StatsEditorialService();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/services/StatsEditorialService.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class StatsEditorialService
|
||||
*
|
||||
* @ingroup services
|
||||
*
|
||||
* @brief Helper class that encapsulates business logic for getting
|
||||
* editorial stats
|
||||
*/
|
||||
|
||||
namespace APP\services;
|
||||
|
||||
class StatsEditorialService extends \PKP\services\PKPStatsEditorialService
|
||||
{
|
||||
/**
|
||||
* Process the sectionIds param when getting the query builder
|
||||
*
|
||||
* @param array $args
|
||||
*/
|
||||
protected function getQueryBuilder($args = [])
|
||||
{
|
||||
$statsQB = parent::getQueryBuilder($args);
|
||||
if (!empty(($args['sectionIds']))) {
|
||||
$statsQB->filterBySections($args['sectionIds']);
|
||||
}
|
||||
return $statsQB;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/services/StatsIssueService.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class StatsIssueService
|
||||
*
|
||||
* @ingroup services
|
||||
*
|
||||
* @brief Helper class that encapsulates issue statistics business logic
|
||||
*/
|
||||
|
||||
namespace APP\services;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\services\queryBuilders\StatsIssueQueryBuilder;
|
||||
use APP\statistics\StatisticsHelper;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\services\PKPStatsServiceTrait;
|
||||
|
||||
class StatsIssueService
|
||||
{
|
||||
use PKPStatsServiceTrait;
|
||||
|
||||
/**
|
||||
* A callback to be used with array_filter() to return records for
|
||||
* the TOC views.
|
||||
*/
|
||||
public function filterRecordTOC(object $record): bool
|
||||
{
|
||||
return $record->assoc_type == Application::ASSOC_TYPE_ISSUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback to be used with array_filter() to return records for
|
||||
* the issue galley views.
|
||||
*/
|
||||
public function filterRecordIssueGalley(object $record): bool
|
||||
{
|
||||
return $record->assoc_type == Application::ASSOC_TYPE_ISSUE_GALLEY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a count of all issues with stats that match the request arguments
|
||||
*/
|
||||
public function getCount(array $args): int
|
||||
{
|
||||
$defaultArgs = $this->getDefaultArgs();
|
||||
$args = array_merge($defaultArgs, $args);
|
||||
unset($args['count']);
|
||||
unset($args['offset']);
|
||||
$metricsQB = $this->getQueryBuilder($args);
|
||||
|
||||
Hook::call('StatsIssue::getCount::queryBuilder', [&$metricsQB, $args]);
|
||||
|
||||
return $metricsQB->getIssueIds()->get()->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the issues with total stats that match the request arguments
|
||||
*/
|
||||
public function getTotals(array $args): array
|
||||
{
|
||||
$defaultArgs = $this->getDefaultArgs();
|
||||
$args = array_merge($defaultArgs, $args);
|
||||
$metricsQB = $this->getQueryBuilder($args);
|
||||
|
||||
Hook::call('StatsIssue::getTotals::queryBuilder', [&$metricsQB, $args]);
|
||||
|
||||
$groupBy = [StatisticsHelper::STATISTICS_DIMENSION_ISSUE_ID];
|
||||
$metricsQB = $metricsQB->getSum($groupBy);
|
||||
|
||||
$orderDirection = $args['orderDirection'] === StatisticsHelper::STATISTICS_ORDER_ASC ? 'asc' : 'desc';
|
||||
$metricsQB->orderBy(StatisticsHelper::STATISTICS_METRIC, $orderDirection);
|
||||
return $metricsQB->get()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metrics by type (toc, issue galley) for an issue
|
||||
* Assumes that the issue ID is provided in parameters
|
||||
*/
|
||||
public function getTotalsByType(int $issueId, int $contextId, ?string $dateStart, ?string $dateEnd): array
|
||||
{
|
||||
$defaultArgs = $this->getDefaultArgs();
|
||||
$args = [
|
||||
'issueIds' => [$issueId],
|
||||
'contextIds' => [$contextId],
|
||||
'dateStart' => $dateStart ?? $defaultArgs['dateStart'],
|
||||
'dateEnd' => $dateEnd ?? $defaultArgs['dateEnd'],
|
||||
];
|
||||
$metricsQB = $this->getQueryBuilder($args);
|
||||
|
||||
Hook::call('StatsIssue::getTotalsByType::queryBuilder', [&$metricsQB, $args]);
|
||||
|
||||
// get toc and galley views for the issue
|
||||
$groupBy = [StatisticsHelper::STATISTICS_DIMENSION_ASSOC_TYPE];
|
||||
$metricsQB = $metricsQB->getSum($groupBy);
|
||||
$metricsByType = $metricsQB->get()->toArray();
|
||||
|
||||
$tocViews = $issueGalleyViews = 0;
|
||||
$tocRecord = array_filter($metricsByType, [$this, 'filterRecordTOC']);
|
||||
if (!empty($tocRecord)) {
|
||||
$tocViews = (int) current($tocRecord)->metric;
|
||||
}
|
||||
$issueGalleyRecord = array_filter($metricsByType, [$this, 'filterRecordIssueGalley']);
|
||||
if (!empty($issueGalleyRecord)) {
|
||||
$issueGalleyViews = current($issueGalleyRecord)->metric;
|
||||
}
|
||||
|
||||
return [
|
||||
'toc' => $tocViews,
|
||||
'galley' => $issueGalleyViews,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default parameters
|
||||
*/
|
||||
public function getDefaultArgs(): array
|
||||
{
|
||||
return [
|
||||
'dateStart' => StatisticsHelper::STATISTICS_EARLIEST_DATE,
|
||||
'dateEnd' => date('Y-m-d', strtotime('yesterday')),
|
||||
|
||||
// Require a context to be specified to prevent unwanted data leakage
|
||||
// if someone forgets to specify the context.
|
||||
'contextIds' => [\PKP\core\PKPApplication::CONTEXT_ID_NONE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a QueryBuilder object with the passed args
|
||||
*/
|
||||
public function getQueryBuilder(array $args = []): StatsIssueQueryBuilder
|
||||
{
|
||||
$statsQB = new StatsIssueQueryBuilder();
|
||||
$statsQB
|
||||
->filterByContexts($args['contextIds'])
|
||||
->before($args['dateEnd'])
|
||||
->after($args['dateStart']);
|
||||
|
||||
if (!empty(($args['issueIds']))) {
|
||||
$statsQB->filterByIssues($args['issueIds']);
|
||||
}
|
||||
|
||||
if (!empty(($args['issueGalleyIds']))) {
|
||||
$statsQB->filterByIssueGalleys($args['issueGalleyIds']);
|
||||
}
|
||||
|
||||
if (!empty($args['assocTypes'])) {
|
||||
$statsQB->filterByAssocTypes($args['assocTypes']);
|
||||
}
|
||||
|
||||
if (isset($args['count'])) {
|
||||
$statsQB->limit($args['count']);
|
||||
if (isset($args['offset'])) {
|
||||
$statsQB->offset($args['offset']);
|
||||
}
|
||||
}
|
||||
|
||||
Hook::call('StatsIssue::queryBuilder', [&$statsQB, $args]);
|
||||
|
||||
return $statsQB;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/services/StatsPublicationService.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class StatsPublicationService
|
||||
*
|
||||
* @ingroup services
|
||||
*
|
||||
* @brief Helper class that encapsulates publication statistics business logic
|
||||
*/
|
||||
|
||||
namespace APP\services;
|
||||
|
||||
use APP\services\queryBuilders\StatsPublicationQueryBuilder;
|
||||
|
||||
class StatsPublicationService extends \PKP\services\PKPStatsPublicationService
|
||||
{
|
||||
protected function getAppSpecificFilters(StatsPublicationQueryBuilder &$statsQB, array $args = []): void
|
||||
{
|
||||
if (!empty(($args['issueIds']))) {
|
||||
$statsQB->filterByIssues($args['issueIds']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/services/QueryBuilders/ContextQueryBuilder.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ContextQueryBuilder
|
||||
*
|
||||
* @ingroup query_builders
|
||||
*
|
||||
* @brief Journal list query builder
|
||||
*/
|
||||
|
||||
namespace APP\services\queryBuilders;
|
||||
|
||||
class ContextQueryBuilder extends \PKP\services\queryBuilders\PKPContextQueryBuilder
|
||||
{
|
||||
/** @copydoc \PKP\services\queryBuilders\PKPContextQueryBuilder::$db */
|
||||
protected $db = 'journals';
|
||||
|
||||
/** @copydoc \PKP\services\queryBuilders\PKPContextQueryBuilder::$dbSettings */
|
||||
protected $dbSettings = 'journal_settings';
|
||||
|
||||
/** @copydoc \PKP\services\queryBuilders\PKPContextQueryBuilder::$dbIdColumn */
|
||||
protected $dbIdColumn = 'journal_id';
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/services/QueryBuilders/GalleyQueryBuilder.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class GalleyQueryBuilder
|
||||
*
|
||||
* @ingroup query_builders
|
||||
*
|
||||
* @brief Class for building database queries for galleys
|
||||
*/
|
||||
|
||||
namespace APP\services\queryBuilders;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\services\queryBuilders\interfaces\EntityQueryBuilderInterface;
|
||||
|
||||
class GalleyQueryBuilder implements EntityQueryBuilderInterface
|
||||
{
|
||||
/** @var array List of columns (see getQuery) */
|
||||
public $columns;
|
||||
|
||||
/** @var array get authors for one or more publications */
|
||||
protected $publicationIds = [];
|
||||
|
||||
public ?array $contextIds = null;
|
||||
|
||||
/**
|
||||
* Set publicationIds filter
|
||||
*
|
||||
* @param array|int $publicationIds
|
||||
*
|
||||
* @return \APP\services\queryBuilders\GalleyQueryBuilder
|
||||
*/
|
||||
public function filterByPublicationIds($publicationIds)
|
||||
{
|
||||
$this->publicationIds = is_array($publicationIds) ? $publicationIds : [$publicationIds];
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function filterByContexts(array $contextIds): self
|
||||
{
|
||||
$this->contextIds = $contextIds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc PKP\services\queryBuilders\interfaces\EntityQueryBuilderInterface::getCount()
|
||||
*/
|
||||
public function getCount()
|
||||
{
|
||||
return $this
|
||||
->getQuery()
|
||||
->select('g.galley_id')
|
||||
->get()
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc PKP\services\queryBuilders\interfaces\EntityQueryBuilderInterface::getCount()
|
||||
*/
|
||||
public function getIds()
|
||||
{
|
||||
return $this
|
||||
->getQuery()
|
||||
->select('g.galley_id')
|
||||
->pluck('g.galley_id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc PKP\services\queryBuilders\interfaces\EntityQueryBuilderInterface::getCount()
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
$this->columns = ['*'];
|
||||
$q = DB::table('publication_galleys as g');
|
||||
|
||||
if (!empty($this->publicationIds)) {
|
||||
$q->whereIn('g.publication_id', $this->publicationIds);
|
||||
}
|
||||
|
||||
// Contexts
|
||||
$q->when($this->contextIds !== null, function (Builder $q) {
|
||||
$q->whereIn('g.galley_id', function (Builder $q) {
|
||||
$q->select('g.galley_id')
|
||||
->from('publication_galleys as g')
|
||||
->leftJoin('publications as p', 'p.publication_id', '=', 'g.publication_id')
|
||||
->leftJoin('submissions as s', 's.submission_id', '=', 'p.submission_id')
|
||||
->whereIn('s.context_id', $this->contextIds);
|
||||
});
|
||||
});
|
||||
|
||||
$q->orderBy('g.seq', 'asc');
|
||||
|
||||
// Add app-specific query statements
|
||||
Hook::call('Galley::getMany::queryObject', [&$q, $this]);
|
||||
|
||||
$q->select($this->columns);
|
||||
|
||||
return $q;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/services/QueryBuilders/StatsEditorialQueryBuilder.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class StatsEditorialQueryBuilder
|
||||
*
|
||||
* @ingroup query_builders
|
||||
*
|
||||
* @brief Editorial statistics list query builder
|
||||
*/
|
||||
|
||||
namespace APP\services\queryBuilders;
|
||||
|
||||
use PKP\services\queryBuilders\PKPStatsEditorialQueryBuilder;
|
||||
|
||||
class StatsEditorialQueryBuilder extends PKPStatsEditorialQueryBuilder
|
||||
{
|
||||
/** @var string The table column name for section IDs */
|
||||
public $sectionIdsColumn = 'section_id';
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/services/queryBuilders/StatsGeoQueryBuilder.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class StatsGeoQueryBuilder
|
||||
*
|
||||
* @ingroup query_builders
|
||||
*
|
||||
* @brief Helper class to construct a query to fetch geographic stats records from the
|
||||
* metrics_submission_geo_monthly table.
|
||||
*/
|
||||
|
||||
namespace APP\services\queryBuilders;
|
||||
|
||||
use APP\submission\Submission;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\services\queryBuilders\PKPStatsGeoQueryBuilder;
|
||||
use PKP\statistics\PKPStatisticsHelper;
|
||||
|
||||
class StatsGeoQueryBuilder extends PKPStatsGeoQueryBuilder
|
||||
{
|
||||
/** Include records for these issues */
|
||||
protected array $issueIds = [];
|
||||
|
||||
public function getSectionColumn(): string
|
||||
{
|
||||
return 'section_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the issues to get records for
|
||||
*/
|
||||
public function filterByIssues(array $issueIds): self
|
||||
{
|
||||
$this->issueIds = $issueIds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function _getAppSpecificQuery(Builder &$q): void
|
||||
{
|
||||
if (!empty($this->issueIds)) {
|
||||
$issueSubmissionIds = DB::table('publications as p')->select('p.submission_id')->distinct()
|
||||
->from('publications as p')
|
||||
->leftJoin('publication_settings as ps', 'ps.setting_name', '=', DB::raw('\'issueId\''))
|
||||
->where('p.status', Submission::STATUS_PUBLISHED)
|
||||
->whereIn('ps.setting_value', $this->issueIds);
|
||||
$q->joinSub($issueSubmissionIds, 'is', function ($join) {
|
||||
$join->on('metrics_submission_geo_monthly.' . PKPStatisticsHelper::STATISTICS_DIMENSION_SUBMISSION_ID, '=', 'is.submission_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/services/queryBuilders/StatsIssueQueryBuilder.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class StatsIssueQueryBuilder
|
||||
*
|
||||
* @ingroup query_builders
|
||||
*
|
||||
* @brief Helper class to construct a query to fetch issue stats records from the
|
||||
* metrics_issue table.
|
||||
*/
|
||||
|
||||
namespace APP\services\queryBuilders;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\statistics\StatisticsHelper;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\services\queryBuilders\PKPStatsQueryBuilder;
|
||||
|
||||
class StatsIssueQueryBuilder extends PKPStatsQueryBuilder
|
||||
{
|
||||
/** Include records for one of these object types: Application::ASSOC_TYPE_ISSUE, Application::ASSOC_TYPE_ISSUE_GALLEY */
|
||||
protected array $assocTypes = [];
|
||||
|
||||
/** Include records for these issues */
|
||||
protected array $issueIds = [];
|
||||
|
||||
/** Include records for these issues galleys */
|
||||
protected array $issueGalleyIds = [];
|
||||
|
||||
/**
|
||||
* Set the issues to get records for
|
||||
*/
|
||||
public function filterByIssues(array $issueIds): self
|
||||
{
|
||||
$this->issueIds = $issueIds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the issues to get records for
|
||||
*/
|
||||
public function filterByIssueGalleys(array $issueGalleyIds): self
|
||||
{
|
||||
$this->issueGalleyIds = $issueGalleyIds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the assocTypes to get records for
|
||||
*/
|
||||
public function filterByAssocTypes(array $assocTypes): self
|
||||
{
|
||||
$this->assocTypes = $assocTypes;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get issue IDs
|
||||
*/
|
||||
public function getIssueIds(): Builder
|
||||
{
|
||||
return $this->_getObject()
|
||||
->select([StatisticsHelper::STATISTICS_DIMENSION_ISSUE_ID])
|
||||
->distinct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc PKPStatsQueryBuilder::getSelectColumns()
|
||||
*/
|
||||
protected function getSelectColumns(array $selectColumns): array
|
||||
{
|
||||
$selectColumns = parent::getSelectColumns($selectColumns);
|
||||
|
||||
// consider PKPStatisticsHelper::STATISTICS_DIMENSION_ASSOC_TYPE because it can be used in reports
|
||||
if (in_array(StatisticsHelper::STATISTICS_DIMENSION_ASSOC_TYPE, $selectColumns)) {
|
||||
foreach ($selectColumns as $i => $selectColumn) {
|
||||
if ($selectColumn == StatisticsHelper::STATISTICS_DIMENSION_ASSOC_TYPE) {
|
||||
$assocTypeIssue = Application::ASSOC_TYPE_ISSUE;
|
||||
$assocTypeIssueGalley = Application::ASSOC_TYPE_ISSUE_GALLEY;
|
||||
$selectColumns[$i] = DB::raw("CASE WHEN issue_galley_id IS NULL THEN '{$assocTypeIssue}' ELSE '{$assocTypeIssueGalley}' END AS assoc_type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $selectColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc PKPStatsQueryBuilder::_getObject()
|
||||
*/
|
||||
protected function _getObject(): Builder
|
||||
{
|
||||
$q = DB::table('metrics_issue');
|
||||
|
||||
if (!empty($this->contextIds)) {
|
||||
$q->whereIn(StatisticsHelper::STATISTICS_DIMENSION_CONTEXT_ID, $this->contextIds);
|
||||
}
|
||||
|
||||
if (!empty($this->issueIds)) {
|
||||
$q->whereIn(StatisticsHelper::STATISTICS_DIMENSION_ISSUE_ID, $this->issueIds);
|
||||
}
|
||||
|
||||
if (!empty($this->issueGalleyIds)) {
|
||||
$q->whereIn(StatisticsHelper::STATISTICS_DIMENSION_ISSUE_GALLEY_ID, $this->issueGalleyIds);
|
||||
}
|
||||
|
||||
if (!empty($this->assocTypes)) {
|
||||
if (in_array(Application::ASSOC_TYPE_ISSUE, $this->assocTypes)) {
|
||||
$q->whereNull(StatisticsHelper::STATISTICS_DIMENSION_ISSUE_GALLEY_ID);
|
||||
} elseif (in_array(Application::ASSOC_TYPE_ISSUE_GALLEY, $this->assocTypes)) {
|
||||
$q->whereNotNull(StatisticsHelper::STATISTICS_DIMENSION_ISSUE_GALLEY_ID);
|
||||
}
|
||||
}
|
||||
|
||||
$q->whereBetween(StatisticsHelper::STATISTICS_DIMENSION_DATE, [$this->dateStart, $this->dateEnd]);
|
||||
|
||||
if ($this->limit > 0) {
|
||||
$q->limit($this->limit);
|
||||
if ($this->offset > 0) {
|
||||
$q->offset($this->offset);
|
||||
}
|
||||
}
|
||||
|
||||
Hook::call('StatsIssue::queryObject', [&$q, $this]);
|
||||
|
||||
return $q;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/services/queryBuilders/StatsPublicationQueryBuilder.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class StatsPublicationQueryBuilder
|
||||
*
|
||||
* @ingroup query_builders
|
||||
*
|
||||
* @brief Helper class to construct a query to fetch stats records from the
|
||||
* metrics_submission table.
|
||||
*/
|
||||
|
||||
namespace APP\services\queryBuilders;
|
||||
|
||||
use APP\submission\Submission;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\services\queryBuilders\PKPStatsPublicationQueryBuilder;
|
||||
use PKP\statistics\PKPStatisticsHelper;
|
||||
|
||||
class StatsPublicationQueryBuilder extends PKPStatsPublicationQueryBuilder
|
||||
{
|
||||
/** Include records for these issues */
|
||||
protected array $issueIds = [];
|
||||
|
||||
public function getSectionColumn(): string
|
||||
{
|
||||
return 'section_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the issues to get records for
|
||||
*/
|
||||
public function filterByIssues(array $issueIds): self
|
||||
{
|
||||
$this->issueIds = $issueIds;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function _getAppSpecificQuery(Builder &$q): void
|
||||
{
|
||||
if (!empty($this->issueIds)) {
|
||||
$issueSubmissionIds = DB::table('publications as p')->select('p.submission_id')->distinct()
|
||||
->from('publications as p')
|
||||
->leftJoin('publication_settings as ps', 'ps.setting_name', '=', DB::raw('\'issueId\''))
|
||||
->where('p.status', Submission::STATUS_PUBLISHED)
|
||||
->whereIn('ps.setting_value', $this->issueIds);
|
||||
$q->joinSub($issueSubmissionIds, 'is', function ($join) {
|
||||
$join->on('metrics_submission.' . PKPStatisticsHelper::STATISTICS_DIMENSION_SUBMISSION_ID, '=', 'is.submission_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user