diff --git a/OrcidProfilePlugin.inc.php b/OrcidProfilePlugin.inc.php index caf4d5a8..cf3f826e 100644 --- a/OrcidProfilePlugin.inc.php +++ b/OrcidProfilePlugin.inc.php @@ -24,8 +24,10 @@ define('ORCID_API_URL_MEMBER_SANDBOX', 'https://api.sandbox.orcid.org/'); define('OAUTH_TOKEN_URL', 'oauth/token'); -define('ORCID_API_VERSION_URL', 'v2.0/'); +//define('ORCID_API_VERSION_URL', 'v2.0/'); +define('ORCID_API_VERSION_URL', 'v1.2/'); define('ORCID_PROFILE_URL', 'orcid-profile'); +define('ORCID_BIO_URL', 'orcid-bio'); class OrcidProfilePlugin extends GenericPlugin { /** @@ -99,16 +101,159 @@ function handleTemplateDisplay($hookName, $args) { ); switch ($template) { - case 'frontend/pages/userRegister.tpl': + case 'user/register.tpl': + $templateMgr->register_outputfilter(array(&$this, 'registrationFilter')); + break; + case 'author/submit/step3.tpl': + $templateMgr->register_outputfilter(array(&$this, 'submitFilter')); + break; + case 'frontend/pages/userRegister.tpl': // OMP $templateMgr->register_outputfilter(array($this, 'registrationFilter')); break; case 'user/publicProfileForm.tpl': $templateMgr->register_outputfilter(array($this, 'profileFilter')); break; + case 'frontend/pages/userLogin.tpl': // OMP + $templateMgr->register_outputfilter(array(&$this, 'loginFilterOMP')); + break; + case 'controllers/grid/users/author/form/authorForm.tpl': // OMP + $templateMgr->register_outputfilter(array(&$this, 'addCheckOrcidButton')); + break; + } return false; } + /** + * Output filter adds ORCiD interaction to OJS login form. + * @param $output string + * @param $templateMgr TemplateManager + * @return $string + */ + function addCheckOrcidButton($output, &$templateMgr) { + $sessionManager = SessionManager::getManager(); + $userSession = $sessionManager->getUserSession(); + + $templateMgr->register_function('plugin_url', array($this, 'smartyPluginUrl')); + + if (preg_match('/]+>/', $output, $matches, PREG_OFFSET_CAPTURE)) { + $match = $matches[0][0]; + $offset = $matches[0][1]; + $context = Request::getContext(); + $templateMgr->assign(array( + 'targetOp' => 'login', + 'orcidProfileOauthPath' => $this->getOauthPath(), + 'orcidClientId' => $this->getSetting($context->getId(), 'orcidClientId'), + )); + $newOutput = substr($output, 0, $offset); + $newOutput .= $templateMgr->fetch($this->getTemplatePath() . 'orcidLoginCheck.tpl'); + $newOutput .= substr($output, $offset); + $output = $newOutput; + } + $templateMgr->unregister_outputfilter('addCheckOrcidButton'); + return $output; + } + + function smartyPluginUrl($params, &$smarty) { + $request = PKPApplication::getRequest(); + $dispatcher = $request->getDispatcher(); + return $dispatcher->url($request, ROUTE_PAGE, null, 'plugins', 'generic', array_merge(array('plugin', $this->getName(), isset($params['path'])?$params['path']:array()))); + } + + /** + * Search for author information in ORCiD registry. + * @param $args array + * @param $request PKPRequest + */ + function orcidSearch($args, $request) { + $plugin =& PluginRegistry::getPlugin('generic', 'orcidprofileplugin'); + $templateMgr =& TemplateManager::getManager($request); + + $authorIndex = Request::getUserVar('authorIndex'); + $orcidButtonId = Request::getUserVar('orcidButtonId'); + $orcidInputId = Request::getUserVar('orcidInputId'); + $templateMgr->assign_by_ref('authorIndex', $authorIndex); + $templateMgr->assign_by_ref('orcidButtonId', $orcidButtonId); + $templateMgr->assign_by_ref('orcidInputId', $orcidInputId); + + switch (Request::getUserVar('targetOp')) { + case 'form': + $templateMgr->display($plugin->getTemplatePath() . 'orcidProfileSearchForm.tpl'); + break; + case 'search': + $journal = Request::getJournal(); // + $name = $request->getUserVar('search-orcid-name'); + $lastname = $request->getUserVar('search-orcid-lastname'); + $email = $request->getUserVar('search-orcid-email'); + $orcidSearchResults = array(); + if (($name && $lastname) || $email) { + // Obtaining a search token + $curl = curl_init(); + curl_setopt_array($curl, array( + CURLOPT_FAILONERROR => true, + CURLOPT_URL => $url = $plugin->getSetting($journal->getId(), 'orcidProfileAPIPath').OAUTH_TOKEN_URL, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => array('Accept: application/json'), + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query(array( + 'scope' => '/read-public', + 'grant_type' => 'client_credentials', + 'client_id' => $plugin->getSetting($journal->getId(), 'orcidClientId'), + 'client_secret' => $plugin->getSetting($journal->getId(), 'orcidClientSecret') + )) + )); + $result = curl_exec($curl); + // Close request to clear up some resources + curl_close($curl); + if ($result) { + $response = json_decode($result, true); + $query = '?q='; + if ($name && $lastname && $email) { + $query .= '(given-names:' . $name . 'ANDfamily-name:' . $lastname . ')ORemail:' . $email; + } elseif ($name && $lastname) { + $query .= 'given-names:' . $name . 'ANDfamily-name:' . $lastname; + } else { + $query .= 'email:' . $email; + } + + // Performing search + $curl = curl_init(); + curl_setopt_array($curl, array( + CURLOPT_FAILONERROR => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => array('Accept: application/json', + 'Content-Type: application/orcidxml', + 'Authorization: Bearer ' . $response['access_token']), + CURLOPT_POST => false, + CURLOPT_URL => $url = $plugin->getSetting($journal->getId(), 'orcidProfileAPIPath') . ORCID_API_VERSION_URL . 'search/' . ORCID_BIO_URL . '/' . $query, + )); + $result = curl_exec($curl); + if ($result) { + // Processing results + $response = json_decode($result, true); + if ($response['orcid-search-results']['num-found'] > 0) { + foreach($response['orcid-search-results']['orcid-search-result'] as $resultItem) { + $name = $resultItem['orcid-profile']['orcid-bio']['personal-details']['given-names']['value']; + $lastname = $resultItem['orcid-profile']['orcid-bio']['personal-details']['family-name']['value']; + $email = $resultItem['orcid-profile']['orcid-bio']['contact-details']['email'][0]['value']; + $orcidiD = $resultItem['orcid-profile']['orcid-identifier']['uri']; + $orcidSearchResults[] = array('name' => $name, + 'lastname' => $lastname, + 'email' => $email, + 'orcidiD' => $orcidiD); + } + } + } + } + + } + $templateMgr->assign_by_ref('orcidSearchResults', $orcidSearchResults); + $templateMgr->display($plugin->getTemplatePath() . 'orcidProfileSearchResults.tpl'); + break; + default: assert(false); + } + } + /** * Return the OAUTH path (prod or sandbox) based on the current API configuration * @return $string @@ -125,6 +270,60 @@ function getOauthPath() { } } + /** + * Output filter adds ORCiD interaction to OJS login form. + * @param $output string + * @param $templateMgr TemplateManager + * @return $string + */ + function loginFilter($output, &$templateMgr) { + $sessionManager = SessionManager::getManager(); + $userSession = $sessionManager->getUserSession(); + if (preg_match('/]+>/', $output, $matches, PREG_OFFSET_CAPTURE)) { // OMP: id="login" OJS: id="signinForm" + $match = $matches[0][0]; + $offset = $matches[0][1]; + $context = Request::getContext(); + $templateMgr->assign(array( + 'targetOp' => 'login', + 'orcidProfileOauthPath' => $this->getOauthPath(), + 'orcidClientId' => $this->getSetting($context->getId(), 'orcidClientId'), + )); + $newOutput = substr($output, 0, $offset); + $newOutput .= $templateMgr->fetch($this->getTemplatePath() . 'orcidLogin.tpl'); + $newOutput .= substr($output, $offset); + $output = $newOutput; + } + $templateMgr->unregister_outputfilter('loginFilter'); + return $output; + } + + /** + * Output filter adds ORCiD interaction to OMP login form. + * @param $output string + * @param $templateMgr TemplateManager + * @return $string + */ + function loginFilterOMP($output, &$templateMgr) { + $sessionManager = SessionManager::getManager(); + $userSession = $sessionManager->getUserSession(); + if (preg_match('/]+>/', $output, $matches, PREG_OFFSET_CAPTURE)) { // OMP: id="login" OJS: id="signinForm" + $match = $matches[0][0]; + $offset = $matches[0][1]; + $context = Request::getContext(); + $templateMgr->assign(array( + 'targetOp' => 'login', + 'orcidProfileOauthPath' => $this->getOauthPath(), + 'orcidClientId' => $this->getSetting($context->getId(), 'orcidClientId'), + )); + $newOutput = substr($output, 0, $offset); + $newOutput .= $templateMgr->fetch($this->getTemplatePath() . 'orcidLogin.tpl'); + $newOutput .= substr($output, $offset); + $output = $newOutput; + } + $templateMgr->unregister_outputfilter('loginFilterOMP'); + return $output; + } + /** * Output filter adds ORCiD interaction to registration form. * @param $output string @@ -300,22 +499,22 @@ function getInstallEmailTemplateDataFile() { /** * 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 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); + //} /** * @see Plugin::getActions() diff --git a/classes/OrcidUserSettingsDAO.inc.php b/classes/OrcidUserSettingsDAO.inc.php new file mode 100644 index 00000000..e52653eb --- /dev/null +++ b/classes/OrcidUserSettingsDAO.inc.php @@ -0,0 +1,83 @@ + diff --git a/locale/en_US/locale.xml b/locale/en_US/locale.xml index 869ddd32..28ab95fc 100644 --- a/locale/en_US/locale.xml +++ b/locale/en_US/locale.xml @@ -13,7 +13,6 @@ --> - ORCID Profile Plugin @@ -33,4 +32,8 @@ Your submission has successfully been associated with your ORCID iD. Your submission could not be successfully associated with your ORCID iD. Please contact the journal manager with your name, ORCID, and details of your submission. OJS was not able to communicate with the ORCID service. Please contact the journal manager with your name, ORCID iD, and details of your submission. + Connect with ORCID + OJS was not able to communicate with the ORCID service. Please contact the journal manager with your name, ORCID, and details of your submission. + There is more than one user with these auth data. + Check if ORCID exists diff --git a/pages/OrcidHandler.inc.php b/pages/OrcidHandler.inc.php index e1b7662e..ea50f50b 100644 --- a/pages/OrcidHandler.inc.php +++ b/pages/OrcidHandler.inc.php @@ -15,6 +15,7 @@ */ import('classes.handler.Handler'); +import('plugins.generic.orcidProfile.classes.OrcidUserSettingsDAO'); class OrcidHandler extends Handler { /** @@ -84,6 +85,81 @@ function orcidAuthorize($args, $request) { window.close(); '; break; + case 'login': + if (!is_null($json)) { + // The user that will be logged in + $loggedInUser = null; + // Check if there is any user that has autoassigned orcidauth parameter on the UserSettings. + $userSettingsDao = new OrcidUserSettingsDAO(); + //$userSettingsDao = DAORegistry::getDAO('UserSettingsDAO'); + $userDao = DAORegistry::getDAO('UserDAO'); + $users = $userSettingsDao->getUsersBySetting('orcidauth', 'http://orcid.org/' . $response['orcid']); + if (is_null($users) || $users->count == 0) { // If no user exists + // Then we should look for someone that has his orcid field filled. + $users = $userSettingsDao->getUsersBySetting('orcid', 'http://orcid.org/' . $response['orcid']); + if (is_null($users) || $users->count == 0) { // If no user exists + // Then we can look if there is any user with the email assigned to any email from ORCID profile + // get all emails + $emails = $json['orcid-profile']['orcid-bio']['contact-details']['email']; + if (!is_null($emails)) { // No emails retrieved from api. Email field may not be public + foreach($emails as $email) { + $user = $userDao->getUserByEmail($email[value], false); + if (!is_null($user)) { + $loggedInUser = $user; + break; + } + } + } + } else { // we have at least one user with his orcid field filled + if (count($users) != 1) { // There are more than one users with that orcidauth. Nothing we can do. Loggin fails + $loggedInUser = false; + Validation::redirectLogin('plugins.generic.oauth.message.oauthTooManyMatches'); + } else { // only one user has the current orcidauth. We can log him in. + $loggedInUser = $users->next(); + } + } + } else { // There is at least one user with its orcidauth assigned to the current value + if ($users->count != 1) { // There are more than one users with that orcidauth. Nothing we can do. Loggin fails + $loggedInUser = false; + Validation::redirectLogin('plugins.generic.oauth.message.oauthTooManyMatches'); + } else { // only one user has the current orcidauth. We can log him in. + $loggedInUser = $users->next(); + } + } + if ($loggedInUser) { + $userDao =& DAORegistry::getDAO('UserDAO'); + $userSettingsDao->updateSetting($loggedInUser->getId(), 'orcidauth', 'http://orcid.org/' . $response['orcid'], 'string'); + + $reason = null; + // The user is valid, mark user as logged in in current session + $sessionManager =& SessionManager::getManager(); + // Regenerate session ID first + $sessionManager->regenerateSessionId(); + $session =& $sessionManager->getUserSession(); + $session->setSessionVar('userId', $loggedInUser->getId()); + $session->setUserId($loggedInUser->getId()); + $session->setSessionVar('username', $loggedInUser->getUsername()); + //$session->setRemember($remember); + $loggedInUser->setDateLastLogin(Core::getCurrentDate()); + $userDao->updateObject($loggedInUser); + Validation::redirectLogin(); + } else { // OAuth successful, but not linked to a user account (yet) + $sessionManager = SessionManager::getManager(); + $userSession = $sessionManager->getUserSession(); + $user = $userSession->getUser(); + if (isset($user)) { + // If the user is authenticated, link this user account + $userSettingsDao->updateSetting($user->getId(), 'orcidauth', 'http://orcid.org/' . $response['orcid'], 'string'); + $userSettingsDao->updateSetting($user->getId(), 'orcid', 'http://orcid.org/' . $response['orcid'], 'string'); + } else { + // Otherwise, send the user to the login screen (keep track of the oauthUniqueId to link upon login!) + $userSession->setSessionVar('orcidauth', 'http://orcid.org/' . $response['orcid']); + } + } + Validation::redirectLogin('plugins.generic.orcidProfile.oauthLoginError'); + } + Validation::redirectLogin('plugins.generic.orcidProfile.oauthTooManyMatches'); + break; default: assert(false); } } @@ -152,6 +228,92 @@ function orcidVerify($args, $request) { )); $templateMgr->display('common/message.tpl'); } + + /** + * Verify an incoming author claim for an ORCiD association. + * @param $args array + * @param $request PKPRequest + */ + function orcidSearch($args, $request) { + import('lib.pkp.classes.core.JSONMessage'); + + $context = Request::getContext(); + $op = Request::getRequestedOp(); + $plugin = PluginRegistry::getPlugin('generic', 'orcidprofileplugin'); + $templateMgr = TemplateManager::getManager($request); + $contextId = ($context == null) ? 0 : $context->getId(); + $orcid = $request->getUserVar('orcid'); + + $response = ""; + + if (isset($orcid) && trim($orcid) != "") { + // fetch the access token + $curl = curl_init(); + curl_setopt_array($curl, array( + CURLOPT_FAILONERROR => true, + CURLOPT_URL => $url = $plugin->getSetting($contextId, 'orcidProfileAPIPath').OAUTH_TOKEN_URL, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => array('Accept: application/json'), + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query(array( + 'scope' => '/read-public', + 'grant_type' => 'client_credentials', + 'client_id' => $plugin->getSetting($contextId, 'orcidClientId'), + 'client_secret' => $plugin->getSetting($contextId, 'orcidClientSecret') + )) + )); + $result = curl_exec($curl); + // Close request to clear up some resources + curl_close($curl); + if ($result) { + $response = json_decode($result, true); + $query = '?q='; + + $query .= 'orcid:' . $orcid; + + + // Performing search + $curl = curl_init(); + curl_setopt_array($curl, array( + CURLOPT_FAILONERROR => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => array('Accept: application/json', + 'Content-Type: application/orcidxml', + 'Authorization: Bearer ' . $response['access_token']), + CURLOPT_POST => false, + CURLOPT_URL => $url = $plugin->getSetting($contextId, 'orcidProfileAPIPath') . ORCID_API_VERSION_URL . 'search/' . ORCID_BIO_URL . '/' . $query, + )); + $result = curl_exec($curl); + if ($result) { + $response = json_decode($result, true); + + if ($response['orcid-search-results']['num-found'] == 0) { + $response = "The ORCID " . $orcid . " does not correspond to a registered researcher"; + return new JSONMessage(false, $response); + } else { + $profile = $response['orcid-search-results']['orcid-search-result'][0]['orcid-profile']['orcid-bio']['personal-details']; + $givenName = $profile['given-names']['value']; + $familyName = $profile['family-name']['value']; + + $response = "The ORCID " . $orcid . " correspond to a registered researcher - Name: " . $givenName . " - Family Name: " . $familyName; + return new JSONMessage(true, $response); + } + } else { + $response = "API Connectivity error"; + return new JSONMessage(false, $response); + } + } else { + $response = "API Connectivity error"; + return new JSONMessage(false, $response); + } + } else { + $response = "No ORCID defined"; + return new JSONMessage(false, $response); + } + + + + } } ?> diff --git a/templates/orcidLogin.tpl b/templates/orcidLogin.tpl new file mode 100644 index 00000000..67446c34 --- /dev/null +++ b/templates/orcidLogin.tpl @@ -0,0 +1,25 @@ +{** + * plugins/generic/orcidProfile/orcidProfile.tpl + * + * Copyright (c) 2015-2016 University of Pittsburgh + * Copyright (c) 2014-2016 Simon Fraser University Library + * Copyright (c) 2003-2016 John Willinsky + * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING. + * + * ORCID Profile authorization form + * + *} + +{literal} + +{/literal} + diff --git a/templates/orcidLoginCheck.tpl b/templates/orcidLoginCheck.tpl new file mode 100644 index 00000000..67802c70 --- /dev/null +++ b/templates/orcidLoginCheck.tpl @@ -0,0 +1,42 @@ +{** + * plugins/generic/orcidProfile/orcidProfile.tpl + * + * Copyright (c) 2015-2016 University of Pittsburgh + * Copyright (c) 2014-2016 Simon Fraser University Library + * Copyright (c) 2003-2016 John Willinsky + * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING. + * + * ORCID Profile authorization form + * + *} + +{literal} + +{/literal} + + +