<?php

/**
 * @file controllers/wizard/fileUpload/form/PKPSubmissionFilesUploadBaseForm.php
 *
 * Copyright (c) 2014-2021 Simon Fraser University
 * Copyright (c) 2003-2021 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
 *
 * @class PKPSubmissionFilesUploadBaseForm
 *
 * @ingroup controllers_wizard_fileUpload_form
 *
 * @brief Form for adding/editing a submission file
 */

namespace PKP\controllers\wizard\fileUpload\form;

use APP\core\Application;
use APP\core\Request;
use APP\facades\Repo;
use APP\template\TemplateManager;
use Exception;
use PKP\db\DAORegistry;
use PKP\form\Form;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\ConfirmationModal;
use PKP\security\Role;
use PKP\stageAssignment\StageAssignment;
use PKP\submission\reviewRound\ReviewRound;
use PKP\submission\reviewRound\ReviewRoundDAO;
use PKP\submissionFile\SubmissionFile;
use PKP\user\User;

class PKPSubmissionFilesUploadBaseForm extends Form
{
    /** @var int */
    public $_stageId;

    /** @var ReviewRound */
    public $_reviewRound;

    /** @var array the submission files for this submission and file stage */
    public $_submissionFiles;

    /**
     * Constructor.
     *
     * @param Request $request
     * @param string $template
     * @param int $submissionId
     * @param int $stageId One of the WORKFLOW_STAGE_ID_* constants.
     * @param int $fileStage
     * @param bool $revisionOnly
     * @param ReviewRound $reviewRound
     * @param int $revisedFileId
     * @param int $assocType
     * @param int $assocId
     * @param int $queryId
     */
    public function __construct(
        $request,
        $template,
        $submissionId,
        $stageId,
        $fileStage,
        $revisionOnly = false,
        $reviewRound = null,
        $revisedFileId = null,
        $assocType = null,
        $assocId = null,
        $queryId = null
    ) {
        // Check the incoming parameters.
        if (!is_numeric($submissionId) || $submissionId <= 0 ||
            !is_numeric($fileStage) || $fileStage <= 0 ||
            !is_numeric($stageId) || $stageId < 1 || $stageId > 5 ||
            isset($assocType) !== isset($assocId)) {
            throw new \Exception('Invalid parameters!');
        }

        // Initialize class.
        parent::__construct($template);
        $this->_stageId = $stageId;

        if ($reviewRound) {
            $this->_reviewRound = & $reviewRound;
        } elseif ($assocType == Application::ASSOC_TYPE_REVIEW_ASSIGNMENT && !$reviewRound) {
            // Get the review assignment object.
            $reviewAssignment = Repo::reviewAssignment()->get((int) $assocId);
            if ($reviewAssignment->getDateCompleted()) {
                throw new \Exception('Review already completed!');
            }

            // Get the review round object.
            /** @var ReviewRoundDAO */
            $reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO');
            $this->_reviewRound = $reviewRoundDao->getById($reviewAssignment->getReviewRoundId());
        } elseif (!$assocType && !$reviewRound) {
            $reviewRound = null;
        }

        $this->setData('fileStage', (int)$fileStage);
        $this->setData('submissionId', (int)$submissionId);
        $this->setData('revisionOnly', (bool)$revisionOnly);
        $this->setData('revisedFileId', $revisedFileId ? (int)$revisedFileId : null);
        $this->setData('reviewRoundId', $reviewRound ? $reviewRound->getId() : null);
        $this->setData('assocType', $assocType ? (int)$assocType : null);
        $this->setData('assocId', $assocId ? (int)$assocId : null);
        $this->setData('queryId', $queryId ? (int) $queryId : null);

        // Add validators.
        $this->addCheck(new \PKP\form\validation\FormValidatorPost($this));
    }


    //
    // Setters and Getters
    //
    /**
     * Get the workflow stage id.
     *
     * @return int
     */
    public function getStageId()
    {
        return $this->_stageId;
    }

    /**
     * Get the review round object (if any).
     *
     * @return ReviewRound
     */
    public function getReviewRound()
    {
        return $this->_reviewRound;
    }

    /**
     * Get the revised file id (if any).
     *
     * @return int the revised file id
     */
    public function getRevisedFileId()
    {
        return $this->getData('revisedFileId') ? (int)$this->getData('revisedFileId') : null;
    }

    /**
     * Get the associated type
     *
     * @return int
     */
    public function getAssocType()
    {
        return $this->getData('assocType');
    }

    /**
     * Get the associated id.
     *
     * @return int
     */
    public function getAssocId()
    {
        return $this->getData('assocId');
    }

    /**
     * Get the submission files belonging to the
     * submission and to the file stage.
     *
     * @return array a list of SubmissionFile instances.
     */
    public function getSubmissionFiles()
    {
        if (is_null($this->_submissionFiles)) {
            if ($this->getStageId() == WORKFLOW_STAGE_ID_INTERNAL_REVIEW || $this->getStageId() == WORKFLOW_STAGE_ID_EXTERNAL_REVIEW) {
                // If we have a review stage id then we also expect a review round.
                if (!$this->getData('fileStage') == SubmissionFile::SUBMISSION_FILE_QUERY && !$this->getReviewRound() instanceof ReviewRound) {
                    throw new Exception('Can not request submission files for a review stage without specifying a review round.');
                }
                // Can only upload submission files, review files, review attachments, dependent files, or query attachments.
                if (!in_array($this->getData('fileStage'), [
                    SubmissionFile::SUBMISSION_FILE_SUBMISSION,
                    SubmissionFile::SUBMISSION_FILE_REVIEW_FILE,
                    SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_FILE,
                    SubmissionFile::SUBMISSION_FILE_REVIEW_ATTACHMENT,
                    SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION,
                    SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_REVISION,
                    SubmissionFile::SUBMISSION_FILE_QUERY,
                    SubmissionFile::SUBMISSION_FILE_DEPENDENT,
                    SubmissionFile::SUBMISSION_FILE_ATTACHMENT,
                ])) {
                    throw new Exception('The file stage is not valid for the review stage.');
                }

                // Hide the revision selector for review
                // attachments to make it easier for reviewers
                $reviewRound = $this->getReviewRound();
                if ($this->getData('fileStage') == SubmissionFile::SUBMISSION_FILE_REVIEW_ATTACHMENT) {
                    $this->_submissionFiles = [];
                } elseif ($reviewRound) {
                    // Retrieve the submission files for the given review round.
                    $submissionId = (int) $this->getData('submissionId');
                    $submission = Repo::submission()->get($submissionId);
                    if ($submission->getData('contextId') !== Application::get()->getRequest()->getContext()->getId()) {
                        throw new Exception('Can not request submission files from another context.');
                    }

                    $this->_submissionFiles = Repo::submissionFile()
                        ->getCollector()
                        ->filterByFileStages([(int) $this->getData('fileStage')])
                        ->filterByReviewRoundIds([(int) $reviewRound->getId()])
                        ->filterBySubmissionIds([$submissionId])
                        ->getMany()
                        ->toArray();
                } else {
                    // No review round, e.g. for dependent or query files
                    $this->_submissionFiles = [];
                }
            } else {
                $collector = Repo::submissionFile()
                    ->getCollector()
                    ->filterByFileStages([(int) $this->getData('fileStage')])
                    ->filterBySubmissionIds([(int) $this->getData('submissionId')]);
                if ($this->getAssocType() && $this->getAssocType() != Application::ASSOC_TYPE_SUBMISSION) {
                    $collector = $collector->filterByAssoc(
                        $this->getAssocType(),
                        [$this->getAssocId()]
                    );
                }

                $this->_submissionFiles = $collector->getMany()->toArray();
            }
        }

        return $this->_submissionFiles;
    }

    /**
     * Get the submission files possible to select/consider for revision by the given user.
     *
     * @param User $user
     * @param SubmissionFile $uploadedFile uploaded file
     *
     * @return array a list of SubmissionFile instances.
     */
    public function getRevisionSubmissionFilesSelection($user, $uploadedFile = null)
    {
        $allSubmissionFiles = $this->getSubmissionFiles();
        $submissionFiles = [];
        foreach ($allSubmissionFiles as $submissionFile) {
            // The uploaded file must be excluded from the list of revisable files.
            if ($uploadedFile && $uploadedFile->getId() == $submissionFile->getId()) {
                continue;
            }
            // Replaces StageAssignmentDAO::getBySubmissionAndRoleIds
            $hasAnyAssignments = StageAssignment::withSubmissionIds([$submissionFile->getData('submissionId')])
                ->withRoleIds([Role::ROLE_ID_AUTHOR])
                ->withStageIds([$this->getStageId()])
                ->withUserId($user->getId())
                ->exists();

            $allowedFileStages = [
                SubmissionFile::SUBMISSION_FILE_REVIEW_ATTACHMENT,
                SubmissionFile::SUBMISSION_FILE_REVIEW_FILE,
                SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_FILE,
            ];

            // #11341 Might not be necessary, as the fileType is now filtered also for review stage in getSubmissionFiles
            if (
                in_array($submissionFile->getFileStage(), $allowedFileStages) &&
                $hasAnyAssignments
            ) {
                // Authors are not permitted to revise reviewer documents.
                continue;
            }
            $submissionFiles[] = $submissionFile;
        }
        return $submissionFiles;
    }

    //
    // Implement template methods from Form
    //
    /**
     * @copydoc Form::readInputData()
     */
    public function readInputData()
    {
        // Only Genre and revised file can be set in the form. All other
        // information is generated on our side.
        $this->readUserVars(['revisedFileId']);
    }

    /**
     * @copydoc Form::fetch()
     *
     * @param null|mixed $template
     */
    public function fetch($request, $template = null, $display = false)
    {
        // Set the workflow stage.
        $this->setData('stageId', $this->getStageId());

        // Set the review round id, if any.
        $reviewRound = $this->getReviewRound();
        if ($reviewRound instanceof ReviewRound) {
            $this->setData('reviewRoundId', $reviewRound->getId());
        }

        // Retrieve the uploaded file (if any).
        $uploadedFile = $this->getData('uploadedFile');

        $user = $request->getUser();

        // Initialize the list with files available for review.
        $submissionFileOptions = [];
        $currentSubmissionFileGenres = [];

        // Go through all files and build a list of files available for review.
        $revisedFileId = $this->getRevisedFileId();
        $foundRevisedFile = false;
        $submissionFiles = $this->getRevisionSubmissionFilesSelection($user, $uploadedFile);

        foreach ((array) $submissionFiles as $submissionFile) {
            // Is this the revised file?
            if ($revisedFileId && $revisedFileId == $submissionFile->getId()) {
                // This is the revised submission file, so pass its data on to the form.
                $this->setData('genreId', $submissionFile->getGenreId());
                $foundRevisedFile = true;
            }

            // Create an entry in the list of existing files which
            // the user can select from in case he chooses to upload
            // a revision.
            $fileName = $submissionFile->getLocalizedData('name') != '' ? $submissionFile->getLocalizedData('name') : __('common.untitled');

            $submissionFileOptions[$submissionFile->getId()] = $fileName;
            $currentSubmissionFileGenres[$submissionFile->getId()] = $submissionFile->getGenreId();

            $lastSubmissionFile = $submissionFile;
        }

        // If there is only one option for a file to review, and user must revise, do not show the selector.
        if (count($submissionFileOptions) == 1 && $this->getData('revisionOnly')) {
            // There was only one option, use the last added submission file
            $this->setData('revisedFileId', $lastSubmissionFile->getId());
            $this->setData('genreId', $lastSubmissionFile->getGenreId());
        }

        // If this is not a "review only" form then add a default item.
        if (count($submissionFileOptions) && !$this->getData('revisionOnly')) {
            $submissionFileOptions = ['' => __('submission.upload.uploadNewFile')] + $submissionFileOptions;
        }

        // Make sure that the revised file (if any) really was among
        // the retrieved submission files in the current file stage.
        if ($revisedFileId && !$foundRevisedFile) {
            throw new \Exception('Invalid revised file id!');
        }

        // Set the review file candidate data in the template.
        $this->setData('currentSubmissionFileGenres', $currentSubmissionFileGenres);
        $this->setData('submissionFileOptions', $submissionFileOptions);

        // Show ensuring an anonymous review link.
        $context = $request->getContext();
        if ($context->getData('showEnsuringLink') && in_array($this->getStageId(), [WORKFLOW_STAGE_ID_SUBMISSION, WORKFLOW_STAGE_ID_INTERNAL_REVIEW, WORKFLOW_STAGE_ID_EXTERNAL_REVIEW])) {
            $ensuringLink = new LinkAction(
                'addUser',
                new ConfirmationModal(
                    __('review.anonymousPeerReview'),
                    __('review.anonymousPeerReview.title'),
                    'primary'
                ),
                __('review.anonymousPeerReview.title')
            );

            $templateMgr = TemplateManager::getManager($request);
            $templateMgr->assign('ensuringLink', $ensuringLink);
        }

        return parent::fetch($request, $template, $display);
    }
}
