<?php

/**
 * @file OrcidProfilePlugin.inc.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
 */

use GuzzleHttp\Exception\ClientException;

import('lib.pkp.classes.plugins.GenericPlugin');
import('plugins.generic.orcidProfile.classes.OrcidValidator');

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 {

	const PUBID_TO_ORCID_EXT_ID = ["doi" => "doi", "other::urn" => "urn"];
	const USER_GROUP_TO_ORCID_ROLE = ["Author" => "AUTHOR", "Translator" => "CHAIR_OR_TRANSLATOR", "Journal manager" => "AUTHOR"];

	private $submissionIdToBePublished;
	private $currentContextId;

	/**
	 * @copydoc Plugin::register()
	 * @param $category
	 * @param $path
	 * @param null $mainContextId
	 * @return bool
	 */
	function register($category, $path, $mainContextId = null) {
		$success = parent::register($category, $path, $mainContextId);

		if (!Config::getVar('general', 'installed') || defined('RUNNING_UPGRADE')) return true;

		$this->currentContextId = ($mainContextId === null) ? $this->getCurrentContextId() : $mainContextId;

		HookRegistry::register('ArticleHandler::view', array(&$this, 'submissionView'));
		HookRegistry::register('PreprintHandler::view', array(&$this, 'submissionView'));

		// Insert the OrcidHandler to handle ORCID redirects
		HookRegistry::register('LoadHandler', array($this, 'setupCallbackHandler'));

		// Register callback for Smarty filters; add CSS
		HookRegistry::register('TemplateManager::display', array($this, 'handleTemplateDisplay'));

		// Add "Connect ORCID" button to PublicProfileForm
		HookRegistry::register('User::PublicProfile::AdditionalItems', array($this, 'handleUserPublicProfileDisplay'));

		// Display additional ORCID access information and checkbox to send e-mail to authors in the AuthorForm
		HookRegistry::register('authorform::display', array($this, 'handleFormDisplay'));

		// Send email to author, if the added checkbox was ticked
		HookRegistry::register('authorform::execute', array($this, 'handleAuthorFormExecute'));

		// Handle ORCID on user registration
		HookRegistry::register('registrationform::execute', array($this, 'collectUserOrcidId'));

		// Send emails to authors without ORCID id upon submission
		HookRegistry::register('submissionsubmitstep3form::execute', array($this, 'handleSubmissionSubmitStep3FormExecute'));

		HookRegistry::register('authordao::getAdditionalFieldNames', array($this, 'handleAdditionalFieldNames'));

		// Add more ORCiD fields to UserDAO
		HookRegistry::register('userdao::getAdditionalFieldNames', array($this, 'handleAdditionalFieldNames'));

		// Send emails to authors without authorised ORCID access on promoting a submission to copy editing. Not included in OPS.
		if ($this->getSetting($this->currentContextId, 'sendMailToAuthorsOnPublication')) {
			HookRegistry::register('EditorAction::recordDecision', array($this, 'handleEditorAction'));
		}

		HookRegistry::register('Publication::publish', array($this, 'handlePublicationStatusChange'));

		HookRegistry::register('ThankReviewerForm::thankReviewer', array($this, 'handleThankReviewer'));

		HookRegistry::register('Publication::validatePublish', [$this, 'validate']);

		// Add more ORCiD fields to author Schema
		HookRegistry::register('Schema::get::author', function ($hookName, $args) {
			$schema = $args[0];

			$schema->properties->orcidSandbox = (object)[
				'type' => 'string',
				'writeOnly' => 'true',
				'validation' => ['nullable']
			];
			$schema->properties->orcidAccessToken = (object)[
				'type' => 'string',
				'writeOnly' => 'true',
				'validation' => ['nullable']
			];
			$schema->properties->orcidAccessScope = (object)[
				'type' => 'string',
				'writeOnly' => 'true',
				'validation' => ['nullable']
			];
			$schema->properties->orcidRefreshToken = (object)[
				'type' => 'string',
				'writeOnly' => 'true',
				'validation' => ['nullable']
			];
			$schema->properties->orcidAccessExpiresOn = (object)[
				'type' => 'string',
				'writeOnly' => 'true',
				'validation' => ['nullable']
			];
			$schema->properties->orcidAccessDenied = (object)[
				'type' => 'string',
				'writeOnly' => 'true',
				'validation' => ['nullable']
			];
			$schema->properties->orcidEmailToken = (object)[
				'type' => 'string',
				'writeOnly' => 'true',
				'validation' => ['nullable']
			];
			$schema->properties->orcidWorkPutCode = (object)[
				'type' => 'string',
				'writeOnly' => 'true',
				'validation' => ['nullable']
			];
		});
		return $success;
	}

	/**
	 * @param $hookName
	 * @param $args
	 */
	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);
			}
		}
	}

	/**
	 * Load a setting for a specific journal or load it from the config.inc.php if it is specified there.
	 *
	 * @param  $contextId int The id of the journal from which the plugin settings should be loaded.
	 * @param  $name string   Name of the setting.
	 * @return mixed          The setting value, either from the database for this context
	 *                        or from the global configuration file.
	 */
	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);
		}

		$config_value = $config_value ?? parent::getSetting($contextId, $name);
		if ($name == 'orcidProfileAPIPath') {
			if ($config_value == 'https://orcid.org/') {
				$config_value = ORCID_API_URL_PUBLIC;
			} elseif ($config_value == 'https://sandbox.orcid.org/') {
				$config_value = ORCID_API_URL_PUBLIC_SANDBOX;
			}
		}
		return $config_value;
	}

    /**
     * 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') {
            $this->import('pages/OrcidHandler');
            define('HANDLER_CLASS', 'OrcidHandler');
            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
	 */
	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 $hookName string
	 * @param $args Form[]
	 *
	 * @return bool
	 * @see Form::display()
	 *
	 */
	function handleFormDisplay($hookName, $args) {
		$request = PKPApplication::get()->getRequest();
		$templateMgr = TemplateManager::getManager($request);
		switch ($hookName) {
			case 'authorform::display':
				$authorForm =& $args[0];
				$author = $authorForm->getAuthor();
				if ($author) {
					$authenticated = !empty($author->getData('orcidAccessToken'));
					$templateMgr->assign(
						array(
							'orcidAccessToken' => $author->getData('orcidAccessToken'),
							'orcidAccessScope' => $author->getData('orcidAccessScope'),
							'orcidAccessExpiresOn' => $author->getData('orcidAccessExpiresOn'),
							'orcidAccessDenied' => $author->getData('orcidAccessDenied'),
							'orcidAuthenticated' => $authenticated
						)
					);
				}

				$templateMgr->registerFilter("output", array($this, 'authorFormFilter'));
				break;
		}
		return false;
	}

	/**
	 * Hook callback: register output filter for user registration and article display.
	 *
	 * @param $hookName string
	 * @param $args array
	 * @return bool
	 * @see TemplateManager::display()
	 *
	 */
	function handleTemplateDisplay($hookName, $args) {
		$templateMgr =& $args[0];
		$template =& $args[1];
		$request = PKPApplication::get()->getRequest();

		// Assign our private stylesheet, for front and back ends.
		$templateMgr->addStyleSheet(
			'orcidProfile',
			$request->getBaseUrl() . '/' . $this->getStyleSheet(),
			array(
				'contexts' => array('frontend', 'backend')
			)
		);

		return false;
	}

	/**
	 * Return the location of the plugin's CSS file
	 *
	 * @return string
	 */
	function getStyleSheet() {
		return $this->getPluginPath() . '/css/orcidProfile.css';
	}

	/**
	 * Output filter adds ORCiD interaction to registration form.
	 *
	 * @param $output string
	 * @param $templateMgr TemplateManager
	 * @return string
	 */
	function registrationFilter($output, $templateMgr) {
		$request = Application::get()->getRequest();
		$context = $request->getContext();
		if ($context != null) {
			if (preg_match('/<form[^>]+id="register"[^>]+>/', $output, $matches, PREG_OFFSET_CAPTURE)) {
				$match = $matches[0][0];
				$offset = $matches[0][1];
				$targetOp = 'register';
				$templateMgr->assign(array(
					'targetOp' => $targetOp,
					'orcidUrl' => $this->getOrcidUrl(),
					'orcidOAuthUrl' => $this->buildOAuthUrl('orcidAuthorize', array('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', array($this, 'registrationFilter'));
			}
		}
		return $output;
	}

	/**
	 * Return the ORCID website url (prod or sandbox) based on the current API configuration
	 *
	 * @return string
	 */
	function getOrcidUrl() {
		$request = Application::get()->getRequest();
		$context = $request->getContext();
		$contextId = ($context == null) ? 0 : $context->getId();

		$apiPath = $this->getSetting($contextId, 'orcidProfileAPIPath');
		if ($apiPath == ORCID_API_URL_PUBLIC || $apiPath == ORCID_API_URL_MEMBER) {
			return ORCID_URL;
		} else {
			return ORCID_URL_SANDBOX;
		}
	}

	/**
	 * Return an ORCID OAuth authorization link with
	 *
	 * @param  $handlerMethod string containing a valid method of the OrcidHandler
	 * @param  $redirectParams Array associative array with additional request parameters for the redirect URL
	 */
	function buildOAuthUrl($handlerMethod, $redirectParams) {
		$request = PKPApplication::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, ROUTE_PAGE, null, 'orcidapi',
			$handlerMethod, null, $redirectParams);

		// overwrite redirect base url if variable is provided in config.inc.php
		$orcidRedirectBaseUrl = Config::getVar('orcidProfilePlugin', 'orcid_redirect_base_url');
		if (!ValidatorFactory::make([$orcidRedirectBaseUrl], [['required','url']])->fails()) {
			$redirectUrl = preg_replace("#^https{0,1}:\/\/(.*)\/#U", $orcidRedirectBaseUrl, $redirectUrl);
		}

		return $this->getOauthPath() . 'authorize?' . http_build_query(
				array(
					'client_id' => $this->getSetting($contextId, 'orcidClientId'),
					'response_type' => 'code',
					'scope' => $scope,
					'redirect_uri' => $redirectUrl)
			);
	}

	/**
	 * Return a string of the ORCiD SVG icon
	 *
	 * @return string
	 */
	function getIcon() {
		$path = Core::getBaseDir() . '/' . $this->getPluginPath() . '/templates/images/orcid.svg';
		return file_exists($path) ? file_get_contents($path) : '';
	}


	/**
	 * @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 the OAUTH path (prod or sandbox) based on the current API configuration
	 *
	 * @return string
	 */
	function getOauthPath() {
		return $this->getOrcidUrl() . 'oauth/';
	}

	public function isSandbox()
	{
		$isSandBox = $this->getSetting($this->getCurrentContextId(), 'orcidProfileAPIPath') == ORCID_API_URL_MEMBER_SANDBOX ||
			$this->getSetting($this->getCurrentContextId(), 'orcidProfileAPIPath') == ORCID_API_URL_PUBLIC_SANDBOX;
		return $isSandBox;
	}

	/**
	 * Renders additional content for the PublicProfileForm.
	 *
	 * Called by
	 * @param $hookName
	 * @param $params
	 * @return bool @see lib/pkp/templates/user/publicProfileForm.tpl
	 */
	function handleUserPublicProfileDisplay($hookName, $params) {
		$templateMgr =& $params[1];
		$output =& $params[2];
		$request = Application::get()->getRequest();
		$context = $request->getContext();
		$user = $request->getUser();
		$contextId = ($context == null) ? 0 : $context->getId();
		$targetOp = 'profile';
		$templateMgr->assign(
			array(
				'targetOp' => $targetOp,
				'orcidUrl' => $this->getOrcidUrl(),
				'orcidOAuthUrl' => $this->buildOAuthUrl('orcidAuthorize', array('targetOp' => $targetOp)),
				'orcidClientId' => $this->getSetting($contextId, 'orcidClientId'),
				'orcidIcon' => $this->getIcon(),
				'orcidAuthenticated' => !empty($user->getData('orcidAccessToken')),
			)
		);

		$output = $templateMgr->fetch($this->getTemplateResource('orcidProfile.tpl'));
		return true;
	}

	/**
	 * Output filter adds ORCiD interaction to contributors metadata add/edit form.
	 *
	 * @param $output string
	 * @param $templateMgr TemplateManager
	 * @return string
	 */
	function authorFormFilter($output, $templateMgr) {
		if (preg_match('/<input[^>]+name="submissionId"[^>]*>/', $output, $matches, PREG_OFFSET_CAPTURE)) {
			$match = $matches[0][0];
			$offset = $matches[0][1];
			$templateMgr->assign('orcidIcon', $this->getIcon());
			$newOutput = substr($output, 0, $offset + strlen($match));
			$newOutput .= $templateMgr->fetch($this->getTemplateResource('authorFormOrcid.tpl'));
			$newOutput .= substr($output, $offset + strlen($match));
			$output = $newOutput;
			$templateMgr->unregisterFilter('output', array($this, 'authorFormFilter'));
		}
		return $output;
	}

	/**
	 * handleAuthorFormexecute sends an e-mail to the author if a specific checkbox was ticked in the author form.
	 *
	 * @param $hookname string
	 * @param $args AuthorForm[]
	 * @see AuthorForm::execute() The function calling the hook.
	 *
	 */
	function handleAuthorFormExecute($hookname, $args) {
		$form =& $args[0];
		$form->readUserVars(array('requestOrcidAuthorization', 'deleteOrcid'));

		$requestAuthorization = $form->getData('requestOrcidAuthorization');
		$deleteOrcid = $form->getData('deleteOrcid');
		$author = $form->getAuthor();

		if ($author && $requestAuthorization) {
			$this->sendAuthorMail($author);
		}

		if ($author && $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 = PKPApplication::get()->getRequest();
		$context = $request->getContext();

		// This should only ever happen within a context, never site-wide.
		if ($context != null) {

			$contextId = $context->getId();

			if ($this->isMemberApiEnabled($contextId)) {
				$mailTemplate = 'ORCID_REQUEST_AUTHOR_AUTHORIZATION';
			} else {
				$mailTemplate = 'ORCID_COLLECT_AUTHOR_ID';
			}

			$mail = $this->getMailTemplate($mailTemplate, $context);
			$emailToken = md5(microtime() . $author->getEmail());

			$author->setData('orcidEmailToken', $emailToken);

			$publicationDao = DAORegistry::getDAO('PublicationDAO');
			/** @var PublicationDAO $publicationDao */
			$publication = $publicationDao->getById($author->getData('publicationId'));

			$oauthUrl = $this->buildOAuthUrl('orcidVerify', array('token' => $emailToken, 'state' => $publication->getId()));
			$aboutUrl = $request->getDispatcher()->url($request, ROUTE_PAGE, null, 'orcidapi', 'about', null);

			// Set From to primary journal contact
			$mail->setFrom($context->getData('contactEmail'), $context->getData('contactName'));

			// Send to author
			$mail->setRecipients(array(array('name' => $author->getFullName(), 'email' => $author->getEmail())));

			// Send the mail with parameters
			$mail->sendWithParams([
				'orcidAboutUrl' => $aboutUrl,
				'authorOrcidUrl' => $oauthUrl,
				'authorName' => htmlspecialchars($author->getFullName()),
				'articleTitle' => htmlspecialchars($publication->getLocalizedTitle()), // Backwards compatibility only
				'submissionTitle' => htmlspecialchars($publication->getLocalizedTitle()),
			]);

			if ($updateAuthor) {
				$authorDao = DAORegistry::getDAO('AuthorDAO');
				$authorDao->updateObject($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
	 * @return void
	 */
	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) {
			$authorDao = DAORegistry::getDAO('AuthorDAO');
			$authorDao->updateObject($author);
		}
	}

	/**
	 * Instantiate a MailTemplate
	 *
	 * @param string $emailKey
	 * @param Context $context
	 *
	 * @return MailTemplate
	 */
	function getMailTemplate($emailKey, $context = null) {
		import('lib.pkp.classes.mail.MailTemplate');
		return new MailTemplate($emailKey, null, $context, false);
	}

	/**
	 * Collect the ORCID when registering a user.
	 *
	 * @param $hookName string
	 * @param $params array
	 * @return bool
	 */
	function collectUserOrcidId($hookName, $params) {
		$form = $params[0];
		$user = $form->user;

		$form->readUserVars(array('orcid','orcidAccessToken','orcidAccessScope','orcidRefreshToken','orcidAccessExpiresOn','orcidSandbox'));
		$user->setOrcid($form->getData('orcid'));
		$user->setData('orcidAccessToken', $form->getData('orcidAccessToken'));
		$user->setData('orcidAccessScope', $form->getData('orcidAccessScope'));
		$user->setData('orcidRefreshToken', $form->getData('orcidRefreshToken'));
		if ($form->getData('orcidAccessExpiresOn')) {
			$user->setData('orcidAccessExpiresOn', $form->getData('orcidAccessExpiresOn')->toDateTimeString);
		}
		$user->setData('orcidSandbox', true);


		return false;
	}

	/**
	 * Output filter adds ORCiD interaction to the 3rd step submission form.
	 *
	 * @param $hookName
	 * @param $params
	 * @return bool
	 */
	function handleSubmissionSubmitStep3FormExecute($hookName, $params) {
		$form = $params[0];
		// Have to use global Request access because request is not passed to hook
		$publicationDao = DAORegistry::getDAO('PublicationDAO');
		/* @var $publicationDao PublicationDAO */
		$publication = $publicationDao->getById($form->submission->getData('currentPublicationId'));
		$authors = $publication->getData('authors');

		$request = Application::get()->getRequest();
		$user = $request->getUser();
		//error_log("OrcidProfilePlugin: authors[0] = " . var_export($authors[0], true));
		//error_log("OrcidProfilePlugin: user = " . var_export($user, true));
		if ($authors[0]->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'));
			$authors[0]->setData('orcidAccessToken', $user->getData('orcidAccessToken'));
			$authors[0]->setData('orcidAccessScope', $user->getData('orcidAccessScope'));
			$authors[0]->setData('orcidRefreshToken', $user->getData('orcidRefreshToken'));
			$authors[0]->setData('orcidAccessExpiresOn', $user->getData('orcidAccessExpiresOn'));
			$authors[0]->setData('orcidSandbox', $user->getData('orcidSandbox'));

			$authorDao = DAORegistry::getDAO('AuthorDAO');
			/* @var $authorDao AuthorDAO */
			$authorDao->updateObject($authors[0]);

			//error_log("OrcidProfilePlugin: author = " . var_export($authors[0], true));
		}
		return false;
	}

	/**
	 * Add additional ORCID specific fields to the Author and User objects
	 *
	 * @param $hookName string
	 * @param $params array
	 *
	 * @return bool
	 */
	function handleAdditionalFieldNames($hookName, $params) {
		$fields =& $params[1];
		$fields[] = 'orcidSandbox';
		$fields[] = 'orcidAccessToken';
		$fields[] = 'orcidAccessScope';
		$fields[] = 'orcidRefreshToken';
		$fields[] = 'orcidAccessExpiresOn';
		$fields[] = 'orcidAccessDenied';

		return false;
	}

	/**
	 * @copydoc Plugin::getDescription()
	 */
	function getDescription() {
		return __('plugins.generic.orcidProfile.description');
	}

	/**
	 * @see PKPPlugin::getInstallEmailTemplatesFile()
	 */
	function getInstallEmailTemplatesFile() {
		return ($this->getPluginPath() . '/emailTemplates.xml');
	}

	/**
	 * Extend the {url ...} smarty to support this plugin.
	 */
	function smartyPluginUrl($params, $smarty) {
		$path = array($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, array($params['path']));
		} else {
			$params['path'] = $path;
		}

		if (!empty($params['id'])) {
			$params['path'] = array_merge($params['path'], array($params['id']));
			unset($params['id']);
		}
		return $smarty->smartyUrl($params, $smarty);
	}

	function submissionView($hookName, $args) {
		$request = $args[0];
		$templateMgr = TemplateManager::getManager($request);
		$templateMgr->assign(array("orcidIcon" => $this->getIcon()));
	}

	/**
	 * @see Plugin::getActions()
	 */
	function getActions($request, $actionArgs) {
		$router = $request->getRouter();
		import('lib.pkp.classes.linkAction.request.AjaxModal');
		return array_merge(
			array(
				new LinkAction(
					'settings',
					new AjaxModal($router->url($request, null, null, 'manage', null, array('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, array('verb' => 'status', 'plugin' => $this->getName(), 'category' => 'generic')), $this->getDisplayName()),
					__('common.status'),
					null
				),
			),
			parent::getActions($request, $actionArgs)
		);
	}

	/**
	 * @copydoc Plugin::getDisplayName()
	 */
	function getDisplayName() {
		return __('plugins.generic.orcidProfile.displayName');
	}


	function setEnabled($enabled) {
		$contextId = $this->getCurrentContextId();
		$request = Application::get()->getRequest();
		$validator = new OrcidValidator($this);

		if ($this->isSitePlugin()) {
			$contextId = 0;
		}
		if ($request->getUserVar('save') == 1) {
			$clientId = $request->getUserVar('orcidClientId');
			$clientSecret = $request->getUserVar('orcidClientSecret');
		} else {
			$clientId = $this->getSetting($contextId, 'orcidClientId');
			$clientSecret = $this->getSetting($contextId, 'orcidClientSecret');

		}

		if (!$validator->validateClientSecret($clientSecret) or !$validator->validateClientId($clientId)) {
			$enabled = false;

		}
		$this->updateSetting($contextId, 'enabled', $enabled, 'bool');
	}

	function manage($args, $request) {
		$context = $request->getContext();
		$contextId = ($context == null) ? 0 : $context->getId();

		switch ($request->getUserVar('verb')) {
			case 'settings':
				$templateMgr = TemplateManager::getManager($request);
				$templateMgr->registerPlugin('function', 'plugin_url', array($this, 'smartyPluginUrl'));
				$apiOptions = [
					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'
				];
				$templateMgr->assign('orcidApiUrls', $apiOptions);

				$isoCodes = new \Sokil\IsoCodes\IsoCodesFactory();
				$countries = array();
				foreach ($isoCodes->getCountries() as $country) {
					$countries[$country->getAlpha2()] = $country->getLocalName();
				}
				asort($countries);
				$templateMgr->assign('countries', $countries);
				$templateMgr->assign('logLevelOptions', [
					'ERROR' => 'plugins.generic.orcidProfile.manager.settings.logLevel.error',
					'ALL' => 'plugins.generic.orcidProfile.manager.settings.logLevel.all'
				]);

				$this->import('classes.form.OrcidProfileSettingsForm');
				$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':
				$this->import('classes.form.OrcidProfileStatusForm');
				$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 $hookName string
	 * @param $args Issue[] Issue object that will be published
	 *
	 **@see
	 *
	 */
	public function handlePublicationStatusChange($hookName, $args) {
		$newPublication =& $args[0];
		/** @var $newPublication Publication */
		$publication =& $args[1];
		/** @var $publication Publication */
		$submission =& $args[2];
		/** @var $submission Submission */

		$request = PKPApplication::get()->getRequest();

		switch ($newPublication->getData('status')) {
			case STATUS_PUBLISHED:
			case STATUS_SCHEDULED:
				$this->logInfo("Expected publication status (5 or 3):  ".$newPublication->getData('status'));
				$this->publishAuthorWorkToOrcid($newPublication, $request);
				break;
		}
	}

	/**
	 * @param Submission $submission
	 * @param Request $request
	 * @return false|void
	 */
	public function publishReviewerWorkToOrcid(Submission $submission, Request $request) {
		$context = $request->getContext();
		$requestVars  = $request->getUserVars();
		import('lib.pkp.classes.submission.reviewAssignment.ReviewAssignmentDAO');
		$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /* @var $reviewAssignmentDao ReviewAssignmentDAO */
		$reviewAssignmentId = $requestVars['reviewAssignmentId'];
		if (isset($reviewAssignmentId)) {
		$review = $reviewAssignmentDao->getById($reviewAssignmentId);
		$reviewer = Services::get('user')->get($review->getData('reviewerId'));
			if ($reviewer->getOrcid() && $reviewer->getData('orcidAccessToken')) {
				$orcidAccessExpiresOn = Carbon\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();
					$requestsSuccess = [];

					try {
						$response = $httpClient->request(
							$method,
							$uri,
							[
								'headers' => $headers,
								'json' => $orcidReview,
								'allow_redirects' => ['strict' => true],
							]
						);
					} catch (ClientException $exception) {
						$reason = $exception->getResponse()->getBody(false);
						$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");
							$requestsSuccess[$orcid] = true;
							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);
							$authorDao = DAORegistry::getDAO('AuthorDAO');
							$authorDao->updateObject($reviewer);
							$requestsSuccess[$orcid] = true;
							$this->logInfo("Review added to profile, putCode: $putCode");
							break;
						default:
							$this->logError("Unexpected status $httpstatus response, body: $responseHeaders");
							$requestsSuccess[$orcid] = false;
					}
				}

				}

		}
	}

	/**
	 * 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 for which the data will be sent to ORCID
	 * @param $submission Submission for which the data will be sent to ORCID
	 * @return bool||bool[]
	 *
	 **/
	public function publishAuthorWorkToOrcid($publication, $request) {
		$this::logInfo("Publishing  publication with id ".$publication->getData('id')  );
		$templateMgr = TemplateManager::getManager($request);
		$context = $request->getContext();
		$contextId = $context->getId();
		$publicationId = $publication->getId();
		$submissionId = $publication->getData('submissionId');

		if (!$this->isMemberApiEnabled($contextId)) {
			// Sending to ORCID only works with the member API
			$this->logInfo("Member API disabled");
			return false;
		}

		$issueId = $publication->getData('issueId');
		if (isset($issueId)) {
			$issue = Services::get('issue')->get($issueId);
		}

		$authorDao = DAORegistry::getDAO('AuthorDAO');
		/** @var $authorDao AuthorDAO */
		$authors = $authorDao->getByPublicationId($publicationId);

		// Collect valid ORCID ids and access tokens from submission contributors
		$authorsWithOrcid = [];
		foreach ($authors as $author) {
			if ($author->getOrcid() && $author->getData('orcidAccessToken')) {
				$orcidAccessExpiresOn = Carbon\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, $request, $issue);
		$this::logInfo("Request body (without put-code): " . json_encode($orcidWork));

		$requestsSuccess = [];
		foreach ($authorsWithOrcid as $orcid => $author) {
			$this->logInfo("Publishing author work ". $author->getData('orcid'));
			$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,
						'allow_redirects' => ['strict' => true],
					]
				);

			} catch (ClientException $exception) {
				$reason = $exception->getResponse()->getBody(false);
				$this->logInfo("Publication fail: $reason");
				$templateMgr->assign('orcidAPIError', $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);
					$authorDao->updateObject($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);
						$authorDao->updateObject($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;
			}
		}
		if (array_product($requestsSuccess)) {
			return true;
		} else {
			return $requestsSuccess;
		}
	}
	/**
	 * Encode DOI according to ANSI/NISO Z39.84-2005, Appendix E.
	 * @param $pubId string
	 * @return string
	 */
	function _doiURLEncode($pubId) {
		$search = array ('%', '"', '#', ' ', '<', '>', '{');
		$replace = array ('%25', '%22', '%23', '%20', '%3c', '%3e', '%7b');
		$pubId = str_replace($search, $replace, $pubId);
		return $pubId;
	}

	public function buildOrcidReview($submission, $review, $request, $issue = null) {
		$publicationUrl = $request->getDispatcher()->url($request, ROUTE_PAGE, null, 'article', 'view', $submission->getId());
		$context = $request->getContext();
		$publicationLocale = ($submission->getData('locale')) ? $submission->getData('locale') : 'en_US';
		$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $context->getId()); // DO not remove
		$supportedSubmissionLocales = $context->getSupportedSubmissionLocales();

		if (!empty($review->getData('dateCompleted'))  && $context->getData('onlineIssn')) {
			$publicationPublishDate = Carbon\Carbon::parse($submission->getData('datePublished'));
			$reviewCompletionDate = Carbon\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() == 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()->getLocalizedTitle() ?? '']
				];


				if (!empty($submission->getData('pub-id::doi'))) {
					$externalIds = [

						'external-id-type' => 'doi',
						'external-id-value' => $submission->getData('pub-id::doi'),
						'external-id-url' => [
							'value' =>  $this->getResolvingURL($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->getLocalizedTitle($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  $message string Message to write
	 * @return void
	 */
	public function logInfo($message) {
		if ($this->getSetting($this->currentContextId, 'logLevel') === 'ERROR') {
			return;
		} else {
			self::writeLog($message, 'INFO');
		}
	}

	/**
	 * Write error message to log.
	 *
	 * @param  $message string Message to write
	 * @return void
	 */
	public function logError($message) {
		self::writeLog($message, 'ERROR');
	}

	/**
	 * creates the DOI URL
	 * @param $pubId
	 * @return string
	 */
	function getResolvingURL($pubId) {
		return 'https://doi.org/'.$this->_doiURLEncode($pubId);
	}

	/**
	 * 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 Submission $submission the submission we want to extract data from.
	 * @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|null $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 $issue = null) {
		$submission = Services::get('submission')->get($publication->getData('submissionId'));

		$applicationName = Application::get()->getName();
		$bibtexCitation = '';

		$publicationLocale = ($publication->getData('locale')) ? $publication->getData('locale') : 'en_US';
		$supportedSubmissionLocales = $context->getSupportedSubmissionLocales();

		$articleUrl = $request->getDispatcher()->url($request, ROUTE_PAGE, null, 'article', 'view', $submission->getId());

		$pubId = $publication->getStoredPubId($this->getPubIdType());
		$publicationUrl = $pubId ? $this->getResolvingURL($pubId) : $articleUrl;

		$orcidWork = [
			'title' => [
				'title' => [
					'value' => $publication->getLocalizedTitle() ?? ''
				],
				'subtitle' => [
					'value' => $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';
		}
		if ($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;
	}

	/**
	 * Write a message with specified level to log
	 *
	 * @param  $message string Message to write
	 * @param  $level   string Error level to add to message
	 * @return void
	 */
	protected 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 main pubid type
	 * @return string
	 */
	private  function getPubIdType (){
		return 'doi';
	}

	/**
	 * 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|null $issue The Issue object the Article object belongs to.
	 * @return array            An associative array corresponding to ORCID external-id JSON.
	 */
	protected function buildOrcidExternalIds($submission, $publication, $context, $issue, $articleUrl) {
		$contextId = $context->getId();

		$externalIds = array();
		$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $contextId);
		// Add doi, urn, etc. for article
		$articleHasStoredPubId = false;
		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
				if ($issue !== null) {
					$pubId = $issue->getStoredPubId($pubIdType);
					if ($pubId) {
						$externalIds[] = [
							'external-id-type' => self::PUBID_TO_ORCID_EXT_ID[$pubIdType],
							'external-id-value' => $pubId,
							'external-id-url' => [
								'value' => $plugin->getResolvingURL($contextId, $pubId)
							],
							'external-id-relationship' => 'part-of'
						];
					}
				}
			}
		} else {
			error_log("OrcidProfilePlugin::buildOrcidExternalIds: No pubId plugins could be loaded");
		}

		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.
	 *
	 * @return array Associative array with year, month and day or only year
	 */
	protected function buildOrcidPublicationDate($publication, $issue = null) {
		$publicationPublishDate = Carbon\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  $authors Author[] Array of Author objects
	 * @param  $contextId int    Id of the context the Author objects belong to
	 * @return array[]           Array of associative arrays,
	 *                           one for each contributor
	 */
	protected 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_US')];

			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;
	}

	/**
	 * @return string Path to a custom ORCID log file.
	 */
	public static function logFilePath() {
		return Config::getVar('files', 'files_dir') . '/orcid.log';
	}

	/**
	 * Set the current id of the context (atm only considered for logging settings).
	 *
	 * @param $contextId int the Id of the currently active context (journal)
	 */
	public function setCurrentContextId($contextId) {
		$this->currentContextId = $contextId;
	}

	/**
	 * handleEditorAction handles promoting a submission to copyediting.
	 *
	 * @param $hookName string Name the hook was registered with
	 * @param $args array 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'] == SUBMISSION_EDITOR_DECISION_ACCEPT) {
			$publication = $submission->getCurrentPublication();

			if (isset($publication)) {
				$authorDao = DAORegistry::getDAO('AuthorDAO');
				/** @var AuthorDAO $authorDao */

				$authors = $authorDao->getByPublicationId($submission->getCurrentPublication()->getId());

				foreach ($authors as $author) {
					$orcidAccessExpiresOn = Carbon\Carbon::parse($author->getData('orcidAccessExpiresOn'));
					if ($author->getData('orcidAccessToken') == null || $orcidAccessExpiresOn->isPast()) {
						$this->sendAuthorMail($author, true);
					}
				}
			}

		}
	}

	/**
	 * Pre-publication checks
	 * @param $hookName
	 * @param $args
	 * @return false
	 */
	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 && $this->isMemberApiEnabled($this->getCurrentContextId()) && !$author->getData('orcidAccessToken')) {
				$errors['hasUnauthenticatedOrcid'] = __('plugins.generic.orcidProfile.verify.hasUnauthenticatedOrcid');
			} else {
				$orcidIds [] = $authorOrcid;
			}
		}

		return false;
	}

	function getCurrentContextId() {
		$context = Application::get()->getRequest()->getContext();
		return is_null($context) ? 0 : $context->getId();
	}

}

