-
Notifications
You must be signed in to change notification settings - Fork 3
feat: ORCID deposition for codecheckers + CODECHECK form in reviewer tab (#16) #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ | |
| use APP\plugins\generic\codecheck\classes\migration\CodecheckSchemaMigration; | ||
| use APP\plugins\generic\codecheck\classes\Submission\Schema; | ||
| use APP\plugins\generic\codecheck\classes\Submission\SubmissionWizardHandler; | ||
| use APP\plugins\generic\codecheck\classes\Orcid\OrcidAuthHandler; | ||
| use APP\plugins\generic\codecheck\classes\Orcid\OrcidDepositService; | ||
| use APP\plugins\generic\codecheck\classes\Log\CodecheckLogger; | ||
| use PKP\plugins\GenericPlugin; | ||
| use PKP\plugins\Hook; | ||
|
|
@@ -25,8 +27,6 @@ class CodecheckPlugin extends GenericPlugin | |
|
|
||
| public function register($category, $path, $mainContextId = null): bool | ||
| { | ||
| CodecheckLogger::debug('register() called, path=' . $path); | ||
|
|
||
| $success = parent::register($category, $path); | ||
|
|
||
| if ($success && $this->getEnabled()) { | ||
|
|
@@ -67,61 +67,74 @@ public function register($category, $path, $mainContextId = null): bool | |
| Hook::add('Template::SubmissionWizard::Section::Review', function($hookName, $params) use ($codecheckWizard) { | ||
| return $codecheckWizard->addToSubmissionWizardReviewTemplate($hookName, $params); | ||
| }); | ||
|
|
||
| // ORCID: automatically deposit when an article is published | ||
| Hook::add('Publication::publish', $this->onPublicationPublish(...)); | ||
| } | ||
|
|
||
| return $success; | ||
| } | ||
|
|
||
| /** | ||
| * Setup the `CodecheckApiHandler` | ||
| * | ||
| * @param string $hookname The name of the hook | ||
| * @param array $args The arguments passed by the hook | ||
| * | ||
| * @return void | ||
| * Triggered when an editor publishes an article. | ||
| */ | ||
| public function onPublicationPublish(string $hookName, array $args): bool | ||
| { | ||
| $publication = $args[0]; | ||
| $submission = Repo::submission()->get($publication->getData('submissionId')); | ||
|
|
||
| if (!$submission) return false; | ||
| if (!$submission->getData('codecheckOptIn')) return false; | ||
|
|
||
| $context = Application::get()->getRequest()->getContext(); | ||
| if (!$this->getSetting($context->getId(), Constants::ORCID_ENABLED)) return false; | ||
|
|
||
| try { | ||
| $depositService = new OrcidDepositService($this); | ||
| $results = $depositService->depositForSubmission($submission->getId()); | ||
| foreach ($results as $result) { | ||
| if ($result['status'] === 'success') { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please make a proposal how to include user facing messaging about the results and failures here. If that means we need a database table to capture the deposition state, then that is fine.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I already store the deposition state in the The results are surfaced to editors in the CODECHECK workflow tab via the ORCID Deposition section, which shows each codechecker's current status badge (NOT AUTHORISED / PENDING / DEPOSITED / FAILED) and displays the error message inline when a deposit fails. This is loaded via the |
||
| CodecheckLogger::info('ORCID deposited for ' . $result['orcidId'] . ' put-code=' . $result['putCode']); | ||
| } else { | ||
| CodecheckLogger::error('ORCID deposit failed for ' . $result['orcidId'] . ': ' . ($result['error'] ?? 'unknown')); | ||
| } | ||
| } | ||
| } catch (\Throwable $e) { | ||
| CodecheckLogger::error('ORCID deposit exception on publish: ' . $e->getMessage()); | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Setup the CodecheckApiHandler. | ||
| * The constructor handles the request and exits — no need to set a router handler. | ||
| */ | ||
| public function setupAPIHandler(string $hookName, array $args): void | ||
| { | ||
| $request = $args[0]; | ||
| $router = $request->getRouter(); | ||
| $router = $request->getRouter(); | ||
|
|
||
| if (!($router instanceof \PKP\core\APIRouter)) { | ||
| return; | ||
| } | ||
| if (!($router instanceof \PKP\core\APIRouter)) return; | ||
|
|
||
| if (str_contains($request->getRequestPath(), 'api/v1/codecheck')) { | ||
| CodecheckLogger::debug('Instantiating the CODECHECK APIHandler'); | ||
| $apiHandler = new CodecheckApiHandler($this, $request); | ||
| CodecheckLogger::debug('API request: ' . $request->getRequestPath()); | ||
| new CodecheckApiHandler($this, $request); | ||
| // Constructor handles the response and exits internally | ||
| } | ||
|
|
||
| if (!isset($apiHandler)) { | ||
| return; | ||
| } | ||
|
|
||
| $router->setHandler($apiHandler); | ||
| exit; | ||
| } | ||
|
|
||
| /** | ||
| * Declare the handler function to process the actual page PATH | ||
| * | ||
| * @param string $hookName The name of the invoked hook | ||
| * @param array $args Hook parameters | ||
| * | ||
| * @return bool Hook handling status | ||
| * Declare the handler function to process the actual page PATH. | ||
| */ | ||
| public function setCodecheckPageHandler($hookName, $args) | ||
| { | ||
| $request = Application::get()->getRequest(); | ||
| $templateMgr = TemplateManager::getManager($request); | ||
|
|
||
| $page = &$args[0]; | ||
| $op = &$args[1]; | ||
| $page = &$args[0]; | ||
| $op = &$args[1]; | ||
| $handler = &$args[3]; | ||
|
|
||
|
|
||
| // Construct a path to look for | ||
| $path = $page; | ||
| if ($op !== 'index') { | ||
| $path .= "/{$op}"; | ||
|
|
@@ -130,13 +143,19 @@ public function setCodecheckPageHandler($hookName, $args) | |
| $path .= '/' . implode('/', $ops); | ||
| } | ||
|
|
||
| // Check if this is a request for a static page or preview. | ||
| // ORCID OAuth routes | ||
| if ($page === 'codecheck' && $op === 'orcid') { | ||
| $subOp = $request->getRequestedArgs()[0] ?? ''; | ||
| if (in_array($subOp, ['startAuth', 'callback'], true)) { | ||
| $handler = new OrcidAuthHandler($this); | ||
| $args[1] = $subOp; | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| if ($page = 'codecheck' && $op == 'info') { | ||
| // Trick the handler into dealing with it normally | ||
| $page = 'pages'; | ||
| $op = 'view'; | ||
|
|
||
| // It is -- attach the static pages handler. | ||
| $handler = new CodecheckPageHandler($this); | ||
| return true; | ||
| } | ||
|
|
@@ -176,12 +195,36 @@ public function callbackTemplateManagerDisplay($hookName, $args): bool | |
| { | ||
| $templateMgr = $args[0]; | ||
| $request = Application::get()->getRequest(); | ||
|
|
||
| if ($request->getRequestedOp() == 'workflow') { | ||
|
|
||
| // ---------------------------------------------------------------- | ||
| // Editor workflow page | ||
|
nuest marked this conversation as resolved.
|
||
| // ---------------------------------------------------------------- | ||
| if ($request->getRequestedOp() == 'editorial' && $request->getRequestedPage() == 'dashboard') { | ||
| $context = $request->getContext(); | ||
| $contextId = $context->getId(); | ||
|
|
||
| $orcidAuthUrl = $request->getBaseUrl() . '/index.php/' . $context->getPath() . '/codecheck/orcid/startAuth'; | ||
|
|
||
| $orcidConfig = json_encode([ | ||
| 'enabled' => (bool) $this->getSetting($contextId, Constants::ORCID_ENABLED), | ||
| 'authUrl' => $orcidAuthUrl, | ||
| 'apiType' => $this->getSetting($contextId, Constants::ORCID_API_TYPE) | ||
| ?? Constants::ORCID_API_TYPE_SANDBOX, | ||
| 'apiBaseUrl' => $request->getBaseUrl() . '/index.php/' . $context->getPath(), | ||
| ]); | ||
|
|
||
| $templateMgr->addJavaScript( | ||
| 'codecheck-orcid-config', | ||
| 'window.codecheckOrcidConfig = ' . $orcidConfig . ';', | ||
| [ | ||
| 'inline' => true, | ||
| 'contexts' => ['backend'], | ||
| 'priority' => TemplateManager::STYLE_SEQUENCE_LAST | ||
| ] | ||
| ); | ||
|
|
||
| $submission = $request->getRouter()->getHandler()->getAuthorizedContextObject(ASSOC_TYPE_SUBMISSION); | ||
|
|
||
| if ($submission) { | ||
| $publication = $submission->getCurrentPublication(); | ||
| $templateMgr->setState([ | ||
| 'codecheckSubmission' => [ | ||
| 'id' => $submission->getId(), | ||
|
|
@@ -191,23 +234,57 @@ public function callbackTemplateManagerDisplay($hookName, $args): bool | |
| 'dataRepository' => $submission->getData('dataRepository'), | ||
| 'manifestFiles' => $submission->getData('manifestFiles'), | ||
| 'dataAvailabilityStatement' => $submission->getData('dataAvailabilityStatement'), | ||
| ] | ||
| ], | ||
| ]); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| // ---------------------------------------------------------------- | ||
| // Reviewer page — inject submission data + ORCID config for Vue | ||
| // ---------------------------------------------------------------- | ||
| if ($request->getRequestedPage() == 'reviewer' && $request->getRequestedOp() == 'submission') { | ||
| $requestArgs = $request->getRequestedArgs(); | ||
| $submissionId = (int) ($requestArgs[0] ?? 0); | ||
|
|
||
| if ($submissionId) { | ||
| $context = $request->getContext(); | ||
| $contextId = $context->getId(); | ||
| $submission = Repo::submission()->get($submissionId); | ||
|
|
||
| if ($submission && $submission->getData('codecheckOptIn')) { | ||
| $orcidAuthUrl = $request->getBaseUrl() . '/index.php/' . $context->getPath() . '/codecheck/orcid/startAuth'; | ||
|
|
||
| $reviewerData = json_encode([ | ||
| 'submissionId' => $submission->getId(), | ||
| 'codecheckOptIn' => true, | ||
| 'orcid' => [ | ||
| 'enabled' => (bool) $this->getSetting($contextId, Constants::ORCID_ENABLED), | ||
| 'authUrl' => $orcidAuthUrl, | ||
| 'apiType' => $this->getSetting($contextId, Constants::ORCID_API_TYPE) ?? Constants::ORCID_API_TYPE_SANDBOX, | ||
| 'apiBaseUrl' => $request->getBaseUrl() . '/index.php/' . $context->getPath(), | ||
| ], | ||
| ]); | ||
|
|
||
| $templateMgr->addJavaScript( | ||
| 'codecheck-reviewer-data', | ||
| 'window.codecheckReviewerData = ' . $reviewerData . ';', | ||
| [ | ||
| 'inline' => true, | ||
| 'contexts' => ['backend'], | ||
| 'priority' => TemplateManager::STYLE_SEQUENCE_LAST, | ||
| ] | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| public function getUrlPageRoute(string $page): string | ||
| { | ||
| $request = Application::get()->getRequest(); | ||
| return $request->getDispatcher()->url( | ||
| $request, | ||
| ROUTE_PAGE, | ||
| null, | ||
| $page | ||
| ); | ||
| return $request->getDispatcher()->url($request, ROUTE_PAGE, null, $page); | ||
| } | ||
|
|
||
| public function addOptInToSchema(string $hookName, array $args): bool | ||
|
|
@@ -235,17 +312,16 @@ public function addOptInCheckbox(string $hookName, \PKP\components\forms\FormCom | |
| $request = Application::get()->getRequest(); | ||
| $context = $request->getContext(); | ||
| $codecheckMode = $this->getSetting($context->getId(), Constants::CODECHECK_MODE); | ||
| error_log("[CODECHECK Settings] Mode: " . $codecheckMode); | ||
| $checkboxValue = false; | ||
| $checkboxValue = false; | ||
| $checkboxDisabled = false; | ||
| $codecheckDescription = __('plugins.generic.codecheck.optIn.description', [ | ||
| 'codecheckLink' => "<a href='{$this->getUrlPageRoute("codecheck")}/info' target='_blank'>" . __('plugins.generic.codecheck.displayName') . "</a>" | ||
| ]); | ||
|
|
||
| if($codecheckMode == 'opt-out') { | ||
| if ($codecheckMode == 'opt-out') { | ||
| $checkboxValue = true; | ||
| } elseif ($codecheckMode == 'mandatory') { | ||
| $checkboxValue = true; | ||
| $checkboxValue = true; | ||
| $checkboxDisabled = true; | ||
| $codecheckDescription = __('plugins.generic.codecheck.mandatory.description', [ | ||
| 'codecheckLink' => "<a href='{$this->getUrlPageRoute("codecheck")}/info' target='_blank'>" . __('plugins.generic.codecheck.displayName') . "</a>" | ||
|
|
@@ -257,12 +333,12 @@ public function addOptInCheckbox(string $hookName, \PKP\components\forms\FormCom | |
| 'type' => 'checkbox', | ||
| 'options' => [ | ||
| [ | ||
| 'value' => 1, | ||
| 'label' => $codecheckDescription, | ||
| 'value' => 1, | ||
| 'label' => $codecheckDescription, | ||
| 'disabled' => $checkboxDisabled, | ||
| ] | ||
| ], | ||
| 'value' => $checkboxValue, | ||
| 'value' => $checkboxValue, | ||
| 'groupId' => 'default' | ||
| ])); | ||
|
|
||
|
|
@@ -287,10 +363,7 @@ public function saveOptIn(string $hookName, array $params): bool | |
| public function saveWizardFieldsFromRequest(string $hookName, array $params): bool | ||
| { | ||
| $submission = $params[1]; | ||
|
|
||
| if (!$submission) { | ||
| return false; | ||
| } | ||
| if (!$submission) return false; | ||
|
|
||
| $request = Application::get()->getRequest(); | ||
|
|
||
|
|
@@ -319,9 +392,6 @@ public function saveWizardFieldsFromRequest(string $hookName, array $params): bo | |
|
|
||
| /** | ||
| * Provide a name for this plugin | ||
| * | ||
| * The name will appear in the Plugin Gallery where editors can | ||
| * install, enable and disable plugins. | ||
| */ | ||
| public function getDisplayName(): string | ||
| { | ||
|
|
@@ -330,34 +400,21 @@ public function getDisplayName(): string | |
|
|
||
| /** | ||
| * Provide a description for this plugin | ||
| * | ||
| * The description will appear in the Plugin Gallery where editors can | ||
| * install, enable and disable plugins. | ||
| */ | ||
| public function getDescription(): string | ||
| { | ||
| return __('plugins.generic.codecheck.description'); | ||
| } | ||
|
|
||
| /** | ||
| * Add a settings action to the plugin's entry in the CODECHECK plugins list. | ||
| * | ||
| * @param Request $request | ||
| * @param array $actionArgs | ||
| * Add a settings action to the plugin's entry in the plugins list. | ||
| */ | ||
| public function getActions($request, $actionArgs): array | ||
| { | ||
| $actions = new Actions($this); | ||
| return $actions->execute($request, $actionArgs, parent::getActions($request, $actionArgs)); | ||
| } | ||
|
|
||
| /** | ||
| * Load a form when the `settings` button is clicked and | ||
| * save the form when the user saves it. | ||
| * | ||
| * @param array $args | ||
| * @param Request $request | ||
| */ | ||
| public function manage($args, $request): JSONMessage | ||
| { | ||
| $manage = new Manage($this); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This ssems to require that the ORCID credentials must be provided by codecheckers before the publication.
_Is it also possible that a CODECHECK goes to the OJS backend after the article is published and then triggers the deposition?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, absolutely. The automatic deposition on publication is just a convenience, if the codechecker has not authorised before publication, or if the deposit fails, editors can still trigger it manually at any time using the "Deposit to ORCID" button in the CODECHECK workflow tab. The button remains available after publication so codecheckers can authorise and deposit retroactively.