Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 134 additions & 77 deletions CodecheckPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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()) {
Expand Down Expand Up @@ -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

Copy link
Copy Markdown
Member

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?

Copy link
Copy Markdown
Member Author

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.

{
$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') {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already store the deposition state in the codecheck_orcid_tokens table, each row has a deposit_status column (pending, success, failed), a put_code for successful deposits, and an error_message for failures.

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 orcid-status API endpoint on every page load so the state is always current.

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}";
Expand All @@ -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;
}
Expand Down Expand Up @@ -176,12 +195,36 @@ public function callbackTemplateManagerDisplay($hookName, $args): bool
{
$templateMgr = $args[0];
$request = Application::get()->getRequest();

if ($request->getRequestedOp() == 'workflow') {

// ----------------------------------------------------------------
// Editor workflow page
Comment thread
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(),
Expand All @@ -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
Expand Down Expand Up @@ -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>"
Expand All @@ -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'
]));

Expand All @@ -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();

Expand Down Expand Up @@ -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
{
Expand All @@ -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);
Expand Down
Loading
Loading