JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-}JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-}JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-}JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-}JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-}JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-}JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-}JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-}JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-}JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-}JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-}JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-}JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-}JFIFICC_PROFILElcmsmntrRGB XYZ  acspMSFTsawsctrl-hand=@=@t," desc_cprt wtptrXYZ,gXYZ@bXYZTrTRCh`gTRCh`bTRCh`descuRGBtextCC0XYZ TXYZ o8XYZ bXYZ $curv*|uN  bj. C$)j.~39?FWM6Tv\dluV~,6۾ewC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?|WH?cS?Ne.r˿ޱ5\YYhFOejT7PZ[qs2c/$Ep[Gqo(Nù=QHci;OipX=Ģ8d^mQeӴm1OsL/x2];i6p!zU -/uX!=<-} .
LIBYA CYBER ARMY
Logo of a company Instagram@3g86    Server : Apache
System : Linux uta-edu.server.ly 4.18.0-513.11.1.el8_9.x86_64 #1 SMP Wed Jan 17 02:00:40 EST 2024 x86_64
User : utripoli ( 1001)
PHP Version : 7.4.33
Disable Function : NONE
Directory :  /home/utripoli/public_html/alqalam/plugins/generic/orcidProfile/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/utripoli/public_html/alqalam/plugins/generic/orcidProfile/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);
        }

        return $config_value ?: parent::getSetting($contextId, $name);
    }

    /**
     * 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)
    {
        $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,
                            ]
                        );
                    } 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;
    }

    /**
     * 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();
                $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)
    {
        $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,
                    ]
                );
            } 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;
    }
}

3g86 2022