[ Mini Kiebo ]
Server: Windows NT DESKTOP-5B8S0D4 6.2 build 9200 (Windows 8 Professional Edition) i586
Path:
D:
/
Backup
/
05122024
/
htdocs
/
jurnal-kesmas
/
plugins
/
generic
/
orcidProfile
/
[
Home
]
File: OrcidProfilePlugin.php
<?php /** * @file OrcidProfilePlugin.php * * Copyright (c) 2015-2022 University of Pittsburgh * 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 OrcidProfilePlugin * * @ingroup plugins_generic_orcidProfile * * @brief ORCID Profile plugin class */ namespace APP\plugins\generic\orcidProfile; use APP\issue\Issue; use APP\journal\Journal; use APP\plugins\generic\citationStyleLanguage\CitationStyleLanguagePlugin; use APP\publication\Publication; use APP\author\Author; use APP\controllers\grid\users\author\form\AuthorForm; use APP\core\Application; use APP\core\Request; use APP\core\Services; use APP\decision\Decision; use APP\facades\Repo; use APP\plugins\generic\orcidProfile\classes\form\OrcidProfileSettingsForm; use APP\plugins\generic\orcidProfile\classes\form\OrcidProfileStatusForm; use APP\plugins\generic\orcidProfile\classes\OrcidValidator; use APP\plugins\generic\orcidProfile\mailables\OrcidCollectAuthorId; use APP\plugins\generic\orcidProfile\mailables\OrcidRequestAuthorAuthorization; use APP\submission\Submission; use APP\template\TemplateManager; use Carbon\Carbon; use Exception; use GuzzleHttp\Exception\ClientException; use Illuminate\Support\Facades\Mail; use PKP\components\forms\FieldOptions; use PKP\components\forms\FieldText; use PKP\components\forms\publication\ContributorForm; use PKP\config\Config; use PKP\core\Core; use PKP\core\JSONMessage; use PKP\core\PKPApplication; use PKP\db\DAORegistry; use PKP\facades\Locale; use PKP\form\Form; use PKP\install\Installer; use PKP\linkAction\LinkAction; use PKP\linkAction\request\AjaxModal; use PKP\plugins\GenericPlugin; use PKP\plugins\Hook; use PKP\plugins\PluginRegistry; use PKP\services\PKPSchemaService; use PKP\submission\PKPSubmission; use PKP\submission\reviewAssignment\ReviewAssignment; use PKP\submission\reviewAssignment\ReviewAssignmentDAO; use Sokil\IsoCodes\Database\Countries\Country; define('ORCID_URL', 'https://orcid.org/'); define('ORCID_URL_SANDBOX', 'https://sandbox.orcid.org/'); define('ORCID_API_URL_PUBLIC', 'https://pub.orcid.org/'); define('ORCID_API_URL_PUBLIC_SANDBOX', 'https://pub.sandbox.orcid.org/'); define('ORCID_API_URL_MEMBER', 'https://api.orcid.org/'); define('ORCID_API_URL_MEMBER_SANDBOX', 'https://api.sandbox.orcid.org/'); define('ORCID_API_VERSION_URL', 'v3.0/'); define('ORCID_API_SCOPE_PUBLIC', '/authenticate'); define('ORCID_API_SCOPE_MEMBER', '/activities/update'); define('OAUTH_TOKEN_URL', 'oauth/token'); define('ORCID_EMPLOYMENTS_URL', 'employments'); define('ORCID_PROFILE_URL', 'person'); define('ORCID_EMAIL_URL', 'email'); define('ORCID_WORK_URL', 'work'); define('ORCID_REVIEW_URL', 'peer-review'); class OrcidProfilePlugin extends GenericPlugin { public const PUBID_TO_ORCID_EXT_ID = ['doi' => 'doi', 'other::urn' => 'urn']; public const USER_GROUP_TO_ORCID_ROLE = ['Author' => 'AUTHOR', 'Translator' => 'CHAIR_OR_TRANSLATOR', 'Journal manager' => 'AUTHOR']; private $currentContextId; /** * @copydoc Plugin::register() * * @param null|mixed $mainContextId */ public function register($category, $path, $mainContextId = null) { $success = parent::register($category, $path, $mainContextId); if (Application::isUnderMaintenance()) { return true; } if ($success && $this->getEnabled($mainContextId)) { $contextId = ($mainContextId === null) ? $this->getCurrentContextId() : $mainContextId; $validator = new OrcidValidator($this); $clientId = $this->getSetting($contextId, 'orcidClientId'); $clientSecret = $this->getSetting($contextId, 'orcidClientSecret'); if (!$validator->validateClientSecret($clientSecret) || !$validator->validateClientId($clientId)) { error_log(new Exception('The ORCID plugin is enabled, but its settings are invalid. In order to fix, access the plugin settings and try to save the form')); return $success; } Hook::add('ArticleHandler::view', [&$this, 'submissionView']); Hook::add('PreprintHandler::view', [&$this, 'submissionView']); // Insert the OrcidProfileHandler to handle ORCID redirects Hook::add('LoadHandler', [$this, 'setupCallbackHandler']); // Register callback for Smarty filters; add CSS Hook::add('TemplateManager::display', [$this, 'handleTemplateDisplay']); // Add "Connect ORCID" button to PublicProfileForm Hook::add('User::PublicProfile::AdditionalItems', [$this, 'handleUserPublicProfileDisplay']); // Display additional ORCID access information and checkbox to send e-mail to authors in the AuthorForm Hook::add('authorform::display', [$this, 'handleFormDisplay']); // Send email to author, if the added checkbox was ticked Hook::add('authorform::execute', [$this, 'handleAuthorFormExecute']); // Handle ORCID on user registration Hook::add('registrationform::execute', [$this, 'collectUserOrcidId']); // Send emails to authors without ORCID id upon submission //TODO Hook::add('submissionsubmitstep3form::execute', [$this, 'handleSubmissionSubmitStep3FormExecute']); // Send emails to authors without authorised ORCID access on promoting a submission to copy editing. Not included in OPS. if ($this->getSetting($contextId, 'sendMailToAuthorsOnPublication')) { Hook::add('EditorAction::recordDecision', [$this, 'handleEditorAction']); } Hook::add('Publication::publish', [$this, 'handlePublicationStatusChange']); Hook::add('ThankReviewerForm::thankReviewer', [$this, 'handleThankReviewer']); // Add more ORCiD fields to author Schema Hook::add('Schema::get::author', function ($hookName, $args) { $schema = &$args[0]; $schema->properties->orcidSandbox = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; $schema->properties->orcidAccessToken = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; $schema->properties->orcidAccessScope = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; $schema->properties->orcidRefreshToken = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; $schema->properties->orcidAccessExpiresOn = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; $schema->properties->orcidAccessDenied = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; $schema->properties->orcidEmailToken = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; $schema->properties->orcidWorkPutCode = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; }); // Add more ORCiD fields to user Schema Hook::add('Schema::get::user', function ($hookName, $args) { $schema = &$args[0]; $schema->properties->orcidAccessToken = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; $schema->properties->orcidAccessScope = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; $schema->properties->orcidRefreshToken = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; $schema->properties->orcidAccessExpiresOn = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; $schema->properties->orcidAccessDenied = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; $schema->properties->orcidReviewPutCode = (object)[ 'type' => 'string', 'apiSummary' => true, 'validation' => ['nullable'] ]; }); Services::get('schema')->get(PKPSchemaService::SCHEMA_USER, true); Hook::add('Mailer::Mailables', [$this, 'addMailable']); Hook::add('Author::edit', [$this, 'handleAuthorFormExecute']); Hook::add('Form::config::before', [$this, 'addOrcidFormFields']); Hook::add('Installer::postInstall', [$this, 'updateSchema']); Hook::add('Publication::validatePublish', [$this, 'validate']); } return $success; } /** * Load a setting for a specific journal or load it from the config.inc.php if it is specified there. * * @param int $contextId The id of the journal from which the plugin settings should be loaded. * @param string $name Name of the setting. * * @return mixed The setting value, either from the database for this context * or from the global configuration file. */ public function getSetting($contextId, $name) { switch ($name) { case 'orcidProfileAPIPath': $config_value = Config::getVar('orcid', 'api_url'); break; case 'orcidClientId': $config_value = Config::getVar('orcid', 'client_id'); break; case 'orcidClientSecret': $config_value = Config::getVar('orcid', 'client_secret'); break; case 'country': $config_value = Config::getVar('orcid', 'country'); break; case 'city': $config_value = Config::getVar('orcid', 'city'); break; default: return parent::getSetting($contextId, $name); } $config_value = $config_value ?? parent::getSetting($contextId, $name); if ($name == 'orcidProfileAPIPath') { if ($config_value == 'https://orcid.org/') { $config_value = ORCID_API_URL_PUBLIC; } elseif ($config_value == 'https://sandbox.orcid.org/') { $config_value = ORCID_API_URL_PUBLIC_SANDBOX; } } return $config_value; } /** * adds orcid form fields. * * @param string $hookName * @param Form $form */ public function addOrcidFormFields($hookName, $form): bool { if (!$form instanceof ContributorForm) { return Hook::CONTINUE; } $form->removeField('orcid'); $form->addField(new FieldText('orcid', [ 'label' => __('user.orcid'), 'optIntoEdit' => true, 'optIntoEditLabel' => __('common.override'), 'tooltip' => __('plugins.generic.orcidProfile.about.orcidExplanation'), ]), [FIELD_POSITION_AFTER, 'url']); $form->addField(new FieldOptions('requestOrcidAuthorization', [ 'label' => __('plugins.generic.orcidProfile.verify.title'), 'options' => [ [ 'label' => __('plugins.generic.orcidProfile.author.requestAuthorization'), 'value' > false, ] ] ]), [FIELD_POSITION_AFTER, 'orcid']); $form->addField( new FieldOptions('deleteORCID', [ 'label' => __('plugins.generic.orcidProfile.displayName'), 'options' => [ [ 'label' => __('plugins.generic.orcidProfile.author.deleteORCID'), 'value' > false, ] ], 'showWhen' => 'orcid', ]), [FIELD_POSITION_AFTER, 'orcid'] ); return Hook::CONTINUE; } /** * @param string $hookName * @param array $args */ public function handleThankReviewer($hookName, $args) { $request = PKPApplication::get()->getRequest(); $context = $request->getContext(); $newPublication = & $args[0]; if ($this->isMemberApiEnabled($this->currentContextId)) { if ($this->getSetting($context->getId(), 'country') && $this->getSetting($context->getId(), 'city')) { $this->publishReviewerWorkToOrcid($newPublication, $request); } } } /** * @return bool True if the ORCID Member API has been selected in this context. */ public function isMemberApiEnabled($contextId) { $apiUrl = $this->getSetting($contextId, 'orcidProfileAPIPath'); if ($apiUrl === ORCID_API_URL_MEMBER || $apiUrl === ORCID_API_URL_MEMBER_SANDBOX) { return true; } else { return false; } } /** * @return JSONMessage|null */ public function publishReviewerWorkToOrcid(Submission $submission, Request $request) { // Application is set to sandbox mode and will not run the features of plugin if (Config::getVar('general', 'sandbox', false)) { error_log('Application is set to sandbox mode and will not have any interaction with orcid service'); return new JSONMessage(false, __('common.sandbox')); } $context = $request->getContext(); $requestVars = $request->getUserVars(); /** @var ReviewAssignmentDAO */ $reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); $reviewAssignmentId = $requestVars['reviewAssignmentId']; if (isset($reviewAssignmentId)) { $review = $reviewAssignmentDao->getById($reviewAssignmentId); $reviewer = Repo::user()->get($review->getData('reviewerId')); if ($reviewer->getOrcid() && $reviewer->getData('orcidAccessToken')) { $orcidAccessExpiresOn = Carbon::parse($reviewer->getData('orcidAccessExpiresOn')); if ($orcidAccessExpiresOn->isFuture()) { # Extract only the ORCID from the stored ORCID uri $orcid = basename(parse_url($reviewer->getOrcid(), PHP_URL_PATH)); $orcidReview = $this->buildOrcidReview($submission, $review, $request); $uri = $this->getSetting($context->getId(), 'orcidProfileAPIPath') . ORCID_API_VERSION_URL . $orcid . '/' . ORCID_REVIEW_URL; $method = 'POST'; if ($putCode = $reviewer->getData('orcidReviewPutCode')) { $uri .= '/' . $putCode; $method = 'PUT'; $orcidReview['put-code'] = $putCode; } $headers = [ 'Content-Type' => ' application/vnd.orcid+json; qs=4', 'Accept' => 'application/json', 'Authorization' => 'Bearer ' . $reviewer->getData('orcidAccessToken') ]; $httpClient = Application::get()->getHttpClient(); try { $response = $httpClient->request( $method, $uri, [ 'headers' => $headers, 'json' => $orcidReview, 'allow_redirects' => ['strict' => true], ] ); } catch (ClientException $exception) { $reason = $exception->getResponse()->getBody(); $this->logInfo("Publication fail: {$reason}"); return new JSONMessage(false); } $httpStatus = $response->getStatusCode(); $this->logInfo("Response status: {$httpStatus}"); $responseHeaders = $response->getHeaders(); switch ($httpStatus) { case 200: $this->logInfo("Review updated in profile, putCode: {$putCode}"); break; case 201: $location = $responseHeaders['Location'][0]; // Extract the ORCID work put code for updates/deletion. $putCode = basename(parse_url($location, PHP_URL_PATH)); $reviewer->setData('orcidReviewPutCode', $putCode); Repo::user()->edit($reviewer, ['orcidReviewPutCode']); $this->logInfo("Review added to profile, putCode: {$putCode}"); break; default: $this->logError("Unexpected status {$httpStatus} response, body: {$responseHeaders}"); } } } } } public function buildOrcidReview($submission, $review, $request, $issue = null) { $publicationUrl = $request->getDispatcher()->url($request, PKPApplication::ROUTE_PAGE, null, 'article', 'view', $submission->getId()); $context = $request->getContext(); $publicationLocale = ($submission->getData('locale')) ? $submission->getData('locale') : 'en'; $pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $context->getId()); // DO not remove $supportedSubmissionLocales = $context->getSupportedSubmissionLocales(); if (!empty($review->getData('dateCompleted')) && $context->getData('onlineIssn')) { $reviewCompletionDate = Carbon::parse($review->getData('dateCompleted')); $orcidReview = [ 'reviewer-role' => 'reviewer', 'review-type' => 'review', 'review-completion-date' => [ 'year' => [ 'value' => $reviewCompletionDate->format('Y') ], 'month' => [ 'value' => $reviewCompletionDate->format('m') ], 'day' => [ 'value' => $reviewCompletionDate->format('d') ] ], 'review-group-id' => 'issn:' . $context->getData('onlineIssn'), 'convening-organization' => [ 'name' => $context->getData('publisherInstitution'), 'address' => [ 'city' => $this->getSetting($context->getId(), 'city'), 'country' => $this->getSetting($context->getId(), 'country') ] ], 'review-identifiers' => ['external-id' => [ [ 'external-id-type' => 'source-work-id', 'external-id-value' => $review->getData('reviewRoundId'), 'external-id-relationship' => 'part-of'] ]] ]; if ($review->getReviewMethod() == ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN) { $orcidReview['subject-url'] = ['value' => $publicationUrl]; $orcidReview['review-url'] = ['value' => $publicationUrl]; $orcidReview['subject-type'] = 'journal-article'; $orcidReview['subject-name'] = [ 'title' => ['value' => $submission->getCurrentPublication()->getLocalizedData('title') ?? ''] ]; if (!empty($submission->getData('pub-id::doi'))) { $externalIds = [ 'external-id-type' => 'doi', 'external-id-value' => $submission->getData('pub-id::doi'), 'external-id-url' => [ 'value' => 'https://doi.org/' . $submission->getData('pub-id::doi') ], 'external-id-relationship' => 'self' ]; $orcidReview['subject-external-identifier'] = $externalIds; } } $translatedTitleAvailable = false; foreach ($supportedSubmissionLocales as $defaultLanguage) { if ($defaultLanguage !== $publicationLocale) { $iso2LanguageCode = substr($defaultLanguage, 0, 2); $defaultTitle = $submission->getLocalizedData($iso2LanguageCode); if (strlen($defaultTitle) > 0 && !$translatedTitleAvailable) { $orcidReview['subject-name']['translated-title'] = ['value' => $defaultTitle, 'language-code' => $iso2LanguageCode]; $translatedTitleAvailable = true; } } } return $orcidReview; } } /** * Write info message to log. * * @param string $message Message to write */ public function logInfo($message) { if ($this->getSetting($this->currentContextId, 'logLevel') === 'ERROR') { return; } self::writeLog($message, 'INFO'); } /** * Write error message to log. * * @param string $message Message to write */ public function logError($message) { if ($this->getSetting($this->currentContextId, 'logLevel') === 'ERROR') { return; } self::writeLog($message, 'ERROR'); } /** * Write a message with specified level to log * * @param string $message Message to write * @param string $level Error level to add to message */ private static function writeLog($message, $level) { $fineStamp = date('Y-m-d H:i:s') . substr(microtime(), 1, 4); error_log("{$fineStamp} {$level} {$message}\n", 3, self::logFilePath()); } /** * @return string Path to a custom ORCID log file. */ public static function logFilePath() { return Config::getVar('files', 'files_dir') . '/orcid.log'; } /** * Hook callback: register pages for each sushi-lite method * This URL is of the form: orcidapi/{$orcidrequest} * * @see PKPPageRouter::route() */ public function setupCallbackHandler($hookName, $params) { $page = $params[0]; if ($this->getEnabled() && $page == 'orcidapi') { define('HANDLER_CLASS', OrcidProfileHandler::class); return true; } return false; } /** * Check if there exist a valid orcid configuration section in the global config.inc.php of OJS. * * @return boolean True, if the config file has api_url, client_id and client_secret set in an [orcid] section */ public function isGloballyConfigured() { $apiUrl = Config::getVar('orcid', 'api_url'); $clientId = Config::getVar('orcid', 'client_id'); $clientSecret = Config::getVar('orcid', 'client_secret'); return isset($apiUrl) && trim($apiUrl) && isset($clientId) && trim($clientId) && isset($clientSecret) && trim($clientSecret); } /** * Hook callback to handle form display. * Registers output filter for public user profile and author form. * * @param string $hookName * @param Form[] $args * * @return bool * * @see Form::display() * */ public function handleFormDisplay($hookName, $args) { //TODO $request = Application::get()->getRequest(); $templateMgr = TemplateManager::getManager($request); switch ($hookName) { case 'authorform::display': /** @var AuthorForm */ $authorForm = &$args[0]; $author = $authorForm->getAuthor(); if ($author) { $authenticated = !empty($author->getData('orcidAccessToken')); $templateMgr->assign( [ 'orcidAccessToken' => $author->getData('orcidAccessToken'), 'orcidAccessScope' => $author->getData('orcidAccessScope'), 'orcidAccessExpiresOn' => $author->getData('orcidAccessExpiresOn'), 'orcidAccessDenied' => $author->getData('orcidAccessDenied'), 'orcidAuthenticated' => $authenticated ] ); } $templateMgr->registerFilter('output', [$this, 'authorFormFilter']); break; } return false; } /** * Output filter adds ORCiD interaction to contributors metadata add/edit form. * * @param $output string * @param $templateMgr TemplateManager * @return string */ function authorFormFilter($output, $templateMgr) { if (preg_match('/<input[^>]+name="submissionId"[^>]*>/', $output, $matches, PREG_OFFSET_CAPTURE)) { $match = $matches[0][0]; $offset = $matches[0][1]; $templateMgr->assign('orcidIcon', $this->getIcon()); $newOutput = substr($output, 0, $offset + strlen($match)); $newOutput .= $templateMgr->fetch($this->getTemplateResource('authorFormOrcid.tpl')); $newOutput .= substr($output, $offset + strlen($match)); $output = $newOutput; $templateMgr->unregisterFilter('output', [$this, 'authorFormFilter']); } return $output; } /** * Hook callback: register output filter for user registration and article display. * * @param string $hookName * @param array $args * * @return bool * * @see TemplateManager::display() * */ public function handleTemplateDisplay($hookName, $args) { //TODO orcid $templateMgr = &$args[0]; $template = &$args[1]; $request = Application::get()->getRequest(); // Assign our private stylesheet, for front and back ends. $templateMgr->addStyleSheet( 'orcidProfile', $request->getBaseUrl() . '/' . $this->getStyleSheet(), [ 'contexts' => ['frontend', 'backend'] ] ); switch ($template) { case 'frontend/pages/userRegister.tpl': $templateMgr->registerFilter('output', [$this, 'registrationFilter']); break; } return false; } /** * Return the location of the plugin's CSS file * * @return string */ public function getStyleSheet() { return $this->getPluginPath() . '/css/orcidProfile.css'; } public function isSandbox() { $apiUrl = $this->getSetting($this->getCurrentContextId(), 'orcidProfileAPIPath'); return ($apiUrl == ORCID_API_URL_MEMBER_SANDBOX); } /** * Output filter adds ORCiD interaction to registration form. * * @param string $output * @param TemplateManager $templateMgr * * @return string */ public function registrationFilter($output, $templateMgr) { if (preg_match('/<form[^>]+id="register"[^>]+>/', $output, $matches, PREG_OFFSET_CAPTURE)) { $match = $matches[0][0]; $offset = $matches[0][1]; $targetOp = 'register'; $templateMgr->assign([ 'targetOp' => $targetOp, 'orcidUrl' => $this->getOrcidUrl(), 'orcidOAuthUrl' => $this->buildOAuthUrl('orcidAuthorize', ['targetOp' => $targetOp]), 'orcidIcon' => $this->getIcon(), ]); $newOutput = substr($output, 0, $offset + strlen($match)); $newOutput .= $templateMgr->fetch($this->getTemplateResource('orcidProfile.tpl')); $newOutput .= substr($output, $offset + strlen($match)); $output = $newOutput; $templateMgr->unregisterFilter('output', [$this, 'registrationFilter']); } return $output; } /** * Return the ORCID website url (prod or sandbox) based on the current API configuration * * @return string */ public function getOrcidUrl() { $request = Application::get()->getRequest(); $context = $request->getContext(); $contextId = ($context == null) ? 0 : $context->getId(); $apiPath = $this->getSetting($contextId, 'orcidProfileAPIPath'); return in_array($apiPath, [ORCID_API_URL_PUBLIC, ORCID_API_URL_MEMBER]) ? ORCID_URL : ORCID_URL_SANDBOX; } /** * Return an ORCID OAuth authorization link with * * @param string $handlerMethod containting a valid method of the OrcidProfileHandler * @param array $redirectParams associative array with additional request parameters for the redirect URL */ public function buildOAuthUrl($handlerMethod, $redirectParams) { $request = Application::get()->getRequest(); $context = $request->getContext(); // This should only ever happen within a context, never site-wide. assert($context != null); $contextId = $context->getId(); if ($this->isMemberApiEnabled($contextId)) { $scope = ORCID_API_SCOPE_MEMBER; } else { $scope = ORCID_API_SCOPE_PUBLIC; } // We need to construct a page url, but the request is using the component router. // Use the Dispatcher to construct the url and set the page router. $redirectUrl = $request->getDispatcher()->url( $request, Application::ROUTE_PAGE, null, 'orcidapi', $handlerMethod, null, $redirectParams ); return $this->getOauthPath() . 'authorize?' . http_build_query( [ 'client_id' => $this->getSetting($contextId, 'orcidClientId'), 'response_type' => 'code', 'scope' => $scope, 'redirect_uri' => $redirectUrl] ); } /** * Return the OAUTH path (prod or sandbox) based on the current API configuration * * @return string */ public function getOauthPath() { return $this->getOrcidUrl() . 'oauth/'; } /** * Return a string of the ORCiD SVG icon * * @return string */ public function getIcon() { $path = Core::getBaseDir() . '/' . $this->getPluginPath() . '/templates/images/orcid.svg'; return file_exists($path) ? file_get_contents($path) : ''; } /** * Renders additional content for the PublicProfileForm. * * Called by @param string $output * * * @return bool * * @see lib/pkp/templates/user/publicProfileForm.tpl * */ public function handleUserPublicProfileDisplay($hookName, $params) { $templateMgr = &$params[1]; $output = &$params[2]; $request = Application::get()->getRequest(); $context = $request->getContext(); $userId = $request->getUser()->getId(); $user = Repo::user()->get($userId); $contextId = ($context == null) ? 0 : $context->getId(); $targetOp = 'profile'; $templateMgr->assign( [ 'targetOp' => $targetOp, 'orcidUrl' => $this->getOrcidUrl(), 'orcidOAuthUrl' => $this->buildOAuthUrl('orcidAuthorize', ['targetOp' => $targetOp]), 'orcidClientId' => $this->getSetting($contextId, 'orcidClientId'), 'orcidIcon' => $this->getIcon(), 'orcidAuthenticated' => !empty($user->getData('orcidAccessToken')), ] ); $output = $templateMgr->fetch($this->getTemplateResource('orcidProfile.tpl')); return true; } /** * handleAuthorFormExecute sends an e-mail to the author if a specific checkbox was ticked in the author form. * * @param string $hookname * @param AuthorForm[] $args * * @see AuthorForm::execute() The function calling the hook. * */ public function handleAuthorFormExecute($hookname, $args) { if (count($args) == 3) { /** @var Author */ $author = &$args[0]; $values = $args[2]; if ($author && $values['requestOrcidAuthorization']) { $this->sendAuthorMail($author); } if ($author && $values['deleteORCID']) { $author->setOrcid(null); $this->removeOrcidAccessToken($author, false); } } } /** * Send mail with ORCID authorization link to the e-mail address of the supplied Author object. * * @param Author $author * @param bool $updateAuthor If true update the author fields in the database. * Use this only if not called from a function, which does this anyway. */ public function sendAuthorMail($author, $updateAuthor = false) { $request = Application::get()->getRequest(); $context = $request->getContext(); // This should only ever happen within a context, never site-wide. if ($context != null) { $contextId = $context->getId(); $publicationId = $author->getData('publicationId'); $publication = Repo::publication()->get($publicationId); $submission = Repo::submission()->get($publication->getData('submissionId')); $emailToken = md5(microtime() . $author->getEmail()); $author->setData('orcidEmailToken', $emailToken); $oauthUrl = $this->buildOAuthUrl('orcidVerify', ['token' => $emailToken, 'state' => $publicationId]); if ($this->isMemberApiEnabled($contextId)) { $mailable = new OrcidRequestAuthorAuthorization($context, $submission, $oauthUrl); } else { $mailable = new OrcidCollectAuthorId($context, $submission, $oauthUrl); } // Set From to primary journal contact $mailable->from($context->getData('contactEmail'), $context->getData('contactName')); // Send to author $mailable->recipients([$author]); $emailTemplateKey = $mailable::getEmailTemplateKey(); $emailTemplate = Repo::emailTemplate()->getByKey($contextId, $emailTemplateKey); $mailable->body($emailTemplate->getLocalizedData('body')) ->subject($emailTemplate->getLocalizedData('subject')); Mail::send($mailable); if ($updateAuthor) { Repo::author()->dao->update($author); } } } /** * Remove all data fields, which belong to an ORCID access token from the * given Author object. Also updates fields in the db. * * @param Author $author object with ORCID access token */ public function removeOrcidAccessToken($author, $saveAuthor = true) { $author->setData('orcidAccessToken', null); $author->setData('orcidAccessScope', null); $author->setData('orcidRefreshToken', null); $author->setData('orcidAccessExpiresOn', null); $author->setData('orcidSandbox', null); if ($saveAuthor) { Repo::author()->dao->update($author); } } /** * Collect the ORCID when registering a user. * * @param string $hookName * @param array $params * * @return bool */ public function collectUserOrcidId($hookName, $params) { $form = $params[0]; $user = $form->user; $form->readUserVars(['orcid']); $user->setOrcid($form->getData('orcid')); return false; } /** * Output filter adds ORCiD interaction to the 3rd step submission form. * * * @return bool */ public function handleSubmissionSubmitStep3FormExecute($hookName, $params) { $form = $params[0]; // Have to use global Request access because request is not passed to hook $publication = Repo::publication()->get($form->submission->getData('currentPublicationId')); $authors = $publication->getData('authors'); $request = Application::get()->getRequest(); $user = $request->getUser(); $author = $authors->first(); //error_log("OrcidProfilePlugin: authors[0] = " . var_export($authors[0], true)); //error_log("OrcidProfilePlugin: user = " . var_export($user, true)); if ($author?->getOrcid() === $user->getOrcid()) { // if the author and user share the same ORCID id // copy the access token from the user //error_log("OrcidProfilePlugin: user->orcidAccessToken = " . $user->getData('orcidAccessToken')); $author->setData('orcidAccessToken', $user->getData('orcidAccessToken')); $author->setData('orcidAccessScope', $user->getData('orcidAccessScope')); $author->setData('orcidRefreshToken', $user->getData('orcidRefreshToken')); $author->setData('orcidAccessExpiresOn', $user->getData('orcidAccessExpiresOn')); $author->setData('orcidSandbox', $user->getData('orcidSandbox')); Repo::author()->dao->update($author); //error_log("OrcidProfilePlugin: author = " . var_export($authors[0], true)); } return false; } /** * Add additional ORCID specific fields to the Author and User objects * * @param string $hookName * @param array $params * * @return bool */ public function handleAdditionalFieldNames($hookName, $params) { $fields = &$params[1]; $fields[] = 'orcidSandbox'; $fields[] = 'orcidAccessToken'; $fields[] = 'orcidAccessScope'; $fields[] = 'orcidRefreshToken'; $fields[] = 'orcidAccessExpiresOn'; $fields[] = 'orcidAccessDenied'; return false; } /** * @copydoc Plugin::getDescription() */ public function getDescription() { return __('plugins.generic.orcidProfile.description'); } /** * @see PKPPlugin::getInstallEmailTemplatesFile() */ public function getInstallEmailTemplatesFile() { return ($this->getPluginPath() . '/emailTemplates.xml'); } /** * Extend the {url ...} smarty to support this plugin. */ public function smartyPluginUrl($params, $smarty) { $path = [$this->getCategory(), $this->getName()]; if (is_array($params['path'])) { $params['path'] = array_merge($path, $params['path']); } elseif (!empty($params['path'])) { $params['path'] = array_merge($path, [$params['path']]); } else { $params['path'] = $path; } if (!empty($params['id'])) { $params['path'] = array_merge($params['path'], [$params['id']]); unset($params['id']); } return $smarty->smartyUrl($params, $smarty); } public function submissionView($hookName, $args) { $request = $args[0]; $templateMgr = TemplateManager::getManager($request); $templateMgr->assign(['orcidIcon' => $this->getIcon()]); } /** * @see Plugin::getActions() */ public function getActions($request, $actionArgs) { $router = $request->getRouter(); return array_merge( $this->getEnabled() ? [ new LinkAction( 'settings', new AjaxModal( $router->url( $request, null, null, 'manage', null, [ 'verb' => 'settings', 'plugin' => $this->getName(), 'category' => 'generic' ] ), $this->getDisplayName() ), __('manager.plugins.settings'), null ), new LinkAction( 'status', new AjaxModal($router->url($request, null, null, 'manage', null, ['verb' => 'status', 'plugin' => $this->getName(), 'category' => 'generic']), $this->getDisplayName()), __('common.status'), null ) ] : [], parent::getActions($request, $actionArgs) ); } /** * @see Plugin::manage() */ public function getDisplayName() { return __('plugins.generic.orcidProfile.displayName'); } public function manage($args, $request) { $context = $request->getContext(); $contextId = ($context == null) ? 0 : $context->getId(); switch ($request->getUserVar('verb')) { case 'settings': $templateMgr = TemplateManager::getManager($request); $templateMgr->registerPlugin('function', 'plugin_url', [$this, 'smartyPluginUrl']); $templateMgr->assign('orcidApiUrls', [ ORCID_API_URL_PUBLIC => 'plugins.generic.orcidProfile.manager.settings.orcidProfileAPIPath.public', ORCID_API_URL_PUBLIC_SANDBOX => 'plugins.generic.orcidProfile.manager.settings.orcidProfileAPIPath.publicSandbox', ORCID_API_URL_MEMBER => 'plugins.generic.orcidProfile.manager.settings.orcidProfileAPIPath.member', ORCID_API_URL_MEMBER_SANDBOX => 'plugins.generic.orcidProfile.manager.settings.orcidProfileAPIPath.memberSandbox' ]); $countries = collect(Locale::getCountries()) ->mapWithKeys(fn (Country $country) => [$country->getAlpha2() => $country->getLocalName()]) ->sort(fn (string $a, string $b) => strcoll($a, $b)) ->toArray(); $templateMgr->assign('countries', $countries); $templateMgr->assign('logLevelOptions', [ 'ERROR' => 'plugins.generic.orcidProfile.manager.settings.logLevel.error', 'ALL' => 'plugins.generic.orcidProfile.manager.settings.logLevel.all' ]); $form = new OrcidProfileSettingsForm($this, $contextId); if ($request->getUserVar('save')) { $form->readInputData(); if ($form->validate()) { $form->execute(); return new JSONMessage(true); } } else { $form->initData(); } return new JSONMessage(true, $form->fetch($request)); case 'status': $form = new OrcidProfileStatusForm($this, $contextId); $form->initData(); return new JSONMessage(true, $form->fetch($request)); } return parent::manage($args, $request); } /** * handlePublishIssue sends all submissions for which the authors hava an ORCID and access token * to ORCID. This hook will be called on publication of a new issue. * * @param string $hookName * @param Issue[] $args Issue object that will be published * **@see * */ public function handlePublicationStatusChange($hookName, $args) { /** @var Publication $newPublication */ $newPublication = &$args[0]; $request = Application::get()->getRequest(); switch ($newPublication->getData('status')) { case PKPSubmission::STATUS_PUBLISHED: $this->sendSubmissionToOrcid($newPublication, $request); break; case PKPSubmission::STATUS_SCHEDULED: $this->sendSubmissionToOrcid($newPublication, $request); break; } } /** * sendSubmissionToOrcid posts JSON consisting of submission, journal and issue meta data * to ORCID profiles of submission authors. * * @see https://github.com/ORCID/ORCID-Source/tree/master/orcid-model/src/main/resources/record_2.1 * for documentation and examples of the ORCID JSON format. * * @param Publication $publication Publication for which the data will be sent to ORCID * * @return bool|bool[]|JSONMessage * **/ public function sendSubmissionToOrcid($publication, $request) { // Application is set to sandbox mode and will not run the features of plugin if (Config::getVar('general', 'sandbox', false)) { error_log('Application is set to sandbox mode and will not have any interaction with orcid service'); return new JSONMessage(false, __('common.sandbox')); } $context = $request->getContext(); $contextId = $this->currentContextId = $context->getId(); $publicationId = $publication->getId(); $submissionId = $publication->getData('submissionId'); if (!$this->isMemberApiEnabled($contextId)) { // Sending to ORCID only works with the member API return false; } $issueId = $publication->getData('issueId'); if (isset($issueId)) { $issue = Repo::issue()->get($issueId); } $authors = Repo::author() ->getCollector() ->filterByPublicationIds([$publicationId]) ->getMany(); // Collect valid ORCID ids and access tokens from submission contributors $authorsWithOrcid = []; foreach ($authors as $author) { if ($author->getOrcid() && $author->getData('orcidAccessToken')) { $orcidAccessExpiresOn = Carbon::parse($author->getData('orcidAccessExpiresOn')); if ($orcidAccessExpiresOn->isFuture()) { # Extract only the ORCID from the stored ORCID uri $orcid = basename(parse_url($author->getOrcid(), PHP_URL_PATH)); $authorsWithOrcid[$orcid] = $author; } else { $this->logError("Token expired on {$orcidAccessExpiresOn} for author " . $author->getId() . ', deleting orcidAccessToken!'); $this->removeOrcidAccessToken($author); } } } if (empty($authorsWithOrcid)) { $this->logInfo('No contributor with ORICD id or valid access token for submission ' . $submissionId); return false; } $orcidWork = $this->buildOrcidWork($publication, $context, $authors->toArray(), $request, $issue); $this->logInfo('Request body (without put-code): ' . json_encode($orcidWork)); $requestsSuccess = []; foreach ($authorsWithOrcid as $orcid => $author) { $uri = $this->getSetting($contextId, 'orcidProfileAPIPath') . ORCID_API_VERSION_URL . $orcid . '/' . ORCID_WORK_URL; $method = 'POST'; if ($putCode = $author->getData('orcidWorkPutCode')) { // Submission has already been sent to ORCID. Use PUT to update meta data $uri .= '/' . $putCode; $method = 'PUT'; $orcidWork['put-code'] = $putCode; } else { // Remove put-code from body because the work has not yet been sent unset($orcidWork['put-code']); } $headers = [ 'Content-type: application/vnd.orcid+json', 'Accept' => 'application/json', 'Authorization' => 'Bearer ' . $author->getData('orcidAccessToken') ]; $this->logInfo("{$method} {$uri}"); $this->logInfo('Header: ' . var_export($headers, true)); $httpClient = Application::get()->getHttpClient(); try { $response = $httpClient->request( $method, $uri, [ 'headers' => $headers, 'json' => $orcidWork, 'allow_redirects' => ['strict' => true], ] ); } catch (ClientException $exception) { $reason = $exception->getResponse()->getBody(); $this->logInfo("Publication fail: {$reason}"); return new JSONMessage(false); } $httpstatus = $response->getStatusCode(); $this->logInfo("Response status: {$httpstatus}"); $responseHeaders = $response->getHeaders(); switch ($httpstatus) { case 200: // Work updated $this->logInfo("Work updated in profile, putCode: {$putCode}"); $requestsSuccess[$orcid] = true; break; case 201: $location = $responseHeaders['Location'][0]; // Extract the ORCID work put code for updates/deletion. $putCode = intval(basename(parse_url($location, PHP_URL_PATH))); $this->logInfo("Work added to profile, putCode: {$putCode}"); $author->setData('orcidWorkPutCode', $putCode); Repo::author()->dao->update($author); $requestsSuccess[$orcid] = true; break; case 401: // invalid access token, token was revoked $error = json_decode($response->getBody(), true); if ($error['error'] === 'invalid_token') { $this->logError($error['error_description'] . ', deleting orcidAccessToken from author'); $this->removeOrcidAccessToken($author); } $requestsSuccess[$orcid] = false; break; case 403: $this->logError('Work update forbidden: ' . $response->getBody()); $requestsSuccess[$orcid] = false; break; case 404: // a work has been deleted from a ORCID record. putCode is no longer valid. if ($method === 'PUT') { $this->logError('Work deleted from ORCID record, deleting putCode form author'); $author->setData('orcidWorkPutCode', null); Repo::author()->dao->update($author); $requestsSuccess[$orcid] = false; } else { $this->logError("Unexpected status {$httpstatus} response, body: " . $response->getBody()); $requestsSuccess[$orcid] = false; } break; case 409: $this->logError('Work already added to profile, response body: ' . $response->getBody()); $requestsSuccess[$orcid] = false; break; default: $this->logError("Unexpected status {$httpstatus} response, body: " . $response->getBody()); $requestsSuccess[$orcid] = false; } } return array_product($requestsSuccess) ? true : $requestsSuccess; } /** * Build an associative array with submission meta data, which can be encoded to a valid ORCID work JSON structure. * * @see https://github.com/ORCID/ORCID-Source/blob/master/orcid-model/src/main/resources/record_2.1/samples/write_sample/bulk-work-2.1.json * Example of valid ORCID JSON for adding works to an ORCID record. * * @param Publication $publication extract data from this Article * @param Journal $context Context object the Submission is part of * @param Author[] $authors Array of Author objects, the contributors of the publication * @param Issue $issue Issue the Article is part of * @param Request $request the current request * * @return array an associative array with article meta data corresponding to ORCID work JSON structure */ public function buildOrcidWork($publication, $context, $authors, $request, $issue = null) { $submission = Repo::submission()->get($publication->getData('submissionId')); $applicationName = Application::get()->getName(); $bibtexCitation = ''; $publicationLocale = ($publication->getData('locale')) ? $publication->getData('locale') : 'en'; $supportedSubmissionLocales = $context->getSupportedSubmissionLocales(); $publicationUrl = $request->getDispatcher()->url($request, Application::ROUTE_PAGE, null, 'article', 'view', $submission->getId()); $orcidWork = [ 'title' => [ 'title' => [ 'value' => trim(strip_tags($publication->getLocalizedTitle($publicationLocale))) ?? '' ], 'subtitle' => [ 'value' => trim(strip_tags($publication->getLocalizedData('subtitle', $publicationLocale))) ?? '' ] ], 'journal-title' => [ 'value' => $context->getName($publicationLocale) ?? '' ], 'short-description' => trim(strip_tags($publication->getLocalizedData('abstract', $publicationLocale))) ?? '', 'external-ids' => [ 'external-id' => $this->buildOrcidExternalIds($submission, $publication, $context, $issue, $publicationUrl) ], 'publication-date' => $this->buildOrcidPublicationDate($publication, $issue), 'url' => $publicationUrl, 'language-code' => substr($publicationLocale, 0, 2), 'contributors' => [ 'contributor' => $this->buildOrcidContributors($authors, $context, $publication) ] ]; if ($applicationName == 'ojs2') { PluginRegistry::loadCategory('generic'); $citationPlugin = PluginRegistry::getPlugin('generic', 'citationstylelanguageplugin'); /** @var CitationStyleLanguagePlugin $citationPlugin */ $bibtexCitation = trim(strip_tags($citationPlugin->getCitation($request, $submission, 'bibtex', $issue, $publication))); $orcidWork['citation'] = [ 'citation-type' => 'bibtex', 'citation-value' => $bibtexCitation, ]; $orcidWork['type'] = 'journal-article'; } elseif ($applicationName == 'ops') { $orcidWork['type'] = 'preprint'; } $translatedTitleAvailable = false; foreach ($supportedSubmissionLocales as $defaultLanguage) { if ($defaultLanguage !== $publicationLocale) { $iso2LanguageCode = substr($defaultLanguage, 0, 2); $defaultTitle = $publication->getLocalizedData($iso2LanguageCode); if (strlen($defaultTitle) > 0 && !$translatedTitleAvailable) { $orcidWork['title']['translated-title'] = ['value' => $defaultTitle, 'language-code' => $iso2LanguageCode]; $translatedTitleAvailable = true; } } } return $orcidWork; } /** * Build the external identifiers ORCID JSON structure from article, journal and issue meta data. * * @see https://pub.orcid.org/v2.0/identifiers Table of valid ORCID identifier types. * * @param Submission $submission The Article object for which the external identifiers should be build. * @param Publication $publication The Article object for which the external identifiers should be build. * @param Journal $context Context the Submission is part of. * @param Issue $issue The Issue object the Article object belongs to. * * @return array An associative array corresponding to ORCID external-id JSON. */ private function buildOrcidExternalIds($submission, $publication, $context, $issue, $articleUrl) { $contextId = $context->getId(); $externalIds = []; $pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $contextId); // Add doi, urn, etc. for article $articleHasStoredPubId = false; if (is_array($pubIdPlugins) || $context->areDoisEnabled()) { // Handle non-DOI pubIds if (is_array($pubIdPlugins)) { foreach ($pubIdPlugins as $plugin) { if (!$plugin->getEnabled()) { continue; } $pubIdType = $plugin->getPubIdType(); # Add article ids $pubId = $publication->getStoredPubId($pubIdType); if ($pubId) { $externalIds[] = [ 'external-id-type' => self::PUBID_TO_ORCID_EXT_ID[$pubIdType], 'external-id-value' => $pubId, 'external-id-url' => [ 'value' => $plugin->getResolvingURL($contextId, $pubId) ], 'external-id-relationship' => 'self' ]; $articleHasStoredPubId = true; } # Add issue ids if they exist $pubId = $issue->getStoredPubId($pubIdType); if ($pubId) { $externalIds[] = [ 'external-id-type' => self::PUBID_TO_ORCID_EXT_ID[$pubIdType], 'external-id-value' => $pubId, 'external-id-url' => [ 'value' => $plugin->getResolvingURL($contextId, $pubId) ], 'external-id-relationship' => 'part-of' ]; } } // Handle DOIs if ($context->areDoisEnabled()) { # Add article ids $doiObject = $publication->getData('doiObject'); if ($doiObject) { $externalIds[] = [ 'external-id-type' => self::PUBID_TO_ORCID_EXT_ID['doi'], 'external-id-value' => $doiObject->getData('doi'), 'external-id-url' => [ 'value' => $doiObject->getResolvingUrl() ], 'external-id-relationship' => 'self' ]; $articleHasStoredPubId = true; } } # Add issue ids if they exist if ($issue) { $doiObject = $issue->getData('doiObject'); if ($doiObject) { $externalIds[] = [ 'external-id-type' => self::PUBID_TO_ORCID_EXT_ID['doi'], 'external-id-value' => $doiObject->getData('doi'), 'external-id-url' => [ 'value' => $doiObject->getResolvingUrl() ], 'external-id-relationship' => 'part-of' ]; } } } } else { error_log('OrcidProfilePlugin::buildOrcidExternalIds: No pubId plugins could be loaded'); } if (!$articleHasStoredPubId) { // No pubidplugins available or article does not have any stored pubid // Use URL as an external-id $externalIds[] = [ 'external-id-type' => 'uri', 'external-id-value' => $articleUrl, 'external-id-relationship' => 'self' ]; } // Add journal online ISSN // TODO What about print ISSN? if ($context->getData('onlineIssn')) { $externalIds[] = [ 'external-id-type' => 'issn', 'external-id-value' => $context->getData('onlineIssn'), 'external-id-relationship' => 'part-of' ]; } return $externalIds; } /** * Parse issue year and publication date and use the older on of the two as * the publication date of the ORCID work. * * @param null|mixed $issue * * @return array Associative array with year, month and day or only year */ private function buildOrcidPublicationDate($publication, $issue = null) { $publicationPublishDate = Carbon::parse($publication->getData('datePublished')); return [ 'year' => ['value' => $publicationPublishDate->format('Y')], 'month' => ['value' => $publicationPublishDate->format('m')], 'day' => ['value' => $publicationPublishDate->format('d')] ]; } /** * Build associative array fitting for ORCID contributor mentions in an * ORCID work from the supplied Authors array. * * @param Author[] $authors Array of Author objects * * @return array[] Array of associative arrays, * one for each contributor */ private function buildOrcidContributors($authors, $context, $publication) { $contributors = []; $first = true; foreach ($authors as $author) { // TODO Check if e-mail address should be added $fullName = $author->getLocalizedGivenName() . ' ' . $author->getLocalizedFamilyName(); if (strlen($fullName) == 0) { $this->logError('Contributor Name not defined' . $author->getAllData()); } $contributor = [ 'credit-name' => $fullName, 'contributor-attributes' => [ 'contributor-sequence' => $first ? 'first' : 'additional' ] ]; $userGroup = $author->getUserGroup(); $role = self::USER_GROUP_TO_ORCID_ROLE[$userGroup->getName('en')]; if ($role) { $contributor['contributor-attributes']['contributor-role'] = $role; } if ($author->getOrcid()) { $orcid = basename(parse_url($author->getOrcid(), PHP_URL_PATH)); if ($author->getData('orcidSandbox')) { $uri = ORCID_URL_SANDBOX . $orcid; $host = 'sandbox.orcid.org'; } else { $uri = $author->getOrcid(); $host = 'orcid.org'; } $contributor['contributor-orcid'] = [ 'uri' => $uri, 'path' => $orcid, 'host' => $host ]; } $first = false; $contributors[] = $contributor; } return $contributors; } /** * handleEditorAction handles promoting a submission to copyediting. * * @param string $hookName Name the hook was registered with * @param array $args Hook arguments, &$submission, &$editorDecision, &$result, &$recommendation. * * @see EditorAction::recordDecision() The function calling the hook. */ public function handleEditorAction($hookName, $args) { $submission = $args[0]; /** @var Submission $submission */ $decision = $args[1]; if ($decision['decision'] == Decision::ACCEPT) { $publication = $submission->getCurrentPublication(); if (isset($publication)) { $authors = Repo::author()->getCollector() ->filterByPublicationIds([$submission->getCurrentPublication()->getId()]) ->getMany(); foreach ($authors as $author) { $orcidAccessExpiresOn = Carbon::parse($author->getData('orcidAccessExpiresOn')); if ($author->getData('orcidAccessToken') == null || $orcidAccessExpiresOn->isPast()) { $this->sendAuthorMail($author, true); } } } } } /** * Set the current id of the context (atm only considered for logging settings). * * @param int $contextId the Id of the currently active context (journal) */ public function setCurrentContextId($contextId) { $this->currentContextId = $contextId; } /** * Add mailable to the list of mailables in the application */ public function addMailable(string $hookName, array $args): void { foreach ([OrcidCollectAuthorId::class, OrcidRequestAuthorAuthorization::class] as $mailable) { $args[0]->push($mailable); } } /** * @copydoc Plugin::updateSchema() */ public function updateSchema($hookName, $args) { $installer = $args[0]; $result = &$args[1]; $migration = new OrcidProfileEmailDataMigration($installer, $this); try { $migration->up(); } catch (Exception $e) { $installer->setError(Installer::INSTALLER_ERROR_DB, __('installer.installMigrationError', ['class' => get_class($migration), 'message' => $e->getMessage()])); $result = false; } } /** * Pre-publication checks * * @return false */ public function validate($hookName, $args) { $errors = & $args[0]; $publication = $args[1]; $orcidIds = []; foreach ($publication->getData('authors') as $author) { $authorOrcid = $author->getData('orcid'); if ($authorOrcid and in_array($authorOrcid, $orcidIds)) { $errors['hasDuplicateOrcids'] = __('plugins.generic.orcidProfile.verify.duplicateOrcidAuthor'); } elseif ($authorOrcid && !$author->getData('orcidAccessToken')) { $errors['hasUnauthenticatedOrcid'] = __('plugins.generic.orcidProfile.verify.hasUnauthenticatedOrcid'); } else { $orcidIds[] = $authorOrcid; } } return false; } }