From 6040b1aa03cf47cd6d9782178e1e09968a1eaf7b Mon Sep 17 00:00:00 2001 From: Erik Hanson Date: Thu, 11 Apr 2024 15:31:01 -0700 Subject: [PATCH] WIP: Code review fixes --- api/v1/orcid/OrcidController.php | 50 ----- api/v1/orcid/index.php | 3 - .../forms/context/OrcidSettingsForm.php | 22 ++- ...Migration.php => I9771_OrcidMigration.php} | 14 +- classes/orcid/OrcidManager.php | 111 +++++++++-- classes/orcid/OrcidReview.php | 45 +++-- classes/orcid/OrcidWork.php | 182 +++++++++--------- classes/orcid/actions/AuthorizeUserData.php | 179 +++++++++++------ classes/orcid/actions/SendAuthorMail.php | 12 ++ .../orcid/actions/VerifyAuthorWithOrcid.php | 12 ++ dbscripts/xml/upgrade.xml | 1 + jobs/orcid/DepositOrcidSubmission.php | 14 ++ jobs/orcid/PublishReviewerWorkToOrcid.php | 14 ++ pages/orcid/OrcidHandler.php | 14 ++ pages/orcid/index.php | 17 ++ schemas/context.json | 6 +- templates/frontend/pages/orcidAbout.tpl | 6 +- templates/frontend/pages/orcidVerify.tpl | 6 +- 18 files changed, 449 insertions(+), 259 deletions(-) delete mode 100644 api/v1/orcid/OrcidController.php delete mode 100644 api/v1/orcid/index.php rename classes/migration/upgrade/v3_5_0/{OrcidMigration.php => I9771_OrcidMigration.php} (81%) diff --git a/api/v1/orcid/OrcidController.php b/api/v1/orcid/OrcidController.php deleted file mode 100644 index 4cf3e873ec2..00000000000 --- a/api/v1/orcid/OrcidController.php +++ /dev/null @@ -1,50 +0,0 @@ -authorizeOrcid(...)) - ->name('orcid.authorize'); - Route::post('verify', $this->verify(...)) - ->name('orcid.verify'); - } - - public function authorizeOrcid(Request $illuminateRequest): JsonResponse - { - return response()->json([], Response::HTTP_OK); - } - - public function verify(Request $illuminateRequest): JsonResponse - { - return response()->json([], Response::HTTP_OK); - } -} diff --git a/api/v1/orcid/index.php b/api/v1/orcid/index.php deleted file mode 100644 index 08f607485ad..00000000000 --- a/api/v1/orcid/index.php +++ /dev/null @@ -1,3 +0,0 @@ - __('orcidProfile.manager.settings.description'), ])); - // ORCID API settings can be configured globally via config file or from this settings form + // ORCID API settings can be configured globally via the site settings form or from this settings form if (OrcidManager::isGloballyConfigured()) { $site = Application::get()->getRequest()->getSite(); @@ -129,10 +143,10 @@ public function __construct(string $action, array $locales, \PKP\context\Context 'label' => __('orcidProfile.manager.settings.logSectionTitle'), 'description' => __('orcidProfile.manager.settings.logLevel.help'), 'options' => [ - ['value' => 'ERROR', 'label' => __('orcidProfile.manager.settings.logLevel.error')], - ['value' => 'ALL', 'label' => __('orcidProfile.manager.settings.logLevel.all')], + ['value' => OrcidManager::LOG_LEVEL_ERROR, 'label' => __('orcidProfile.manager.settings.logLevel.error')], + ['value' => OrcidManager::LOG_LEVEL_INFO, 'label' => __('orcidProfile.manager.settings.logLevel.all')], ], - 'value' => $context->getData(OrcidManager::LOG_LEVEL) ?? 'ERROR', + 'value' => $context->getData(OrcidManager::LOG_LEVEL) ?? OrcidManager::LOG_LEVEL_ERROR, ])); } diff --git a/classes/migration/upgrade/v3_5_0/OrcidMigration.php b/classes/migration/upgrade/v3_5_0/I9771_OrcidMigration.php similarity index 81% rename from classes/migration/upgrade/v3_5_0/OrcidMigration.php rename to classes/migration/upgrade/v3_5_0/I9771_OrcidMigration.php index b068fbd8cdd..50340b07bb6 100644 --- a/classes/migration/upgrade/v3_5_0/OrcidMigration.php +++ b/classes/migration/upgrade/v3_5_0/I9771_OrcidMigration.php @@ -1,5 +1,17 @@ getData(self::ENABLED); } + /** + * Gets the main ORCID URL, either production or sandbox. + * + * Will first check if globally configured and prioritize site-level settings over context-level setting. + */ public static function getOrcidUrl(?Context $context = null): string { if (self::isGloballyConfigured()) { @@ -87,15 +109,20 @@ public static function getOrcidUrl(?Context $context = null): string return in_array($apiType, [self::API_PUBLIC_PRODUCTION, self::API_MEMBER_PRODUCTION]) ? self::ORCID_URL : self::ORCID_URL_SANDBOX; } + /** + * Gets the ORCID API URL, one of sandbox/production, member/public API URLs. + * + * Will first check if globally configured and prioritize site-level settings over context-level setting. + */ public static function getApiPath(?Context $context = null): string { if (self::isGloballyConfigured()) { - $apiType = Application::get()->getRequest()->getSite()->getData(OrcidManager::API_TYPE); + $apiType = Application::get()->getRequest()->getSite()->getData(self::API_TYPE); } else { if ($context === null) { $context = Application::get()->getRequest()->getContext(); } - $apiType = $context->getData(OrcidManager::API_TYPE); + $apiType = $context->getData(self::API_TYPE); } return match ($apiType) { @@ -106,22 +133,27 @@ public static function getApiPath(?Context $context = null): string }; } + /** + * Returns whether the ORCID integration is set to use the sandbox domain. + * + * Will first check if globally configured and prioritize site-level settings over context-level setting. + */ public static function isSandbox(?Context $context = null): bool { if (self::isGloballyConfigured()) { - $apiType = Application::get()->getRequest()->getSite()->getData(OrcidManager::API_TYPE); + $apiType = Application::get()->getRequest()->getSite()->getData(self::API_TYPE); } else { if ($context === null) { $context = Application::get()->getRequest()->getContext(); } - $apiType = $context->getData(OrcidManager::API_TYPE); + $apiType = $context->getData(self::API_TYPE); } return in_array($apiType, [self::API_PUBLIC_SANDBOX, self::API_MEMBER_SANDBOX]); } /** - * TODO: update as needed for new API + * Constructs an ORCID OAuth URL with correct scope/API based on configured settings * * @param string $handlerMethod Previously: containting a valid method of the OrcidProfileHandler * @param array $redirectParams Additional request parameters for the redirect URL @@ -138,7 +170,6 @@ public static function buildOAuthUrl(string $handlerMethod, array $redirectParam $scope = self::isMemberApiEnabled() ? self::ORCID_API_SCOPE_MEMBER : self::ORCID_API_SCOPE_PUBLIC; - // TODO: This is the previous URL. Will be an API URL in new iteration // 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( @@ -160,6 +191,11 @@ public static function buildOAuthUrl(string $handlerMethod, array $redirectParam ); } + /** + * Gets the configured city for use with review contributions. + * + * Will first check if globally configured and prioritize site-level settings over context-level setting. + */ public static function getCity(?Context $context = null): string { if ($context === null) { @@ -169,14 +205,25 @@ public static function getCity(?Context $context = null): string return $context->getData(self::CITY) ?? ''; } + /** + * Gets the configured country for use with review contributions. + * + * Will first check if globally configured and prioritize site-level settings over context-level setting. + */ public static function getCountry(?Context $context = null): string { if ($context === null) { $context = Application::get()->getRequest()->getContext(); } - return $context->getData(OrcidManager::COUNTRY) ?? ''; + return $context->getData(self::COUNTRY) ?? ''; } + + /** + * Returns true of member API (as opposed to public API) is in use. + * + * Will first check if globally configured and prioritize site-level settings over context-level setting. + */ public static function isMemberApiEnabled(?Context $context = null): bool { if (self::isGloballyConfigured()) { @@ -185,7 +232,7 @@ public static function isMemberApiEnabled(?Context $context = null): bool if ($context === null) { $context = Application::get()->getRequest()->getContext(); } - $apiType = $context->getData(OrcidManager::API_TYPE); + $apiType = $context->getData(self::API_TYPE); } if (in_array($apiType, [self::API_MEMBER_PRODUCTION, self::API_MEMBER_SANDBOX])) { @@ -195,29 +242,44 @@ public static function isMemberApiEnabled(?Context $context = null): bool } } + /** + * Gets the currently configured log level. Can only bet set at the context-level, not the site-level. + */ public static function getLogLevel(?Context $context = null): string { if ($context === null) { $context = Application::get()->getRequest()->getContext(); } - return $context->getData(OrcidManager::LOG_LEVEL) ?? 'ERROR'; + return $context->getData(self::LOG_LEVEL) ?? self::LOG_LEVEL_ERROR; } + + /** + * Checks whether option to email authors for verification/authorization has been configured (context-level only). + */ public static function shouldSendMailToAuthors(?Context $context = null): bool { if ($context === null) { $context = Application::get()->getRequest()->getContext(); } - return $context->getData(OrcidManager::SEND_MAIL_TO_AUTHORS_ON_PUBLICATION) ?? false; + return $context->getData(self::SEND_MAIL_TO_AUTHORS_ON_PUBLICATION) ?? false; } + /** + * Helper method that gets OAuth endpoint for configured ORCID URL (production or sandbox) + */ public static function getOauthPath(): string { return self::getOrcidUrl() . 'oauth/'; } + /** + * Gets the configured client ID. Used to connect to the ORCID API. + * + * Will first check if globally configured and prioritize site-level settings over context-level setting. + */ public static function getClientId(?Context $context = null): string { if (self::isGloballyConfigured()) { @@ -231,6 +293,11 @@ public static function getClientId(?Context $context = null): string } } + /** + * Gets the configured client secret. Used to connect to the ORCID API. + * + * Will first check if globally configured and prioritize site-level settings over context-level setting. + */ public static function getClientSecret(?Context $context = null): string { if (self::isGloballyConfigured()) { @@ -264,21 +331,31 @@ public static function removeOrcidAccessToken(Author $author, bool $updateAuthor } } + /** + * Write out log message at the INFO level. + */ public static function logInfo(string $message): void { - if (self::getLogLevel() !== 'INFO') { + if (self::getLogLevel() !== self::LOG_LEVEL_INFO) { return; } - self::writeLog($message, 'INFO'); + self::writeLog($message, self::LOG_LEVEL_INFO); } + + /** + * Write out log message at the ERROR level. + */ public static function logError(string $message): void { - if (self::getLogLevel() !== 'ERROR') { + if (self::getLogLevel() !== self::LOG_LEVEL_ERROR) { return; } - self::writeLog($message, 'ERROR'); + self::writeLog($message, self::LOG_LEVEL_ERROR); } + /** + * Helper method to write log message out to the configured log file. + */ private static function writeLog(string $message, string $level): void { $fineStamp = date('Y-m-d H:i:s') . substr(microtime(), 1, 4); diff --git a/classes/orcid/OrcidReview.php b/classes/orcid/OrcidReview.php index cf0028c5f8b..7f57ac0685b 100644 --- a/classes/orcid/OrcidReview.php +++ b/classes/orcid/OrcidReview.php @@ -1,12 +1,25 @@ submission->getId(), ); - $publicationLocale = ($this->submission->getData('locale')) ? $this->submission->getData('locale') : 'en'; - // TODO: Check why it shouldn't be removed - $pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $this->context->getId()); // DO not remove - $supportedSubmissionLocales = $this->context->getSupportedSubmissionLocales(); + $submissionLocale = $this->submission->getData('locale'); + $currentPublication = $this->submission->getCurrentPublication(); if (!empty($this->review->getData('dateCompleted')) && $this->context->getData('onlineIssn')) { $reviewCompletionDate = Carbon::parse($this->review->getData('dateCompleted')); @@ -84,14 +95,14 @@ private function buildOrcidReview(): array 'title' => ['value' => $this->submission->getCurrentPublication()->getLocalizedData('title') ?? ''] ]; - - if (!empty($this->submission->getData('pub-id::doi'))) { + if (!empty($currentPublication->getDoi())) { + /** @var Doi $doiObject */ + $doiObject = $currentPublication->getData('doiObject'); $externalIds = [ - 'external-id-type' => 'doi', - 'external-id-value' => $this->submission->getData('pub-id::doi'), + 'external-id-value' => $currentPublication->getDoi(), 'external-id-url' => [ - 'value' => 'https://doi.org/' . $this->submission->getData('pub-id::doi') + 'value' => $doiObject->getResolvingUrl(), ], 'external-id-relationship' => 'self' @@ -100,17 +111,13 @@ private function buildOrcidReview(): array } } - $translatedTitleAvailable = false; - foreach ($supportedSubmissionLocales as $defaultLanguage) { - if ($defaultLanguage !== $publicationLocale) { - $iso2LanguageCode = substr($defaultLanguage, 0, 2); - $defaultTitle = $this->submission->getLocalizedData($iso2LanguageCode); - if (strlen($defaultTitle) > 0 && !$translatedTitleAvailable) { - $orcidReview['subject-name']['translated-title'] = ['value' => $defaultTitle, 'language-code' => $iso2LanguageCode]; - $translatedTitleAvailable = true; - } + $allTitles = $currentPublication->getData('title'); + foreach ($allTitles as $locale => $title) { + if ($locale !== $submissionLocale) { + $orcidReview['subject-name']['translated-title'] = ['value' => $title, 'language-code' => LocaleConversion::getIso1FromLocale($locale)]; } } + return $orcidReview; } else { // TODO: Check how this should be handled. diff --git a/classes/orcid/OrcidWork.php b/classes/orcid/OrcidWork.php index 518ffec4b49..dc2a080f797 100644 --- a/classes/orcid/OrcidWork.php +++ b/classes/orcid/OrcidWork.php @@ -1,5 +1,17 @@ getName(); $bibtexCitation = ''; - $publicationLocale = ($this->publication->getData('locale')) ? $this->publication->getData('locale') : 'en'; - $supportedSubmissionLocales = $this->context->getSupportedSubmissionLocales(); - + $publicationLocale = $this->publication->getData('locale'); $publicationUrl = Application::get()->getDispatcher()->url( Application::get()->getRequest(), @@ -65,7 +76,7 @@ private function build(): array ] ], 'journal-title' => [ - 'value' => $this->context->getName($publicationLocale) ?? '' + 'value' => $this->context->getName($publicationLocale) ?? $this->context->getName($this->context->getPrimaryLocale()), ], 'short-description' => trim(strip_tags($this->publication->getLocalizedData('abstract', $publicationLocale))) ?? '', @@ -74,7 +85,7 @@ private function build(): array ], 'publication-date' => $this->buildOrcidPublicationDate($this->publication, $this->issue), 'url' => $publicationUrl, - 'language-code' => substr($publicationLocale, 0, 2), + 'language-code' => LocaleConversion::getIso1FromLocale($publicationLocale), 'contributors' => [ 'contributor' => $this->buildOrcidContributors($this->authors, $this->context, $this->publication) ] @@ -94,15 +105,9 @@ private function build(): array $orcidWork['type'] = 'preprint'; } - $translatedTitleAvailable = false; - foreach ($supportedSubmissionLocales as $defaultLanguage) { - if ($defaultLanguage !== $publicationLocale) { - $iso2LanguageCode = substr($defaultLanguage, 0, 2); - $defaultTitle = $this->publication->getLocalizedData($iso2LanguageCode); - if (strlen($defaultTitle) > 0 && !$translatedTitleAvailable) { - $orcidWork['title']['translated-title'] = ['value' => $defaultTitle, 'language-code' => $iso2LanguageCode]; - $translatedTitleAvailable = true; - } + foreach ($this->publication->getData('title') as $locale => $title) { + if ($locale !== $publicationLocale) { + $orcidWork['title']['translated-title'] = ['value' => $title, 'language-code' => LocaleConversion::getIso1FromLocale($locale)]; } } @@ -121,7 +126,7 @@ private function build(): array * * @return array An associative array corresponding to ORCID external-id JSON. */ - private function buildOrcidExternalIds($submission, $publication, $context, $issue, $articleUrl) + private function buildOrcidExternalIds(Submission $submission, Publication $publication, Context $context, Issue $issue, string $articleUrl): array { $contextId = $context->getId(); @@ -129,82 +134,77 @@ private function buildOrcidExternalIds($submission, $publication, $context, $iss $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 non-DOI pubIds + if (!empty($pubIdPlugins)) { + foreach ($pubIdPlugins as $plugin) { + if (!$plugin->getEnabled()) { + continue; } - // 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; - } + $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 - 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' - ]; - } + $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' + ]; } } - } else { - error_log('OrcidProfilePlugin::buildOrcidExternalIds: No pubId plugins could be loaded'); + } + + // Handle DOIs + if ($context->areDoisEnabled()) { + # Add article ids + $publicationDoiObject = $publication->getData('doiObject'); + + if ($publicationDoiObject) { + $externalIds[] = [ + 'external-id-type' => self::PUBID_TO_ORCID_EXT_ID['doi'], + 'external-id-value' => $publicationDoiObject->getData('doi'), + 'external-id-url' => [ + 'value' => $publicationDoiObject->getResolvingUrl() + ], + 'external-id-relationship' => 'self' + ]; + + $articleHasStoredPubId = true; + } + + // Add issue ids if they exist + $issueDoiObject = $issue->getData('doiObject'); + if ($issueDoiObject) { + $externalIds[] = [ + 'external-id-type' => self::PUBID_TO_ORCID_EXT_ID['doi'], + 'external-id-value' => $issueDoiObject->getData('doi'), + 'external-id-url' => [ + 'value' => $issueDoiObject->getResolvingUrl() + ], + 'external-id-relationship' => 'part-of' + ]; + } } if (!$articleHasStoredPubId) { @@ -231,14 +231,14 @@ private function buildOrcidExternalIds($submission, $publication, $context, $iss } /** - * Parse issue year and publication date and use the older on of the two as + * Parse issue year and publication date and use the older one 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) + private function buildOrcidPublicationDate(Publication $publication, ?Issue $issue = null): array { $publicationPublishDate = Carbon::parse($publication->getData('datePublished')); @@ -258,20 +258,14 @@ private function buildOrcidPublicationDate($publication, $issue = null) * @return array[] Array of associative arrays, * one for each contributor */ - private function buildOrcidContributors($authors, $context, $publication) + private function buildOrcidContributors(array $authors, Context $context, Publication $publication): array { $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) { - OrcidManager::logError('Contributor Name not defined' . $author->getAllData()); - } $contributor = [ - 'credit-name' => $fullName, + 'credit-name' => $author->getFullName(), 'contributor-attributes' => [ 'contributor-sequence' => $first ? 'first' : 'additional' ] diff --git a/classes/orcid/actions/AuthorizeUserData.php b/classes/orcid/actions/AuthorizeUserData.php index e334eeaa80f..647adf49725 100644 --- a/classes/orcid/actions/AuthorizeUserData.php +++ b/classes/orcid/actions/AuthorizeUserData.php @@ -1,5 +1,17 @@ request->getContext(); $httpClient = Application::get()->getHttpClient(); + $errorMessages = []; + // API Request: GetOAuth token and ORCID - $tokenResponse = $httpClient->request( - 'POST', - $url = OrcidManager::getApiPath($context) . OrcidManager::OAUTH_TOKEN_URL, - [ - 'form_params' => [ - 'code' => $this->request->getUserVar('code'), - 'grant_type' => 'authorization_code', - 'client_id' => OrcidManager::getClientId($context), - 'client_secret' => OrcidManager::getClientSecret($context), - ], - 'headers' => ['Accept' => 'application/json'], - 'allow_redirects' => ['strict' => true], - ] - ); - - if ($tokenResponse->getStatusCode() !== 200) { - error_log('ORCID token URL error: ' . $tokenResponse->getStatusCode() . ' (' . __FILE__ . ' line ' . __LINE__ . ', URL ' . $url . ')'); - $orcid = null; - $orcidUri = null; - $accessToken = null; - $tokenData = []; - } else { - $tokenData = json_decode($tokenResponse->getBody(), true); - $orcid = $tokenData['orcid']; - $orcidUri = (OrcidManager::isSandbox($context) ? OrcidManager::ORCID_URL_SANDBOX : OrcidManager::ORCID_URL) . $orcid; - $accessToken = $tokenData['access_token']; + try { + $tokenResponse = $httpClient->request( + 'POST', + $url = OrcidManager::getApiPath($context) . OrcidManager::OAUTH_TOKEN_URL, + [ + 'form_params' => [ + 'code' => $this->request->getUserVar('code'), + 'grant_type' => 'authorization_code', + 'client_id' => OrcidManager::getClientId($context), + 'client_secret' => OrcidManager::getClientSecret($context), + ], + 'headers' => ['Accept' => 'application/json'], + ] + ); + + if ($tokenResponse->getStatusCode() !== 200) { + error_log('ORCID token URL error: ' . $tokenResponse->getStatusCode() . ' (' . __FILE__ . ' line ' . __LINE__ . ', URL ' . $url . ')'); + $orcid = null; + $orcidUri = null; + $accessToken = null; + $tokenData = []; + $errorMessages[] = 'ORCID authorization failed: ORCID token URL error: ' . $tokenResponse->getStatusCode(); + } else { + $tokenData = json_decode($tokenResponse->getBody(), true); + $orcid = $tokenData['orcid']; + $orcidUri = (OrcidManager::isSandbox($context) ? OrcidManager::ORCID_URL_SANDBOX : OrcidManager::ORCID_URL) . $orcid; + $accessToken = $tokenData['access_token']; + } + } catch (ClientException $exception) { + $reason = $exception->getResponse()->getBody(); + $message = "AuthorizeUserData::execute failed: {$reason}"; + OrcidManager::logInfo($message); + $errorMessages[] = 'ORCID authorization failed: ' . $message; } switch ($this->request->getUserVar('targetOp')) { case 'register': // API request: get user profile (for names; email; etc) - $profileResponse = $httpClient->request( - 'GET', - $url = OrcidManager::getApiPath($context) . ORCID_API_VERSION_URL . urlencode($orcid) . '/' . ORCID_PROFILE_URL, - [ - 'headers' => [ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer ' . $accessToken, - ], - ] - ); - if ($profileResponse->getStatusCode() != 200) { - error_log('ORCID profile URL error: ' . $profileResponse->getStatusCode() . ' (' . __FILE__ . ' line ' . __LINE__ . ', URL ' . $url . ')'); + try { + $profileResponse = $httpClient->request( + 'GET', + $url = OrcidManager::getApiPath($context) . ORCID_API_VERSION_URL . urlencode($orcid) . '/' . ORCID_PROFILE_URL, + [ + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $accessToken, + ], + ] + ); + if ($profileResponse->getStatusCode() != 200) { + error_log('ORCID profile URL error: ' . $profileResponse->getStatusCode() . ' (' . __FILE__ . ' line ' . __LINE__ . ', URL ' . $url . ')'); + $errorMessages[] = 'Failed to fetch ORCID profile data.'; + $profileJson = null; + } else { + $profileJson = json_decode($profileResponse->getBody(), true); + } + } catch (ClientException $exception) { + $errorMessages[] = 'Failed to fetch ORCID profile data.'; $profileJson = null; - } else { - $profileJson = json_decode($profileResponse->getBody(), true); } + // API request: get employments (for affiliation field) - $employmentsResponse = $httpClient->request( - 'GET', - $url = OrcidManager::getApiPath($context) . ORCID_API_VERSION_URL . urlencode($orcid) . '/' . ORCID_EMPLOYMENTS_URL, - [ - 'headers' => [ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer ' . $accessToken, - ], - ] - ); - if ($employmentsResponse->getStatusCode() != 200) { - error_log('ORCID deployments URL error: ' . $employmentsResponse->getStatusCode() . ' (' . __FILE__ . ' line ' . __LINE__ . ', URL ' . $url . ')'); + try { + $employmentsResponse = $httpClient->request( + 'GET', + $url = OrcidManager::getApiPath($context) . ORCID_API_VERSION_URL . urlencode($orcid) . '/' . ORCID_EMPLOYMENTS_URL, + [ + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $accessToken, + ], + ] + ); + if ($employmentsResponse->getStatusCode() != 200) { + $errorMessages[] = 'Failed to fetch ORCID employment data'; + error_log('ORCID deployments URL error: ' . $employmentsResponse->getStatusCode() . ' (' . __FILE__ . ' line ' . __LINE__ . ', URL ' . $url . ')'); + $employmentJson = null; + } else { + $employmentJson = json_decode($employmentsResponse->getBody(), true); + } + } catch (ClientException $exception) { + $errorMessages[] = 'Failed to fetch ORCID employment data'; $employmentJson = null; - } else { - $employmentJson = json_decode($employmentsResponse->getBody(), true); } - // Suppress errors for nonexistent array indexes + // Suppress errors for nonexistent array indexes TODO: don't do this echo ' - '; @@ -119,7 +161,24 @@ public function execute(): void } } - private function setOrcidData($userOrAuthor, $orcidUri, $orcidResponse) + /** + * Display frontend UI notification with contents of `$this->errorMessage` + */ + private function renderFrontendErrorNotification(array $errorMessages): string + { + $returner = ''; + + foreach ($errorMessages as $errorMessage) { + $returner .= 'opener.pkp.eventBus.$emit("notify", "' . $errorMessage . '", "warning");'; + } + + return $returner; + } + + /** + * Sets ORCID token access data on the provided user or author + */ + private function setOrcidData(Identity $userOrAuthor, string $orcidUri, array $orcidResponse): Identity { // Save the access token $orcidAccessExpiresOn = Carbon::now(); diff --git a/classes/orcid/actions/SendAuthorMail.php b/classes/orcid/actions/SendAuthorMail.php index 09196f973b3..20b877b5d45 100644 --- a/classes/orcid/actions/SendAuthorMail.php +++ b/classes/orcid/actions/SendAuthorMail.php @@ -1,5 +1,17 @@ + diff --git a/jobs/orcid/DepositOrcidSubmission.php b/jobs/orcid/DepositOrcidSubmission.php index 590abac2eaf..d1becbee676 100644 --- a/jobs/orcid/DepositOrcidSubmission.php +++ b/jobs/orcid/DepositOrcidSubmission.php @@ -1,5 +1,19 @@