first commit
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file jobs/BaseJob.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 BaseJob
|
||||
*
|
||||
* @ingroup support
|
||||
*
|
||||
* @brief Abstract class for Jobs
|
||||
*/
|
||||
|
||||
namespace PKP\jobs;
|
||||
|
||||
use APP\core\Application;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use PKP\config\Config;
|
||||
|
||||
abstract class BaseJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 3;
|
||||
|
||||
/**
|
||||
* The number of SECONDS to wait before retrying the job.
|
||||
*/
|
||||
public int $backoff = 5;
|
||||
|
||||
/**
|
||||
* The maximum number of SECONDS a job should get processed before consider failed
|
||||
*/
|
||||
public int $timeout = 60;
|
||||
|
||||
/**
|
||||
* The maximum number of unhandled exceptions to allow before failing.
|
||||
*/
|
||||
public int $maxExceptions = 3;
|
||||
|
||||
/**
|
||||
* Indicate if the job should be marked as failed on timeout.
|
||||
*/
|
||||
public bool $failOnTimeout = true;
|
||||
|
||||
/**
|
||||
* Initialize the job
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->connection = $this->defaultConnection();
|
||||
$this->queue = Config::getVar('queues', 'default_queue', 'queue');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queue job default connection to execute
|
||||
*/
|
||||
protected function defaultConnection(): string
|
||||
{
|
||||
if (Application::isUnderMaintenance()) {
|
||||
return 'sync';
|
||||
}
|
||||
|
||||
return Config::getVar('queues', 'default_connection', 'database');
|
||||
}
|
||||
|
||||
/**
|
||||
* handle the queue job execution process
|
||||
*/
|
||||
abstract public function handle();
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file jobs/bulk/BulkEmailSender.php
|
||||
*
|
||||
* Copyright (c) 2014-2023 Simon Fraser University
|
||||
* Copyright (c) 2000-2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class BulkEmailSender
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Job to send bulk emails
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\bulk;
|
||||
|
||||
use APP\facades\Repo;
|
||||
use Illuminate\Bus\Batchable;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use PKP\jobs\BaseJob;
|
||||
use PKP\mail\Mailable;
|
||||
|
||||
class BulkEmailSender extends BaseJob
|
||||
{
|
||||
use Batchable;
|
||||
|
||||
/**
|
||||
* The maximum number of SECONDS a job should get processed before consider failed
|
||||
*/
|
||||
public int $timeout = 180;
|
||||
|
||||
/**
|
||||
* The user ids to send email
|
||||
*/
|
||||
protected array $userIds;
|
||||
|
||||
/**
|
||||
* The associated context id
|
||||
*/
|
||||
protected int $contextId;
|
||||
|
||||
/**
|
||||
* Mail subject
|
||||
*/
|
||||
protected string $subject;
|
||||
|
||||
/**
|
||||
* Mail body
|
||||
*/
|
||||
protected string $body;
|
||||
|
||||
/**
|
||||
* From email to send mail
|
||||
*/
|
||||
protected object|array|string $fromEmail;
|
||||
|
||||
/**
|
||||
* From name to send mail
|
||||
*/
|
||||
protected mixed $fromName;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(array $userIds, int $contextId, string $subject, string $body, object|array|string $fromEmail, mixed $fromName)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->userIds = $userIds;
|
||||
$this->contextId = $contextId;
|
||||
$this->subject = $subject;
|
||||
$this->body = $body;
|
||||
$this->fromEmail = $fromEmail;
|
||||
$this->fromName = $fromName;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$users = Repo::user()
|
||||
->getCollector()
|
||||
->filterByContextIds([$this->contextId])
|
||||
->filterByUserIds($this->userIds)
|
||||
->getMany();
|
||||
|
||||
foreach ($users as $user) {
|
||||
$mailable = new Mailable();
|
||||
$mailable
|
||||
->from($this->fromEmail, $this->fromName)
|
||||
->to($user->getEmail(), $user->getFullName())
|
||||
->subject($this->subject)
|
||||
->body($this->body);
|
||||
|
||||
Mail::send($mailable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file jobs/doi/DepositContext.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class DepositContext
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Job to deposit all DOIs and associated metadata to the configured registration agency for a given context
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\doi;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use PKP\context\Context;
|
||||
use PKP\context\ContextDAO;
|
||||
use PKP\job\exceptions\JobException;
|
||||
use PKP\jobs\BaseJob;
|
||||
|
||||
class DepositContext extends BaseJob
|
||||
{
|
||||
protected int $contextId;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
*/
|
||||
public function __construct(int $contextId)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->contextId = $contextId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
/** @var ContextDAO $contextDao */
|
||||
$contextDao = Application::getContextDAO();
|
||||
|
||||
/** @var Context $context */
|
||||
$context = $contextDao->getById($this->contextId);
|
||||
|
||||
if (!$context) {
|
||||
throw new JobException(JobException::INVALID_PAYLOAD);
|
||||
}
|
||||
|
||||
// NB: Only run at context level if automatic deposit is enabled. Otherwise, automatic deposit will always run,
|
||||
// regardless of configuration status.
|
||||
if (!$context->getData(Context::SETTING_DOI_AUTOMATIC_DEPOSIT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Repo::doi()->depositAll($context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file jobs/doi/DepositSubmission.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class DepositSubmission
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Job to deposit submission DOI and metadata to the configured registration agency
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\doi;
|
||||
|
||||
use APP\facades\Repo;
|
||||
use APP\plugins\IDoiRegistrationAgency;
|
||||
use PKP\context\Context;
|
||||
use PKP\job\exceptions\JobException;
|
||||
use PKP\jobs\BaseJob;
|
||||
|
||||
class DepositSubmission extends BaseJob
|
||||
{
|
||||
protected int $submissionId;
|
||||
|
||||
protected Context $context;
|
||||
|
||||
/**
|
||||
* @var IDoiRegistrationAgency The configured DOI registration agency
|
||||
*/
|
||||
protected IDoiRegistrationAgency $agency;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
*/
|
||||
public function __construct(int $submissionId, Context $context, IDoiRegistrationAgency $agency)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->submissionId = $submissionId;
|
||||
$this->context = $context;
|
||||
$this->agency = $agency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$submission = Repo::submission()->get($this->submissionId);
|
||||
|
||||
if (!$submission || !$this->agency) {
|
||||
throw new JobException(JobException::INVALID_PAYLOAD);
|
||||
}
|
||||
|
||||
$this->agency->depositSubmissions([$submission], $this->context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file jobs/email/EditorialReminder.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 EditorialReminder
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Class to handle a job to send an editorial reminder
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\email;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Services;
|
||||
use APP\facades\Repo;
|
||||
use APP\notification\Notification;
|
||||
use APP\notification\NotificationManager;
|
||||
use APP\submission\Submission;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use PKP\context\Context;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\facades\Locale;
|
||||
use PKP\jobs\BaseJob;
|
||||
use PKP\mail\mailables\EditorialReminder as MailablesEditorialReminder;
|
||||
use PKP\notification\NotificationSubscriptionSettingsDAO;
|
||||
use PKP\notification\PKPNotification;
|
||||
use PKP\submission\reviewRound\ReviewRound;
|
||||
use PKP\submission\reviewRound\ReviewRoundDAO;
|
||||
use PKP\user\User;
|
||||
use PKP\workflow\WorkflowStageDAO;
|
||||
|
||||
class EditorialReminder extends BaseJob
|
||||
{
|
||||
protected int $editorId;
|
||||
protected int $contextId;
|
||||
|
||||
public function __construct(int $editorId, int $contextId)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->editorId = $editorId;
|
||||
$this->contextId = $contextId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if (!$this->isSubscribed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Context $context */
|
||||
$context = Services::get('context')->get($this->contextId);
|
||||
$editor = Repo::user()->get($this->editorId);
|
||||
|
||||
// Don't use the request locale because this job is
|
||||
// run during a scheduled task
|
||||
$requestLocale = Locale::getLocale();
|
||||
Locale::setLocale($this->getLocale($editor, $context));
|
||||
|
||||
$submissionIds = Repo::submission()
|
||||
->getCollector()
|
||||
->assignedTo([$this->editorId])
|
||||
->filterByContextIds([$this->contextId])
|
||||
->filterByStatus([Submission::STATUS_QUEUED])
|
||||
->filterByIncomplete(false)
|
||||
->getIds();
|
||||
|
||||
$outstanding = [];
|
||||
$submissions = [];
|
||||
|
||||
/** @var int $submissionId */
|
||||
foreach ($submissionIds as $submissionId) {
|
||||
$submission = Repo::submission()->get($submissionId);
|
||||
$submissions[$submissionId] = $submission;
|
||||
|
||||
if ($submission->getData('stageId') == WORKFLOW_STAGE_ID_SUBMISSION) {
|
||||
$outstanding[$submissionId] = __('editor.submission.status.waitingInitialReview');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($submission->getData('stageId'), [WORKFLOW_STAGE_ID_INTERNAL_REVIEW, WORKFLOW_STAGE_ID_EXTERNAL_REVIEW])) {
|
||||
/** @var ReviewRoundDAO $reviewRoundDao */
|
||||
$reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO');
|
||||
$reviewRound = $reviewRoundDao->getLastReviewRoundBySubmissionId($submission->getId(), $submission->getData('stageId'));
|
||||
|
||||
if ($reviewRound->getStatus() === ReviewRound::REVIEW_ROUND_STATUS_PENDING_REVIEWERS) {
|
||||
$outstanding[$submissionId] = __('editor.submission.roundStatus.pendingReviewers');
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($reviewRound->getStatus() === ReviewRound::REVIEW_ROUND_STATUS_REVIEWS_COMPLETED) {
|
||||
$outstanding[$submissionId] = __('editor.submission.roundStatus.reviewsCompleted');
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($reviewRound->getStatus() === ReviewRound::REVIEW_ROUND_STATUS_REVIEWS_OVERDUE) {
|
||||
$outstanding[$submissionId] = __('editor.submission.roundStatus.reviewOverdue');
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($reviewRound->getStatus() === ReviewRound::REVIEW_ROUND_STATUS_REVISIONS_SUBMITTED) {
|
||||
$outstanding[$submissionId] = __('editor.submission.roundStatus.revisionsSubmitted');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($submission->getData('stageId'), [WORKFLOW_STAGE_ID_EDITING, WORKFLOW_STAGE_ID_PRODUCTION])) {
|
||||
$lastActivityTimestamp = strtotime($submission->getData('dateLastActivity'));
|
||||
if ($lastActivityTimestamp < strtotime('+30 days')) {
|
||||
/** @var WorkflowStageDAO $workflowStageDao */
|
||||
$workflowStageDao = DAORegistry::getDAO('WorkflowStageDAO');
|
||||
$outstanding[$submissionId] = __(
|
||||
'editor.submission.status.inactiveDaysInStage',
|
||||
[
|
||||
'days' => 30,
|
||||
'stage' => __($workflowStageDao->getTranslationKeyFromId($submission->getData('stageId')))
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($outstanding) > 20) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($outstanding)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Context or user was removed since job was created, or the user was disabled
|
||||
if (!$context || !$editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notificationManager = new NotificationManager();
|
||||
$notification = $notificationManager->createNotification(
|
||||
Application::get()->getRequest(),
|
||||
$this->editorId,
|
||||
PKPNotification::NOTIFICATION_TYPE_EDITORIAL_REMINDER,
|
||||
$this->contextId
|
||||
);
|
||||
|
||||
$mailable = new MailablesEditorialReminder($context);
|
||||
$emailTemplate = Repo::emailTemplate()->getByKey($context->getId(), $mailable::getEmailTemplateKey());
|
||||
|
||||
$mailable
|
||||
->setOutstandingTasks($outstanding, $submissions, $submissionIds->count())
|
||||
->from($context->getContactEmail(), $context->getLocalizedName(Locale::getLocale()))
|
||||
->recipients([$editor])
|
||||
->subject($emailTemplate->getLocalizedData('subject'))
|
||||
->body($emailTemplate->getLocalizedData('body'))
|
||||
->allowUnsubscribe($notification);
|
||||
|
||||
Mail::send($mailable);
|
||||
|
||||
// Restore the current locale after the email is sent
|
||||
Locale::setLocale($requestLocale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this editor subscribed to this email?
|
||||
*/
|
||||
protected function isSubscribed(): bool
|
||||
{
|
||||
/** @var NotificationSubscriptionSettingsDAO $notificationSubscriptionSettingsDao */
|
||||
$notificationSubscriptionSettingsDao = DAORegistry::getDAO('NotificationSubscriptionSettingsDAO');
|
||||
$blockedEmails = $notificationSubscriptionSettingsDao->getNotificationSubscriptionSettings('blocked_emailed_notification', $this->editorId, $this->contextId);
|
||||
return !in_array(Notification::NOTIFICATION_TYPE_EDITORIAL_REMINDER, $blockedEmails);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the locale to use with this email
|
||||
*
|
||||
* Returns the context's primary locale, or the first locale
|
||||
* supported by the context and the user.
|
||||
*
|
||||
* @return string Locale key. Example: en
|
||||
*/
|
||||
protected function getLocale(User $editor, Context $context): string
|
||||
{
|
||||
$locale = $context->getPrimaryLocale();
|
||||
|
||||
// A user's locales may not be an array due to bug with data structure
|
||||
// See: https://github.com/pkp/pkp-lib/issues/8023
|
||||
if ($editor->getLocales() === false) {
|
||||
return $locale;
|
||||
}
|
||||
|
||||
$locales = array_intersect($editor->getLocales(), $context->getSupportedLocales());
|
||||
if (!empty($locales) && !in_array($context->getPrimaryLocale(), $locales)) {
|
||||
$locale = $locales[0];
|
||||
}
|
||||
|
||||
return $locale;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file jobs/metadata/BatchMetadataChangedJob.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 BatchMetadataChangedJob
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Class to handle the Batch Metadata Changed job
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\metadata;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use PKP\job\exceptions\JobException;
|
||||
use PKP\jobs\BaseJob;
|
||||
|
||||
class BatchMetadataChangedJob extends BaseJob
|
||||
{
|
||||
/** @var array $submissionIds Submission ids associated */
|
||||
public $submissionIds;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
*/
|
||||
public function __construct(array $submissionIds)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->submissionIds = $submissionIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$successful = 0;
|
||||
|
||||
$submissionSearchIndex = Application::getSubmissionSearchIndex();
|
||||
|
||||
foreach ($this->submissionIds as $currentSubmissionId) {
|
||||
$submission = Repo::submission()->get($currentSubmissionId);
|
||||
|
||||
if (!$submission) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$submissionSearchIndex->submissionMetadataChanged($submission);
|
||||
$submissionSearchIndex->submissionFilesChanged($submission);
|
||||
|
||||
$successful++;
|
||||
}
|
||||
|
||||
if (!$successful) {
|
||||
throw new JobException(JobException::INVALID_PAYLOAD);
|
||||
}
|
||||
|
||||
$submissionSearchIndex->submissionChangesFinished();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file jobs/metadata/MetadataChangedJob.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 MetadataChangedJob
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Class to handle the Metadata Changed job
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\metadata;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use PKP\job\exceptions\JobException;
|
||||
use PKP\jobs\BaseJob;
|
||||
|
||||
class MetadataChangedJob extends BaseJob
|
||||
{
|
||||
/**
|
||||
* @var int The submission ID
|
||||
*/
|
||||
protected $submissionId;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
*/
|
||||
public function __construct(int $submissionId)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->submissionId = $submissionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$submission = Repo::submission()->get($this->submissionId);
|
||||
|
||||
if (!$submission) {
|
||||
throw new JobException(JobException::INVALID_PAYLOAD);
|
||||
}
|
||||
|
||||
$submissionSearchIndex = Application::getSubmissionSearchIndex();
|
||||
$submissionSearchIndex->submissionMetadataChanged($submission);
|
||||
$submissionSearchIndex->submissionFilesChanged($submission);
|
||||
$submissionSearchIndex->submissionChangesFinished();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file jobs/notifications/NewAnnouncementNotifyUsers.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class NewAnnouncementNotifyUsers
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Class to send system notifications when a new announcement is added
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\notifications;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\notification\Notification;
|
||||
use Illuminate\Bus\Batchable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use PKP\announcement\Announcement;
|
||||
use PKP\context\Context;
|
||||
use PKP\emailTemplate\EmailTemplate;
|
||||
use PKP\job\exceptions\JobException;
|
||||
use PKP\jobs\BaseJob;
|
||||
use PKP\mail\mailables\AnnouncementNotify;
|
||||
use PKP\notification\managerDelegate\AnnouncementNotificationManager;
|
||||
use PKP\user\User;
|
||||
|
||||
class NewAnnouncementNotifyUsers extends BaseJob
|
||||
{
|
||||
use Batchable;
|
||||
|
||||
protected Collection $recipientIds;
|
||||
protected int $contextId;
|
||||
protected int $announcementId;
|
||||
protected string $locale;
|
||||
|
||||
// Sender of the email
|
||||
protected ?User $sender;
|
||||
|
||||
public function __construct(
|
||||
Collection $recipientIds,
|
||||
int $contextId,
|
||||
int $announcementId,
|
||||
string $locale,
|
||||
?User $sender = null // Leave null to not send an email
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->recipientIds = $recipientIds;
|
||||
$this->contextId = $contextId;
|
||||
$this->announcementId = $announcementId;
|
||||
$this->locale = $locale;
|
||||
$this->sender = $sender;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$announcement = Repo::announcement()->get($this->announcementId);
|
||||
// Announcement was removed
|
||||
if (!$announcement) {
|
||||
throw new JobException(JobException::INVALID_PAYLOAD);
|
||||
}
|
||||
|
||||
$announcementNotificationManager = new AnnouncementNotificationManager(Notification::NOTIFICATION_TYPE_NEW_ANNOUNCEMENT);
|
||||
$announcementNotificationManager->initialize($announcement);
|
||||
$context = Application::getContextDAO()->getById($this->contextId);
|
||||
$template = Repo::emailTemplate()->getByKey($context->getId(), AnnouncementNotify::getEmailTemplateKey());
|
||||
|
||||
foreach ($this->recipientIds as $recipientId) {
|
||||
/** @var int $recipientId */
|
||||
$recipient = Repo::user()->get($recipientId);
|
||||
if (!$recipient) {
|
||||
continue;
|
||||
}
|
||||
$notification = $announcementNotificationManager->notify($recipient);
|
||||
|
||||
if (!$this->sender) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Send email
|
||||
$mailable = $this->createMailable($context, $recipient, $announcement, $template)
|
||||
->allowUnsubscribe($notification);
|
||||
|
||||
$mailable->setData($this->locale);
|
||||
Mail::send($mailable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new announcement notification email
|
||||
*/
|
||||
protected function createMailable(
|
||||
Context $context,
|
||||
User $recipient,
|
||||
Announcement $announcement,
|
||||
EmailTemplate $template
|
||||
): AnnouncementNotify {
|
||||
$mailable = new AnnouncementNotify($context, $announcement);
|
||||
|
||||
$mailable->sender($this->sender);
|
||||
$mailable->recipients([$recipient]);
|
||||
$mailable->body($template->getLocalizedData('body', $this->locale));
|
||||
$mailable->subject($template->getLocalizedData('subject', $this->locale));
|
||||
|
||||
return $mailable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file jobs/notifications/StatisticsReportMail.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class StatisticsReportMail
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Class to send email to editors with monthly editorial report
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\notifications;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Services;
|
||||
use APP\facades\Repo;
|
||||
use APP\notification\NotificationManager;
|
||||
use DateTimeImmutable;
|
||||
use Illuminate\Bus\Batchable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use IntlDateFormatter;
|
||||
use PKP\context\Context;
|
||||
use PKP\jobs\BaseJob;
|
||||
use PKP\mail\mailables\StatisticsReportNotify;
|
||||
use PKP\notification\PKPNotification;
|
||||
use SplFileObject;
|
||||
|
||||
class StatisticsReportMail extends BaseJob
|
||||
{
|
||||
use Batchable;
|
||||
|
||||
protected Collection $userIds;
|
||||
protected int $contextId;
|
||||
protected DateTimeImmutable $dateStart;
|
||||
protected DateTimeImmutable $dateEnd;
|
||||
|
||||
public function __construct(Collection $userIds, int $contextId, DateTimeImmutable $dateStart, DateTimeImmutable $dateEnd)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->userIds = $userIds;
|
||||
$this->contextId = $contextId;
|
||||
$this->dateStart = $dateStart;
|
||||
$this->dateEnd = $dateEnd;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$contextDao = Application::getContextDAO();
|
||||
$context = $contextDao->getById($this->contextId); /** @var Context $context */
|
||||
$locale = $context->getPrimaryLocale();
|
||||
$template = Repo::emailTemplate()->getByKey($this->contextId, StatisticsReportNotify::getEmailTemplateKey());
|
||||
|
||||
$editorialTrends = Services::get('editorialStats')->getOverview([
|
||||
'contextIds' => [$context->getId()],
|
||||
'dateStart' => $this->dateStart->format('Y-m-d'),
|
||||
'dateEnd' => $this->dateEnd->format('Y-m-d'),
|
||||
]);
|
||||
$editorialTrendsTotal = Services::get('editorialStats')->getOverview(['contextIds' => [$context->getId()]]);
|
||||
$totalSubmissions = Services::get('editorialStats')->countSubmissionsReceived(['contextIds' => [$context->getId()]]);
|
||||
$formatter = IntlDateFormatter::create(
|
||||
$locale,
|
||||
IntlDateFormatter::FULL,
|
||||
IntlDateFormatter::FULL,
|
||||
null,
|
||||
null,
|
||||
'MMMM'
|
||||
);
|
||||
$month = $formatter->format($this->dateStart);
|
||||
$year = $this->dateStart->format('Y');
|
||||
$filePath = $this->createCsvAttachment($editorialTrends, $editorialTrendsTotal, $locale, $month, $year);
|
||||
|
||||
foreach ($this->userIds as $userId) {
|
||||
/** @var int $userId */
|
||||
$user = Repo::user()->get($userId);
|
||||
if (!$user) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notificationManager = new NotificationManager();
|
||||
$notification = $notificationManager->createNotification(
|
||||
Application::get()->getRequest(),
|
||||
$user->getId(),
|
||||
PKPNotification::NOTIFICATION_TYPE_EDITORIAL_REPORT,
|
||||
$this->contextId
|
||||
);
|
||||
|
||||
$mailable = new StatisticsReportNotify($context, $editorialTrends, $totalSubmissions, $month, $year);
|
||||
$mailable->recipients([$user]);
|
||||
$mailable->from($context->getContactEmail(), $context->getContactName());
|
||||
$mailable->subject($template->getLocalizedData('subject', $locale));
|
||||
$mailable->body($template->getLocalizedData('body', $locale));
|
||||
$mailable->setData($locale);
|
||||
$mailable->attach($filePath, ['as' => 'editorial-report.csv']);
|
||||
$mailable->allowUnsubscribe($notification);
|
||||
|
||||
Mail::send($mailable);
|
||||
}
|
||||
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a csv file on the file system
|
||||
*
|
||||
* @return string the full path to the attachment
|
||||
*/
|
||||
protected function createCsvAttachment(array $editorialTrends, array $editorialTrendsTotal, string $locale, $month, $year): string
|
||||
{
|
||||
$userRolesOverview = Repo::user()->getRolesOverview(Repo::user()->getCollector()->filterByContextIds([$this->contextId]));
|
||||
|
||||
// Create the CSV file attachment
|
||||
// Active submissions by stage
|
||||
$file = new SplFileObject(tempnam(sys_get_temp_dir(), 'tmp'), 'wb');
|
||||
// Adds BOM (byte order mark) to enforce the UTF-8 format
|
||||
try {
|
||||
$file->fwrite("\xEF\xBB\xBF");
|
||||
$file->fputcsv([
|
||||
__('stats.submissionsActive', [], $locale),
|
||||
__('stats.total', [], $locale)
|
||||
]);
|
||||
foreach (Application::getApplicationStages() as $stageId) {
|
||||
$file->fputcsv([
|
||||
__(Application::getWorkflowStageName($stageId), [], $locale),
|
||||
Services::get('editorialStats')->countActiveByStages($stageId)
|
||||
]);
|
||||
}
|
||||
|
||||
$file->fputcsv([]);
|
||||
|
||||
// Editorial trends
|
||||
$file->fputcsv([
|
||||
__('stats.trends', [], $locale),
|
||||
$month . __('common.commaListSeparator', [], $locale) . $year,
|
||||
__('stats.total', [], $locale)
|
||||
]);
|
||||
foreach ($editorialTrends as $i => $stat) {
|
||||
$file->fputcsv([
|
||||
__($stat['name'], [], $locale),
|
||||
$stat['value'],
|
||||
$editorialTrendsTotal[$i]['value']
|
||||
]);
|
||||
}
|
||||
|
||||
$file->fputcsv([]);
|
||||
|
||||
// Count of users by role
|
||||
$file->fputcsv([
|
||||
__('manager.users', [], $locale),
|
||||
__('stats.total', [], $locale)
|
||||
]);
|
||||
foreach ($userRolesOverview as $role) {
|
||||
$file->fputcsv([
|
||||
__($role['name'], [], $locale),
|
||||
$role['value']
|
||||
]);
|
||||
}
|
||||
|
||||
$filePath = $file->getRealPath();
|
||||
} finally {
|
||||
$file = null;
|
||||
}
|
||||
|
||||
return $filePath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file jobs/notifications/StatisticsReportNotify.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class StatisticsReportNotify
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Class to create system notifications for editors about editorial report
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\notifications;
|
||||
|
||||
use APP\facades\Repo;
|
||||
use Illuminate\Bus\Batchable;
|
||||
use Illuminate\Support\Collection;
|
||||
use PKP\jobs\BaseJob;
|
||||
use PKP\notification\managerDelegate\EditorialReportNotificationManager;
|
||||
|
||||
class StatisticsReportNotify extends BaseJob
|
||||
{
|
||||
use Batchable;
|
||||
|
||||
protected Collection $userIds;
|
||||
protected EditorialReportNotificationManager $notificationManager;
|
||||
|
||||
public function __construct(Collection $userIds, EditorialReportNotificationManager $notificationManager)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->userIds = $userIds;
|
||||
$this->notificationManager = $notificationManager;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
foreach ($this->userIds as $userId) {
|
||||
/** @var int $userId */
|
||||
$user = Repo::user()->get($userId);
|
||||
if (!$user) {
|
||||
continue;
|
||||
}
|
||||
$this->notificationManager->notify($user);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file jobs/statistics/ArchiveUsageStatsLogFile.php
|
||||
*
|
||||
* Copyright (c) 2024 Simon Fraser University
|
||||
* Copyright (c) 2024 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class ArchiveUsageStatsLogFile
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Archive usage stats log file.
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\statistics;
|
||||
|
||||
use APP\statistics\StatisticsHelper;
|
||||
use Exception;
|
||||
use PKP\file\FileManager;
|
||||
use PKP\job\exceptions\JobException;
|
||||
use PKP\jobs\BaseJob;
|
||||
use PKP\site\Site;
|
||||
use PKP\task\FileLoader;
|
||||
|
||||
class ArchiveUsageStatsLogFile extends BaseJob
|
||||
{
|
||||
/**
|
||||
* The load ID = usage stats log file name
|
||||
*/
|
||||
protected string $loadId;
|
||||
|
||||
protected Site $site;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(string $loadId, Site $site)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->loadId = $loadId;
|
||||
$this->site = $site;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
// Move the archived file back to staging
|
||||
$filename = $this->loadId;
|
||||
$archiveFilePath = StatisticsHelper::getUsageStatsDirPath() . '/' . FileLoader::FILE_LOADER_PATH_ARCHIVE . '/' . $filename;
|
||||
$dispatchFilePath = StatisticsHelper::getUsageStatsDirPath() . '/' . FileLoader::FILE_LOADER_PATH_DISPATCH . '/' . $filename;
|
||||
if (!rename($dispatchFilePath, $archiveFilePath)) {
|
||||
$message = __(
|
||||
'admin.job.archiveLogFile.error',
|
||||
[
|
||||
'file' => $filename,
|
||||
'dispatchFilePath' => $dispatchFilePath,
|
||||
'archiveFilePath' => $archiveFilePath
|
||||
]
|
||||
);
|
||||
throw new JobException($message);
|
||||
}
|
||||
if ($this->site->getData('compressStatsLogs')) {
|
||||
try {
|
||||
$fileMgr = new FileManager();
|
||||
$fileMgr->gzCompressFile($archiveFilePath);
|
||||
} catch (Exception $e) {
|
||||
error_log($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file jobs/statistics/CompileContextMetrics.php
|
||||
*
|
||||
* Copyright (c) 2024 Simon Fraser University
|
||||
* Copyright (c) 2024 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class CompileContextMetrics
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Compile context metrics.
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\statistics;
|
||||
|
||||
use APP\statistics\TemporaryTotalsDAO;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\jobs\BaseJob;
|
||||
|
||||
class CompileContextMetrics extends BaseJob
|
||||
{
|
||||
/**
|
||||
* The load ID = usage stats log file name
|
||||
*/
|
||||
protected string $loadId;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(string $loadId)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->loadId = $loadId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$temporaryTotalsDao = DAORegistry::getDAO('TemporaryTotalsDAO'); /** @var TemporaryTotalsDAO $temporaryTotalsDao */
|
||||
$temporaryTotalsDao->compileContextMetrics($this->loadId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file jobs/statistics/CompileMonthlyMetrics.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 CompileMonthlyMetrics
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Compile and store monthly usage stats from the daily records.
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\statistics;
|
||||
|
||||
use APP\core\Services;
|
||||
use PKP\jobs\BaseJob;
|
||||
use PKP\site\Site;
|
||||
|
||||
class CompileMonthlyMetrics extends BaseJob
|
||||
{
|
||||
/**
|
||||
* The month the usage metrics should be aggregated by, in format YYYYMM.
|
||||
*/
|
||||
protected string $month;
|
||||
|
||||
protected Site $site;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param string $month In format YYYYMM
|
||||
*/
|
||||
public function __construct(string $month, Site $site)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->month = $month;
|
||||
$this->site = $site;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$currentMonth = date('Ym');
|
||||
$lastMonth = date('Ym', strtotime('last month'));
|
||||
|
||||
$geoService = Services::get('geoStats');
|
||||
$geoService->deleteMonthlyMetrics($this->month);
|
||||
$geoService->addMonthlyMetrics($this->month);
|
||||
if (!$this->site->getData('keepDailyUsageStats') && $this->month != $currentMonth && $this->month != $lastMonth) {
|
||||
$geoService->deleteDailyMetrics($this->month);
|
||||
}
|
||||
|
||||
$counterService = Services::get('sushiStats');
|
||||
$counterService->deleteMonthlyMetrics($this->month);
|
||||
$counterService->addMonthlyMetrics($this->month);
|
||||
if (!$this->site->getData('keepDailyUsageStats') && $this->month != $currentMonth && $this->month != $lastMonth) {
|
||||
$counterService->deleteDailyMetrics($this->month);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file jobs/statistics/CompileSubmissionMetrics.php
|
||||
*
|
||||
* Copyright (c) 2024 Simon Fraser University
|
||||
* Copyright (c) 2024 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class CompileSubmissionMetrics
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Compile submission metrics.
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\statistics;
|
||||
|
||||
use APP\statistics\TemporaryTotalsDAO;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\jobs\BaseJob;
|
||||
|
||||
class CompileSubmissionMetrics extends BaseJob
|
||||
{
|
||||
/**
|
||||
* The load ID = usage stats log file name
|
||||
*/
|
||||
protected string $loadId;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(string $loadId)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->loadId = $loadId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$temporaryTotalsDao = DAORegistry::getDAO('TemporaryTotalsDAO'); /** @var TemporaryTotalsDAO $temporaryTotalsDao */
|
||||
$temporaryTotalsDao->compileSubmissionMetrics($this->loadId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file jobs/statistics/PKPProcessUsageStatsLogFile.php
|
||||
*
|
||||
* Copyright (c) 2024 Simon Fraser University
|
||||
* Copyright (c) 2024 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PKPProcessUsageStatsLogFile
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Compile context metrics.
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\statistics;
|
||||
|
||||
use APP\statistics\StatisticsHelper;
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use PKP\core\Core;
|
||||
use PKP\job\exceptions\JobException;
|
||||
use PKP\jobs\BaseJob;
|
||||
use PKP\task\FileLoader;
|
||||
use SplFileObject;
|
||||
|
||||
abstract class PKPProcessUsageStatsLogFile extends BaseJob
|
||||
{
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param string $loadId Usage stats log file name
|
||||
*/
|
||||
public function __construct(protected string $loadId)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete entries in usage stats temporary tables by loadId
|
||||
*/
|
||||
abstract protected function deleteByLoadId(): void;
|
||||
|
||||
/**
|
||||
* Get valid assoc types that an usage event can contain
|
||||
*/
|
||||
abstract protected function getValidAssocTypes(): array;
|
||||
|
||||
/**
|
||||
* Insert usage stats log entry into temporary tables
|
||||
*/
|
||||
abstract protected function insertTemporaryUsageStatsData(object $entry, int $lineNumber): void;
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$filename = $this->loadId;
|
||||
$dispatchFilePath = StatisticsHelper::getUsageStatsDirPath() . '/' . FileLoader::FILE_LOADER_PATH_DISPATCH . '/' . $filename;
|
||||
if (!file_exists($dispatchFilePath)) {
|
||||
throw new JobException(__(
|
||||
'admin.job.processLogFile.fileNotFound',
|
||||
['file' => $dispatchFilePath]
|
||||
));
|
||||
}
|
||||
$this->process($dispatchFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse log file line by line and add the lines into the usage stats temporary DB tables.
|
||||
*/
|
||||
protected function process(string $dispatchFilePath): void
|
||||
{
|
||||
$splFileObject = new SplFileObject($dispatchFilePath, 'r');
|
||||
if (!$splFileObject) {
|
||||
// reject file -- move the file from dispatch to reject folder
|
||||
$filename = $this->loadId;
|
||||
$rejectFilePath = StatisticsHelper::getUsageStatsDirPath() . '/' . FileLoader::FILE_LOADER_PATH_REJECT . '/' . $filename;
|
||||
if (!rename($dispatchFilePath, $rejectFilePath)) {
|
||||
$message = __('admin.job.compileMetrics.returnToStaging.error', ['file' => $filename,
|
||||
'dispatchFilePath' => $dispatchFilePath, 'rejectFilePath' => $rejectFilePath]);
|
||||
error_log($message);
|
||||
}
|
||||
throw new JobException(__('admin.job.processLogFile.openFileFailed', ['file' => $dispatchFilePath]));
|
||||
}
|
||||
|
||||
// Make sure we don't have any temporary records associated
|
||||
// with the current load ID in database.
|
||||
$this->deleteByLoadId();
|
||||
|
||||
$lineNumber = 0;
|
||||
while (!$splFileObject->eof()) {
|
||||
$lineNumber++;
|
||||
$line = $splFileObject->fgets();
|
||||
if (empty($line) || substr($line, 0, 1) === '#') {
|
||||
continue;
|
||||
} // Spacing or comment lines. This actually should not occur in the new format.
|
||||
|
||||
$entryData = json_decode($line);
|
||||
if ($entryData === null) {
|
||||
// This line is not in the right format.
|
||||
$message = __(
|
||||
'admin.job.processLogFile.wrongLoglineFormat',
|
||||
['file' => $this->loadId, 'lineNumber' => $lineNumber]
|
||||
);
|
||||
error_log($message);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->validateLogEntry($entryData);
|
||||
} catch (Exception $e) {
|
||||
$message = __(
|
||||
'admin.job.processLogFile.invalidLogEntry',
|
||||
['file' => $this->loadId, 'lineNumber' => $lineNumber, 'error' => $e->getMessage()]
|
||||
);
|
||||
error_log($message);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Avoid bots.
|
||||
if (Core::isUserAgentBot($entryData->userAgent)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->insertTemporaryUsageStatsData($entryData, $lineNumber);
|
||||
}
|
||||
//explicitly assign null, so that the file can be deleted
|
||||
$splFileObject = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the usage stats log entry
|
||||
*
|
||||
* @throws Exception.
|
||||
*/
|
||||
protected function validateLogEntry(object $entry): void
|
||||
{
|
||||
if (!$this->validateDate($entry->time)) {
|
||||
throw new Exception(__('admin.job.processLogFile.invalidLogEntry.time'));
|
||||
}
|
||||
// check hashed IP ?
|
||||
// check canonicalUrl ?
|
||||
if (!is_int($entry->contextId)) {
|
||||
throw new Exception(__('admin.job.processLogFile.invalidLogEntry.contextId'));
|
||||
}
|
||||
if (!empty($entry->submissionId) && !is_int($entry->submissionId)) {
|
||||
throw new Exception(__('admin.job.processLogFile.invalidLogEntry.submissionId'));
|
||||
}
|
||||
|
||||
$validAssocTypes = $this->getValidAssocTypes();
|
||||
if (!in_array($entry->assocType, $validAssocTypes)) {
|
||||
throw new Exception(__('admin.job.processLogFile.invalidLogEntry.assocType'));
|
||||
}
|
||||
$validFileTypes = [
|
||||
StatisticsHelper::STATISTICS_FILE_TYPE_PDF,
|
||||
StatisticsHelper::STATISTICS_FILE_TYPE_DOC,
|
||||
StatisticsHelper::STATISTICS_FILE_TYPE_HTML,
|
||||
StatisticsHelper::STATISTICS_FILE_TYPE_OTHER,
|
||||
];
|
||||
if (!empty($entry->fileType) && !in_array($entry->fileType, $validFileTypes)) {
|
||||
throw new Exception(__('admin.job.processLogFile.invalidLogEntry.fileType'));
|
||||
}
|
||||
if (!empty($entry->country) && (!ctype_alpha($entry->country) || (strlen($entry->country) !== 2))) {
|
||||
throw new Exception(__('admin.job.processLogFile.invalidLogEntry.country'));
|
||||
}
|
||||
if (!empty($entry->region) && (!ctype_alnum($entry->region) || (strlen($entry->region) > 3))) {
|
||||
throw new Exception(__('admin.job.processLogFile.invalidLogEntry.region'));
|
||||
}
|
||||
if (!is_array($entry->institutionIds)) {
|
||||
throw new Exception(__('admin.job.processLogFile.invalidLogEntry.institutionIds'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate date, check if the date is a valid date and in requested format
|
||||
*/
|
||||
protected function validateDate(string $datetime, string $format = 'Y-m-d H:i:s'): bool
|
||||
{
|
||||
$d = DateTime::createFromFormat($format, $datetime);
|
||||
return $d && $d->format($format) === $datetime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file jobs/statistics/RemoveDoubleClicks.php
|
||||
*
|
||||
* Copyright (c) 2024 Simon Fraser University
|
||||
* Copyright (c) 2024 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class RemoveDoubleClicks
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Remove Double Clicks according to COUNTER guidelines.
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\statistics;
|
||||
|
||||
use APP\statistics\StatisticsHelper;
|
||||
use APP\statistics\TemporaryTotalsDAO;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\jobs\BaseJob;
|
||||
|
||||
class RemoveDoubleClicks extends BaseJob
|
||||
{
|
||||
/**
|
||||
* The load ID = usage stats log file name
|
||||
*/
|
||||
protected string $loadId;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(string $loadId)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->loadId = $loadId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$temporaryTotalsDao = DAORegistry::getDAO('TemporaryTotalsDAO'); /** @var TemporaryTotalsDAO $temporaryTotalsDao */
|
||||
$temporaryTotalsDao->removeDoubleClicks($this->loadId, StatisticsHelper::COUNTER_DOUBLE_CLICK_TIME_FILTER_SECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file jobs/submissions/RemoveSubmissionFileFromSearchIndexJob.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 RemoveSubmissionFileFromSearchIndexJob
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Class to handle the Submission File deletion as a Job
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\submissions;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\jobs\BaseJob;
|
||||
use PKP\search\SubmissionSearch;
|
||||
|
||||
class RemoveSubmissionFileFromSearchIndexJob extends BaseJob
|
||||
{
|
||||
/**
|
||||
* The submission id of the targeted submission
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $submissionId;
|
||||
|
||||
/**
|
||||
* The submission file id of the targeted submission file to delete
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $submissionFileId;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(int $submissionId, int $submissionFileId)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->submissionId = $submissionId;
|
||||
$this->submissionFileId = $submissionFileId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$submissionSearchIndex = Application::getSubmissionSearchIndex();
|
||||
$submissionSearchIndex->deleteTextIndex(
|
||||
$this->submissionId,
|
||||
SubmissionSearch::SUBMISSION_SEARCH_GALLEY_FILE,
|
||||
$this->submissionFileId
|
||||
);
|
||||
$submissionSearchIndex->submissionChangesFinished();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file jobs/submissions/RemoveSubmissionFromSearchIndexJob.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 RemoveSubmissionFromSearchIndexJob
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Class to handle the Deleted Submission data update as a Job
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\submissions;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\jobs\BaseJob;
|
||||
|
||||
class RemoveSubmissionFromSearchIndexJob extends BaseJob
|
||||
{
|
||||
/**
|
||||
* The submission id of the targeted submission to delete
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $submissionId;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
*/
|
||||
public function __construct(int $submissionId)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->submissionId = $submissionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$submissionSearchIndex = Application::getSubmissionSearchIndex();
|
||||
$submissionSearchIndex->deleteTextIndex($this->submissionId);
|
||||
$submissionSearchIndex->submissionChangesFinished();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file jobs/submissions/UpdateSubmissionSearchJob.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 UpdateSubmissionSearchJob
|
||||
*
|
||||
* @ingroup jobs
|
||||
*
|
||||
* @brief Class to handle the Submission Search data update as a Job
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\submissions;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use PKP\job\exceptions\JobException;
|
||||
use PKP\jobs\BaseJob;
|
||||
use PKP\submission\PKPSubmission;
|
||||
|
||||
class UpdateSubmissionSearchJob extends BaseJob
|
||||
{
|
||||
/**
|
||||
* The maximum number of SECONDS a job should get processed before consider failed
|
||||
*/
|
||||
public int $timeout = 180;
|
||||
|
||||
/**
|
||||
* The submission ID
|
||||
*/
|
||||
protected int $submissionId;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
*/
|
||||
public function __construct(int $submissionId)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->submissionId = $submissionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$submission = Repo::submission()->get($this->submissionId);
|
||||
|
||||
if (!$submission) {
|
||||
throw new JobException(JobException::INVALID_PAYLOAD);
|
||||
}
|
||||
|
||||
$submissionSearchIndex = Application::getSubmissionSearchIndex();
|
||||
if ($submission->getData('status') !== PKPSubmission::STATUS_PUBLISHED) {
|
||||
$submissionSearchIndex->deleteTextIndex($submission->getId());
|
||||
} else {
|
||||
$submissionSearchIndex->submissionMetadataChanged($submission);
|
||||
$submissionSearchIndex->submissionFilesChanged($submission);
|
||||
}
|
||||
|
||||
Application::getSubmissionSearchDAO()->flushCache();
|
||||
$submissionSearchIndex->submissionChangesFinished();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file jobs/testJobs/TestJobFailure.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class TestJobFailure
|
||||
*
|
||||
* @brief Example failed TestJob with a valid FQN (@see https://www.php.net/manual/pt_BR/language.namespaces.rules.php)
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\testJobs;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Bus\Batchable;
|
||||
use PKP\job\models\Job;
|
||||
use PKP\jobs\BaseJob;
|
||||
|
||||
class TestJobFailure extends BaseJob
|
||||
{
|
||||
use Batchable;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 1;
|
||||
|
||||
/**
|
||||
* The maximum number of unhandled exceptions to allow before failing.
|
||||
*/
|
||||
public int $maxExceptions = 1;
|
||||
|
||||
/**
|
||||
* Initialize the job
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->connection = config('queue.default');
|
||||
$this->queue = Job::TESTING_QUEUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* handle the queue job execution process
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
throw new Exception('cli.test.job');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file jobs/testJobs/TestJobSuccess.php
|
||||
*
|
||||
* Copyright (c) 2014-2022 Simon Fraser University
|
||||
* Copyright (c) 2000-2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class TestJobSuccess
|
||||
*
|
||||
* @brief Example successful TestJob with a valid FQN (@see https://www.php.net/manual/pt_BR/language.namespaces.rules.php)
|
||||
*/
|
||||
|
||||
namespace PKP\jobs\testJobs;
|
||||
|
||||
use Illuminate\Bus\Batchable;
|
||||
use PKP\job\models\Job;
|
||||
use PKP\jobs\BaseJob;
|
||||
|
||||
class TestJobSuccess extends BaseJob
|
||||
{
|
||||
use Batchable;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 1;
|
||||
|
||||
/**
|
||||
* Initialize the job
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->connection = config('queue.default');
|
||||
$this->queue = Job::TESTING_QUEUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* handle the queue job execution process
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user