forked from pkp/pkp-lib
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from pkp/master
Update fork for OJS 3.3
- Loading branch information
Showing
2,536 changed files
with
272,023 additions
and
109,688 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
<?php | ||
|
||
/** | ||
* @file api/v1/_email/PKPEmailHandler.inc.php | ||
* | ||
* Copyright (c) 2014-2020 Simon Fraser University | ||
* Copyright (c) 2003-2020 John Willinsky | ||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING. | ||
* | ||
* @class PKPEmailHandler | ||
* @ingroup api_v1_announcement | ||
* | ||
* @brief Handle API requests for announcement operations. | ||
* | ||
*/ | ||
use \Illuminate\Queue\Capsule\Manager as Queue; | ||
use Illuminate\Database\Capsule\Manager as Capsule; | ||
use \Psr\Http\Message\ServerRequestInterface; | ||
|
||
import('lib.pkp.classes.handler.APIHandler'); | ||
import('classes.core.Services'); | ||
|
||
class PKPEmailHandler extends APIHandler { | ||
|
||
/** Number of emails to send in each job */ | ||
const EMAILS_PER_JOB = 100; | ||
|
||
/** | ||
* Constructor | ||
*/ | ||
public function __construct() { | ||
$this->_handlerPath = '_email'; | ||
$this->_endpoints = [ | ||
'POST' => [ | ||
[ | ||
'pattern' => $this->getEndpointPattern(), | ||
'handler' => [$this, 'create'], | ||
'roles' => [ROLE_ID_SITE_ADMIN, ROLE_ID_MANAGER], | ||
], | ||
], | ||
'PUT' => [ | ||
[ | ||
'pattern' => $this->getEndpointPattern() . '/{queueId}', | ||
'handler' => [$this, 'process'], | ||
'roles' => [ROLE_ID_SITE_ADMIN, ROLE_ID_MANAGER], | ||
], | ||
], | ||
]; | ||
parent::__construct(); | ||
} | ||
|
||
/** | ||
* @copydoc PKPHandler::authorize | ||
*/ | ||
public function authorize($request, &$args, $roleAssignments) { | ||
import('lib.pkp.classes.security.authorization.PolicySet'); | ||
$rolePolicy = new PolicySet(COMBINING_PERMIT_OVERRIDES); | ||
|
||
import('lib.pkp.classes.security.authorization.RoleBasedHandlerOperationPolicy'); | ||
foreach ($roleAssignments as $role => $operations) { | ||
$rolePolicy->addPolicy(new RoleBasedHandlerOperationPolicy($request, $role, $operations)); | ||
} | ||
$this->addPolicy($rolePolicy); | ||
|
||
return parent::authorize($request, $args, $roleAssignments); | ||
} | ||
|
||
/** | ||
* Create a jobs queue to send a bulk email to users in one or | ||
* more user groups | ||
* | ||
* @param ServerRequestInterface $slimRequest | ||
* @param APIResponse $response | ||
* @param array $args arguments | ||
* @return APIResponse | ||
*/ | ||
public function create(ServerRequestInterface $slimRequest, APIResponse $response, array $args) { | ||
$context = $this->getRequest()->getContext(); | ||
$contextId = $context->getId(); | ||
|
||
$requestParams = $slimRequest->getParsedBody(); | ||
|
||
$params = []; | ||
foreach ($requestParams as $param => $val) { | ||
switch ($param) { | ||
case 'userGroupIds': | ||
if (!is_array($val)) { | ||
$val = strlen(trim($val)) | ||
? explode(',', $val) | ||
: []; | ||
} | ||
$params[$param] = array_map('intval', $val); | ||
break; | ||
case 'body': | ||
case 'subject': | ||
$params[$param] = $val; | ||
break; | ||
case 'copy': | ||
$params[$param] = (bool) $val; | ||
break; | ||
} | ||
} | ||
|
||
$errors = []; | ||
if (empty($params['body'])) { | ||
$errors['body'] = [__('api.emails.400.missingBody')]; | ||
} | ||
|
||
if (empty($params['subject'])) { | ||
$errors['subject'] = [__('api.emails.400.missingSubject')]; | ||
} | ||
|
||
if (empty($params['userGroupIds'])) { | ||
$errors['userGroupIds'] = [__('api.emails.400.missingUserGroups')]; | ||
} | ||
|
||
if ($errors) { | ||
return $response->withJson($errors, 400); | ||
} | ||
|
||
$userGroupDao = DAORegistry::getDAO('UserGroupDAO'); | ||
foreach ($params['userGroupIds'] as $userGroupId) { | ||
if (!$userGroupDao->contextHasGroup($contextId, $userGroupId)) { | ||
return $response->withJson([ | ||
'userGroupIds' => [__('api.emails.403.notAllowedUserGroup')], | ||
], 403); | ||
} | ||
} | ||
|
||
// Only permit emails to be sent to active users in this context | ||
$params['status'] = 'active'; | ||
$params['contextId'] = $contextId; | ||
|
||
$userIds = Services::get('user')->getIds($params); | ||
$subject = $params['subject']; | ||
$body = $params['body']; | ||
$fromEmail = $context->getData('contactEmail'); | ||
$fromName = $context->getData('contactName'); | ||
$queueId = 'email_' . uniqid(); | ||
|
||
if (!empty($params['copy'])) { | ||
$currentUserId = $this->getRequest()->getUser()->getId(); | ||
if (!in_array($currentUserId, $userIds)) { | ||
$userIds[] = $currentUserId; | ||
} | ||
} | ||
|
||
$batches = array_chunk($userIds, self::EMAILS_PER_JOB); | ||
foreach ($batches as $userIds) { | ||
Queue::push(function() use ($userIds, $contextId, $subject, $body, $fromEmail, $fromName) { | ||
import('lib.pkp.classes.mail.Mail'); | ||
$users = Services::get('user')->getMany([ | ||
'contextId' => $contextId, | ||
'userIds' => $userIds, | ||
]); | ||
foreach ($users as $user) { | ||
$mail = new Mail(); | ||
$mail->setFrom($fromEmail, $fromName); | ||
$mail->setRecipients([ | ||
[ | ||
'name' => $user->getFullName(), | ||
'email' => $user->getEmail(), | ||
], | ||
]); | ||
$mail->setSubject($subject); | ||
$mail->setBody($body); | ||
$mail->send(); | ||
} | ||
}, [], $queueId, 'persistent'); | ||
} | ||
|
||
return $response->withJson([ | ||
'queueId' => $queueId, | ||
'totalJobs' => count($batches), | ||
], 200); | ||
} | ||
|
||
/** | ||
* Process a jobs queue for sending a bulk email | ||
* | ||
* @param ServerRequestInterface $slimRequest | ||
* @param APIResponse $response | ||
* @param array $args arguments | ||
* @return APIResponse | ||
*/ | ||
public function process(ServerRequestInterface $slimRequest, APIResponse $response, array $args) { | ||
$countRunning = Capsule::table('jobs') | ||
->where('queue', $args['queueId']) | ||
->whereNotNull('reserved_at') | ||
->count(); | ||
$countPending = $this->countPending($args['queueId']); | ||
|
||
// Don't run another job if one is already running. | ||
// This should ensure jobs are run one after the other and | ||
// prevent long-running jobs from running simultaneously | ||
// and piling onto the server like a DDOS attack. | ||
if (!$countRunning && $countPending) { | ||
$laravelContainer = Registry::get('laravelContainer'); | ||
$worker = new Illuminate\Queue\Worker( | ||
$laravelContainer['queue'], | ||
$laravelContainer['events'], | ||
$laravelContainer['exception.handler'], | ||
function() { | ||
return false; // is not down for maintenance | ||
} | ||
); | ||
$options = new Illuminate\Queue\WorkerOptions(); | ||
$worker->runNextJob('persistent', $args['queueId'], $options); | ||
|
||
// Update count of pending jobs | ||
$countPending = $this->countPending($args['queueId']); | ||
} | ||
|
||
return $response->withJson([ | ||
'pendingJobs' => $countPending, | ||
], 200); | ||
} | ||
|
||
/** | ||
* Return a count of the pending jobs in a given queue | ||
* | ||
* @param string $queueId | ||
* @return int | ||
*/ | ||
protected function countPending(string $queueId) : int { | ||
return Capsule::table('jobs') | ||
->where('queue', $queueId) | ||
->count(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.