<?php

/**
 * @file plugins/generic/reviewerCredits/ReviewerCredits.inc.php
 *
 * Copyright (c) 2015-2018 University of Pittsburgh
 * Copyright (c) 2014-2018 Simon Fraser University
 * Copyright (c) 2003-2018 John Willinsky
 * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING.
 *
 * @class ReviewerCreditsPlugin
 * @ingroup plugins_generic_reviewerCredits
 *
 * @brief ReviewerCredits plugin class
 */

import( 'lib.pkp.classes.plugins.GenericPlugin' );

define( 'REVIEWER_CREDITS_URL', 'https://reviewercredits.com/wp-json' );
define( 'REVIEWER_CREDITS_AUTH_ENDPOINT', '/jwt-auth/v1/token' );
define( 'REVIEWER_CREDITS_CLAIM_ENDPOINT', '/reviewer-credits/v10/journal/claim' );

//define('REVIEWER_CREDITS_BASIC_AUTH_CRED', 'user:passwd');

class ReviewerCreditsPlugin extends GenericPlugin {

	protected $_apiFlag = false;
	protected $_consentFlag = true;

	/**
	 * @copydoc Plugin::register()
	 */
	public function register( $category, $path, $mainContextId = null ) {
		$success = parent::register( $category, $path, $mainContextId );
		if ( ! Config::getVar( 'general', 'installed' ) || defined( 'RUNNING_UPGRADE' ) ) {
			return true;
		}
		if ( $success && $this->getEnabled( $mainContextId ) ) {
			HookRegistry::register( 'LoadHandler', array( $this, 'callbackGetInfo' ) );
			HookRegistry::register( 'TemplateManager::fetch', array( $this, 'handleTemplateDisplay' ) );
			HookRegistry::register( 'reviewerreviewstep3form::execute', array( $this, 'callbackSendClaim' ) );
			$this->_registerTemplateResource();
		}

		return $success;
	}

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

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

	/**
	 * @copydoc PKPPlugin::getTemplatePath
	 */
	public function getTemplatePath( $inCore = false ) {
		if ( method_exists( $this, 'getTemplateResourceName' ) ) {
			//##--> OJS 3.1.1
			return $this->getTemplateResourceName() . ':templates/';
		}

		//##--> OJS 3.1.2 onward
		return parent::getTemplatePath( $inCore ) . '/';
	}

	/**
	 * @copydoc Plugin::getActions()
	 */
	public function getActions( $request, $verb ) {
		$router = $request->getRouter();
		import( 'lib.pkp.classes.linkAction.request.AjaxModal' );

		return array_merge(
			$this->getEnabled() ? 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
				),
			) : array(),
			parent::getActions( $request, $verb )
		);
	}

	/**
	 * @see Plugin::manage()
	 */
	public function manage( $args, $request ) {

		$notificationManager = new NotificationManager();
		$context             = $request->getContext();
		$contextId           = ( $context == null ) ? 0 : $context->getId();

		//Checking if the plugin installed in correct directory.
		if ( $this->pluginPath !== "plugins/generic/reviewerCredits" ) {
			$notificationManager->createTrivialNotification( $contextId, NOTIFICATION_TYPE_ERROR, array( 'contents' => __( 'plugins.generic.reviewerCredits.manager.settings.invalidPath' ) ) );
		}

		switch ( $request->getUserVar( 'verb' ) ) {
			case 'settings':
				$templateMgr = TemplateManager::getManager();
				$templateMgr->register_function( 'plugin_url', array( $this, 'smartyPluginUrl' ) );
				/* Future implementation
				$apiOptions = array(
					RC_API_REV_ID_TYPE_EMAIL => 'plugins.generic.reviewerCredits.manager.settings.rcReviewerIdType.email',
					RC_API_REV_ID_TYPE_ORCID => 'plugins.generic.reviewerCredits.manager.settings.rcReviewerIdType.orcid',
				);

				$templateMgr->assign('rcReviewerIdType', $apiOptions);*/

				$this->import( 'ReviewerCreditsSettingsForm' );
				$form                = new ReviewerCreditsSettingsForm( $this, $contextId );
				$notificationManager = new NotificationManager();
				if ( $request->getUserVar( 'save' ) ) {
					$form->readInputData();
					if ( $form->validate() ) {
						$form->execute();
						//Showing journal credentials successfully configured message.
						$notificationManager->createTrivialNotification( $contextId, NOTIFICATION_TYPE_SUCCESS, array( 'contents' => __( 'plugins.generic.reviewerCredits.manager.settings.success' ) ) );

						return new JSONMessage( true );
					}
				} else {
					$form->initData();
				}

				return new JSONMessage( true, $form->fetch( $request ) );
		}

		return parent::manage( $args, $request );
	}

	/**
	 * Check the correct operation to trigger the API Call
	 */
	public function callbackGetInfo( $hookName, $op ) {
		if ( $op[0] == 'reviewer' && $op[1] == 'saveStep' ) {
			$request = Application::getRequest();
			$args    = $request->getUserVars();
			if ( $args['step'] == 3 ) {
				$this->_apiFlag = true;
			}
		}
	}

	/**
	 * This method manage and send the Peer Review Claim information to ReviewerCredits
	 */
	public function callbackSendClaim( $hookName, $args ) {
		if ( $this->_apiFlag && isset( $_POST['confirmSendRC'] ) && $_POST['confirmSendRC'] == 1 ) {
			$request               = Application::getRequest();
			$context               = $request->getContext();
			$userDao               = DAORegistry::getDAO( 'UserDAO' );
			$reviewerSubmission    = $args[0]->getReviewerSubmission();
			$reviewer              = $userDao->getById( $reviewerSubmission->getReviewerId() );
			$tryArray              = array( 'orcid', 'email' );
			$claimPayload          = new stdClass();
			$claimPayload->firstName = $reviewer->getGivenName(AppLocale::getLocale());
			$claimPayload->lastName = $reviewer->getFamilyName(AppLocale::getLocale());
			$authPayload           = new stdClass();
			$notificationManager   = new NotificationManager();
			$authPayload->username = $this->getSetting( $context->getId(), 'reviewerCreditsJournalLogin' );
			$authPayload->password = $this->getSetting( $context->getId(), 'reviewerCreditsJournalPassword' );
			if ( ! empty( $authPayload->username ) && ! empty( $authPayload->password ) ) {
				$apiAuthToken = $this->_getToken( $authPayload );
				if ( ! $apiAuthToken->error ) {
					$credentialMessages = array();
					while ( count( $tryArray ) > 0 ) {
						if ( in_array( 'orcid', $tryArray ) ) {
							$claimPayload->reviewerIdentifier = $reviewer->getOrcid();
							if ( empty( $claimPayload->reviewerIdentifier ) ) {
								$claimPayload->reviewerIdentifier     = $reviewer->getEmail();
								$claimPayload->reviewerIdentifierType = 'email';
								$tryArray                             = array();
							} else {
								$claimPayload->reviewerIdentifierType = 'orcid';
								$rawOrcid                             = preg_replace( '{[^0-9X]}', '', $claimPayload->reviewerIdentifier );
								$hrOrcid                              = chunk_split( $rawOrcid, 4, '-' );
								$hrOrcid                              = substr( $hrOrcid, 0, - 1 );
								$claimPayload->reviewerIdentifier     = $hrOrcid;
								unset( $tryArray['0'] );
							}
						} else {
							$claimPayload->reviewerIdentifier     = $reviewer->getEmail();
							$claimPayload->reviewerIdentifierType = 'email';
							$tryArray                             = array();
						}
						$credentialMessages[]        = strtoupper( $claimPayload->reviewerIdentifierType ) . ': "' . $claimPayload->reviewerIdentifier . '"';
						$claimPayload->dateCompleted = $reviewerSubmission->getDateCompleted();
						//##--> if dateCompleted is empty the submission is a new submission
						if ( empty( $claimPayload->dateCompleted ) ) {
							$claimPayload->dateCompleted = date( 'Y/m/d' );
							$reviewAssignment            = $args[0]->getReviewAssignment();
							$claimPayload->manuscriptID  = 'PR' . $reviewerSubmission->getReviewId() . '-S' . $reviewAssignment->getSubmissionId() . '-R' . $reviewerSubmission->getRound();
							//##--> Not used
							// $claimPayload->editorName    = '';
							$dateTime              = DateTime::createFromFormat( 'Y-m-d H:i:s', $reviewerSubmission->getDateDue() );
							$claimPayload->dateDue = $dateTime->format( 'Y/m/d' );
							usleep( 50000 );
							$apiClaimResponse = $this->_insertClaim( $claimPayload, $apiAuthToken->data );
							if ( $apiClaimResponse->error ) {
								if ( $apiClaimResponse->noUser && count( $tryArray ) == 0 ) {
									if ( count( $credentialMessages ) > 1 ) {
										$credentialString = join( ' or ', $credentialMessages );
									} else {
										$credentialString = $credentialMessages[0];
									}
									$notificationManager->createTrivialNotification( $reviewer->getId(),
										NOTIFICATION_TYPE_WARNING,
										array( 'contents' => __( 'plugins.generic.reviewerCredits.notification.noUser',
											array( 'crMessage' => $credentialString) ) ) );
								} elseif ( $apiClaimResponse->noUser && count( $tryArray ) > 0 ) {
									continue;
								} else {
									$notificationManager->createTrivialNotification( $reviewer->getId(), NOTIFICATION_TYPE_ERROR, array( 'contents' => __( 'plugins.generic.reviewerCredits.notification.failed' ) ) );
									$tryArray = array();
								}
							} else {

								$message = $apiClaimResponse->placeholder ? array( 'contents' => __( 'plugins.generic.reviewerCredits.notification.placeholder' ) ) : array( 'contents' => __( 'plugins.generic.reviewerCredits.notification.success' ) );
								$notificationManager->createTrivialNotification( $reviewer->getId(), NOTIFICATION_TYPE_SUCCESS, $message );
								$tryArray = array();
							}
						}
					}
				} else {
					$notificationManager->createTrivialNotification( $reviewer->getId(), NOTIFICATION_TYPE_ERROR, array( 'contents' => __( 'plugins.generic.reviewerCredits.notification.failedAuth' ) ) );
				}
			}
		}
	}

	/**
	 * Hook callback: register output filter.
	 * @see TemplateManager::display()
	 */
	public function handleTemplateDisplay( $hookName, $args ) {
		$templateMgr = $args[0];
		$template    = $args[1];
		$request     = (new Application) -> getRequest();
		if ( $template == 'reviewer/review/step3.tpl' ) {
			if ( method_exists( $templateMgr, 'register_outputfilter' ) ) {
				//##--> OJS 3.1.1
				$templateMgr->register_outputfilter( array( $this, 'profileFilter' ) );
			} else {
				//##--> OJS 3.1.2 onward
				$templateMgr->registerFilter( 'output', array( $this, 'profileFilter' ) );
			}
		}

		return false;
	}

	/**
	 * Output filter adds ReviewerCredits checkbox to reviewer submission form.
	 *
	 * @param $output string
	 * @param $templateMgr TemplateManager
	 *
	 * @return $string
	 */
	public function profileFilter( $output, $templateMgr ) {
		if ( preg_match( '|div.*formButtons|', $output ) && $this->_consentFlag == true ) {
			if ( ! preg_match( '|disabled|', $output ) ) {
				$this->_consentFlag = false;
				if ( method_exists( $this, 'getTemplateResource' ) ) {
					$output = $templateMgr->fetch( $this->getTemplateResource( 'confirmRCEdit.tpl' ) ) . $output;
				} else {
					$output = $templateMgr->fetch( $this->getTemplatePath() . 'confirmRCEdit.tpl' ) . $output;
				}
			}
		}

		return $output;
	}

	/**
	 * Journal credentials check.
	 *
	 * @param $username string
	 * @param $password string
	 *
	 * @return bool
	 */
	public function verifyCredentials( $username, $password ) {
		$authPayload           = new stdClass();
		$authPayload->username = $username;
		$authPayload->password = $password;
		$apiAuthToken          = $this->_getToken( $authPayload );

		return $apiAuthToken;
	}

	/**
	 * This method call the ReviewerCredits API to obtain an authorization token
	 */
	protected function _getToken( $authPayload ) {
		$output        = new stdClass();
		$response      = $this->_makeApiCall( REVIEWER_CREDITS_AUTH_ENDPOINT, $authPayload );
		$outputMessage = '';
		if ( $response->status != 200 ) {
			if ( property_exists( $response, 'payload' ) ) {
				$outputMessage = $response->payload->message;
			}
			if ( property_exists( $response->payload->data, 'json_error_message' ) ) {
				if ( ! empty( $outputMessage ) ) {
					$outputMessage .= ' - ';
				}
				$outputMessage .= strip_tags( $response->payload->data->json_error_message );
			}

			if ( property_exists( $response, 'message' ) ) {
				$outputMessage .= $response->message;
			}
			$output->error = true;
		} else {
			$outputMessage = $response->payload->token;
			$output->error = false;
		}
		$output->data = $outputMessage;

		return $output;
	}

	/**
	 * This method call the ReviewerCredits API to create a new Peer Review Claim
	 */
	protected function _insertClaim( $claimPayload, $token ) {
		$output   = new stdClass();
		$response = $this->_makeApiCall( REVIEWER_CREDITS_CLAIM_ENDPOINT, $claimPayload, $token );
		if ( $response->status != 200 && $response->status != 422 ) {
			if ( property_exists( $response, 'payload' ) ) {
				$outputMessage = $response->payload->message;
			}
			if ( property_exists( $response->payload, 'payload' ) ) {
				$arrayMessage = array();
				foreach ( $response->payload->payload as $key => $element ) {
					$arrayMessage[] = $key . ': ' . $element;
				}
				if ( count( $arrayMessage ) > 0 ) {
					if ( ! empty( $outputMessage ) ) {
						$outputMessage .= ' - ';
					}
					if ( count( $arrayMessage ) > 1 ) {
						$outputMessage .= join( '; ', $arrayMessage );
					} else {
						$outputMessage .= $arrayMessage[0];
					}
				}
			}
			$output->noUser = false;
			$output->error  = true;
		} else {
			if ( $response->status != 200 ) {
				$outputMessage  = 0;
				$output->noUser = true;
				$output->error  = true;
			} else {

				$outputMessage  = $response->payload->claimId;

				if($response->message == "placeholder") {
					$output->placeholder = true;
				}

				$output->noUser = false;
				$output->error  = false;
			}
		}
		$output->data = $outputMessage;

		return $output;
	}

	/**
	 * This method make the call to the ReviewerCredits API using cURL
	 */
	protected function _makeApiCall( $endpoint, $rawPayload, $token = null ) {
		$output  = new stdClass();
		$payload = json_encode( $rawPayload );
		$headers = array(
			'Content-Type:application/json',
			'Content-Length: ' . strlen( $payload )
		);
		if ( defined( 'REVIEWER_CREDITS_BASIC_AUTH_CRED' ) ) {
			$headerAuth = 'Authorization: Basic ' . base64_encode( REVIEWER_CREDITS_BASIC_AUTH_CRED );
			if ( ! is_null( $token ) ) {
				$headerAuth .= ', Bearer ' . $token;
			}
			$headers[] = $headerAuth;
		} else {
			if ( ! is_null( $token ) ) {
				$headers[] = 'Authorization: Bearer ' . $token;
			}
		}
		$ch = curl_init();
		curl_setopt( $ch, CURLOPT_URL, REVIEWER_CREDITS_URL . $endpoint );
		curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'POST' );
		curl_setopt( $ch, CURLOPT_POSTFIELDS, $payload );
		curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
		$rawOutput  = curl_exec( $ch );
		$httpStatus = curl_getinfo( $ch, CURLINFO_HTTP_CODE );

		if ( $rawOutput === false ) {
			$output->status  = 0;
			$output->message = curl_error( $ch );
		} elseif ( $httpStatus != 200 ) {
			$output->status  = $httpStatus;
			$output->message = 'HTTP Status ' . $httpStatus;
			$output->payload = json_decode( $rawOutput );
		} else {
			$output->status  = 200;
			$output->message = 'OK';
			$output->payload = json_decode( $rawOutput );
		}

		curl_close( $ch );

		return $output;
	}
}