first commit
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/task/DepositDois.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2003-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class DepositDois
|
||||
*
|
||||
* @ingroup classes_task
|
||||
*
|
||||
* @brief Dispatches job to automatically deposit DOIs for all configured contexts
|
||||
*/
|
||||
|
||||
namespace PKP\task;
|
||||
|
||||
use APP\core\Services;
|
||||
use APP\services\ContextService;
|
||||
use PKP\jobs\doi\DepositContext;
|
||||
use PKP\scheduledTask\ScheduledTask;
|
||||
|
||||
class DepositDois extends ScheduledTask
|
||||
{
|
||||
/**
|
||||
* @copydoc ScheduledTask::getName()
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return __('admin.scheduledTask.depositDois');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function executeActions()
|
||||
{
|
||||
/** @var ContextService $contextService */
|
||||
$contextService = Services::get('context');
|
||||
$contextIds = $contextService->getIds(['isEnabled' => true]);
|
||||
|
||||
foreach ($contextIds as $contextId) {
|
||||
dispatch(new DepositContext($contextId));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/task/EditorialReminders.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2003-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class EditorialReminders
|
||||
*
|
||||
* @ingroup classes_task
|
||||
*
|
||||
* @brief Dispatches jobs to send a reminder email to editors of outstanding tasks
|
||||
*/
|
||||
|
||||
namespace PKP\task;
|
||||
|
||||
use APP\core\Services;
|
||||
use APP\facades\Repo;
|
||||
use APP\services\ContextService;
|
||||
use PKP\jobs\email\EditorialReminder;
|
||||
use PKP\scheduledTask\ScheduledTask;
|
||||
use PKP\scheduledTask\ScheduledTaskHelper;
|
||||
use PKP\security\Role;
|
||||
use PKP\user\Collector;
|
||||
|
||||
class EditorialReminders extends ScheduledTask
|
||||
{
|
||||
public function getName()
|
||||
{
|
||||
return __('mailable.editorialReminder.description');
|
||||
}
|
||||
|
||||
protected function executeActions()
|
||||
{
|
||||
/** @var ContextService $contextService */
|
||||
$contextService = Services::get('context');
|
||||
$contextIds = $contextService->getIds(['isEnabled' => true]);
|
||||
|
||||
foreach ($contextIds as $contextId) {
|
||||
$this->addExecutionLogEntry(
|
||||
__('admin.scheduledTask.editorialReminder.logStart', ['contextId' => $contextId]),
|
||||
ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_NOTICE
|
||||
);
|
||||
$userIds = Repo::user()->getCollector()
|
||||
->filterByContextIds([$contextId])
|
||||
->filterByRoleIds([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR])
|
||||
->filterByStatus(Collector::STATUS_ACTIVE)
|
||||
->getIds();
|
||||
|
||||
/** @var int $userId */
|
||||
foreach ($userIds as $userId) {
|
||||
dispatch(new EditorialReminder($userId, $contextId));
|
||||
}
|
||||
$this->addExecutionLogEntry(
|
||||
__(
|
||||
'admin.scheduledTask.editorialReminder.logEnd',
|
||||
[
|
||||
'count' => $userIds->count(),
|
||||
'userIds' => $userIds->join(', '),
|
||||
'contextId' => $contextId,
|
||||
]
|
||||
),
|
||||
ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_NOTICE
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/task/FileLoader.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class FileLoader
|
||||
*
|
||||
* @ingroup classes_task
|
||||
*
|
||||
* @brief Base scheduled task class to reliably handle files processing.
|
||||
*/
|
||||
|
||||
namespace PKP\task;
|
||||
|
||||
use Exception;
|
||||
use PKP\config\Config;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\file\FileManager;
|
||||
use PKP\scheduledTask\ScheduledTask;
|
||||
use PKP\scheduledTask\ScheduledTaskHelper;
|
||||
use PKP\site\Site;
|
||||
use PKP\site\SiteDAO;
|
||||
|
||||
abstract class FileLoader extends ScheduledTask
|
||||
{
|
||||
public const FILE_LOADER_RETURN_TO_STAGING = 1;
|
||||
public const FILE_LOADER_RETURN_TO_DISPATCH = 2;
|
||||
public const FILE_LOADER_ERROR_MESSAGE_TYPE = 'common.error';
|
||||
public const FILE_LOADER_WARNING_MESSAGE_TYPE = 'common.warning';
|
||||
|
||||
public const FILE_LOADER_PATH_STAGING = 'stage';
|
||||
public const FILE_LOADER_PATH_PROCESSING = 'processing';
|
||||
public const FILE_LOADER_PATH_REJECT = 'reject';
|
||||
public const FILE_LOADER_PATH_ARCHIVE = 'archive';
|
||||
public const FILE_LOADER_PATH_DISPATCH = 'dispatch';
|
||||
|
||||
/** The current claimed filename that the script is working on. */
|
||||
private string $_claimedFilename;
|
||||
|
||||
/** Base directory path for the filesystem. */
|
||||
private string $_basePath;
|
||||
|
||||
/** Stage directory path. */
|
||||
private string $_stagePath;
|
||||
|
||||
/** Processing directory path. */
|
||||
private string $_processingPath;
|
||||
|
||||
/** Archive directory path. */
|
||||
private string $_archivePath;
|
||||
|
||||
/** Dispatch directory path. */
|
||||
private string $_dispatchPath;
|
||||
|
||||
/** Reject directory path. */
|
||||
private string $_rejectPath;
|
||||
|
||||
/** Admin email. */
|
||||
private string $_adminEmail;
|
||||
|
||||
/** Admin name. */
|
||||
private string $_adminName;
|
||||
|
||||
/** List of staged back files after processing. */
|
||||
private array $_stagedBackFiles = [];
|
||||
|
||||
/** Whether to compress the archived files or not. */
|
||||
private bool $_compressArchives = false;
|
||||
|
||||
/** List of files that should only be considered. */
|
||||
private array $_onlyConsiderFiles = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $args script arguments
|
||||
*/
|
||||
public function __construct(array $args)
|
||||
{
|
||||
parent::__construct($args);
|
||||
|
||||
// Canonicalize the base path.
|
||||
$basePath = rtrim($args[0], '/');
|
||||
$basePathFolder = basename($basePath);
|
||||
// We assume that the parent folder of the base path
|
||||
// does already exist and can be canonicalized.
|
||||
$basePathParent = realpath(dirname($basePath));
|
||||
if ($basePathParent === false) {
|
||||
$basePath = null;
|
||||
} else {
|
||||
$basePath = "{$basePathParent}/{$basePathFolder}";
|
||||
}
|
||||
$this->_basePath = $basePath;
|
||||
|
||||
// Configure paths.
|
||||
if (!is_null($basePath)) {
|
||||
$this->_stagePath = "{$basePath}/" . self::FILE_LOADER_PATH_STAGING;
|
||||
$this->_archivePath = "{$basePath}/" . self::FILE_LOADER_PATH_ARCHIVE;
|
||||
$this->_rejectPath = "{$basePath}/" . self::FILE_LOADER_PATH_REJECT;
|
||||
$this->_processingPath = "{$basePath}/" . self::FILE_LOADER_PATH_PROCESSING;
|
||||
$this->_dispatchPath = "{$basePath}/" . self::FILE_LOADER_PATH_DISPATCH;
|
||||
}
|
||||
|
||||
// Set admin email and name.
|
||||
$siteDao = DAORegistry::getDAO('SiteDAO'); /** @var SiteDAO $siteDao */
|
||||
$site = $siteDao->getSite(); /** @var Site $site */
|
||||
$this->_adminEmail = $site->getLocalizedContactEmail();
|
||||
$this->_adminName = $site->getLocalizedContactName();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Getters and setters.
|
||||
//
|
||||
/**
|
||||
* Return the staging path.
|
||||
*/
|
||||
public function getStagePath(): string
|
||||
{
|
||||
return $this->_stagePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the processing path.
|
||||
*/
|
||||
public function getProcessingPath(): string
|
||||
{
|
||||
return $this->_processingPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the reject path.
|
||||
*/
|
||||
public function getRejectPath(): string
|
||||
{
|
||||
return $this->_rejectPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the archive path.
|
||||
*/
|
||||
public function getArchivePath(): string
|
||||
{
|
||||
return $this->_archivePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dispatch path.
|
||||
*/
|
||||
public function getDispatchPath(): string
|
||||
{
|
||||
return $this->_dispatchPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the archives must be compressed or not.
|
||||
*/
|
||||
public function getCompressArchives(): bool
|
||||
{
|
||||
return $this->_compressArchives;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the archives must be compressed or not.
|
||||
*/
|
||||
public function setCompressArchives(bool $compressArchives): void
|
||||
{
|
||||
$this->_compressArchives = $compressArchives;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the files that should only be considered.
|
||||
*/
|
||||
public function getOnlyConsiderFiles(): array
|
||||
{
|
||||
return $this->_onlyConsiderFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the files that should only be considered.
|
||||
*/
|
||||
public function setOnlyConsiderFiles(array $onlyConsiderFiles): void
|
||||
{
|
||||
$this->_onlyConsiderFiles = $onlyConsiderFiles;
|
||||
}
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* A public helper function that can be used to ensure
|
||||
* that the file structure has actually been installed.
|
||||
*
|
||||
* @param bool $install Set this parameter to true to
|
||||
* install the folder structure if it is missing.
|
||||
*
|
||||
* @return bool True if the folder structure exists,
|
||||
* otherwise false.
|
||||
*/
|
||||
public function checkFolderStructure(bool $install = false): bool
|
||||
{
|
||||
// Make sure that the base path is inside the private files dir.
|
||||
// The files dir has appropriate write permissions and is assumed
|
||||
// to be protected against information leak and symlink attacks.
|
||||
$filesDir = realpath(Config::getVar('files', 'files_dir'));
|
||||
if (is_null($this->_basePath) || strpos($this->_basePath, $filesDir) !== 0) {
|
||||
$this->addExecutionLogEntry(
|
||||
__('admin.fileLoader.wrongBasePathLocation', ['path' => $this->_basePath]),
|
||||
ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_ERROR
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check folder presence and readability.
|
||||
$pathsToCheck = [
|
||||
$this->_stagePath,
|
||||
$this->_archivePath,
|
||||
$this->_rejectPath,
|
||||
$this->_processingPath,
|
||||
$this->_dispatchPath
|
||||
];
|
||||
$fileManager = null;
|
||||
foreach ($pathsToCheck as $path) {
|
||||
if (!(is_dir($path) && is_readable($path))) {
|
||||
if ($install) {
|
||||
// Try installing the folder if it is missing.
|
||||
if (is_null($fileManager)) {
|
||||
$fileManager = new FileManager();
|
||||
}
|
||||
$fileManager->mkdirtree($path);
|
||||
}
|
||||
|
||||
// Try again.
|
||||
if (!(is_dir($path) && is_readable($path))) {
|
||||
// Give up...
|
||||
$this->addExecutionLogEntry(
|
||||
__('admin.fileLoader.pathNotAccessible', ['path' => $path]),
|
||||
ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_ERROR
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Protected methods.
|
||||
//
|
||||
/**
|
||||
* @copydoc ScheduledTask::executeActions()
|
||||
*/
|
||||
protected function executeActions(): bool
|
||||
{
|
||||
if (!$this->checkFolderStructure()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$foundErrors = false;
|
||||
while (!is_null($filePath = $this->claimNextFile())) {
|
||||
if ($filePath === false) {
|
||||
// Problem claiming the file.
|
||||
$foundErrors = true;
|
||||
break;
|
||||
}
|
||||
try {
|
||||
$result = $this->processFile($filePath);
|
||||
} catch (Exception $e) {
|
||||
$foundErrors = true;
|
||||
$this->rejectFile();
|
||||
$this->addExecutionLogEntry($e->getMessage(), ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($result === self::FILE_LOADER_RETURN_TO_STAGING) {
|
||||
// Send the file back to staging
|
||||
$foundErrors = true;
|
||||
$this->stageFile();
|
||||
// Let the script know what files were sent back to staging,
|
||||
// so it doesn't claim them again thereby entering an infinite loop.
|
||||
$this->_stagedBackFiles[] = $this->_claimedFilename;
|
||||
} elseif ($result === self::FILE_LOADER_RETURN_TO_DISPATCH) {
|
||||
// Move the file to dispatch folder, where a dispatched job will find it
|
||||
$this->dispatchFile();
|
||||
$this->addExecutionLogEntry(__(
|
||||
'admin.fileLoader.fileDispatched',
|
||||
['filename' => $filePath]
|
||||
), ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);
|
||||
} else {
|
||||
$this->archiveFile();
|
||||
}
|
||||
|
||||
if ($result === true) {
|
||||
$this->addExecutionLogEntry(__(
|
||||
'admin.fileLoader.fileProcessed',
|
||||
['filename' => $filePath]
|
||||
), ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);
|
||||
}
|
||||
}
|
||||
return !$foundErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the passed file.
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return mixed True or self::FILE_LOADER_RETURN_TO_STAGING
|
||||
*
|
||||
* @see FileLoader::executeActions() to understand the expected return values.
|
||||
*
|
||||
*/
|
||||
abstract protected function processFile(string $filePath): bool|int;
|
||||
|
||||
/**
|
||||
* Move file between filesystem directories.
|
||||
*
|
||||
* @return string The destination path of the moved file.
|
||||
*/
|
||||
protected function moveFile(string $sourceDir, string $destDir, string $filename): string
|
||||
{
|
||||
$currentFilePath = "{$sourceDir}/{$filename}";
|
||||
$destinationPath = "{$destDir}/{$filename}";
|
||||
|
||||
if (!rename($currentFilePath, $destinationPath)) {
|
||||
$message = __('admin.fileLoader.moveFileFailed', ['filename' => $filename,
|
||||
'currentFilePath' => $currentFilePath, 'destinationPath' => $destinationPath]);
|
||||
$this->addExecutionLogEntry($message, ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
|
||||
|
||||
// Script should always stop if it can't manipulate files inside
|
||||
// its own directory system.
|
||||
fatalError($message);
|
||||
}
|
||||
|
||||
return $destinationPath;
|
||||
}
|
||||
|
||||
//
|
||||
// Private helper methods.
|
||||
//
|
||||
/**
|
||||
* Claim the first file that's inside the staging folder.
|
||||
*
|
||||
* @return mixed The claimed file path or false if
|
||||
* the claim was not successful.
|
||||
*/
|
||||
private function claimNextFile(): string|false|null
|
||||
{
|
||||
$stageDir = opendir($this->_stagePath);
|
||||
$processingFilePath = false;
|
||||
|
||||
while ($filename = readdir($stageDir)) {
|
||||
if ($filename == '..' || $filename == '.' ||
|
||||
in_array($filename, $this->_stagedBackFiles) ||
|
||||
(!empty($this->_onlyConsiderFiles) && !in_array($filename, $this->_onlyConsiderFiles))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$processingFilePath = $this->moveFile($this->_stagePath, $this->_processingPath, $filename);
|
||||
break;
|
||||
}
|
||||
|
||||
if (pathinfo($processingFilePath, PATHINFO_EXTENSION) == 'gz') {
|
||||
$fileMgr = new FileManager();
|
||||
try {
|
||||
$processingFilePath = $fileMgr->gzDecompressFile($processingFilePath);
|
||||
$filename = pathinfo($processingFilePath, PATHINFO_BASENAME);
|
||||
} catch (Exception $e) {
|
||||
$this->moveFile($this->_processingPath, $this->_stagePath, $filename);
|
||||
$this->addExecutionLogEntry($e->getMessage(), ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($processingFilePath) {
|
||||
$this->_claimedFilename = $filename;
|
||||
return $processingFilePath;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject the current claimed file.
|
||||
*/
|
||||
private function rejectFile(): void
|
||||
{
|
||||
$this->moveFile($this->_processingPath, $this->_rejectPath, $this->_claimedFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the current claimed file into the dispatch folder.
|
||||
*/
|
||||
protected function dispatchFile(): void
|
||||
{
|
||||
$this->moveFile($this->_processingPath, $this->_dispatchPath, $this->_claimedFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Archive the current claimed file.
|
||||
*/
|
||||
private function archiveFile(): void
|
||||
{
|
||||
$this->moveFile($this->_processingPath, $this->_archivePath, $this->_claimedFilename);
|
||||
if ($this->getCompressArchives()) {
|
||||
try {
|
||||
$fileMgr = new FileManager();
|
||||
$filePath = "{$this->_archivePath}/{$this->_claimedFilename}";
|
||||
$fileMgr->gzCompressFile($filePath);
|
||||
} catch (Exception $e) {
|
||||
$this->addExecutionLogEntry($e->getMessage(), ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage the current claimed file.
|
||||
*/
|
||||
private function stageFile(): void
|
||||
{
|
||||
$this->moveFile($this->_processingPath, $this->_stagePath, $this->_claimedFilename);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\task\FileLoader', '\FileLoader');
|
||||
foreach ([
|
||||
'FILE_LOADER_RETURN_TO_STAGING',
|
||||
'FILE_LOADER_ERROR_MESSAGE_TYPE',
|
||||
'FILE_LOADER_WARNING_MESSAGE_TYPE',
|
||||
'FILE_LOADER_PATH_STAGING',
|
||||
'FILE_LOADER_PATH_PROCESSING',
|
||||
'FILE_LOADER_PATH_REJECT',
|
||||
'FILE_LOADER_PATH_ARCHIVE',
|
||||
] as $constantName) {
|
||||
define($constantName, constant('\FileLoader::' . $constantName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/tasks/PKPUsageStatsLoader.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 PKPUsageStatsLoader
|
||||
*
|
||||
* @ingroup tasks
|
||||
*
|
||||
* @brief Scheduled task to extract transform and load usage statistics data into database.
|
||||
*/
|
||||
|
||||
namespace PKP\task;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Services;
|
||||
use APP\statistics\StatisticsHelper;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use PKP\file\FileManager;
|
||||
use PKP\jobs\statistics\CompileMonthlyMetrics;
|
||||
use PKP\scheduledTask\ScheduledTaskHelper;
|
||||
use PKP\site\Site;
|
||||
use Throwable;
|
||||
|
||||
abstract class PKPUsageStatsLoader extends FileLoader
|
||||
{
|
||||
/**
|
||||
* If the log files should be automatically moved to te stage folder.
|
||||
* This is the case for daily log file processing.
|
||||
* This is not the case if the whole month is reprocessed - all log files for the given month should be manually placed in the stage folder.
|
||||
*/
|
||||
private bool $autoStage;
|
||||
|
||||
/** List of months the processed daily log files are from, to consider for monthly aggregation */
|
||||
private array $months = [];
|
||||
|
||||
/** List of log files that needs to be processed within this scheduled task, and the jobs needs to be chained for. */
|
||||
private array $logFiles = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(array $args)
|
||||
{
|
||||
$this->autoStage = true;
|
||||
|
||||
// if log files for a whole month should be reprocessed,
|
||||
// the month is given as parameter
|
||||
if (!empty($args)) {
|
||||
$reprocessMonth = current($args);
|
||||
$reprocessFiles = $this->getStagedFilesByMonth($reprocessMonth);
|
||||
$this->setOnlyConsiderFiles($reprocessFiles);
|
||||
$this->autoStage = false;
|
||||
}
|
||||
|
||||
// shall the archived log files be compressed
|
||||
$site = Application::get()->getRequest()->getSite();
|
||||
if ($site->getData('compressStatsLogs')) {
|
||||
$this->setCompressArchives(true);
|
||||
}
|
||||
|
||||
// Define the base filesystem path.
|
||||
$basePath = StatisticsHelper::getUsageStatsDirPath();
|
||||
$args[0] = $basePath;
|
||||
parent::__construct($args);
|
||||
|
||||
$this->checkFolderStructure(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc FileLoader::getName()
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return __('admin.scheduledTask.usageStatsLoader');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the jobs needed to process a usage stats log file and compile the stats.
|
||||
* The jobs have to be in the right execution order.
|
||||
*
|
||||
* @return BaseJob[]
|
||||
*/
|
||||
abstract protected function getFileJobs(string $filePath, Site $site): array;
|
||||
|
||||
/**
|
||||
* @copydoc FileLoader::executeActions()
|
||||
*/
|
||||
protected function executeActions(): bool
|
||||
{
|
||||
// It's possible that the processing directory has files that
|
||||
// were being processed but the php process was stopped before
|
||||
// finishing the processing, or there may be a concurrent process running.
|
||||
// Warn the user if this is the case.
|
||||
$processingDirFiles = glob($this->getProcessingPath() . '/' . '*');
|
||||
$processingDirError = is_array($processingDirFiles) && count($processingDirFiles);
|
||||
// If the processing directory is not empty (and this is not the reprocessing of the older log files)
|
||||
// log that message
|
||||
if ($processingDirError && !empty($this->getOnlyConsiderFiles())) {
|
||||
$this->addExecutionLogEntry(__('admin.scheduledTask.usageStatsLoader.processingPathNotEmpty', ['directory' => $this->getProcessingPath()]), ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
|
||||
}
|
||||
if ($this->autoStage) {
|
||||
$this->autoStage();
|
||||
}
|
||||
$processFilesResult = parent::executeActions();
|
||||
if (!$processFilesResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$site = Application::get()->getRequest()->getSite();
|
||||
$jobs = [];
|
||||
foreach ($this->logFiles as $filePath) {
|
||||
$jobsPerFile = $this->getFileJobs($filePath, $site);
|
||||
$jobs = array_merge($jobs, $jobsPerFile);
|
||||
}
|
||||
foreach ($this->months as $month) {
|
||||
$compileMonthlyMetricsJob = new CompileMonthlyMetrics($month, $site);
|
||||
$jobs = array_merge($jobs, [$compileMonthlyMetricsJob]);
|
||||
}
|
||||
// Bus::chain() cannot accept an empty array
|
||||
if (!empty($jobs)) {
|
||||
Bus::chain($jobs)
|
||||
->catch(function (Throwable $e) {
|
||||
})
|
||||
->dispatch();
|
||||
|
||||
$this->addExecutionLogEntry(__(
|
||||
'admin.scheduledTask.usageStatsLoader.jobDispatched'
|
||||
), ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);
|
||||
}
|
||||
|
||||
return (!$processingDirError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the log file's date is later than the first installation of the new log file format,
|
||||
* so that the log file can be processed.
|
||||
*/
|
||||
protected function isDateValid(string $loadId): bool
|
||||
{
|
||||
$date = substr($loadId, -12, 8);
|
||||
// Get the date when the version that uses the new log file format (and COUNTER R5) is installed.
|
||||
// Only the log files later than that day can be (regularly) processed here.
|
||||
$statsService = Services::get('sushiStats');
|
||||
$dateR5Installed = date('Ymd', strtotime($statsService->getEarliestDate()));
|
||||
if ($date < $dateR5Installed) {
|
||||
// the log file is in old log file format
|
||||
// return the file to staging and
|
||||
// log the error
|
||||
$this->addExecutionLogEntry(__(
|
||||
'admin.scheduledTask.usageStatsLoader.veryOldLogFile',
|
||||
['file' => $loadId]
|
||||
), ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if stats for the log file's month do not already exist.
|
||||
* Return true if they do not exist, so that log file can be processed.
|
||||
* Else, return the file to staging and log the error that
|
||||
* the CLI script for reprocessing should be called.
|
||||
* If the log files of the month are being reprocessed,
|
||||
* the CLI reprocessing script will first remove all the stats for the month,
|
||||
* so that this function will return true in that case.
|
||||
*/
|
||||
protected function isMonthValid(string $loadId, string $month): bool
|
||||
{
|
||||
$currentMonth = date('Ym');
|
||||
$lastMonth = date('Ym', strtotime('last month'));
|
||||
$site = Application::get()->getRequest()->getSite();
|
||||
// If the daily metrics are not kept, and this is not the current month (which is kept in the DB)
|
||||
// the CLI script to reprocess the whole month should be called.
|
||||
if (!$site->getData('keepDailyUsageStats') && $month != $currentMonth && $month != $lastMonth) {
|
||||
$statsService = Services::get('sushiStats');
|
||||
$counterMonthExists = $statsService->monthExists($month);
|
||||
$geoService = Services::get('geoStats');
|
||||
$geoMonthExists = $geoService->monthExists($month);
|
||||
if ($counterMonthExists || $geoMonthExists) {
|
||||
$this->addExecutionLogEntry(__(
|
||||
'admin.scheduledTask.usageStatsLoader.monthExists',
|
||||
['file' => $loadId]
|
||||
), ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the log file's month to the list of months to be considered for the
|
||||
* stats aggregation after the current log files are processed.
|
||||
*/
|
||||
protected function considerMonthForStatsAggregation(string $month): void
|
||||
{
|
||||
if (!in_array($month, $this->months)) {
|
||||
$this->months[] = $month;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @copydoc FileLoader::processFile()
|
||||
* The file name MUST be of form usage_events_YYYYMMDD.log
|
||||
* If the function successfully finishes, the file will be archived.
|
||||
*/
|
||||
protected function processFile(string $filePath): bool|int
|
||||
{
|
||||
$loadId = basename($filePath);
|
||||
$month = substr($loadId, -12, 6);
|
||||
// if the file is not being reprocessed using the CLI tool
|
||||
if (!in_array($loadId, $this->getOnlyConsiderFiles())) {
|
||||
// Check if the log file is an old log file and if the stats for the month already exist
|
||||
if (!$this->isDateValid($loadId) || !$this->isMonthValid($loadId, $month)) {
|
||||
return self::FILE_LOADER_RETURN_TO_STAGING;
|
||||
}
|
||||
}
|
||||
// Add this log file to the list, so that all jobs, for all files can be chained.
|
||||
$this->logFiles[] = $loadId;
|
||||
// Add this log file's month to the list of months the stats need to be aggregated for.
|
||||
$this->considerMonthForStatsAggregation($month);
|
||||
return self::FILE_LOADER_RETURN_TO_DISPATCH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto stage usage stats log files, also moving files that
|
||||
* might be in processing folder to stage folder.
|
||||
*/
|
||||
protected function autoStage(): void
|
||||
{
|
||||
// Copy all log files to stage directory, except the current day one.
|
||||
$fileManager = new FileManager();
|
||||
$logFiles = [];
|
||||
$logsDirFiles = glob($this->getUsageEventLogsPath() . '/*');
|
||||
if (is_array($logsDirFiles)) {
|
||||
$logFiles = array_merge($logFiles, $logsDirFiles);
|
||||
}
|
||||
// It's possible that the processing directory have files that
|
||||
// were being processed but the php process was stopped before
|
||||
// finishing the processing. Just copy them to the stage directory too.
|
||||
$processingDirFiles = glob($this->getProcessingPath() . '/*');
|
||||
if (is_array($processingDirFiles)) {
|
||||
$logFiles = array_merge($logFiles, $processingDirFiles);
|
||||
}
|
||||
|
||||
foreach ($logFiles as $filePath) {
|
||||
if ($fileManager->fileExists($filePath)) {
|
||||
$filename = pathinfo($filePath, PATHINFO_BASENAME);
|
||||
$currentDayFilename = $this->getUsageEventCurrentDayLogName();
|
||||
if ($filename == $currentDayFilename) {
|
||||
continue;
|
||||
}
|
||||
$this->moveFile(pathinfo($filePath, PATHINFO_DIRNAME), $this->getStagePath(), $filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get staged usage log files belonging to a month, that should be reprocessed
|
||||
*/
|
||||
protected function getStagedFilesByMonth(string $month): array
|
||||
{
|
||||
$files = [];
|
||||
$stagePath = StatisticsHelper::getUsageStatsDirPath() . '/' . self::FILE_LOADER_PATH_STAGING;
|
||||
$stageDir = opendir($stagePath);
|
||||
while ($filename = readdir($stageDir)) {
|
||||
if (str_starts_with($filename, 'usage_events_' . $month)) {
|
||||
$files[] = $filename;
|
||||
}
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the usage event logs directory path.
|
||||
*/
|
||||
protected function getUsageEventLogsPath(): string
|
||||
{
|
||||
return StatisticsHelper::getUsageStatsDirPath() . '/usageEventLogs';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current day usage event log name.
|
||||
*/
|
||||
protected function getUsageEventCurrentDayLogName(): string
|
||||
{
|
||||
return 'usage_events_' . date('Ymd') . '.log';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/task/ProcessQueueJobs.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 ProcessQueueJobs
|
||||
*
|
||||
* @ingroup tasks
|
||||
*
|
||||
* @brief Class to process queue jobs via the schedular task
|
||||
*/
|
||||
|
||||
namespace PKP\task;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\config\Config;
|
||||
use PKP\queue\JobRunner;
|
||||
use PKP\scheduledTask\ScheduledTask;
|
||||
|
||||
class ProcessQueueJobs extends ScheduledTask
|
||||
{
|
||||
/**
|
||||
* @copydoc ScheduledTask::getName()
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return __('admin.scheduledTask.processQueueJobs');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @copydoc ScheduledTask::executeActions()
|
||||
*/
|
||||
public function executeActions()
|
||||
{
|
||||
if (Application::isUnderMaintenance() || !Config::getVar('queues', 'job_runner', true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$jobQueue = app('pkpJobQueue');
|
||||
|
||||
$jobBuilder = $jobQueue->getJobModelBuilder();
|
||||
|
||||
if ($jobBuilder->count() <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Executes all pending jobs when running the runScheduledTasks.php on the CLI
|
||||
if (runOnCLI('runScheduledTasks.php')) {
|
||||
while ($jobBuilder->count()) {
|
||||
$jobQueue->runJobInQueue();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Executes a limited number of jobs when processing a request
|
||||
(new JobRunner($jobQueue))
|
||||
->withMaxExecutionTimeConstrain()
|
||||
->withMaxJobsConstrain()
|
||||
->withMaxMemoryConstrain()
|
||||
->withEstimatedTimeToProcessNextJobConstrain()
|
||||
->processJobs($jobBuilder);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/task/PublishSubmissions.php
|
||||
*
|
||||
* Copyright (c) 2013-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PublishSubmissions
|
||||
*
|
||||
* @ingroup tasks
|
||||
*
|
||||
* @brief Class to published submissions scheduled for publication.
|
||||
*/
|
||||
|
||||
namespace PKP\task;
|
||||
|
||||
use APP\core\Services;
|
||||
use APP\facades\Repo;
|
||||
use APP\submission\Submission;
|
||||
use PKP\core\Core;
|
||||
use PKP\scheduledTask\ScheduledTask;
|
||||
|
||||
class PublishSubmissions extends ScheduledTask
|
||||
{
|
||||
/**
|
||||
* @copydoc ScheduledTask::getName()
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return __('admin.scheduledTask.publishSubmissions');
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc ScheduledTask::executeActions()
|
||||
*/
|
||||
public function executeActions()
|
||||
{
|
||||
$contextIds = Services::get('context')->getIds([
|
||||
'isEnabled' => true,
|
||||
]);
|
||||
foreach ($contextIds as $contextId) {
|
||||
$submissions = Repo::submission()
|
||||
->getCollector()
|
||||
->filterByContextIds([$contextId])
|
||||
->filterByStatus([Submission::STATUS_SCHEDULED])
|
||||
->getMany();
|
||||
|
||||
foreach ($submissions as $submission) {
|
||||
$datePublished = $submission->getCurrentPublication()->getData('datePublished');
|
||||
if ($datePublished && strtotime($datePublished) <= strtotime(Core::getCurrentDate())) {
|
||||
Repo::publication()->publish($submission->getCurrentPublication());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\task\PublishSubmissions', '\PublishSubmissions');
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/task/RemoveFailedJobs.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 RemoveFailedJobs
|
||||
*
|
||||
* @ingroup tasks
|
||||
*
|
||||
* @brief Remove the much older failed jobs form the failed list
|
||||
*/
|
||||
|
||||
namespace PKP\task;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use PKP\config\Config;
|
||||
use PKP\scheduledTask\ScheduledTask;
|
||||
|
||||
class RemoveFailedJobs extends ScheduledTask
|
||||
{
|
||||
/**
|
||||
* @copydoc ScheduledTask::getName()
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return __('admin.scheduledTask.removeFailedJobs');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @copydoc ScheduledTask::executeActions()
|
||||
*/
|
||||
public function executeActions()
|
||||
{
|
||||
$cleanUpPeriod = (int) Config::getVar('queues', 'delete_failed_jobs_after', null);
|
||||
|
||||
// No need to run the clean up process if the cleaning period is not defined in config
|
||||
if (!$cleanUpPeriod) {
|
||||
return true;
|
||||
}
|
||||
|
||||
app('queue.failer')->prune(Carbon::now()->subDays($cleanUpPeriod)->startOfDay());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/task/RemoveUnvalidatedExpiredUser.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 RemoveUnvalidatedExpiredUser
|
||||
*
|
||||
* @ingroup tasks
|
||||
*
|
||||
* @brief Class to remove all unvalidated and expired users after validation timeout
|
||||
*/
|
||||
|
||||
namespace PKP\task;
|
||||
|
||||
use APP\facades\Repo;
|
||||
use Carbon\Carbon;
|
||||
use PKP\config\Config;
|
||||
use PKP\scheduledTask\ScheduledTask;
|
||||
|
||||
class RemoveUnvalidatedExpiredUsers extends ScheduledTask
|
||||
{
|
||||
/**
|
||||
* @copydoc ScheduledTask::getName()
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return __('admin.scheduledTask.removeUnvalidatedExpiredUsers');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @copydoc ScheduledTask::executeActions()
|
||||
*/
|
||||
public function executeActions()
|
||||
{
|
||||
// No need to remove invalidated users if validation requirement is turned off
|
||||
if (!Config::getVar('email', 'require_validation', false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$validationMaxDeadlineInDays = (int) Config::getVar('general', 'user_validation_period');
|
||||
|
||||
if ($validationMaxDeadlineInDays <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$dateTillValid = Carbon::now()->startOfDay()->subDays($validationMaxDeadlineInDays);
|
||||
|
||||
Repo::user()->deleteUnvalidatedExpiredUsers($dateTillValid);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/task/ReviewReminder.php
|
||||
*
|
||||
* Copyright (c) 2013-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ReviewReminder
|
||||
*
|
||||
* @ingroup tasks
|
||||
*
|
||||
* @brief Class to perform automated reminders for reviewers.
|
||||
*/
|
||||
|
||||
namespace PKP\task;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use PKP\context\Context;
|
||||
use PKP\core\Core;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\log\event\PKPSubmissionEventLogEntry;
|
||||
use PKP\mail\mailables\ReviewRemindAuto;
|
||||
use PKP\mail\mailables\ReviewResponseRemindAuto;
|
||||
use PKP\scheduledTask\ScheduledTask;
|
||||
use PKP\security\AccessKeyManager;
|
||||
use PKP\submission\PKPSubmission;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignment;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
|
||||
class ReviewReminder extends ScheduledTask
|
||||
{
|
||||
/**
|
||||
* @copydoc ScheduledTask::getName()
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return __('admin.scheduledTask.reviewReminder');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the automatic review reminder to the reviewer.
|
||||
*/
|
||||
public function sendReminder(
|
||||
ReviewAssignment $reviewAssignment,
|
||||
PKPSubmission $submission,
|
||||
Context $context,
|
||||
ReviewRemindAuto|ReviewResponseRemindAuto $mailable
|
||||
): void {
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$reviewId = $reviewAssignment->getId();
|
||||
|
||||
$reviewer = Repo::user()->get($reviewAssignment->getReviewerId());
|
||||
if (!isset($reviewer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$primaryLocale = $context->getPrimaryLocale();
|
||||
$emailTemplate = Repo::emailTemplate()->getByKey($context->getId(), $mailable::getEmailTemplateKey());
|
||||
$mailable->subject($emailTemplate->getLocalizedData('subject', $primaryLocale))
|
||||
->body($emailTemplate->getLocalizedData('body', $primaryLocale))
|
||||
->from($context->getData('contactEmail'), $context->getData('contactName'))
|
||||
->recipients([$reviewer]);
|
||||
|
||||
$mailable->setData($primaryLocale);
|
||||
|
||||
$application = Application::get();
|
||||
$request = $application->getRequest();
|
||||
$dispatcher = $application->getDispatcher();
|
||||
$reviewerAccessKeysEnabled = $context->getData('reviewerAccessKeysEnabled');
|
||||
if ($reviewerAccessKeysEnabled) { // Give one-click access if enabled
|
||||
$accessKeyManager = new AccessKeyManager();
|
||||
|
||||
// Key lifetime is the typical review period plus four weeks
|
||||
$keyLifetime = ($context->getData('numWeeksPerReview') + 4) * 7;
|
||||
$accessKey = $accessKeyManager->createKey($context->getId(), $reviewer->getId(), $reviewId, $keyLifetime);
|
||||
|
||||
$reviewUrlArgs = ['submissionId' => $reviewAssignment->getSubmissionId(), 'reviewId' => $reviewId, 'key' => $accessKey];
|
||||
$submissionReviewUrl = $dispatcher->url($request, PKPApplication::ROUTE_PAGE, $context->getPath(), 'reviewer', 'submission', null, $reviewUrlArgs);
|
||||
$mailable->addData(['submissionReviewUrl' => $submissionReviewUrl]);
|
||||
}
|
||||
|
||||
// deprecated template variables OJS 2.x
|
||||
$mailable->addData([
|
||||
'messageToReviewer' => __('reviewer.step1.requestBoilerplate'),
|
||||
'abstractTermIfEnabled' => ($submission->getLocalizedAbstract() == '' ? '' : __('common.abstract')),
|
||||
]);
|
||||
|
||||
Mail::send($mailable);
|
||||
|
||||
$reviewAssignment->setDateReminded(Core::getCurrentDate());
|
||||
$reviewAssignment->setReminderWasAutomatic(1);
|
||||
$reviewAssignmentDao->updateObject($reviewAssignment);
|
||||
|
||||
$eventLog = Repo::eventLog()->newDataObject([
|
||||
'assocType' => PKPApplication::ASSOC_TYPE_SUBMISSION,
|
||||
'assocId' => $submission->getId(),
|
||||
'eventType' => PKPSubmissionEventLogEntry::SUBMISSION_LOG_REVIEW_REMIND_AUTO,
|
||||
'userId' => null,
|
||||
'message' => 'submission.event.reviewer.reviewerRemindedAuto',
|
||||
'isTranslated' => false,
|
||||
'dateLogged' => Core::getCurrentDate(),
|
||||
'recipientId' => $reviewer->getId(),
|
||||
'recipientName' => $reviewer->getFullName(),
|
||||
]);
|
||||
Repo::eventLog()->add($eventLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc ScheduledTask::executeActions()
|
||||
*/
|
||||
public function executeActions()
|
||||
{
|
||||
$submission = null;
|
||||
$context = null;
|
||||
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
$contextDao = Application::getContextDAO();
|
||||
|
||||
$incompleteAssignments = $reviewAssignmentDao->getIncompleteReviewAssignments();
|
||||
$inviteReminderDays = $submitReminderDays = null;
|
||||
foreach ($incompleteAssignments as $reviewAssignment) {
|
||||
// Avoid review assignments that a reminder exists for.
|
||||
if ($reviewAssignment->getDateReminded() !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch the submission
|
||||
if ($submission == null || $submission->getId() != $reviewAssignment->getSubmissionId()) {
|
||||
unset($submission);
|
||||
$submission = Repo::submission()->get($reviewAssignment->getSubmissionId());
|
||||
// Avoid review assignments without submission in database.
|
||||
if (!$submission) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($submission->getStatus() != PKPSubmission::STATUS_QUEUED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch the context
|
||||
if ($context == null || $context->getId() != $submission->getContextId()) {
|
||||
unset($context);
|
||||
$context = $contextDao->getById($submission->getContextId());
|
||||
|
||||
$inviteReminderDays = $context->getData('numDaysBeforeInviteReminder');
|
||||
$submitReminderDays = $context->getData('numDaysBeforeSubmitReminder');
|
||||
}
|
||||
|
||||
$mailable = null;
|
||||
if ($submitReminderDays >= 1 && $reviewAssignment->getDateDue() != null) {
|
||||
$checkDate = strtotime($reviewAssignment->getDateDue());
|
||||
if (time() - $checkDate > 60 * 60 * 24 * $submitReminderDays) {
|
||||
$mailable = new ReviewRemindAuto($context, $submission, $reviewAssignment);
|
||||
}
|
||||
}
|
||||
if ($inviteReminderDays >= 1 && $reviewAssignment->getDateConfirmed() == null) {
|
||||
$checkDate = strtotime($reviewAssignment->getDateResponseDue());
|
||||
if (time() - $checkDate > 60 * 60 * 24 * $inviteReminderDays) {
|
||||
$mailable = new ReviewResponseRemindAuto($context, $submission, $reviewAssignment);
|
||||
}
|
||||
}
|
||||
|
||||
if ($mailable) {
|
||||
$this->sendReminder($reviewAssignment, $submission, $context, $mailable);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/task/StatisticsReport.php
|
||||
*
|
||||
* Copyright (c) 2013-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class StatisticsReport
|
||||
*
|
||||
* @ingroup tasks
|
||||
*
|
||||
* @brief Class responsible to send the monthly statistics report.
|
||||
*/
|
||||
|
||||
namespace PKP\task;
|
||||
|
||||
use APP\core\Application;
|
||||
use DateTimeImmutable;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\jobs\notifications\StatisticsReportMail;
|
||||
use PKP\jobs\notifications\StatisticsReportNotify;
|
||||
use PKP\mail\Mailer;
|
||||
use PKP\notification\managerDelegate\EditorialReportNotificationManager;
|
||||
use PKP\notification\NotificationSubscriptionSettingsDAO;
|
||||
use PKP\notification\PKPNotification;
|
||||
use PKP\scheduledTask\ScheduledTask;
|
||||
use PKP\security\Role;
|
||||
|
||||
class StatisticsReport extends ScheduledTask
|
||||
{
|
||||
/** @var array List of roles that might be notified */
|
||||
private $_roleIds = [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR];
|
||||
|
||||
/**
|
||||
* @copydoc ScheduledTask::getName()
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return __('admin.scheduledTask.statisticsReport');
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc ScheduledTask::executeActions()
|
||||
*/
|
||||
public function executeActions(): bool
|
||||
{
|
||||
$contextDao = Application::get()->getContextDAO();
|
||||
$dateStart = new DateTimeImmutable('first day of previous month midnight');
|
||||
$dateEnd = new DateTimeImmutable('first day of this month midnight');
|
||||
|
||||
$jobs = [];
|
||||
for ($contexts = $contextDao->getAll(true); $context = $contexts->next();) {
|
||||
if (!$context->getData('editorialStatsEmail')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var NotificationSubscriptionSettingsDAO $notificationSubscriptionSettingsDao */
|
||||
$notificationSubscriptionSettingsDao = DAORegistry::getDAO('NotificationSubscriptionSettingsDAO');
|
||||
$editorialReportNotificationManager = new EditorialReportNotificationManager(PKPNotification::NOTIFICATION_TYPE_EDITORIAL_REPORT);
|
||||
$editorialReportNotificationManager->initialize(
|
||||
$context
|
||||
);
|
||||
|
||||
$userIdsToNotify = $notificationSubscriptionSettingsDao->getSubscribedUserIds(
|
||||
[NotificationSubscriptionSettingsDAO::BLOCKED_NOTIFICATION_KEY],
|
||||
[PKPNotification::NOTIFICATION_TYPE_EDITORIAL_REPORT],
|
||||
[$context->getId()],
|
||||
$this->_roleIds
|
||||
);
|
||||
|
||||
foreach ($userIdsToNotify->chunk(PKPNotification::NOTIFICATION_CHUNK_SIZE_LIMIT) as $notifyUserIds) {
|
||||
$notifyJob = new StatisticsReportNotify(
|
||||
$notifyUserIds,
|
||||
$editorialReportNotificationManager
|
||||
);
|
||||
$jobs[] = $notifyJob;
|
||||
}
|
||||
|
||||
$userIdsToMail = $notificationSubscriptionSettingsDao->getSubscribedUserIds(
|
||||
[NotificationSubscriptionSettingsDAO::BLOCKED_NOTIFICATION_KEY, NotificationSubscriptionSettingsDAO::BLOCKED_EMAIL_NOTIFICATION_KEY],
|
||||
[PKPNotification::NOTIFICATION_TYPE_EDITORIAL_REPORT],
|
||||
[$context->getId()],
|
||||
$this->_roleIds
|
||||
);
|
||||
|
||||
foreach ($userIdsToMail->chunk(Mailer::BULK_EMAIL_SIZE_LIMIT) as $mailUserIds) {
|
||||
$mailJob = new StatisticsReportMail(
|
||||
$mailUserIds,
|
||||
$context->getId(),
|
||||
$dateStart,
|
||||
$dateEnd
|
||||
);
|
||||
$jobs[] = $mailJob;
|
||||
}
|
||||
}
|
||||
|
||||
Bus::batch($jobs)->dispatch();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/task/UpdateIPGeoDB.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 UpdateIPGeoDB
|
||||
*
|
||||
* @ingroup tasks
|
||||
*
|
||||
* @brief Class responsible to monthly update of the DB-IP city lite database used for Geo statistics.
|
||||
*/
|
||||
|
||||
namespace PKP\task;
|
||||
|
||||
use APP\core\Application;
|
||||
use Exception;
|
||||
use PKP\file\FileManager;
|
||||
use PKP\file\PrivateFileManager;
|
||||
use PKP\scheduledTask\ScheduledTask;
|
||||
use PKP\scheduledTask\ScheduledTaskHelper;
|
||||
use PKP\statistics\PKPStatisticsHelper;
|
||||
|
||||
class UpdateIPGeoDB extends ScheduledTask
|
||||
{
|
||||
/**
|
||||
* @copydoc ScheduledTask::getName()
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return __('admin.scheduledTask.updateGeoDB');
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc ScheduledTask::executeActions()
|
||||
*/
|
||||
public function executeActions(): bool
|
||||
{
|
||||
$dbipCityLiteFileName = 'dbip-city-lite-' . date('Y') . '-' . date('m') . '.mmdb.gz';
|
||||
$dbipCityLiteFile = 'https://download.db-ip.com/free/' . $dbipCityLiteFileName;
|
||||
|
||||
$fileMgr = new PrivateFileManager();
|
||||
$downloadedFile = PKPStatisticsHelper::getUsageStatsDirPath() . '/' . $dbipCityLiteFileName;
|
||||
$finalFileName = PKPStatisticsHelper::getGeoDBPath();
|
||||
|
||||
try {
|
||||
$client = Application::get()->getHttpClient();
|
||||
$client->request('GET', $dbipCityLiteFile, ['sink' => $downloadedFile]);
|
||||
} catch (Exception $e) {
|
||||
$this->addExecutionLogEntry($e->getMessage(), ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$decompressedFile = $fileMgr->gzDecompressFile($downloadedFile);
|
||||
} catch (Exception $e) {
|
||||
$this->addExecutionLogEntry($e->getMessage(), ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rename($decompressedFile, $finalFileName)) {
|
||||
$this->addExecutionLogEntry(__('admin.scheduledTask.updateGeoDB.fileRename.error', ['sourceFilename' => $decompressedFile,
|
||||
'targetFilename' => $finalFileName]), ScheduledTaskHelper::SCHEDULED_TASK_MESSAGE_TYPE_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
return $fileMgr->setMode($finalFileName, FileManager::FILE_MODE_MASK);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user