Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
217 changes: 183 additions & 34 deletions CodecheckPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,24 @@
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;
use PKP\components\forms\FieldOptions;
use APP\facades\Repo;
use APP\plugins\generic\codecheck\api\v1\CodecheckApiHandler;
use PKP\core\JSONMessage;
use APP\plugins\generic\codecheck\classes\Constants;
use APP\plugins\generic\codecheck\controllers\page\CodecheckPageHandler;

class CodecheckPlugin extends GenericPlugin
{
private CodecheckSchemaMigration $migration;

public function register($category, $path, $mainContextId = null): bool
{
error_log('[CodecheckPlugin] register() called, path=' . $path);

$success = parent::register($category, $path);

if ($success && $this->getEnabled()) {
Expand All @@ -40,6 +45,8 @@ public function register($category, $path, $mainContextId = null): bool
Hook::add('Submission::validate', $this->saveWizardFieldsFromRequest(...));
// Add hook for Ajax API calls
Hook::add('Dispatcher::dispatch', [$this, 'setupAPIHandler']);
// Add hook for the custom CODECHECK Pages
Hook::add('LoadHandler', $this->setCodecheckPageHandler(...));
// Add hook for the Template Manager
Hook::add('TemplateManager::display', $this->callbackTemplateManagerDisplay(...));

Expand All @@ -60,32 +67,99 @@ 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;
}

/**
* 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')) {
error_log("[CODECHECK Plugin] Instanciating the CODECHECK APIHandler");
$apiHandler = new CodecheckApiHandler($request);
error_log("[CODECHECK Plugin] API request: " . $request->getRequestPath() . "\n");
CodecheckLogger::debug('API request: ' . $request->getRequestPath());
new CodecheckApiHandler($this, $request);
// Constructor handles the response and exits internally
}
}

/**
* Declare the handler function to process the actual page PATH.
*/
public function setCodecheckPageHandler($hookName, $args)
{
$request = Application::get()->getRequest();

$page = &$args[0];
$op = &$args[1];
$handler = &$args[3];

$path = $page;
if ($op !== 'index') {
$path .= "/{$op}";
}
if ($ops = $request->getRequestedArgs()) {
$path .= '/' . implode('/', $ops);
}

if (!isset($apiHandler)) {
return;
// 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;
}
}

$router->setHandler($apiHandler);
exit;
if ($page = 'codecheck' && $op == 'info') {
$page = 'pages';
$op = 'view';
$handler = new CodecheckPageHandler($this);
return true;
}
return false;
}

private function addAssets(): void
Expand Down Expand Up @@ -121,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 @@ -136,14 +234,59 @@ 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);
}

public function addOptInToSchema(string $hookName, array $args): bool
{
$schema = $args[0];
Expand All @@ -166,18 +309,27 @@ public function addOptInToSchema(string $hookName, array $args): bool
public function addOptInCheckbox(string $hookName, \PKP\components\forms\FormComponent $form): bool
{
if ($form->id === 'submitStart' || $form->id === 'submissionStart' || str_contains($form->id, 'start')) {
$request = Application::get()->getRequest();
$context = $request->getContext();
$codecheckMode = $this->getSetting($context->getId(), Constants::CODECHECK_MODE);
$checkboxValue = false;

if ($codecheckMode == 'opt-out') {
$checkboxValue = true;
}

$form->addField(new FieldOptions('codecheckOptIn', [
'label' => __('plugins.generic.codecheck.displayName'),
'type' => 'checkbox',
'options' => [
[
'value' => 1,
'label' => __('plugins.generic.codecheck.optIn.description', [
'codecheckLink' => '<a href="https://codecheck.org.uk/" target="_blank">CODECHECK</a>'
'codecheckLink' => "<a href='{$this->getUrlPageRoute("codecheck")}/info' target='_blank'>CODECHECK</a>"
])
]
],
'value' => false,
'value' => $checkboxValue,
'groupId' => 'default'
]));

Expand All @@ -202,10 +354,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 @@ -248,13 +397,6 @@ public function getActions($request, $actionArgs): array
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 All @@ -266,12 +408,19 @@ public function setEnabled($enabled, $contextId = null)
$result = parent::setEnabled($enabled, $contextId);

if ($enabled) {
$migration = new CodecheckSchemaMigration();
$migration->up();
$this->migration = new CodecheckSchemaMigration();
$this->migration->up();
}

return $result;
}

public function resetSchema(): void
{
$this->migration = new CodecheckSchemaMigration();
$this->migration->down();
$this->migration->up();
}
}

if (!PKP_STRICT_MODE) {
Expand Down
Loading
Loading