diff --git a/.github/workflows/stable-3_3_0.yml b/.github/workflows/stable-3_3_0.yml new file mode 100644 index 00000000000..631c3e80833 --- /dev/null +++ b/.github/workflows/stable-3_3_0.yml @@ -0,0 +1,57 @@ +on: + push: + branches: + - '*' + pull_request: + branches: + ['stable-3_3_0'] +name: ojs +jobs: + ojs: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + include: + - php-version: 8.0 + validate: 'validate' + - php-version: 7.3 + database: pgsql + test: 'test' + - php-version: 7.3 + database: mysql + test: 'test' + - php-version: 7.4 + database: pgsql + test: 'test' + - php-version: 7.4 + database: mysql + test: 'test' + - php-version: 8.0 + database: mysql + test: 'test' + - php-version: 8.0 + database: pgsql + test: 'test' + - php-version: 8.1 + database: mysql + test: 'test' + - php-version: 8.1 + database: pgsql + test: 'test' + - php-version: 8.2 + database: mysql + test: 'test' + - php-version: 8.2 + database: pgsql + test: 'test' + + + name: ojs + steps: + - uses: pkp/pkp-github-actions@v1 + with: + node_version: 12 + dataset_branch: 'stable-3_3_0' + DATASETS_ACCESS_KEY: ${{secrets.DATASETS_ACCESS_KEY}} + DEBUG_IN_TMATE: false diff --git a/.gitignore b/.gitignore index c47b72ff513..4b6663dbafc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ temp /plugins/paymethod/paypal/vendor/ .htaccess .project +.vscode .buildpath .settings .DS_Store diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a70c6ffdefa..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,80 +0,0 @@ -# Configure the build matrix -matrix: - include: - # Validation - - env: TEST=validation - php: 7.3 - # PostgreSQL / various PHP - - env: TEST=pgsql - php: 7.3 - - env: TEST=pgsql - php: 7.4 - # MySQL / various PHP - - env: TEST=mysql - php: 7.3 - - env: TEST=mysql - php: 7.4 - # Path info disabled DISABLED pending a fix of pkp/pkp-lib#4414 - # - env: TEST=mysql DISABLE_PATH_INFO=1 - # php: 7.2 -language: php -python: - - 3.3 # Required by Javascript linter/builder -git: - # Inhibit automatic submodule checkout (see below) - submodules: false -cache: - npm: true - directories: - - $HOME/.composer/cache - - $HOME/.cache -addons: - chrome: beta - apt: - update: true - packages: - - libgconf-2-4 -before_install: - # Check out submodules (this script checks out developer forks if necessary) - - ./tools/startSubmodulesTRAVIS.sh - - # Update to latest stable version of npm - - npm i g -npm - - - | - if [[ "$TEST" != "validation" ]]; then - # Prepare for unit and integration tests. - - # Prepare the server environment - ./lib/pkp/tools/travis/prepare-webserver.sh - - # Prepare the local codebase - ./lib/pkp/tools/travis/install-composer-dependencies.sh - npm install && npm run build - else - # Prepare for validation tests. - npm install - ./lib/pkp/tools/travis/install-linter.sh - fi - -script: - - | - if [[ "$TEST" != "validation" ]]; then - # Run the unit and integration tests. - source ./lib/pkp/tools/travis/prepare-tests.sh - ./lib/pkp/tools/travis/run-tests.sh - else - # Run the validation tests. - ./lib/pkp/tools/travis/validate-xml.sh - ./lib/pkp/tools/buildjs.sh -n - ./lib/pkp/tools/checkHelp.sh - ./lib/pkp/tools/travis/validate-json.sh - npm run lint - fi - -after_script: - - cat error.log - -after_failure: - - sudo apt-get install sharutils - - tar cz cypress/screenshots | uuencode /dev/stdout diff --git a/README.md b/README.md index 61e539eb8bd..3b67b88212f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Open Journal Systems (OJS) has been developed by the Public Knowledge Project. For general information about OJS and other open research systems, visit the [PKP web site][pkp]. -[![Build Status](https://travis-ci.org/pkp/ojs.svg?branch=master)](https://travis-ci.org/pkp/ojs) +[![Build Status](https://app.travis-ci.com/pkp/ojs.svg?branch=stable-3_3_0)](https://app.travis-ci.com/pkp/ojs) ## Documentation diff --git a/api/v1/submissions/index.php b/api/v1/submissions/index.php index 9bdb80a9100..69e955dcb02 100644 --- a/api/v1/submissions/index.php +++ b/api/v1/submissions/index.php @@ -16,10 +16,11 @@ * */ $requestPath = Application::get()->getRequest()->getRequestPath(); -if (strpos($requestPath, '/files')) { +$urlParts = explode('/', trim($_SERVER['PATH_INFO'], '/')); +if (count($urlParts) >= 6 && $urlParts[5] == 'files') { import('lib.pkp.api.v1.submissions.PKPSubmissionFileHandler'); return new PKPSubmissionFileHandler(); } else { import('lib.pkp.api.v1.submissions.PKPSubmissionHandler'); return new PKPSubmissionHandler(); -} \ No newline at end of file +} diff --git a/classes/article/ArticleGalleyDAO.inc.php b/classes/article/ArticleGalleyDAO.inc.php index 43ca94cd35e..bf03dee6e51 100644 --- a/classes/article/ArticleGalleyDAO.inc.php +++ b/classes/article/ArticleGalleyDAO.inc.php @@ -14,6 +14,9 @@ * @brief Operations for retrieving and modifying ArticleGalley objects. */ +use Illuminate\Database\Capsule\Manager as Capsule; +use Illuminate\Database\Query\Builder; + import('classes.article.ArticleGalley'); import('lib.pkp.classes.db.SchemaDAO'); import('lib.pkp.classes.plugins.PKPPubIdPluginDAO'); @@ -52,6 +55,24 @@ function newDataObject() { return new ArticleGalley(); } + /** + * @copydoc RepresentationDAO::getById() + */ + function getById($representationId, $publicationId = null) { + $row = Capsule::table($this->tableName) + ->where($this->primaryKeyColumn, (int) $representationId) + ->when(!is_null($publicationId), function(Builder $query) use ($publicationId) { + $query->where('publication_id', (int) $publicationId); + }) + ->first(); + if (!$row) { + return null; + } + // Convert to an assoc array + $rowArray = json_decode(json_encode($row), true); + return $this->_fromRow($rowArray); + } + /** * Retrieve a galley by ID. * @param $pubIdType string One of the NLM pub-id-type values or @@ -289,20 +310,19 @@ function getExportable($contextId, $pubIdType = null, $title = null, $author = n $params[] = $pubIdSettingValue; } - $result = $this->retrieveRange( - $sql = 'SELECT g.* - FROM publication_galleys g - LEFT JOIN publications p ON (p.publication_id = g.publication_id) - LEFT JOIN publication_settings ps ON (ps.publication_id = p.publication_id) - LEFT JOIN submissions s ON (s.submission_id = p.submission_id) - LEFT JOIN submission_files sf ON (g.submission_file_id = sf.submission_file_id) - ' . ($pubIdType != null?' LEFT JOIN publication_galley_settings gs ON (g.galley_id = gs.galley_id)':'') - . ($title != null?' LEFT JOIN publication_settings pst ON (p.publication_id = pst.publication_id)':'') - . ($author != null?' LEFT JOIN authors au ON (p.publication_id = au.publication_id) - LEFT JOIN author_settings asgs ON (asgs.author_id = au.author_id AND asgs.setting_name = \''.IDENTITY_SETTING_GIVENNAME.'\') - LEFT JOIN author_settings asfs ON (asfs.author_id = au.author_id AND asfs.setting_name = \''.IDENTITY_SETTING_FAMILYNAME.'\') - ':'') - . ($pubIdSettingName != null?' LEFT JOIN publication_galley_settings gss ON (g.galley_id = gss.galley_id AND gss.setting_name = ?)':'') .' + $baseSql = ' + FROM publication_galleys g + LEFT JOIN publications p ON (p.publication_id = g.publication_id) + LEFT JOIN publication_settings ps ON (ps.publication_id = p.publication_id) + LEFT JOIN submissions s ON (s.submission_id = p.submission_id) + LEFT JOIN submission_files sf ON (g.submission_file_id = sf.submission_file_id) + ' . ($pubIdType != null?' LEFT JOIN publication_galley_settings gs ON (g.galley_id = gs.galley_id)':'') + . ($title != null?' LEFT JOIN publication_settings pst ON (p.publication_id = pst.publication_id)':'') + . ($author != null?' LEFT JOIN authors au ON (p.publication_id = au.publication_id) + LEFT JOIN author_settings asgs ON (asgs.author_id = au.author_id AND asgs.setting_name = \''.IDENTITY_SETTING_GIVENNAME.'\') + LEFT JOIN author_settings asfs ON (asfs.author_id = au.author_id AND asfs.setting_name = \''.IDENTITY_SETTING_FAMILYNAME.'\') + ':'') + . ($pubIdSettingName != null?' LEFT JOIN publication_galley_settings gss ON (g.galley_id = gss.galley_id AND gss.setting_name = ?)':'') .' WHERE s.status = ? AND s.context_id = ? ' . ($pubIdType != null?' AND gs.setting_name = ? AND gs.setting_value IS NOT NULL':'') @@ -312,12 +332,15 @@ function getExportable($contextId, $pubIdType = null, $title = null, $author = n . (($pubIdSettingName != null && $pubIdSettingValue != null && $pubIdSettingValue == EXPORT_STATUS_NOT_DEPOSITED)?' AND gss.setting_value IS NULL':'') . (($pubIdSettingName != null && $pubIdSettingValue != null && $pubIdSettingValue != EXPORT_STATUS_NOT_DEPOSITED)?' AND gss.setting_value = ?':'') . (($pubIdSettingName != null && is_null($pubIdSettingValue))?' AND (gss.setting_value IS NULL OR gss.setting_value = \'\')':'') .' - GROUP BY g.galley_id - ORDER BY p.date_published DESC, p.publication_id DESC, g.galley_id DESC', + GROUP BY g.galley_id + '; + + $result = $this->retrieveRange( + "SELECT g.* {$baseSql} ORDER BY p.date_published DESC, p.publication_id DESC, g.galley_id DESC", $params, $rangeInfo ); - return new DAOResultFactory($result, $this, '_fromRow', [], $sql, $params, $rangeInfo); + return new DAOResultFactory($result, $this, '_fromRow', [], "SELECT 0 {$baseSql}", $params, $rangeInfo); } } diff --git a/classes/article/AuthorDAO.inc.php b/classes/article/AuthorDAO.inc.php index d84d52cd3f4..724967a5315 100644 --- a/classes/article/AuthorDAO.inc.php +++ b/classes/article/AuthorDAO.inc.php @@ -25,7 +25,7 @@ class AuthorDAO extends PKPAuthorDAO { * Authors will be sorted by (family, given). Note that if journalId is null, * alphabetized authors for all enabled journals are returned. * If authors have the same given names, first names and affiliations in all journal locales, - * as well as country and email (otional), they are considered to be the same. + * as well as country and email (optional), they are considered to be the same. * @param $journalId int Optional journal ID to restrict results to * @param $initial An initial a family name must begin with, "-" for authors with no family names * @param $rangeInfo Range information @@ -90,46 +90,52 @@ function getAuthorsAlphabetizedByJournal($journalId = null, $initial = null, $ra $initialSql .= ')'; } + $baseSql = ' + FROM authors a + JOIN user_groups ug ON (a.user_group_id = ug.user_group_id) + JOIN publications p ON (p.publication_id = a.publication_id) + JOIN submissions s ON (s.current_publication_id = p.publication_id) + LEFT JOIN author_settings agl ON (a.author_id = agl.author_id AND agl.setting_name = ? AND agl.locale = ?) + LEFT JOIN author_settings agpl ON (a.author_id = agpl.author_id AND agpl.setting_name = ? AND agpl.locale = s.locale) + LEFT JOIN author_settings afl ON (a.author_id = afl.author_id AND afl.setting_name = ? AND afl.locale = ?) + LEFT JOIN author_settings afpl ON (a.author_id = afpl.author_id AND afpl.setting_name = ? AND afpl.locale = s.locale) + JOIN ( + SELECT + MIN(aa.author_id) as author_id, + CONCAT( + ' . ($includeEmail ? 'aa.email, \' \', ' : '') . ' + ac.setting_value, + \' \' + ' . $sqlColumnsAuthorSettings . ' + ) as names + FROM authors aa + JOIN publications pp ON (pp.publication_id = aa.publication_id) + LEFT JOIN publication_settings ppss ON (ppss.publication_id = pp.publication_id) + JOIN submissions ss ON (ss.submission_id = pp.submission_id AND ss.current_publication_id = pp.publication_id AND ss.status = ' . STATUS_PUBLISHED . ') + JOIN journals j ON (ss.context_id = j.journal_id) + JOIN issues i ON (ppss.setting_name = ? AND ppss.setting_value = CAST(i.issue_id AS CHAR(20)) AND i.published = 1) + LEFT JOIN author_settings ac ON (ac.author_id = aa.author_id AND ac.setting_name = \'country\') + ' . $sqlJoinAuthorSettings . ' + WHERE j.enabled = 1 + ' . (isset($journalId) ? ' AND j.journal_id = ?' : '') + . $initialSql . ' + GROUP BY names + ) as t1 ON (t1.author_id = a.author_id) + '; + $result = $this->retrieveRange( - $sql = 'SELECT a.*, ug.show_title, s.locale, + "SELECT + a.*, ug.show_title, s.locale, COALESCE(agl.setting_value, agpl.setting_value) AS author_given, - CASE WHEN agl.setting_value <> \'\' THEN afl.setting_value ELSE afpl.setting_value END AS author_family - FROM authors a - JOIN user_groups ug ON (a.user_group_id = ug.user_group_id) - JOIN publications p ON (p.publication_id = a.publication_id) - JOIN submissions s ON (s.current_publication_id = p.publication_id) - LEFT JOIN author_settings agl ON (a.author_id = agl.author_id AND agl.setting_name = ? AND agl.locale = ?) - LEFT JOIN author_settings agpl ON (a.author_id = agpl.author_id AND agpl.setting_name = ? AND agpl.locale = s.locale) - LEFT JOIN author_settings afl ON (a.author_id = afl.author_id AND afl.setting_name = ? AND afl.locale = ?) - LEFT JOIN author_settings afpl ON (a.author_id = afpl.author_id AND afpl.setting_name = ? AND afpl.locale = s.locale) - JOIN ( - SELECT - MIN(aa.author_id) as author_id, - CONCAT( - ' . ($includeEmail ? 'aa.email, \' \', ' : '') . ' - ac.setting_value, - \' \' - ' . $sqlColumnsAuthorSettings . ' - ) as names - FROM authors aa - JOIN publications pp ON (pp.publication_id = aa.publication_id) - LEFT JOIN publication_settings ppss ON (ppss.publication_id = pp.publication_id) - JOIN submissions ss ON (ss.submission_id = pp.submission_id AND ss.current_publication_id = pp.publication_id AND ss.status = ' . STATUS_PUBLISHED . ') - JOIN journals j ON (ss.context_id = j.journal_id) - JOIN issues i ON (ppss.setting_name = ? AND ppss.setting_value = CAST(i.issue_id AS CHAR(20)) AND i.published = 1) - LEFT JOIN author_settings ac ON (ac.author_id = aa.author_id AND ac.setting_name = \'country\') - ' . $sqlJoinAuthorSettings . ' - WHERE j.enabled = 1 - ' . (isset($journalId) ? ' AND j.journal_id = ?' : '') - . $initialSql . ' - GROUP BY names - ) as t1 ON (t1.author_id = a.author_id) - ORDER BY author_family, author_given', + CASE WHEN agl.setting_value <> '' THEN afl.setting_value ELSE afpl.setting_value END AS author_family + {$baseSql} + ORDER BY + author_family, author_given", $params, $rangeInfo ); - return new DAOResultFactory($result, $this, '_fromRow', [], $sql, $params, $rangeInfo); + return new DAOResultFactory($result, $this, '_fromRow', [], "SELECT 0 {$baseSql}", $params, $rangeInfo); } } diff --git a/classes/components/forms/context/MastheadForm.inc.php b/classes/components/forms/context/MastheadForm.inc.php index 450739b6010..d1ffaef63d2 100644 --- a/classes/components/forms/context/MastheadForm.inc.php +++ b/classes/components/forms/context/MastheadForm.inc.php @@ -39,6 +39,11 @@ public function __construct($action, $locales, $context, $imageUploadUrl) { 'groupId' => 'publishing', 'value' => $context->getData('publisherInstitution'), ])) + ->addField(new FieldText('publisherUrl', [ + 'label' => __('common.url'), + 'groupId' => 'publishing', + 'value' => $context->getData('publisherUrl'), + ])) ->addField(new FieldText('onlineIssn', [ 'label' => __('manager.setup.onlineIssn'), 'size' => 'small', diff --git a/classes/components/forms/publication/AssignToIssueForm.inc.php b/classes/components/forms/publication/AssignToIssueForm.inc.php index 5d414e68615..b3171a37a82 100644 --- a/classes/components/forms/publication/AssignToIssueForm.inc.php +++ b/classes/components/forms/publication/AssignToIssueForm.inc.php @@ -45,7 +45,7 @@ public function __construct($action, $publication, $publicationContext) { foreach ($unpublishedIssues as $issue) { $issueOptions[] = [ 'value' => (int) $issue->getId(), - 'label' => $issue->getIssueIdentification(), + 'label' => htmlspecialchars($issue->getIssueIdentification()), ]; } } @@ -58,7 +58,7 @@ public function __construct($action, $publication, $publicationContext) { foreach ($publishedIssues as $issue) { $issueOptions[] = [ 'value' => (int) $issue->getId(), - 'label' => $issue->getIssueIdentification(), + 'label' => htmlspecialchars($issue->getIssueIdentification()), ]; } } diff --git a/classes/components/forms/publication/IssueEntryForm.inc.php b/classes/components/forms/publication/IssueEntryForm.inc.php index e19e36ff325..0a48eb67b41 100644 --- a/classes/components/forms/publication/IssueEntryForm.inc.php +++ b/classes/components/forms/publication/IssueEntryForm.inc.php @@ -54,7 +54,7 @@ public function __construct($action, $locales, $publication, $publicationContext foreach ($unpublishedIssues as $issue) { $issueOptions[] = [ 'value' => (int) $issue->getId(), - 'label' => $issue->getIssueIdentification(), + 'label' => htmlspecialchars($issue->getIssueIdentification()), ]; } } @@ -67,7 +67,7 @@ public function __construct($action, $locales, $publication, $publicationContext foreach ($publishedIssues as $issue) { $issueOptions[] = [ 'value' => (int) $issue->getId(), - 'label' => $issue->getIssueIdentification(), + 'label' => htmlspecialchars($issue->getIssueIdentification()), ]; } } diff --git a/classes/components/forms/publication/PublishForm.inc.php b/classes/components/forms/publication/PublishForm.inc.php index ed52c7135b1..ec7fa379dee 100644 --- a/classes/components/forms/publication/PublishForm.inc.php +++ b/classes/components/forms/publication/PublishForm.inc.php @@ -16,6 +16,7 @@ namespace APP\components\forms\publication; use \PKP\components\forms\FormComponent; use \PKP\components\forms\FieldHTML; +use function PHP81_BC\strftime; define('FORM_PUBLISH', 'publish'); diff --git a/classes/controllers/grid/issues/IssueGridHandler.inc.php b/classes/controllers/grid/issues/IssueGridHandler.inc.php index db6b96f6af2..5bad6691bc8 100644 --- a/classes/controllers/grid/issues/IssueGridHandler.inc.php +++ b/classes/controllers/grid/issues/IssueGridHandler.inc.php @@ -449,11 +449,18 @@ function publishIssue($args, $request) { $assignPublicIdentifiersForm->initData(); return new JSONMessage(true, $assignPublicIdentifiersForm->fetch($request)); } - // Asign pub ids + // Assign pub ids $assignPublicIdentifiersForm->readInputData(); + if (!$assignPublicIdentifiersForm->validate()) { + return new JSONMessage(true, $assignPublicIdentifiersForm->fetch($request)); + } $assignPublicIdentifiersForm->execute(); } + if (!$request->checkCSRF()) { + return new JSONMessage(false); + } + $issue->setCurrent(1); $issue->setPublished(1); $issue->setDatePublished(Core::getCurrentDate()); diff --git a/classes/install/Upgrade.inc.php b/classes/install/Upgrade.inc.php index babc4b829bc..58a514cd3cb 100644 --- a/classes/install/Upgrade.inc.php +++ b/classes/install/Upgrade.inc.php @@ -732,7 +732,7 @@ function repairSuppFilesFilestage() { ->leftJoin('submissions as s', 's.submission_id', '=', 'sf.submission_id') ->where('sf.file_stage', '=', SUBMISSION_FILE_SUBMISSION) ->where('sf.assoc_type', '=', ASSOC_TYPE_REPRESENTATION) - ->where('sf.revision', '=', 'ssf.revision') + ->whereColumn('sf.revision', '=', 'ssf.revision') ->get(); foreach ($rows as $row) { @@ -757,8 +757,8 @@ function repairSuppFilesFilestage() { date('Ymd', strtotime($row->date_uploaded)), strtolower_codesafe($fileManager->parseFileExtension($row->original_file_name)) ); - $oldFileName = $submissionDir . '/' . $submissionFileRevision->_fileStageToPath($submissionFileRevision->getFileStage()) . '/' . $generatedOldFilename; - $newFileName = $submissionDir . '/' . $submissionFileRevision->_fileStageToPath($submissionFileRevision->getFileStage()) . '/' . $generatedNewFilename; + $oldFileName = $submissionDir . '/' . $this->_fileStageToPath($row->file_stage) . '/' . $generatedOldFilename; + $newFileName = $submissionDir . '/' . $this->_fileStageToPath($row->file_stage) . '/' . $generatedNewFilename; if (!Services::get('file')->fs->rename($oldFileName, $newFileName)) { error_log("Unable to move \"$oldFileName\" to \"$newFileName\"."); } @@ -1128,7 +1128,7 @@ function changeUserRolesAndStageAssignmentsForStagePermitSubmissionEdit() { case 'postgres7': case 'postgres8': case 'postgres9': - $stageAssignmentDao->update('UPDATE stage_assignments SET can_change_metadata=1 FROM stage_assignments sa JOIN user_groups ug ON (sa.user_group_id = ug.user_group_id) WHERE ug.role_id IN ' . $roleString); + $stageAssignmentDao->update('UPDATE stage_assignments sa SET can_change_metadata=1 FROM user_groups ug WHERE sa.user_group_id = ug.user_group_id AND ug.role_id IN ' . $roleString); break; default: fatalError("Unknown database type!"); } diff --git a/classes/issue/IssueDAO.inc.php b/classes/issue/IssueDAO.inc.php index a1ae6a18c2f..61dea970a32 100644 --- a/classes/issue/IssueDAO.inc.php +++ b/classes/issue/IssueDAO.inc.php @@ -221,7 +221,7 @@ function getIssuesByIdentification($journalId, $volume = null, $number = null, $ * @return Issue object */ function getByBestId($issueId, $contextId = null, $useCache = false) { - $params = [$issueId]; + $params = [(string) $issueId]; if ($contextId) $params[] = (int) $contextId; $result = $this->retrieve( @@ -233,7 +233,7 @@ function getByBestId($issueId, $contextId = null, $useCache = false) { if ($row = (array) $result->current()) { $issue = $this->_returnIssueFromRow($row); } elseif (is_int($issueId) || ctype_digit($issueId)) { - $issue = $this->getById($issueId); + $issue = $this->getById($issueId, $contextId); } return $issue ?? null; @@ -567,12 +567,15 @@ function getBySubmissionId($articleId, $journalId = null) { * @return DAOResultFactory */ function getIssues($journalId, $rangeInfo = null) { + $baseSql = ' + FROM issues i WHERE journal_id = ? + '; $result = $this->retrieveRange( - $sql = 'SELECT i.* FROM issues i WHERE journal_id = ? ORDER BY current DESC, date_published DESC', + "SELECT i.* {$baseSql} ORDER BY current DESC, date_published DESC", $params = [(int) $journalId], $rangeInfo ); - return new DAOResultFactory($result, $this, '_returnIssueFromRow', [], $sql, $params, $rangeInfo); // Counted in ExportableIssuesListGridHandler + return new DAOResultFactory($result, $this, '_returnIssueFromRow', [], "SELECT 0 {$baseSql}", $params, $rangeInfo); // Counted in ExportableIssuesListGridHandler } /** @@ -626,24 +629,26 @@ function getExportable($contextId, $pubIdType = null, $pubIdSettingName = null, $params[] = $pubIdSettingValue; } - $result = $this->retrieveRange( - $sql = 'SELECT i.* + $baseSql = ' FROM issues i - LEFT JOIN custom_issue_orders o ON (o.issue_id = i.issue_id) - ' . ($pubIdType != null?' LEFT JOIN issue_settings ist ON (i.issue_id = ist.issue_id)':'') - . ($pubIdSettingName != null?' LEFT JOIN issue_settings iss ON (i.issue_id = iss.issue_id AND iss.setting_name = ?)':'') .' + LEFT JOIN custom_issue_orders o ON (o.issue_id = i.issue_id) + ' . ($pubIdType != null?' LEFT JOIN issue_settings ist ON (i.issue_id = ist.issue_id)':'') + . ($pubIdSettingName != null?' LEFT JOIN issue_settings iss ON (i.issue_id = iss.issue_id AND iss.setting_name = ?)':'') .' WHERE i.published = 1 AND i.journal_id = ? ' . ($pubIdType != null?' AND ist.setting_name = ? AND ist.setting_value IS NOT NULL':'') . (($pubIdSettingName != null && $pubIdSettingValue != null && $pubIdSettingValue == EXPORT_STATUS_NOT_DEPOSITED)?' AND iss.setting_value IS NULL':'') . (($pubIdSettingName != null && $pubIdSettingValue != null && $pubIdSettingValue != EXPORT_STATUS_NOT_DEPOSITED)?' AND iss.setting_value = ?':'') - . (($pubIdSettingName != null && is_null($pubIdSettingValue))?' AND (iss.setting_value IS NULL OR iss.setting_value = \'\')':'') - .' ORDER BY i.date_published DESC', + . (($pubIdSettingName != null && is_null($pubIdSettingValue))?' AND (iss.setting_value IS NULL OR iss.setting_value = \'\')':'') . ' + '; + + $result = $this->retrieveRange( + "SELECT i.* {$baseSql} ORDER BY i.date_published DESC", $params, $rangeInfo ); - return new DAOResultFactory($result, $this, '_returnIssueFromRow', [], $sql, $params, $rangeInfo); + return new DAOResultFactory($result, $this, '_returnIssueFromRow', [], "SELECT 0 {$baseSql}", $params, $rangeInfo); } /** @@ -696,7 +701,7 @@ function resequenceCustomIssueOrders($journalId) { 'journal_id' => (int) $journalId, 'seq' => $i ], - ['issue_id', 'journal_id'] + ['issue_id'] ); $result->next(); } diff --git a/classes/mail/ArticleMailTemplate.inc.php b/classes/mail/ArticleMailTemplate.inc.php index 456d3bcb6d8..a3023c1c726 100644 --- a/classes/mail/ArticleMailTemplate.inc.php +++ b/classes/mail/ArticleMailTemplate.inc.php @@ -27,7 +27,7 @@ function assignParams($paramArray = array()) { if ($sectionId = $publication->getData('sectionId')) { $sectionDao = DAORegistry::getDAO('SectionDAO'); /** @var $sectionDao SectionDAO */ $section = $sectionDao->getById($sectionId); - if ($section) $paramArray['sectionName'] = strip_tags($section->getLocalizedTitle()); + if ($section) $paramArray['sectionName'] = htmlspecialchars($section->getLocalizedTitle()); } parent::assignParams($paramArray); } diff --git a/classes/migration/OJSMigration.inc.php b/classes/migration/OJSMigration.inc.php index 89d36e09ac3..95214b6e132 100644 --- a/classes/migration/OJSMigration.inc.php +++ b/classes/migration/OJSMigration.inc.php @@ -235,7 +235,6 @@ public function up() { $table->bigInteger('journal_id'); $table->float('cost', 8, 2); $table->string('currency_code_alpha', 3); - $table->smallInteger('non_expiring')->default(0); $table->smallInteger('duration')->nullable(); $table->smallInteger('format'); $table->smallInteger('institutional')->default(0); diff --git a/classes/migration/upgrade/OJSv3_3_0UpgradeMigration.inc.php b/classes/migration/upgrade/OJSv3_3_0UpgradeMigration.inc.php index b73fc82f354..c9952cf085b 100644 --- a/classes/migration/upgrade/OJSv3_3_0UpgradeMigration.inc.php +++ b/classes/migration/upgrade/OJSv3_3_0UpgradeMigration.inc.php @@ -44,6 +44,11 @@ public function up() { Capsule::statement("DELETE FROM filters WHERE class_name IN ('plugins.importexport.medra.filter.IssueMedraXmlFilter', 'plugins.importexport.medra.filter.ArticleMedraXmlFilter', 'plugins.importexport.medra.filter.GalleyMedraXmlFilter')"); Capsule::statement("DELETE FROM filter_groups WHERE symbolic IN ('issue=>medra-xml', 'article=>medra-xml', 'galley=>medra-xml')"); Capsule::statement("DELETE FROM scheduled_tasks WHERE class_name='plugins.importexport.medra.MedraInfoSender'"); + Capsule::statement("DELETE FROM versions WHERE product_type='plugins.importexport' AND product='medra'"); + + // pkp/pkp-lib#6807 Make sure all submission/issue last modification dates are set + Capsule::statement('UPDATE issues SET last_modified = date_published WHERE last_modified IS NULL'); + Capsule::statement('UPDATE submissions SET last_modified = NOW() WHERE last_modified IS NULL'); } /** @@ -96,9 +101,25 @@ private function _settingsAsJSON() { $tables = Capsule::connection()->getDoctrineSchemaManager()->listTableNames(); foreach ($tables as $tableName) { if (substr($tableName, -9) !== '_settings' || in_array($tableName, $processedTables)) continue; - Capsule::table($tableName)->where('setting_type', 'object')->get()->each(function ($row) use ($tableName) { - $this->_toJSON($row, $tableName, ['setting_name', 'locale'], 'setting_value'); - }); + if ($tableName === 'plugin_settings') { + Capsule::table($tableName)->where('setting_type', 'object')->get()->each(function ($row) use ($tableName) { + $this->_toJSON($row, $tableName, ['plugin_name', 'context_id', 'setting_name'], 'setting_value'); + }); + } elseif ($tableName == 'review_form_element_settings') { + Capsule::table('review_form_element_settings')->where('setting_type', 'object')->get()->each(function ($row) { + $this->_toJSON($row, 'review_form_element_settings', ['setting_name', 'locale', 'review_form_element_id'], 'setting_value'); + }); + } else { + try { + $settings = Capsule::table($tableName, 's')->where('setting_type', 'object')->get(['setting_name', 'setting_value', 's.*']); + } catch (Exception $e) { + error_log("Failed to migrate the settings entity \"{$tableName}\"\n" . $e); + continue; + } + $settings->each(function ($row) use ($tableName) { + $this->_toJSON($row, $tableName, ['setting_name', 'locale'], 'setting_value'); + }); + } } // Finally, convert values of other tables dependent from DAO::convertToDB @@ -107,21 +128,19 @@ private function _settingsAsJSON() { }); Capsule::table('site')->get()->each(function ($row) { - $oldInstalledLocales = unserialize($row->{'installed_locales'}); - $oldSupportedLocales = unserialize($row->{'supported_locales'}); + $localeToConvert = function($localeType) use($row) { + $serializedValue = $row->{$localeType}; + if (@unserialize($serializedValue) === false) return; + $oldLocaleValue = unserialize($serializedValue); - if (is_array($oldInstalledLocales) && $this->_isNumerical($oldInstalledLocales)) $oldInstalledLocales = array_values($oldInstalledLocales); - if (is_array($oldSupportedLocales) && $this->_isNumerical($oldSupportedLocales)) $oldSupportedLocales = array_values($oldSupportedLocales); + if (is_array($oldLocaleValue) && $this->_isNumerical($oldLocaleValue)) $oldLocaleValue = array_values($oldLocaleValue); - if ($oldInstalledLocales) { - $newInstalledLocales = json_encode($oldInstalledLocales, JSON_UNESCAPED_UNICODE); - Capsule::table('site')->take(1)->update(['installed_locales' => $newInstalledLocales]); - } + $newLocaleValue = json_encode($oldLocaleValue, JSON_UNESCAPED_UNICODE); + Capsule::table('site')->take(1)->update([$localeType => $newLocaleValue]); + }; - if ($oldSupportedLocales) { - $newSupportedLocales = json_encode($oldSupportedLocales, JSON_UNESCAPED_UNICODE); - Capsule::table('site')->take(1)->update(['supported_locales' => $newSupportedLocales]); - } + $localeToConvert('installed_locales'); + $localeToConvert('supported_locales'); }); } @@ -134,23 +153,46 @@ private function _settingsAsJSON() { */ private function _toJSON($row, $tableName, $searchBy, $valueToConvert) { - $oldValue = unserialize($row->{$valueToConvert}); + // Check if value can be unserialized + $serializedOldValue = $row->{$valueToConvert}; + if (@unserialize($serializedOldValue) === false) return; + $oldValue = unserialize($serializedOldValue); + // Reset arrays to avoid keys being mixed up if (is_array($oldValue) && $this->_isNumerical($oldValue)) $oldValue = array_values($oldValue); - if (!$oldValue && !is_array($oldValue)) return; // don't continue if value cannot be unserialized $newValue = json_encode($oldValue, JSON_UNESCAPED_UNICODE); // don't convert utf-8 characters to unicode escaped code - $id = array_key_first((array)$row); // get first/primary key column + // Ensure ID fields are included on the filter to avoid updating similar rows + $tableDetails = Capsule::connection()->getDoctrineSchemaManager()->listTableDetails($tableName); + $primaryKeys = []; + try { + $primaryKeys = $tableDetails->getPrimaryKeyColumns(); + } catch (Exception $e) { + foreach ($tableDetails->getIndexes() as $index) { + if($index->isPrimary() || $index->isUnique()) { + $primaryKeys = $index->getColumns(); + break; + } + } + } - // Remove empty filters - $searchBy = array_filter($searchBy, function ($item) use ($row) { - if (empty($row->{$item})) return false; - return true; - }); + if (!count($primaryKeys)) { + foreach (array_keys(get_object_vars($row)) as $column) { + if (substr($column, -3, '_id')) { + $primaryKeys[] = $column; + } + } + } + + $searchBy = array_merge($searchBy, $primaryKeys); - $queryBuilder = Capsule::table($tableName)->where($id, $row->{$id}); - foreach ($searchBy as $key => $column) { - $queryBuilder = $queryBuilder->where($column, $row->{$column}); + $queryBuilder = Capsule::table($tableName); + foreach (array_unique($searchBy) as $column) { + if ($row->{$column} !== null) { + $queryBuilder->where($column, $row->{$column}); + } else { + $queryBuilder->whereNull($column); + } } $queryBuilder->update([$valueToConvert => $newValue]); } diff --git a/classes/oai/ojs/OAIDAO.inc.php b/classes/oai/ojs/OAIDAO.inc.php index 1bf3e4fb3b0..5b53f17e0f7 100644 --- a/classes/oai/ojs/OAIDAO.inc.php +++ b/classes/oai/ojs/OAIDAO.inc.php @@ -145,7 +145,7 @@ function &getJournalSets($journalId, $offset, $limit, &$total) { * @return array (int, int) */ function getSetJournalSectionId($journalSpec, $sectionSpec, $restrictJournalId = null) { - $journal =& $this->journalDao->getByPath($journalSpec); + $journal = $this->journalDao->getByPath($journalSpec); if (!isset($journal) || (isset($restrictJournalId) && $journal->getId() != $restrictJournalId)) { return array(0, 0); } @@ -233,7 +233,7 @@ function _getRecordsRecordSet($setIds, $from, $until, $set, $submissionId = null } if ($submissionId) $params[] = (int) $submissionId; return $this->retrieve( - 'SELECT GREATEST(a.last_modified, i.last_modified) AS last_modified, + 'SELECT GREATEST(a.last_modified, i.last_modified, p.last_modified) AS last_modified, a.submission_id AS submission_id, j.journal_id AS journal_id, s.section_id AS section_id, @@ -252,8 +252,8 @@ function _getRecordsRecordSet($setIds, $from, $until, $set, $submissionId = null ' . ($excludeJournals ?' AND j.journal_id NOT IN ('.implode(',', $excludeJournals).')':'') . ' ' . (isset($journalId) ?' AND j.journal_id = ?':'') . ' ' . (isset($sectionId) ?' AND p.section_id = ?':'') . ' - ' . ($from?' AND GREATEST(a.last_modified, i.last_modified) >= ' . $this->datetimeToDB($from):'') . ' - ' . ($until?' AND GREATEST(a.last_modified, i.last_modified) <= ' . $this->datetimeToDB($until):'') . ' + ' . ($from?' AND GREATEST(a.last_modified, i.last_modified, p.last_modified) >= ' . $this->datetimeToDB($from):'') . ' + ' . ($until?' AND GREATEST(a.last_modified, i.last_modified, p.last_modified) <= ' . $this->datetimeToDB($until):'') . ' ' . ($submissionId?' AND a.submission_id = ?':'') . ' UNION SELECT dot.date_deleted AS last_modified, diff --git a/classes/plugins/PubIdPlugin.inc.php b/classes/plugins/PubIdPlugin.inc.php index 18bc6085c44..9d8beef777c 100644 --- a/classes/plugins/PubIdPlugin.inc.php +++ b/classes/plugins/PubIdPlugin.inc.php @@ -33,10 +33,10 @@ function manage($args, $request) { $suffixGenerationStrategy = $this->getSetting($context->getId(), $suffixFieldName); if ($suffixGenerationStrategy != 'customId') { $issueEnabled = $this->isObjectTypeEnabled('Issue', $context->getId()); - $submissionEnabled = $this->isObjectTypeEnabled('Publication', $context->getId()); + $publicationEnabled = $this->isObjectTypeEnabled('Publication', $context->getId()); $representationEnabled = $this->isObjectTypeEnabled('Representation', $context->getId()); if ($issueEnabled) { - $issueDao = DAORegistry::getDAO('IssueDAO'); /* @var $issueDao IssueDAO */ + $issueDao = DAORegistry::getDAO('IssueDAO'); /** @var IssueDAO $issueDao */ $issues = $issueDao->getPublishedIssues($context->getId()); while ($issue = $issues->next()) { $issuePubId = $issue->getStoredPubId($this->getPubIdType()); @@ -46,8 +46,8 @@ function manage($args, $request) { } } } - if ($submissionEnabled || $representationEnabled) { - $publicationDao = DAORegistry::getDAO('PublicationDAO'); /* @var $publicationDao PublicationDAO */ + if ($publicationEnabled || $representationEnabled) { + $publicationDao = DAORegistry::getDAO('PublicationDAO'); /** @var PublicationDAO $publicationDao */ $representationDao = Application::getRepresentationDAO(); $submissions = Services::get('submission')->getMany([ 'contextId' => $context->getId(), @@ -56,7 +56,7 @@ function manage($args, $request) { ]); foreach ($submissions as $submission) { $publications = $submission->getData('publications'); - if ($submissionEnabled) { + if ($publicationEnabled) { foreach ($publications as $publication) { $publicationPubId = $publication->getStoredPubId($this->getPubIdType()); if (empty($publicationPubId)) { @@ -102,11 +102,11 @@ function getPubObjectTypes() { * @copydoc PKPPubIdPlugin::checkDuplicate() */ function checkDuplicate($pubId, $pubObjectType, $excludeId, $contextId) { - $issueDao = DAORegistry::getDAO('IssueDAO'); /* @var $issueDao IssueDAO */ + $issueDao = DAORegistry::getDAO('IssueDAO'); /** @var IssueDAO $issueDao */ foreach ($this->getPubObjectTypes() as $type) { if ($type === 'Issue') { $excludeTypeId = $type === $pubObjectType ? $excludeId : null; - if ($issueDao->pubIdExists($type, $pubId, $excludeTypeId, $contextId)) { + if ($issueDao->pubIdExists($this->getPubIdType(), $pubId, $excludeTypeId, $contextId)) { return false; } } @@ -131,7 +131,9 @@ function getPubId($pubObject) { // Initialize variables for publication objects. $issue = ($pubObjectType == 'Issue' ? $pubObject : null); - $submission = ($pubObjectType == 'Submission' ? $pubObject : null); + $submission = null; + // Publication is actually handled differently now, but keep it here however for now. + $publication = ($pubObjectType == 'Publication' ? $pubObject : null); $representation = ($pubObjectType == 'Representation' ? $pubObject : null); $submissionFile = ($pubObjectType == 'SubmissionFile' ? $pubObject : null); @@ -159,7 +161,7 @@ function getPubId($pubObject) { // Retrieve the issue. if (!is_a($pubObject, 'Issue')) { assert(!is_null($submission)); - $issueDao = DAORegistry::getDAO('IssueDAO'); /* @var $issueDao IssueDAO */ + $issueDao = DAORegistry::getDAO('IssueDAO'); /** @var IssueDAO $issueDao */ $issue = $issueDao->getBySubmissionId($submission->getId(), $contextId); } if ($issue && $contextId != $issue->getJournalId()) return null; @@ -181,7 +183,7 @@ function getPubId($pubObject) { $pubIdSuffix = $this->getSetting($contextId, $suffixPatternsFieldNames[$pubObjectType]); // %j - journal initials, remove special characters and uncapitalize - $pubIdSuffix = PKPString::regexp_replace('/%j/', PKPString::regexp_replace('/[^A-Za-z0-9]/', '', PKPString::strtolower($context->getAcronym($context->getPrimaryLocale()))), $pubIdSuffix); + $pubIdSuffix = PKPString::regexp_replace('/%j/', PKPString::regexp_replace('/[^-._;()\/A-Za-z0-9]/', '', PKPString::strtolower($context->getAcronym($context->getPrimaryLocale()))), $pubIdSuffix); // %x - custom identifier if ($pubObject->getStoredPubId('publisher-id')) { @@ -219,7 +221,7 @@ function getPubId($pubObject) { break; default: - $pubIdSuffix = PKPString::regexp_replace('/[^A-Za-z0-9]/', '', PKPString::strtolower($context->getAcronym($context->getPrimaryLocale()))); + $pubIdSuffix = PKPString::regexp_replace('/[^-._;()\/A-Za-z0-9]/', '', PKPString::strtolower($context->getAcronym($context->getPrimaryLocale()))); if ($issue) { $pubIdSuffix .= '.v' . $issue->getVolume() . 'i' . $issue->getNumber(); @@ -255,10 +257,10 @@ function getPubId($pubObject) { * @param $issue Issue */ function clearIssueObjectsPubIds($issue) { - $submissionPubIdEnabled = $this->isObjectTypeEnabled('Submission', $issue->getJournalId()); + $publicationPubIdEnabled = $this->isObjectTypeEnabled('Publication', $issue->getJournalId()); $representationPubIdEnabled = $this->isObjectTypeEnabled('Representation', $issue->getJournalId()); $filePubIdEnabled = $this->isObjectTypeEnabled('SubmissionFile', $issue->getJournalId()); - if (!$submissionPubIdEnabled && !$representationPubIdEnabled && !$filePubIdEnabled) return false; + if (!$publicationPubIdEnabled && !$representationPubIdEnabled && !$filePubIdEnabled) return false; $pubIdType = $this->getPubIdType(); import('lib.pkp.classes.submission.SubmissionFile'); // SUBMISSION_FILE_... constants @@ -267,11 +269,11 @@ function clearIssueObjectsPubIds($issue) { 'contextId' => $issue->getJournalId(), 'issueIds' => $issue->getId(), ]); - $publicationDao = DAORegistry::getDAO('PublicationDAO'); /* @var $publicationDao PublicationDAO */ - $submissionFileDao = DAORegistry::getDAO('SubmissionFileDAO'); /* @var $submissionFileDao SubmissionFileDAO */ + $publicationDao = DAORegistry::getDAO('PublicationDAO'); /** @var PublicationDAO $publicationDao */ + $submissionFileDao = DAORegistry::getDAO('SubmissionFileDAO'); /** @var SubmissionFileDAO $submissionFileDao */ foreach ($submissionIds as $submissionId) { $submission = Services::get('submission')->get($submissionId); - if ($submissionPubIdEnabled) { // Does this option have to be enabled here for? + if ($publicationPubIdEnabled) { // Does this option have to be enabled here for? foreach ((array) $submission->getData('publications') as $publication) { $publicationDao->deletePubId($publication->getId(), $pubIdType); } diff --git a/classes/plugins/PubObjectsExportPlugin.inc.php b/classes/plugins/PubObjectsExportPlugin.inc.php index 88201147833..d4cd9fc1f16 100644 --- a/classes/plugins/PubObjectsExportPlugin.inc.php +++ b/classes/plugins/PubObjectsExportPlugin.inc.php @@ -55,7 +55,7 @@ function register($category, $path, $mainContextId = null) { AppLocale::requireComponents(LOCALE_COMPONENT_APP_MANAGER); $this->addLocaleData(); - + HookRegistry::register('AcronPlugin::parseCronTab', array($this, 'callbackParseCronTab')); foreach ($this->_getDAOs() as $dao) { if ($dao instanceof SchemaDAO) { @@ -194,7 +194,7 @@ function executeExportAction($request, $objects, $filter, $tab, $objectsFileName // Get the XML $exportXml = $this->exportXML($objects, $filter, $context, $noValidation); - + if ($onlyValidateExport) { if (isset($exportXml)) { $this->_sendNotification( @@ -440,7 +440,7 @@ function getAdditionalFieldNames($hookName, $args) { foreach ($this->_getObjectAdditionalSettings() as $fieldName) { $additionalFields[] = $fieldName; } - + return false; } @@ -561,6 +561,8 @@ function executeCLI($scriptName, &$args) { return; } + PluginRegistry::loadCategory('pubIds', true, $context->getId()); + if ($outputFile) { if ($this->isRelativePath($outputFile)) { $outputFile = PWD . '/' . $outputFile; diff --git a/classes/search/ArticleSearch.inc.php b/classes/search/ArticleSearch.inc.php index dad83933311..d7121ba93fe 100644 --- a/classes/search/ArticleSearch.inc.php +++ b/classes/search/ArticleSearch.inc.php @@ -25,9 +25,8 @@ public function getSparseArray($unorderedResults, $orderBy, $orderDir, $exclude) // Calculate a well-ordered (unique) score. $resultCount = count($unorderedResults); $i = 0; - foreach ($unorderedResults as $submissionId => &$data) { - // Reference is necessary to permit modification - $data['score'] = ($resultCount * $data['count']) + $i++; + foreach ($unorderedResults as $submissionId => $data) { + $unorderedResults[$submissionId]['score'] = ($resultCount * $data['count']) + $i++; } // If we got a primary sort order then apply it and use score as secondary @@ -189,7 +188,7 @@ public function getSearchFilters($request) { while ($context = $contexts->next()) { if (in_array( $request->getUserVar('journalTitle'), - (array) $context->getTitle(null) + (array) $context->getName(null) )) break; } } @@ -211,7 +210,7 @@ public function getKeywordsFromSearchFilters($searchFilters) { $indexFieldMap[SUBMISSION_SEARCH_INDEX_TERMS] = 'indexTerms'; $keywords = array(); if (isset($searchFilters['query'])) { - $keywords[null] = $searchFilters['query']; + $keywords[''] = $searchFilters['query']; } foreach($indexFieldMap as $bitmap => $searchField) { if (isset($searchFilters[$searchField]) && !empty($searchFilters[$searchField])) { @@ -310,7 +309,7 @@ public function getSimilarityTerms($submissionId) { if ($article->getData('status') === STATUS_PUBLISHED) { // Retrieve keywords (if any). $submissionSubjectDao = DAORegistry::getDAO('SubmissionKeywordDAO'); /* @var $submissionSubjectDao SubmissionKeywordDAO */ - $allSearchTerms = array_filter($submissionSubjectDao->getKeywords($article->getId(), array(AppLocale::getLocale(), $article->getLocale(), AppLocale::getPrimaryLocale()))); + $allSearchTerms = array_filter($submissionSubjectDao->getKeywords($article->getCurrentPublication()->getId(), array(AppLocale::getLocale(), $article->getLocale(), AppLocale::getPrimaryLocale()))); foreach ($allSearchTerms as $locale => $localeSearchTerms) { $searchTerms += $localeSearchTerms; } diff --git a/classes/search/ArticleSearchDAO.inc.php b/classes/search/ArticleSearchDAO.inc.php index fe21fd4af33..7d0a4ac595a 100644 --- a/classes/search/ArticleSearchDAO.inc.php +++ b/classes/search/ArticleSearchDAO.inc.php @@ -80,8 +80,12 @@ public function getPhraseResults($journal, $phrase, $publishedFrom = null, $publ JOIN publication_settings ps ON (ps.publication_id = p.publication_id AND ps.setting_name=\'issueId\' AND ps.locale=\'\') JOIN issues i ON (CAST(i.issue_id AS CHAR(20)) = ps.setting_value AND i.journal_id = s.context_id) JOIN submission_search_objects o ON (s.submission_id = o.submission_id) + JOIN journals j ON j.journal_id = s.context_id + LEFT JOIN journal_settings js ON j.journal_id = js.journal_id AND js.setting_name = \'publishingMode\' NATURAL JOIN ' . $sqlFrom . ' WHERE + (js.setting_value <> \'' . PUBLISHING_MODE_NONE . '\' OR + js.setting_value IS NULL) AND j.enabled = 1 AND s.status = ' . STATUS_PUBLISHED . ' AND i.published = 1 AND ' . $sqlWhere . ' GROUP BY o.submission_id diff --git a/classes/security/authorization/OjsIssueRequiredPolicy.inc.php b/classes/security/authorization/OjsIssueRequiredPolicy.inc.php index 7a9cfd29fa5..69710f1d55d 100644 --- a/classes/security/authorization/OjsIssueRequiredPolicy.inc.php +++ b/classes/security/authorization/OjsIssueRequiredPolicy.inc.php @@ -49,7 +49,7 @@ function dataObjectEffect() { // access to it. $userRoles = $this->getAuthorizedContextObject(ASSOC_TYPE_USER_ROLES); if (!$issue->getPublished() && count(array_intersect( - $userRoles, + (array) $userRoles, array( ROLE_ID_SITE_ADMIN, ROLE_ID_MANAGER, diff --git a/classes/services/GalleyService.inc.php b/classes/services/GalleyService.inc.php index c34f6385248..5ff4e10b487 100644 --- a/classes/services/GalleyService.inc.php +++ b/classes/services/GalleyService.inc.php @@ -200,7 +200,7 @@ public function validate($action, $props, $allowedLocales, $primaryLocale) { $schemaService->getValidationRules(SCHEMA_GALLEY, $allowedLocales), [ 'locale.regex' => __('validator.localeKey'), - 'urlPath.regex' => __('validator.alpha_dash'), + 'urlPath.regex' => __('validator.alpha_dash_period'), ] ); diff --git a/classes/services/queryBuilders/GalleyQueryBuilder.inc.php b/classes/services/queryBuilders/GalleyQueryBuilder.inc.php index e82c2200cab..1d1458201b8 100644 --- a/classes/services/queryBuilders/GalleyQueryBuilder.inc.php +++ b/classes/services/queryBuilders/GalleyQueryBuilder.inc.php @@ -41,13 +41,11 @@ public function filterByPublicationIds($publicationIds) { public function getCount() { return $this ->getQuery() - ->select('g.galley_id') - ->get() - ->count(); + ->getCountForPagination(); } /** - * @copydoc PKP\Services\QueryBuilders\Interfaces\EntityQueryBuilderInterface::getCount() + * @copydoc PKP\Services\QueryBuilders\Interfaces\EntityQueryBuilderInterface::getIds() */ public function getIds() { return $this @@ -58,7 +56,7 @@ public function getIds() { } /** - * @copydoc PKP\Services\QueryBuilders\Interfaces\EntityQueryBuilderInterface::getCount() + * @copydoc PKP\Services\QueryBuilders\Interfaces\EntityQueryBuilderInterface::getQuery() */ public function getQuery() { $this->columns = ['*']; diff --git a/classes/services/queryBuilders/IssueQueryBuilder.inc.php b/classes/services/queryBuilders/IssueQueryBuilder.inc.php index 7a9a877a448..483847b95da 100644 --- a/classes/services/queryBuilders/IssueQueryBuilder.inc.php +++ b/classes/services/queryBuilders/IssueQueryBuilder.inc.php @@ -202,9 +202,7 @@ public function offsetBy($offset) { public function getCount() { return $this ->getQuery() - ->select('i.issue_id') - ->get() - ->count(); + ->getCountForPagination(); } /** diff --git a/classes/submission/Submission.inc.php b/classes/submission/Submission.inc.php index 124e3f52d5c..297fe4b215e 100644 --- a/classes/submission/Submission.inc.php +++ b/classes/submission/Submission.inc.php @@ -232,7 +232,7 @@ function getLocalizedCoverImageUrl() { function getGalleys() { $galleys = $this->getData('galleys'); if (is_null($galleys)) { - $this->setData('galleys', Application::get()->getRepresentationDAO()->getByPublicationId($this->getCurrentPublication()->getId(), $this->getData('contextId'))->toArray()); + $this->setData('galleys', Application::get()->getRepresentationDAO()->getByPublicationId($this->getData('currentPublicationId'), $this->getData('contextId'))->toArray()); return $this->getData('galleys'); } return $galleys; diff --git a/classes/submission/SubmissionDAO.inc.php b/classes/submission/SubmissionDAO.inc.php index 9c50cfe43ec..08f594c71c5 100644 --- a/classes/submission/SubmissionDAO.inc.php +++ b/classes/submission/SubmissionDAO.inc.php @@ -117,34 +117,36 @@ function getExportable($contextId, $pubIdType = null, $title = null, $author = n $params[] = $pubIdSettingValue; } + $baseSql = ' + FROM submissions s + LEFT JOIN publications p ON s.current_publication_id = p.publication_id + LEFT JOIN publication_settings ps ON p.publication_id = ps.publication_id' + . ($issueId ? ' LEFT JOIN publication_settings psi ON p.publication_id = psi.publication_id AND psi.setting_name = \'issueId\' AND psi.locale = \'\'' : '') + . ($pubIdType != null?' LEFT JOIN publication_settings pspidt ON (p.publication_id = pspidt.publication_id)':'') + . ($title != null?' LEFT JOIN publication_settings pst ON (p.publication_id = pst.publication_id)':'') + . ($author != null?' LEFT JOIN authors au ON (p.publication_id = au.publication_id) + LEFT JOIN author_settings asgs ON (asgs.author_id = au.author_id AND asgs.setting_name = \''.IDENTITY_SETTING_GIVENNAME.'\') + LEFT JOIN author_settings asfs ON (asfs.author_id = au.author_id AND asfs.setting_name = \''.IDENTITY_SETTING_FAMILYNAME.'\') + ':'') + . ($pubIdSettingName != null?' LEFT JOIN submission_settings pss ON (s.submission_id = pss.submission_id AND pss.setting_name = ?)':'') . ' + WHERE s.status = ? + AND s.context_id = ?' + . ($pubIdType != null?' AND pspidt.setting_name = ? AND pspidt.setting_value IS NOT NULL':'') + . ($title != null?' AND (pst.setting_name = ? AND pst.setting_value LIKE ?)':'') + . ($author != null?' AND (asgs.setting_value LIKE ? OR asfs.setting_value LIKE ?)':'') + . ($issueId != null?' AND psi.setting_value = ?':'') + . (($pubIdSettingName != null && $pubIdSettingValue != null && $pubIdSettingValue == EXPORT_STATUS_NOT_DEPOSITED)?' AND pss.setting_value IS NULL':'') + . (($pubIdSettingName != null && $pubIdSettingValue != null && $pubIdSettingValue != EXPORT_STATUS_NOT_DEPOSITED)?' AND pss.setting_value = ?':'') + . (($pubIdSettingName != null && is_null($pubIdSettingValue))?' AND (pss.setting_value IS NULL OR pss.setting_value = \'\')':'') . ' + GROUP BY s.submission_id + '; + $result = $this->retrieveRange( - $sql = 'SELECT s.* - FROM submissions s - LEFT JOIN publications p ON s.current_publication_id = p.publication_id - LEFT JOIN publication_settings ps ON p.publication_id = ps.publication_id' - . ($issueId ? ' LEFT JOIN publication_settings psi ON p.publication_id = psi.publication_id AND psi.setting_name = \'issueId\' AND psi.locale = \'\'' : '') - . ($pubIdType != null?' LEFT JOIN publication_settings pspidt ON (p.publication_id = pspidt.publication_id)':'') - . ($title != null?' LEFT JOIN publication_settings pst ON (p.publication_id = pst.publication_id)':'') - . ($author != null?' LEFT JOIN authors au ON (p.publication_id = au.publication_id) - LEFT JOIN author_settings asgs ON (asgs.author_id = au.author_id AND asgs.setting_name = \''.IDENTITY_SETTING_GIVENNAME.'\') - LEFT JOIN author_settings asfs ON (asfs.author_id = au.author_id AND asfs.setting_name = \''.IDENTITY_SETTING_FAMILYNAME.'\') - ':'') - . ($pubIdSettingName != null?' LEFT JOIN submission_settings pss ON (s.submission_id = pss.submission_id AND pss.setting_name = ?)':'') - . ' WHERE s.status = ? - AND s.context_id = ?' - . ($pubIdType != null?' AND pspidt.setting_name = ? AND pspidt.setting_value IS NOT NULL':'') - . ($title != null?' AND (pst.setting_name = ? AND pst.setting_value LIKE ?)':'') - . ($author != null?' AND (asgs.setting_value LIKE ? OR asfs.setting_value LIKE ?)':'') - . ($issueId != null?' AND psi.setting_value = ?':'') - . (($pubIdSettingName != null && $pubIdSettingValue != null && $pubIdSettingValue == EXPORT_STATUS_NOT_DEPOSITED)?' AND pss.setting_value IS NULL':'') - . (($pubIdSettingName != null && $pubIdSettingValue != null && $pubIdSettingValue != EXPORT_STATUS_NOT_DEPOSITED)?' AND pss.setting_value = ?':'') - . (($pubIdSettingName != null && is_null($pubIdSettingValue))?' AND (pss.setting_value IS NULL OR pss.setting_value = \'\')':'') - . ' GROUP BY s.submission_id - ORDER BY MAX(p.date_published) DESC, s.submission_id DESC', + "SELECT s.* {$baseSql} ORDER BY MAX(p.date_published) DESC, s.submission_id DESC", $params, $rangeInfo ); - return new DAOResultFactory($result, $this, '_fromRow', [], $sql, $params, $rangeInfo); // Counted via paging in CrossRef export + return new DAOResultFactory($result, $this, '_fromRow', [], "SELECT 0 {$baseSql}", $params, $rangeInfo); // Counted via paging in CrossRef export } } diff --git a/classes/submission/form/SubmissionSubmitStep4Form.inc.php b/classes/submission/form/SubmissionSubmitStep4Form.inc.php index 76f52a025b3..d2b921c7a64 100644 --- a/classes/submission/form/SubmissionSubmitStep4Form.inc.php +++ b/classes/submission/form/SubmissionSubmitStep4Form.inc.php @@ -38,31 +38,48 @@ function execute(...$functionParams) { $submission = $this->submission; // Send author notification email import('classes.mail.ArticleMailTemplate'); - $mail = new ArticleMailTemplate($submission, 'SUBMISSION_ACK', null, null, false); - $authorMail = new ArticleMailTemplate($submission, 'SUBMISSION_ACK_NOT_USER', null, null, false); - $request = Application::get()->getRequest(); $context = $request->getContext(); $router = $request->getRouter(); + $user = $request->getUser(); + + $mail = new ArticleMailTemplate($submission, 'SUBMISSION_ACK', null, null, false); if ($mail->isEnabled()) { // submission ack emails should be from the contact. $mail->setFrom($this->context->getData('contactEmail'), $this->context->getData('contactName')); + $mail->addRecipient($user->getEmail(), $user->getFullName()); + + $mail->bccAssignedSubEditors($submission->getId(), WORKFLOW_STAGE_ID_SUBMISSION); + + $mail->assignParams([ + 'authorName' => htmlspecialchars($user->getFullName()), + 'authorUsername' => htmlspecialchars($user->getUsername()), + 'editorialContactSignature' => htmlspecialchars($context->getData('contactName')), + 'submissionUrl' => $router->url($request, null, 'authorDashboard', 'submission', $submission->getId()), + ]); + + if (!$mail->send($request)) { + import('classes.notification.NotificationManager'); + $notificationMgr = new NotificationManager(); + $notificationMgr->createTrivialNotification($request->getUser()->getId(), NOTIFICATION_TYPE_ERROR, array('contents' => __('email.compose.error'))); + } + } + + $authorMail = new ArticleMailTemplate($submission, 'SUBMISSION_ACK_NOT_USER', null, null, false); + if ($authorMail->isEnabled()) { $authorMail->setFrom($this->context->getData('contactEmail'), $this->context->getData('contactName')); - $user = $request->getUser(); $primaryAuthor = $submission->getPrimaryAuthor(); if (!isset($primaryAuthor)) { $authors = $submission->getAuthors(); $primaryAuthor = $authors[0]; } - $mail->addRecipient($user->getEmail(), $user->getFullName()); if ($user->getEmail() != $primaryAuthor->getEmail()) { $authorMail->addRecipient($primaryAuthor->getEmail(), $primaryAuthor->getFullName()); } $assignedAuthors = $submission->getAuthors(); - foreach ($assignedAuthors as $author) { $authorEmail = $author->getEmail(); // only add the author email if they have not already been added as the primary author @@ -71,34 +88,17 @@ function execute(...$functionParams) { $authorMail->addRecipient($author->getEmail(), $author->getFullName()); } } - $mail->bccAssignedSubEditors($submission->getId(), WORKFLOW_STAGE_ID_SUBMISSION); - - $mail->assignParams(array( - 'authorName' => $user->getFullName(), - 'authorUsername' => $user->getUsername(), - 'editorialContactSignature' => $context->getData('contactName'), - 'submissionUrl' => $router->url($request, null, 'authorDashboard', 'submission', $submission->getId()), - )); - $authorMail->assignParams(array( - 'submitterName' => $user->getFullName(), - 'editorialContactSignature' => $context->getData('contactName'), - )); + $authorMail->assignParams([ + 'submitterName' => htmlspecialchars($user->getFullName()), + 'editorialContactSignature' => htmlspecialchars($context->getData('contactName')), + ]); - if (!$mail->send($request)) { + if (!empty($authorMail->getRecipients()) && !$authorMail->send($request)) { import('classes.notification.NotificationManager'); $notificationMgr = new NotificationManager(); $notificationMgr->createTrivialNotification($request->getUser()->getId(), NOTIFICATION_TYPE_ERROR, array('contents' => __('email.compose.error'))); } - - $recipients = $authorMail->getRecipients(); - if (!empty($recipients)) { - if (!$authorMail->send($request)) { - import('classes.notification.NotificationManager'); - $notificationMgr = new NotificationManager(); - $notificationMgr->createTrivialNotification($request->getUser()->getId(), NOTIFICATION_TYPE_ERROR, array('contents' => __('email.compose.error'))); - } - } } // Log submission. diff --git a/classes/submission/reviewer/ReviewerSubmission.inc.php b/classes/submission/reviewer/ReviewerSubmission.inc.php index 7817e949f08..a309eea60c4 100644 --- a/classes/submission/reviewer/ReviewerSubmission.inc.php +++ b/classes/submission/reviewer/ReviewerSubmission.inc.php @@ -352,6 +352,22 @@ public function getStep() { public function setStep($step) { $this->setData('step', $step); } + + /** + * Get the Review Assignment StageId + * @return int + */ + public function getReviewAssignmentStageId(): int { + return $this->getData('reviewAssignmentStageId'); + } + + /** + * Set the Review Assignment StageId + * @param int $reviewAssignmentStageId + */ + public function setReviewAssignmentStageId(int $reviewAssignmentStageId) { + $this->setData('reviewAssignmentStageId', $reviewAssignmentStageId); + } } diff --git a/classes/submission/reviewer/ReviewerSubmissionDAO.inc.php b/classes/submission/reviewer/ReviewerSubmissionDAO.inc.php index 28d1f35e5d9..5955acf1374 100644 --- a/classes/submission/reviewer/ReviewerSubmissionDAO.inc.php +++ b/classes/submission/reviewer/ReviewerSubmissionDAO.inc.php @@ -45,9 +45,37 @@ function __construct() { function getReviewerSubmission($reviewId) { $primaryLocale = AppLocale::getPrimaryLocale(); $locale = AppLocale::getLocale(); + // Each column of review_assignments table is taken separately intentionaly so that + // same named columns like stage_id from different tables is treated separately in _fromRow function $result = $this->retrieve( 'SELECT a.*, - r.*, + r.review_id, + r.submission_id, + r.reviewer_id, + r.competing_interests, + r.recommendation, + r.date_assigned, + r.date_notified, + r.date_confirmed, + r.date_completed, + r.date_acknowledged, + r.date_due, + r.date_response_due, + r.last_modified as last_modified_ra, + r.reminder_was_automatic, + r.declined, + r.cancelled, + r.reviewer_file_id, + r.date_rated, + r.date_reminded, + r.quality, + r.review_round_id, + r.stage_id as stage_id_ra, + r.review_method, + r.round, + r.step, + r.review_form_id, + r.unconsidered, p.date_published, COALESCE(stl.setting_value, stpl.setting_value) AS section_title, COALESCE(sal.setting_value, sapl.setting_value) AS section_abbrev @@ -114,6 +142,7 @@ function _fromRow($row) { $reviewerSubmission->setRound($row['round']); $reviewerSubmission->setStep($row['step']); $reviewerSubmission->setStageId($row['stage_id']); + $reviewerSubmission->setReviewAssignmentStageId($row['stage_id_ra']); $reviewerSubmission->setReviewMethod($row['review_method']); HookRegistry::call('ReviewerSubmissionDAO::_fromRow', array(&$reviewerSubmission, &$row)); @@ -122,9 +151,9 @@ function _fromRow($row) { /** * Update an existing review submission. - * @param $reviewSubmission ReviewSubmission + * @param ReviewerSubmission $reviewSubmission */ - function updateReviewerSubmission($reviewerSubmission) { + function updateReviewerSubmission(ReviewerSubmission $reviewerSubmission) { $this->update( sprintf('UPDATE review_assignments SET submission_id = ?, @@ -156,7 +185,7 @@ function updateReviewerSubmission($reviewerSubmission) { [ (int) $reviewerSubmission->getId(), (int) $reviewerSubmission->getReviewerId(), - (int) $reviewerSubmission->getStageId(), + (int) $reviewerSubmission->getReviewAssignmentStageId(), (int) $reviewerSubmission->getReviewMethod(), (int) $reviewerSubmission->getRound(), (int) $reviewerSubmission->getStep(), diff --git a/classes/subscription/IndividualSubscriptionDAO.inc.php b/classes/subscription/IndividualSubscriptionDAO.inc.php index ed7257e876d..1c24547554d 100644 --- a/classes/subscription/IndividualSubscriptionDAO.inc.php +++ b/classes/subscription/IndividualSubscriptionDAO.inc.php @@ -237,12 +237,10 @@ function deleteById($subscriptionId, $journalId = null) { function deleteByJournalId($journalId) { $result = $this->retrieve('SELECT subscription_id FROM subscriptions WHERE journal_id = ?', [(int) $journalId]); - $returner = true; foreach ($result as $row) { - $returner = $this->deleteById($row->subscription_id); - if (!$returner) break; + $this->deleteById($row->subscription_id); } - return $returner; + return true; } /** @@ -253,12 +251,10 @@ function deleteByJournalId($journalId) { function deleteByUserId($userId) { $result = $this->retrieve('SELECT subscription_id FROM subscriptions WHERE user_id = ?', [(int) $userId]); - $returner = true; foreach ($result as $row) { - $returner = $this->deleteById($row->subscription_id); - if (!$returner) break; + $this->deleteById($row->subscription_id); } - return $returner; + return true; } /** @@ -270,12 +266,10 @@ function deleteByUserId($userId) { function deleteByUserIdForJournal($userId, $journalId) { $result = $this->retrieve('SELECT subscription_id FROM subscriptions WHERE user_id = ? AND journal_id = ?', [(int) $userId, (int) $journalId]); - $returner = true; foreach ($result as $row) { - $returner = $this->deleteById($row->subscription_id); - if (!$returner) break; + $this->deleteById($row->subscription_id); } - return $returner; + return true; } /** @@ -286,13 +280,11 @@ function deleteByUserIdForJournal($userId, $journalId) { function deleteByTypeId($subscriptionTypeId) { $result = $this->retrieve('SELECT subscription_id FROM subscriptions WHERE type_id = ?', [(int) $subscriptionTypeId]); - $returner = true; foreach ($result as $row) { - $returner = $this->deleteById($row->subscription_id); - if (!$returner) break; + $this->deleteById($row->subscription_id); } - return $returner; + return true; } /** @@ -337,21 +329,21 @@ function getAll($rangeInfo = null) { function getByJournalId($journalId, $status = null, $searchField = null, $searchMatch = null, $search = null, $dateField = null, $dateFrom = null, $dateTo = null, $rangeInfo = null) { $userDao = DAORegistry::getDAO('UserDAO'); /* @var $userDao UserDAO */ $params = array_merge($userDao->getFetchParameters(), [(int) $journalId]); - $result = $this->retrieveRange( - $sql = 'SELECT s.*, - ' . $userDao->getFetchColumns() . ' + $baseSql = ' FROM subscriptions s - JOIN subscription_types st ON (s.type_id = st.type_id) - JOIN users u ON (s.user_id = u.user_id) - ' . $userDao->getFetchJoins() . ' + JOIN subscription_types st ON (s.type_id = st.type_id) + JOIN users u ON (s.user_id = u.user_id) + ' . $userDao->getFetchJoins() . ' WHERE st.institutional = 0 AND s.journal_id = ? ' . - parent::_generateSearchSQL($status, $searchField, $searchMatch, $search, $dateField, $dateFrom, $dateTo, $params) . ' ' . - $userDao->getOrderBy() .', s.subscription_id', + parent::_generateSearchSQL($status, $searchField, $searchMatch, $search, $dateField, $dateFrom, $dateTo, $params) . ' + '; + $result = $this->retrieveRange( + 'SELECT s.*, ' . $userDao->getFetchColumns() . $baseSql . $userDao->getOrderBy() . ', s.subscription_id', $params, $rangeInfo ); - return new DAOResultFactory($result, $this, '_fromRow', [], $sql, $params, $rangeInfo); // Counted in subscription grid paging + return new DAOResultFactory($result, $this, '_fromRow', [], "SELECT 0 {$baseSql}", $params, $rangeInfo); // Counted in subscription grid paging } /** @@ -394,7 +386,7 @@ function isValidIndividualSubscription($userId, $journalId, $check = SUBSCRIPTIO AND s.journal_id = ? AND s.status = ' . SUBSCRIPTION_STATUS_ACTIVE . ' AND st.institutional = 0 - AND ((st.non_expiring = 1) OR (st.non_expiring = 0 AND (' . $dateSql . '))) + AND (st.duration IS NULL OR (' . $dateSql . ')) AND (st.format = ' . SUBSCRIPTION_TYPE_FORMAT_ONLINE . ' OR st.format = ' . SUBSCRIPTION_TYPE_FORMAT_PRINT_ONLINE . ')', [(int) $userId, (int) $journalId] diff --git a/classes/subscription/InstitutionalSubscriptionDAO.inc.php b/classes/subscription/InstitutionalSubscriptionDAO.inc.php index 25bab0b86a0..7eee3ae80aa 100644 --- a/classes/subscription/InstitutionalSubscriptionDAO.inc.php +++ b/classes/subscription/InstitutionalSubscriptionDAO.inc.php @@ -415,25 +415,28 @@ function getByJournalId($journalId, $status = null, $searchField = null, $search break; } - - $result = $this->retrieveRange( - $sql = 'SELECT DISTINCT s.*, iss.institution_name, iss.mailing_address, iss.domain, - ' . $userDao->getFetchColumns() .' - FROM subscriptions s - JOIN subscription_types st ON (s.type_id = st.type_id) - JOIN users u ON (s.user_id = u.user_id) - JOIN institutional_subscriptions iss ON (s.subscription_id = iss.subscription_id) - ' . $userDao->getFetchJoins() . ' - ' . $ipRangeSql1 . ' - WHERE st.institutional = 1 + $baseSql = ' + FROM subscriptions s + JOIN subscription_types st ON (s.type_id = st.type_id) + JOIN users u ON (s.user_id = u.user_id) + JOIN institutional_subscriptions iss ON (s.subscription_id = iss.subscription_id) + ' . $userDao->getFetchJoins() . ' + ' . $ipRangeSql1 . ' + WHERE st.institutional = 1 ' . $ipRangeSql2 . ' AND s.journal_id = ? - ' . $searchSql . ' ORDER BY iss.institution_name ASC, s.subscription_id', + ' . $searchSql . ' + '; + + $result = $this->retrieveRange( + 'SELECT DISTINCT s.*, iss.institution_name, iss.mailing_address, iss.domain, ' . $userDao->getFetchColumns() . " + {$baseSql} + ORDER BY iss.institution_name ASC, s.subscription_id", $params, $rangeInfo ); - return new DAOResultFactory($result, $this, '_fromRow', [], $sql, $params, $rangeInfo); // Counted in subscription grid paging + return new DAOResultFactory($result, $this, '_fromRow', [], "SELECT 0 {$baseSql}", $params, $rangeInfo); // Counted in subscription grid paging } /** @@ -479,7 +482,7 @@ function isValidInstitutionalSubscription($domain, $IP, $journalId, $check = SUB AND s.journal_id = ? AND s.status = ' . SUBSCRIPTION_STATUS_ACTIVE . ' AND st.institutional = 1 - AND ((st.non_expiring = 1) OR (st.non_expiring = 0 AND (' . $dateSql . '))) + AND (st.duration IS NULL OR (' . $dateSql . ')) AND (st.format = ' . SUBSCRIPTION_TYPE_FORMAT_ONLINE . ' OR st.format = ' . SUBSCRIPTION_TYPE_FORMAT_PRINT_ONLINE . ')', [$domain, $domain, (int) $journalId] @@ -502,7 +505,7 @@ function isValidInstitutionalSubscription($domain, $IP, $journalId, $check = SUB AND s.journal_id = ? AND s.status = ' . SUBSCRIPTION_STATUS_ACTIVE . ' AND st.institutional = 1 - AND ((st.non_expiring = 1) OR (st.non_expiring = 0 AND (' . $dateSql . '))) + AND (st.duration IS NULL OR (' . $dateSql . ')) AND (st.format = ' . SUBSCRIPTION_TYPE_FORMAT_ONLINE . ' OR st.format = ' . SUBSCRIPTION_TYPE_FORMAT_PRINT_ONLINE . ')) OR (isip.ip_end IS NULL @@ -510,7 +513,7 @@ function isValidInstitutionalSubscription($domain, $IP, $journalId, $check = SUB AND s.journal_id = ? AND s.status = ' . SUBSCRIPTION_STATUS_ACTIVE . ' AND st.institutional = 1 - AND ((st.non_expiring = 1) OR (st.non_expiring = 0 AND (' . $dateSql . '))) + AND (st.duration IS NULL OR (' . $dateSql . ')) AND (st.format = ' . SUBSCRIPTION_TYPE_FORMAT_ONLINE . ' OR st.format = ' . SUBSCRIPTION_TYPE_FORMAT_PRINT_ONLINE . ')))', [$IP, $IP, (int) $journalId, $IP, (int) $journalId] @@ -620,7 +623,7 @@ function _insertSubscriptionIPRanges($subscriptionId, $ipRanges) { $returner = true; - while (list(, $curIPString) = each($ipRanges)) { + foreach ($ipRanges as $curIPString) { $ipStart = null; $ipEnd = null; diff --git a/classes/subscription/SubscriptionAction.inc.php b/classes/subscription/SubscriptionAction.inc.php index cbac2f45ce7..3cfc590411b 100644 --- a/classes/subscription/SubscriptionAction.inc.php +++ b/classes/subscription/SubscriptionAction.inc.php @@ -48,11 +48,11 @@ function sendOnlinePaymentNotificationEmail($request, $subscription, $mailTempla $subscriptionTypeDao = DAORegistry::getDAO('SubscriptionTypeDAO'); /* @var $subscriptionTypeDao SubscriptionTypeDAO */ $subscriptionType = $subscriptionTypeDao->getById($subscription->getTypeId(), $journal->getId()); - $paramArray = array( - 'subscriptionType' => $subscriptionType->getSummaryString(), + $paramArray = [ + 'subscriptionType' => htmlspecialchars($subscriptionType->getSummaryString()), 'userDetails' => $user->getContactSignature(), - 'membership' => $subscription->getMembership() - ); + 'membership' => htmlspecialchars($subscription->getMembership()) + ]; switch($mailTemplateKey) { case 'SUBSCRIPTION_PURCHASE_INDL': @@ -62,9 +62,9 @@ function sendOnlinePaymentNotificationEmail($request, $subscription, $mailTempla case 'SUBSCRIPTION_PURCHASE_INSTL': case 'SUBSCRIPTION_RENEW_INSTL': $paramArray['subscriptionUrl'] = $request->url($journal->getPath(), 'payments', null, null, null, 'institutional'); - $paramArray['institutionName'] = $subscription->getInstitutionName(); - $paramArray['institutionMailingAddress'] = $subscription->getInstitutionMailingAddress(); - $paramArray['domain'] = $subscription->getDomain(); + $paramArray['institutionName'] = htmlspecialchars($subscription->getInstitutionName()); + $paramArray['institutionMailingAddress'] = nl2br(htmlspecialchars($subscription->getInstitutionMailingAddress())); + $paramArray['domain'] = htmlspecialchars($subscription->getDomain()); $paramArray['ipRanges'] = $subscription->getIPRangesString(); break; } @@ -73,10 +73,8 @@ function sendOnlinePaymentNotificationEmail($request, $subscription, $mailTempla $mail = new MailTemplate($mailTemplateKey); $mail->setReplyTo($subscriptionContactEmail, $subscriptionContactName); $mail->addRecipient($subscriptionContactEmail, $subscriptionContactName); - $mail->setSubject($mail->getSubject($journal->getPrimaryLocale())); - $mail->setBody($mail->getBody($journal->getPrimaryLocale())); $mail->assignParams($paramArray); - if (!$mail->send()) { + if ($mail->isEnabled() && !$mail->send()) { import('classes.notification.NotificationManager'); $notificationMgr = new NotificationManager(); $notificationMgr->createTrivialNotification($request->getUser()->getId(), NOTIFICATION_TYPE_ERROR, array('contents' => __('email.compose.error'))); diff --git a/classes/subscription/SubscriptionDAO.inc.php b/classes/subscription/SubscriptionDAO.inc.php index 164f57c3940..785c5fb904b 100644 --- a/classes/subscription/SubscriptionDAO.inc.php +++ b/classes/subscription/SubscriptionDAO.inc.php @@ -178,7 +178,7 @@ abstract function renewSubscription($subscription); * Internal function to generate subscription based search query. * @return string */ - protected function _generateSearchSQL($status = null, $searchField = null, $searchMatch = null, $search = null, $dateField = null, $dateFrom = null, $dateTo = null, &$params) { + protected function _generateSearchSQL($status = null, $searchField = null, $searchMatch = null, $search = null, $dateField = null, $dateFrom = null, $dateTo = null, &$params = null) { $searchSql = ''; if (!empty($search)) switch ($searchField) { diff --git a/classes/subscription/SubscriptionTypeDAO.inc.php b/classes/subscription/SubscriptionTypeDAO.inc.php index a8e069c90cf..b416ac64ac0 100644 --- a/classes/subscription/SubscriptionTypeDAO.inc.php +++ b/classes/subscription/SubscriptionTypeDAO.inc.php @@ -266,11 +266,14 @@ function deleteByJournal($journalId) { * @return object DAOResultFactory containing matching SubscriptionTypes */ function getByJournalId($journalId, $rangeInfo = null) { + $baseSql = ' + FROM subscription_types WHERE journal_id = ? + '; $result = $this->retrieveRange( - $sql = 'SELECT * FROM subscription_types WHERE journal_id = ? ORDER BY seq', + "SELECT * {$baseSql} ORDER BY seq", $params = [(int) $journalId], $rangeInfo); - return new DAOResultFactory($result, $this, '_fromRow', [], $sql, $params, $rangeInfo); // Counted in subscription type grid paging + return new DAOResultFactory($result, $this, '_fromRow', [], "SELECT 0 {$baseSql}", $params, $rangeInfo); // Counted in subscription type grid paging } /** diff --git a/classes/subscription/form/PaymentTypesForm.inc.php b/classes/subscription/form/PaymentTypesForm.inc.php index aefc805f534..509c14c8749 100644 --- a/classes/subscription/form/PaymentTypesForm.inc.php +++ b/classes/subscription/form/PaymentTypesForm.inc.php @@ -49,6 +49,7 @@ public function __construct() { $this->addCheck(new FormValidatorCustom($this, 'membershipFee', 'optional', 'manager.payment.form.numeric', function($membershipFee) { return is_numeric($membershipFee) && $membershipFee >= 0; })); + $this->addCheck(new FormValidatorCSRF($this)); } /** diff --git a/classes/subscription/form/SubscriptionForm.inc.php b/classes/subscription/form/SubscriptionForm.inc.php index 777787bcc49..1c352c3166b 100644 --- a/classes/subscription/form/SubscriptionForm.inc.php +++ b/classes/subscription/form/SubscriptionForm.inc.php @@ -13,6 +13,8 @@ * @brief Base form class for subscription create/edits. */ +use function PHP81_BC\strftime; + import('lib.pkp.classes.form.Form'); class SubscriptionForm extends Form { @@ -68,8 +70,7 @@ public function __construct($template, $subscriptionId = null) { // Subscription type is provided $this->addCheck(new FormValidator($this, 'typeId', 'required', 'manager.subscriptions.form.typeIdRequired')); // Notify email flag is valid value - $this->addCheck(new FormValidatorInSet($this, 'notifyEmail', 'optional', 'manager.subscriptions.form.notifyEmailValid', array('1'))); - + $this->addCheck(new FormValidatorBoolean($this, 'notifyEmail', 'manager.subscriptions.form.notifyEmailValid')); $this->addCheck(new FormValidatorPost($this)); $this->addCheck(new FormValidatorCSRF($this)); } @@ -171,8 +172,8 @@ public function readInputData() { } // If notify email is requested, ensure subscription contact name and email exist. - if ($this->_data['notifyEmail'] == 1) { - $this->addCheck(new FormValidatorCustom($this, 'notifyEmail', 'required', 'manager.subscriptions.form.subscriptionContactRequired', function() { + if ($this->getData('notifyEmail')) { + $this->addCheck(new FormValidatorCustom($this, 'notifyEmail', 'optional', 'manager.subscriptions.form.subscriptionContactRequired', function() { $request = Application::get()->getRequest(); $journal = $request->getJournal(); $subscriptionName = $journal->getData('subscriptionName'); @@ -219,7 +220,7 @@ protected function _prepareNotificationEmail($mailTemplateKey) { $request = Application::get()->getRequest(); $journal = $request->getJournal(); - $journalName = $journal->getLocalizedTitle(); + $journalName = $journal->getLocalizedName(); $user = $userDao->getById($this->subscription->getUserId()); $subscriptionType = $subscriptionTypeDao->getById($this->subscription->getTypeId()); @@ -227,32 +228,28 @@ protected function _prepareNotificationEmail($mailTemplateKey) { $subscriptionEmail = $journal->getData('subscriptionEmail'); $subscriptionPhone = $journal->getData('subscriptionPhone'); $subscriptionMailingAddress = $journal->getData('subscriptionMailingAddress'); - $subscriptionContactSignature = $subscriptionName; + $subscriptionContactSignature = htmlspecialchars($subscriptionName); if ($subscriptionMailingAddress != '') { - $subscriptionContactSignature .= "\n" . $subscriptionMailingAddress; + $subscriptionContactSignature .= "\n" . htmlspecialchars($subscriptionMailingAddress); } if ($subscriptionPhone != '') { - $subscriptionContactSignature .= "\n" . __('user.phone') . ': ' . $subscriptionPhone; + $subscriptionContactSignature .= "\n" . __('user.phone') . ': ' . htmlspecialchars($subscriptionPhone); } - $subscriptionContactSignature .= "\n" . __('user.email') . ': ' . $subscriptionEmail; - - $paramArray = array( - 'subscriberName' => $user->getFullName(), - 'journalName' => $journalName, - 'subscriptionType' => $subscriptionType->getSummaryString(), - 'username' => $user->getUsername(), - 'subscriptionContactSignature' => $subscriptionContactSignature - ); + $subscriptionContactSignature .= "\n" . __('user.email') . ': ' . htmlspecialchars($subscriptionEmail); import('lib.pkp.classes.mail.MailTemplate'); - $mail = new MailTemplate($mailTemplateKey); + $mail = new MailTemplate($mailTemplateKey, $journal->getPrimaryLocale()); $mail->setReplyTo($subscriptionEmail, $subscriptionName); $mail->addRecipient($user->getEmail(), $user->getFullName()); - $mail->setSubject($mail->getSubject($journal->getPrimaryLocale())); - $mail->setBody($mail->getBody($journal->getPrimaryLocale())); - $mail->assignParams($paramArray); + $mail->assignParams([ + 'subscriberName' => htmlspecialchars($user->getFullName()), + 'journalName' => htmlspecialchars($journalName), + 'subscriptionType' => htmlspecialchars($subscriptionType->getSummaryString()), + 'username' => htmlspecialchars($user->getUsername()), + 'subscriptionContactSignature' => nl2br($subscriptionContactSignature), + ]); return $mail; } diff --git a/classes/subscription/form/SubscriptionPolicyForm.inc.php b/classes/subscription/form/SubscriptionPolicyForm.inc.php index 89da3a5989b..51de8ae3c7a 100644 --- a/classes/subscription/form/SubscriptionPolicyForm.inc.php +++ b/classes/subscription/form/SubscriptionPolicyForm.inc.php @@ -133,11 +133,6 @@ public function initData() { */ public function readInputData() { $this->readUserVars(array('subscriptionName', 'subscriptionEmail', 'subscriptionPhone', 'subscriptionMailingAddress', 'subscriptionAdditionalInformation', 'enableOpenAccessNotification', 'subscriptionExpiryPartial', 'enableSubscriptionOnlinePaymentNotificationPurchaseIndividual', 'enableSubscriptionOnlinePaymentNotificationPurchaseInstitutional', 'enableSubscriptionOnlinePaymentNotificationRenewIndividual', 'enableSubscriptionOnlinePaymentNotificationRenewInstitutional', 'numMonthsBeforeSubscriptionExpiryReminder', 'numWeeksBeforeSubscriptionExpiryReminder', 'numWeeksAfterSubscriptionExpiryReminder', 'numMonthsAfterSubscriptionExpiryReminder')); - - $this->addCheck(new FormValidatorInSet($this, 'numMonthsBeforeSubscriptionExpiryReminder', 'required', 'manager.subscriptionPolicies.numMonthsBeforeSubscriptionExpiryReminderValid', array_keys($this->validNumMonthsBeforeExpiry))); - $this->addCheck(new FormValidatorInSet($this, 'numWeeksBeforeSubscriptionExpiryReminder', 'required', 'manager.subscriptionPolicies.numWeeksBeforeSubscriptionExpiryReminderValid', array_keys($this->validNumWeeksBeforeExpiry))); - $this->addCheck(new FormValidatorInSet($this, 'numMonthsAfterSubscriptionExpiryReminder', 'required', 'manager.subscriptionPolicies.numMonthsAfterSubscriptionExpiryReminderValid', array_keys($this->validNumMonthsAfterExpiry))); - $this->addCheck(new FormValidatorInSet($this, 'numWeeksAfterSubscriptionExpiryReminder', 'required', 'manager.subscriptionPolicies.numWeeksAfterSubscriptionExpiryReminderValid', array_keys($this->validNumWeeksAfterExpiry))); } /** diff --git a/classes/tasks/OpenAccessNotification.inc.php b/classes/tasks/OpenAccessNotification.inc.php index 8a3b4c273ab..2c5225ceda9 100644 --- a/classes/tasks/OpenAccessNotification.inc.php +++ b/classes/tasks/OpenAccessNotification.inc.php @@ -42,12 +42,11 @@ public function sendNotification ($users, $journal, $issue) { $email->addRecipient($journal->getData('contactEmail'), $journal->getData('contactName')); $request = Application::get()->getRequest(); - $paramArray = array( - 'journalName' => $journal->getLocalizedName(), + $email->assignParams([ + 'journalName' => htmlspecialchars($journal->getLocalizedName()), 'journalUrl' => $request->url($journal->getPath()), - 'editorialContactSignature' => $journal->getData('contactName') . "\n" . $journal->getLocalizedName(), - ); - $email->assignParams($paramArray); + 'editorialContactSignature' => htmlspecialchars($journal->getData('contactName') . "\n" . $journal->getLocalizedName()), + ]); $submissions = Services::get('submission')->getInSections($issue->getId()); $mimeBoundary = '==boundary_' . md5(microtime()); @@ -68,7 +67,9 @@ public function sendNotification ($users, $journal, $issue) { while ($user = $users->next()) { $email->addBcc($user->getEmail(), $user->getFullName()); } - $email->send(); + if ($email->isEnabled()) { + $email->send(); + } } } diff --git a/classes/tasks/SubscriptionExpiryReminder.inc.php b/classes/tasks/SubscriptionExpiryReminder.inc.php index 25f652f8ead..3411af12beb 100644 --- a/classes/tasks/SubscriptionExpiryReminder.inc.php +++ b/classes/tasks/SubscriptionExpiryReminder.inc.php @@ -45,27 +45,18 @@ protected function sendReminder ($subscription, $journal, $emailKey) { $subscriptionPhone = $journal->getData('subscriptionPhone'); $subscriptionMailingAddress = $journal->getData('subscriptionMailingAddress'); - $subscriptionContactSignature = $subscriptionName; + $subscriptionContactSignature = htmlspecialchars($subscriptionName); AppLocale::requireComponents(LOCALE_COMPONENT_PKP_USER, LOCALE_COMPONENT_APP_COMMON); if ($subscriptionMailingAddress != '') { - $subscriptionContactSignature .= "\n" . $subscriptionMailingAddress; + $subscriptionContactSignature .= "\n" . htmlspecialchars($subscriptionMailingAddress); } if ($subscriptionPhone != '') { - $subscriptionContactSignature .= "\n" . __('user.phone') . ': ' . $subscriptionPhone; + $subscriptionContactSignature .= "\n" . __('user.phone') . ': ' . htmlspecialchars($subscriptionPhone); } - $subscriptionContactSignature .= "\n" . __('user.email') . ': ' . $subscriptionEmail; - - $paramArray = array( - 'subscriberName' => $user->getFullName(), - 'journalName' => $journalName, - 'subscriptionType' => $subscriptionType->getSummaryString(), - 'expiryDate' => $subscription->getDateEnd(), - 'username' => $user->getUsername(), - 'subscriptionContactSignature' => $subscriptionContactSignature - ); + $subscriptionContactSignature .= "\n" . __('user.email') . ': ' . htmlspecialchars($subscriptionEmail); import('lib.pkp.classes.mail.MailTemplate'); $mail = new MailTemplate($emailKey, $journal->getPrimaryLocale(), $journal, false); @@ -74,8 +65,17 @@ protected function sendReminder ($subscription, $journal, $emailKey) { $mail->addRecipient($user->getEmail(), $user->getFullName()); $mail->setSubject($mail->getSubject($journal->getPrimaryLocale())); $mail->setBody($mail->getBody($journal->getPrimaryLocale())); - $mail->assignParams($paramArray); - $mail->send(); + $mail->assignParams([ + 'subscriberName' => htmlspecialchars($user->getFullName()), + 'journalName' => htmlspecialchars($journalName), + 'subscriptionType' => htmlspecialchars($subscriptionType->getSummaryString()), + 'expiryDate' => $subscription->getDateEnd(), + 'username' => htmlspecialchars($user->getUsername()), + 'subscriptionContactSignature' => nl2br($subscriptionContactSignature) + ]); + if ($mail->isEnabled()) { + $mail->send(); + } } /** diff --git a/config.TEMPLATE.inc.php b/config.TEMPLATE.inc.php index 8d7fb137b94..48c56dc0ff2 100644 --- a/config.TEMPLATE.inc.php +++ b/config.TEMPLATE.inc.php @@ -41,6 +41,11 @@ ; (set to 0 to force expiration at end of current session) session_lifetime = 30 +; SameSite configuration for the cookie, see possible values and explanations +; at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite +; To set the "Secure" attribute for the cookie see the setting force_ssl at the [security] group +session_samesite = Lax + ; Enable support for running scheduled tasks ; Set this to On if you have set up the scheduled tasks script to ; execute periodically @@ -88,8 +93,16 @@ ; See FAQ for more details. restful_urls = Off +; Restrict the list of allowed hosts to prevent HOST header injection. +; See docs/README.md for more details. The list should be JSON-formatted. +; An empty string indicates that all hosts should be trusted (not recommended!) +; Example: +; allowed_hosts = '["myjournal.tld", "anotherjournal.tld", "mylibrary.tld"]' +allowed_hosts = '' + ; Allow the X_FORWARDED_FOR header to override the REMOTE_ADDR as the source IP -; Set this to "On" if you are behind a reverse proxy and you control the X_FORWARDED_FOR +; Set this to "On" if you are behind a reverse proxy and you control the +; X_FORWARDED_FOR header. ; Warning: This defaults to "On" if unset for backwards compatibility. trust_x_forwarded_for = Off @@ -105,7 +118,7 @@ ; Set the following parameter to off if you want to work with the uncompiled (non-minified) JavaScript ; source for debugging or if you are working off a development branch without compiled JavaScript. -enable_minified = Off +enable_minified = On ; Provide a unique site ID and OAI base URL to PKP for statistics and security ; alert purposes only. @@ -237,7 +250,8 @@ [security] -; Force SSL connections site-wide +; Force SSL connections site-wide and also sets the "Secure" flag for session cookies +; See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#secure force_ssl = Off ; Force SSL connections for login only @@ -306,10 +320,22 @@ ; smtp_port = 25 ; Enable SMTP authentication -; Supported mechanisms: ssl, tls +; Supported smtp_auth: ssl, tls (see PHPMailer SMTPSecure) ; smtp_auth = ssl ; smtp_username = username ; smtp_password = password +; +; Supported smtp_authtype: RAM-MD5, LOGIN, PLAIN, XOAUTH2 (see PHPMailer AuthType) +; (Leave blank to try them in that order) +; smtp_authtype = + +; The following are required for smtp_authtype = XOAUTH2 (e.g. GMail OAuth) +; (See https://github.com/PHPMailer/PHPMailer/wiki/Using-Gmail-with-XOAUTH2) +; smtp_oauth_provider = Google +; smtp_oauth_email = +; smtp_oauth_clientid = +; smtp_oauth_clientsecret = +; smtp_oauth_refreshtoken = ; Enable suppressing verification of SMTP certificate in PHPMailer ; Note: this is not recommended per PHPMailer documentation @@ -402,7 +428,8 @@ ; Enable OAI front-end to the site oai = On -; OAI Repository identifier +; OAI Repository identifier. This setting forms part of OAI-PMH record IDs. +; Changing this setting may affect existing clients and is not recommended. repository_id = ojs.pkp.sfu.ca ; Maximum number of records per request to serve via OAI diff --git a/controllers/grid/articleGalleys/ArticleGalleyGridHandler.inc.php b/controllers/grid/articleGalleys/ArticleGalleyGridHandler.inc.php index a54424da003..4f049dcada8 100644 --- a/controllers/grid/articleGalleys/ArticleGalleyGridHandler.inc.php +++ b/controllers/grid/articleGalleys/ArticleGalleyGridHandler.inc.php @@ -223,10 +223,9 @@ function loadData($request, $filter = null) { * @return JSONMessage JSON object */ function identifiers($args, $request) { - $representationDao = Application::getRepresentationDAO(); - $representation = $representationDao->getById($request->getUserVar('representationId')); + $representation = $this->getGalley(); import('controllers.tab.pubIds.form.PublicIdentifiersForm'); - $form = new PublicIdentifiersForm($representation); + $form = new PublicIdentifiersForm($representation, null, null, $this->canEdit()); $form->initData(); return new JSONMessage(true, $form->fetch($request)); } @@ -238,10 +237,9 @@ function identifiers($args, $request) { * @return JSONMessage JSON object */ function updateIdentifiers($args, $request) { - $representationDao = Application::getRepresentationDAO(); - $representation = $representationDao->getById($request->getUserVar('representationId')); + $representation = $this->getGalley(); import('controllers.tab.pubIds.form.PublicIdentifiersForm'); - $form = new PublicIdentifiersForm($representation, null, array_merge($this->getRequestArgs(), ['representationId' => $representation->getId()])); + $form = new PublicIdentifiersForm($representation, null, array_merge($this->getRequestArgs(), ['representationId' => $representation->getId()]), $this->canEdit()); $form->readInputData(); if ($form->validate()) { $form->execute(); @@ -261,10 +259,9 @@ function clearPubId($args, $request) { if (!$request->checkCSRF()) return new JSONMessage(false); $submission = $this->getSubmission(); - $representationDao = Application::getRepresentationDAO(); - $representation = $representationDao->getById($request->getUserVar('representationId')); + $representation = $this->getGalley(); import('controllers.tab.pubIds.form.PublicIdentifiersForm'); - $form = new PublicIdentifiersForm($representation); + $form = new PublicIdentifiersForm($representation, null, null, $this->canEdit()); $form->clearPubId($request->getUserVar('pubIdPlugIn')); return new JSONMessage(true); } @@ -361,7 +358,8 @@ function editGalleyTab($args, $request) { $request, $this->getSubmission(), $this->getPublication(), - $this->getGalley() + $this->getGalley(), + $this->canEdit() ); $galleyForm->initData(); return new JSONMessage(true, $galleyForm->fetch($request)); @@ -377,7 +375,7 @@ function updateGalley($args, $request) { $galley = $this->getGalley(); import('controllers.grid.articleGalleys.form.ArticleGalleyForm'); - $galleyForm = new ArticleGalleyForm($request, $this->getSubmission(), $this->getPublication(), $galley); + $galleyForm = new ArticleGalleyForm($request, $this->getSubmission(), $this->getPublication(), $galley, $this->canEdit()); $galleyForm->readInputData(); if ($galleyForm->validate()) { diff --git a/controllers/grid/articleGalleys/ArticleGalleyGridRow.inc.php b/controllers/grid/articleGalleys/ArticleGalleyGridRow.inc.php index f076af991b0..37e608c823e 100644 --- a/controllers/grid/articleGalleys/ArticleGalleyGridRow.inc.php +++ b/controllers/grid/articleGalleys/ArticleGalleyGridRow.inc.php @@ -56,20 +56,20 @@ function initialize($request, $template = null) { $actionArgs = $this->getRequestArgs(); $actionArgs['representationId'] = $rowId; + // Add row-level actions + import('lib.pkp.classes.linkAction.request.AjaxModal'); + $this->addAction(new LinkAction( + 'editGalley', + new AjaxModal( + $router->url($request, null, null, 'editGalley', null, $actionArgs), + ($this->_isEditable)?__('submission.layout.editGalley'):__('submission.layout.viewGalley'), + 'modal_edit' + ), + ($this->_isEditable)?__('grid.action.edit'):__('grid.action.view'), + 'edit' + )); + if ($this->_isEditable) { - // Add row-level actions - import('lib.pkp.classes.linkAction.request.AjaxModal'); - $this->addAction(new LinkAction( - 'editGalley', - new AjaxModal( - $router->url($request, null, null, 'editGalley', null, $actionArgs), - __('submission.layout.editGalley'), - 'modal_edit' - ), - __('grid.action.edit'), - 'edit' - )); - $galley = $this->getData(); if ($galley->getRemoteUrl() == '') { import('lib.pkp.controllers.api.file.linkAction.AddFileLinkAction'); diff --git a/controllers/grid/articleGalleys/form/ArticleGalleyForm.inc.php b/controllers/grid/articleGalleys/form/ArticleGalleyForm.inc.php index 62fedf95da3..30bb30fe2b8 100644 --- a/controllers/grid/articleGalleys/form/ArticleGalleyForm.inc.php +++ b/controllers/grid/articleGalleys/form/ArticleGalleyForm.inc.php @@ -26,22 +26,27 @@ class ArticleGalleyForm extends Form { /** @var ArticleGalley current galley */ var $_articleGalley = null; + /** @var bool indicates whether the form should be editable */ + var $_isEditable = true; + /** * Constructor. * @param $submission Submission * @param $publication Publication * @param $articleGalley ArticleGalley (optional) + * @param bool $isEditable (optional, default = true) */ - function __construct($request, $submission, $publication, $articleGalley = null) { + function __construct($request, $submission, $publication, $articleGalley = null, $isEditable = true) { parent::__construct('controllers/grid/articleGalleys/form/articleGalleyForm.tpl'); $this->_submission = $submission; $this->_publication = $publication; $this->_articleGalley = $articleGalley; + $this->_isEditable = $isEditable; AppLocale::requireComponents(LOCALE_COMPONENT_APP_EDITOR, LOCALE_COMPONENT_PKP_SUBMISSION); $this->addCheck(new FormValidator($this, 'label', 'required', 'editor.issues.galleyLabelRequired')); - $this->addCheck(new FormValidatorRegExp($this, 'urlPath', 'optional', 'validator.alpha_dash', '/^[-_a-z0-9]*$/')); + $this->addCheck(new FormValidatorRegExp($this, 'urlPath', 'optional', 'validator.alpha_dash_period', '/^[a-zA-Z0-9]+([\\.\\-_][a-zA-Z0-9]+)*$/')); $this->addCheck(new FormValidatorPost($this)); $this->addCheck(new FormValidatorCSRF($this)); @@ -79,6 +84,7 @@ function fetch($request, $template = null, $display = false) { 'supportedLocales' => $context->getSupportedSubmissionLocaleNames(), 'submissionId' => $this->_submission->getId(), 'publicationId' => $this->_publication->getId(), + 'formDisabled' => !$this->_isEditable )); return parent::fetch($request, $template, $display); @@ -105,6 +111,10 @@ function validate($callHooks = true) { } } + if (!$this->_isEditable) { + $this->addError('', __('galley.cantEditPublished')); + } + return parent::validate($callHooks); } diff --git a/controllers/grid/issues/IssueGridCellProvider.inc.php b/controllers/grid/issues/IssueGridCellProvider.inc.php index f81d57c78de..b5c9c1be6c4 100644 --- a/controllers/grid/issues/IssueGridCellProvider.inc.php +++ b/controllers/grid/issues/IssueGridCellProvider.inc.php @@ -13,6 +13,8 @@ * @brief Grid cell provider for the issue management grid */ +use function PHP81_BC\strftime; + import('lib.pkp.classes.controllers.grid.GridCellProvider'); class IssueGridCellProvider extends GridCellProvider { @@ -44,7 +46,7 @@ function getCellActions($request, $row, $column, $position = GRID_ACTION_POSITIO 'edit', new AjaxModal( $router->url($request, null, null, 'editIssue', null, array('issueId' => $issue->getId())), - __('editor.issues.editIssue', array('issueIdentification' => $issue->getIssueIdentification())), + __('editor.issues.editIssue', array('issueIdentification' => htmlspecialchars($issue->getIssueIdentification()))), 'modal_edit', true ), diff --git a/controllers/grid/issues/IssueGridRow.inc.php b/controllers/grid/issues/IssueGridRow.inc.php index db2d638e8ba..7964f05177f 100644 --- a/controllers/grid/issues/IssueGridRow.inc.php +++ b/controllers/grid/issues/IssueGridRow.inc.php @@ -39,7 +39,7 @@ function initialize($request, $template = null) { 'edit', new AjaxModal( $router->url($request, null, null, 'editIssue', null, array('issueId' => $issueId)), - __('editor.issues.editIssue', array('issueIdentification' => $issue->getIssueIdentification())), + __('editor.issues.editIssue', array('issueIdentification' => htmlspecialchars($issue->getIssueIdentification()))), 'modal_edit', true), __('grid.action.edit'), diff --git a/controllers/grid/issues/form/IssueForm.inc.php b/controllers/grid/issues/form/IssueForm.inc.php index 281730e417f..70ce3ad687a 100644 --- a/controllers/grid/issues/form/IssueForm.inc.php +++ b/controllers/grid/issues/form/IssueForm.inc.php @@ -45,7 +45,7 @@ function __construct($issue = null) { $this->addCheck(new FormValidatorCustom($this, 'showTitle', 'optional', 'editor.issues.titleRequired', function($showTitle) use ($form) { return !$showTitle || implode('', $form->getData('title'))!='' ? true : false; })); - $this->addCheck(new FormValidatorRegExp($this, 'urlPath', 'optional', 'validator.alpha_dash', '/^[-_a-z0-9]*$/')); + $this->addCheck(new FormValidatorRegExp($this, 'urlPath', 'optional', 'validator.alpha_dash_period', '/^[a-zA-Z0-9]+([\\.\\-_][a-zA-Z0-9]+)*$/')); $this->addCheck(new FormValidatorPost($this)); $this->addCheck(new FormValidatorCSRF($this)); $this->issue = $issue; diff --git a/controllers/grid/issues/form/IssueGalleyForm.inc.php b/controllers/grid/issues/form/IssueGalleyForm.inc.php index e2e82afa568..f9089b030e0 100644 --- a/controllers/grid/issues/form/IssueGalleyForm.inc.php +++ b/controllers/grid/issues/form/IssueGalleyForm.inc.php @@ -36,7 +36,7 @@ function __construct($request, $issue, $issueGalley = null) { AppLocale::requireComponents(LOCALE_COMPONENT_APP_EDITOR, LOCALE_COMPONENT_PKP_SUBMISSION); $this->addCheck(new FormValidator($this, 'label', 'required', 'editor.issues.galleyLabelRequired')); - $this->addCheck(new FormValidatorRegExp($this, 'urlPath', 'optional', 'validator.alpha_dash', '/^[-_a-z0-9]*$/')); + $this->addCheck(new FormValidatorRegExp($this, 'urlPath', 'optional', 'validator.alpha_dash_period', '/^[a-zA-Z0-9]+([\\.\\-_][a-zA-Z0-9]+)*$/')); $this->addCheck(new FormValidatorPost($this)); $this->addCheck(new FormValidatorCSRF($this)); diff --git a/controllers/grid/settings/sections/SectionGridCellProvider.inc.php b/controllers/grid/settings/sections/SectionGridCellProvider.inc.php index 857f04a93a2..1375255d35c 100644 --- a/controllers/grid/settings/sections/SectionGridCellProvider.inc.php +++ b/controllers/grid/settings/sections/SectionGridCellProvider.inc.php @@ -26,7 +26,6 @@ class SectionGridCellProvider extends GridCellProvider { function getTemplateVarsFromRowColumn($row, $column) { $element = $row->getData(); $columnId = $column->getId(); - assert(is_a($element, 'Section') && !empty($columnId)); switch ($columnId) { case 'inactive': return array('selected' => $element['inactive']); diff --git a/controllers/grid/submissions/ExportPublishedSubmissionsListGridCellProvider.inc.php b/controllers/grid/submissions/ExportPublishedSubmissionsListGridCellProvider.inc.php index 39971eb007f..cc115ae6922 100644 --- a/controllers/grid/submissions/ExportPublishedSubmissionsListGridCellProvider.inc.php +++ b/controllers/grid/submissions/ExportPublishedSubmissionsListGridCellProvider.inc.php @@ -19,6 +19,9 @@ class ExportPublishedSubmissionsListGridCellProvider extends DataObjectGridCellP /** @var ImportExportPlugin */ var $_plugin; + var $_authorizedRoles; + var $_titleColumn; + /** * Constructor */ diff --git a/controllers/grid/subscriptions/IndividualSubscriptionForm.inc.php b/controllers/grid/subscriptions/IndividualSubscriptionForm.inc.php index fc5f6da2b9f..8b30bf592f0 100644 --- a/controllers/grid/subscriptions/IndividualSubscriptionForm.inc.php +++ b/controllers/grid/subscriptions/IndividualSubscriptionForm.inc.php @@ -87,9 +87,9 @@ function execute(...$functionArgs) { } // Send notification email - if ($this->_data['notifyEmail'] == 1) { + if ($this->getData('notifyEmail')) { $mail = $this->_prepareNotificationEmail('SUBSCRIPTION_NOTIFY'); - if (!$mail->send()) { + if ($mail->isEnabled() && !$mail->send()) { import('classes.notification.NotificationManager'); $notificationMgr = new NotificationManager(); $request = Application::get()->getRequest(); diff --git a/controllers/grid/subscriptions/InstitutionalSubscriptionForm.inc.php b/controllers/grid/subscriptions/InstitutionalSubscriptionForm.inc.php index a3209923bea..71a946cd469 100644 --- a/controllers/grid/subscriptions/InstitutionalSubscriptionForm.inc.php +++ b/controllers/grid/subscriptions/InstitutionalSubscriptionForm.inc.php @@ -79,7 +79,7 @@ function initData() { 'institutionName' => $this->subscription->getInstitutionName(), 'institutionMailingAddress' => $this->subscription->getInstitutionMailingAddress(), 'domain' => $this->subscription->getDomain(), - 'ipRanges' => join($this->subscription->getIPRanges(), "\r\n"), + 'ipRanges' => join("\r\n", $this->subscription->getIPRanges()), ) ); } @@ -150,9 +150,9 @@ function execute(...$functionArgs) { } // Send notification email - if ($this->_data['notifyEmail'] == 1) { + if ($this->getData('notifyEmail')) { $mail = $this->_prepareNotificationEmail('SUBSCRIPTION_NOTIFY'); - if (!$mail->send()) { + if ($mail->isEnabled() && !$mail->send()) { import('classes.notification.NotificationManager'); $notificationMgr = new NotificationManager(); $request = Application::get()->getRequest(); diff --git a/controllers/grid/toc/TocGridHandler.inc.php b/controllers/grid/toc/TocGridHandler.inc.php index f707dbe7c2b..ae72dc3d424 100644 --- a/controllers/grid/toc/TocGridHandler.inc.php +++ b/controllers/grid/toc/TocGridHandler.inc.php @@ -187,7 +187,11 @@ function setDataElementSequence($request, $sectionId, $gridDataElement, $newSequ if (!$sectionDao->customSectionOrderingExists($issue->getId())) { $sectionDao->setDefaultCustomSectionOrders($issue->getId()); } - $sectionDao->updateCustomSectionOrder($issue->getId(), $sectionId, $newSequence); + if ($sectionDao->getCustomSectionOrder($issue->getId(), $sectionId)) { + $sectionDao->updateCustomSectionOrder($issue->getId(), $sectionId, $newSequence); + } else { + $sectionDao->insertCustomSectionOrder($issue->getId(), $sectionId, $newSequence); + } } /** diff --git a/controllers/grid/users/stageParticipant/form/StageParticipantNotifyForm.inc.php b/controllers/grid/users/stageParticipant/form/StageParticipantNotifyForm.inc.php index 170ae6a9390..1fa55fdb3ba 100644 --- a/controllers/grid/users/stageParticipant/form/StageParticipantNotifyForm.inc.php +++ b/controllers/grid/users/stageParticipant/form/StageParticipantNotifyForm.inc.php @@ -34,7 +34,7 @@ protected function _getStageTemplates() { WORKFLOW_STAGE_ID_SUBMISSION => array('EDITOR_ASSIGN'), WORKFLOW_STAGE_ID_EXTERNAL_REVIEW => array('EDITOR_ASSIGN'), WORKFLOW_STAGE_ID_EDITING => array('COPYEDIT_REQUEST'), - WORKFLOW_STAGE_ID_PRODUCTION => array('LAYOUT_REQUEST', 'LAYOUT_COMPLETE', 'INDEX_REQUEST', 'INDEX_COMPLETE', 'EDITOR_ASSIGN') + WORKFLOW_STAGE_ID_PRODUCTION => array('LAYOUT_REQUEST', 'LAYOUT_COMPLETE', 'EDITOR_ASSIGN') ); } diff --git a/controllers/tab/pubIds/form/PublicIdentifiersForm.inc.php b/controllers/tab/pubIds/form/PublicIdentifiersForm.inc.php index 845a4b40226..e837bc10958 100644 --- a/controllers/tab/pubIds/form/PublicIdentifiersForm.inc.php +++ b/controllers/tab/pubIds/form/PublicIdentifiersForm.inc.php @@ -23,8 +23,8 @@ class PublicIdentifiersForm extends PKPPublicIdentifiersForm { * @param $stageId integer * @param $formParams array */ - function __construct($pubObject, $stageId = null, $formParams = null) { - parent::__construct($pubObject, $stageId, $formParams); + function __construct($pubObject, $stageId = null, $formParams = null, $isEditable = true) { + parent::__construct($pubObject, $stageId, $formParams, $isEditable); } /** diff --git a/cypress/support/index.js b/cypress/support/index.js index 966b11b18dd..a00a94a1f02 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -19,26 +19,3 @@ import './commands' import '@foreachbe/cypress-tinymce' require('cypress-failed-log') - -// See https://stackoverflow.com/questions/58657895/is-there-a-reliable-way-to-have-cypress-exit-as-soon-as-a-test-fails/58660504#58660504 -function abortEarly() { - if (this.currentTest.state === 'failed') { - return cy.task('shouldSkip', true); - } - cy.task('shouldSkip').then(value => { - if (value) this.skip(); - }); -} - -beforeEach(abortEarly); -afterEach(abortEarly); - -before(() => { - if (Cypress.browser.isHeaded) { - // Reset the shouldSkip flag at the start of a run, so that it - // doesn't carry over into subsequent runs. - // Do this only for headed runs because in headless runs, - // the `before` hook is executed for each spec file. - cy.task('resetShouldSkipFlag'); - } -}); diff --git a/cypress/tests/data/20-CreateContext.spec.js b/cypress/tests/data/20-CreateContext.spec.js index 4ae13dd5f00..0786f75da04 100644 --- a/cypress/tests/data/20-CreateContext.spec.js +++ b/cypress/tests/data/20-CreateContext.spec.js @@ -114,7 +114,6 @@ describe('Data suite tests', function() { cy.get('div[id=contact').find('button').contains('Save').click(); cy.get('div[id="contact-contactName-error"]').contains('This field is required.'); cy.get('div[id="contact-contactEmail-error"]').contains('This field is required.'); - cy.get('div[id="contact-mailingAddress-error"]').contains('This field is required.'); cy.get('div[id="contact-supportName-error"]').contains('This field is required.'); cy.get('div[id="contact-supportEmail-error"]').contains('This field is required.'); diff --git a/cypress/tests/data/60-content/AmwandengaSubmission.spec.js b/cypress/tests/data/60-content/AmwandengaSubmission.spec.js index 7b430b04d3d..806c3c4f738 100644 --- a/cypress/tests/data/60-content/AmwandengaSubmission.spec.js +++ b/cypress/tests/data/60-content/AmwandengaSubmission.spec.js @@ -100,7 +100,7 @@ describe('Data suite tests', function() { cy.get('#issue [name="urlPath"]').type('mwandenga-signalling-theory space error'); cy.get('#issue button').contains('Save').click(); - cy.get('#issue [id*="urlPath-error"]').contains('This may only contain letters, numbers, dashes and underscores.'); + cy.get('#issue [id*="urlPath-error"]').contains('This may only contain letters, numbers, dashes, underscores and periods.'); cy.get('#issue [name="urlPath"]').clear().type('mwandenga-signalling-theory'); cy.get('#issue button').contains('Save').click(); cy.get('#issue [role="status"]').contains('Saved'); @@ -153,7 +153,7 @@ describe('Data suite tests', function() { cy.get('#galleys-button').click(); cy.get('[id*="addGalley-button"]').should('not.exist'); - cy.get('[id*="editGalley-button"]').should('not.exist'); + cy.get('[id*="editGalley-button"]').contains('View').should('exist'); }); it('Allow author to edit publication details', function() { @@ -252,7 +252,7 @@ describe('Data suite tests', function() { cy.get('#contributors-grid #component-grid-users-author-authorgrid-row-3 .show_extras').click(); cy.get('#component-grid-users-author-authorgrid-row-3-control-row [id*="editAuthor-button"]').click(); cy.wait(1500); // Wait for the form to settle - cy.get('[name="familyName[en_US]"]').type(' Version 2', {delay: 0}); + cy.get('[name="familyName[en_US]"]').type(' Version 2', {delay: 5}); cy.get('[id^="submitFormButton"]').contains('Save').click(); cy.contains('Author edited.'); cy.wait(1500); // Wait for the grid to reload @@ -263,7 +263,9 @@ describe('Data suite tests', function() { cy.contains('Add galley'); cy.get('#representations-grid .show_extras').click(); cy.get('[id*="editGalley-button"]').click(); + cy.wait(1500); cy.get('#editArticleGalleyMetadataTabs [name="label"]').type(' Version 2'); + cy.wait(1500); cy.get('#editArticleGalleyMetadataTabs [name="urlPath"]').type('pdf'); cy.get('#articleGalleyForm button').contains('Save').click(); cy.wait(1500); diff --git a/cypress/tests/data/60-content/CcorinoSubmission.spec.js b/cypress/tests/data/60-content/CcorinoSubmission.spec.js index 67bae39fdb0..339a24f7ecc 100644 --- a/cypress/tests/data/60-content/CcorinoSubmission.spec.js +++ b/cypress/tests/data/60-content/CcorinoSubmission.spec.js @@ -39,8 +39,11 @@ describe('Data suite tests', function() { cy.get('#metadata-keywords-control-en_US').type('pr', {delay: 0}); cy.wait(500); cy.get('li').contains('Professional Development').click({force: true}); + cy.wait(500); cy.get('#metadata-keywords-control-en_US').type('socia', {delay: 0}); + cy.wait(500); cy.contains('Social Transformation'); + cy.wait(500); cy.get('#metadata-keywords-control-en_US').type('l{downarrow}{downarrow}{enter}', {delay: 0}); cy.get('#metadata button').contains('Save').click(); cy.get('#metadata [role="status"]').contains('Saved'); diff --git a/cypress/tests/integration/Discussions.spec.js b/cypress/tests/integration/Discussions.spec.js new file mode 100644 index 00000000000..b364c4db88f --- /dev/null +++ b/cypress/tests/integration/Discussions.spec.js @@ -0,0 +1,40 @@ +/** + * @file cypress/tests/integration/Discussions.spec.js + * + * Copyright (c) 2014-2021 Simon Fraser University + * Copyright (c) 2000-2021 John Willinsky + * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING. + */ + + describe('Discussions', function() { + var author = 'Corino'; + var discussion = 'Test discussion'; + var message = 'Test discussion message'; + var discussionGrid = '[id^="component-grid-queries"]'; + + it('#8097 Discussion deleted or preserved correctly when modal closed while creating discussion', function() { + cy.findSubmissionAsEditor('dbarnes', null, author); + cy.get(discussionGrid); + cy.contains('Add discussion').click(); + cy.get('#queryForm label:contains("' + author + '")').click(); + cy.get('#queryForm input[name="subject"]').type(discussion); + cy.get('textarea[name="comment"]').then(node => { + cy.setTinyMceContent(node.attr('id'), message); + }); + cy.get('.pkpModalCloseButton').click(); + cy.on('window:confirm', () => true); + cy.reload(); + cy.get(discussionGrid); + cy.contains(discussion).should('not.exist'); + cy.contains('Add discussion').click(); + cy.get('#queryForm label:contains("' + author + '")').click(); + cy.get('#queryForm input[name="subject"]').type(discussion); + cy.get('textarea[name="comment"]').then(node => { + cy.setTinyMceContent(node.attr('id'), message); + }); + cy.get('.pkpModalCloseButton').click(); + cy.on('window:confirm', () => false); + cy.get('#queryForm button:contains("OK")').click(); + cy.get(discussionGrid + ' a:contains("' + discussion + '")'); + }); +}); diff --git a/dbscripts/xml/install.xml b/dbscripts/xml/install.xml index 57428c8941a..3f46f180220 100644 --- a/dbscripts/xml/install.xml +++ b/dbscripts/xml/install.xml @@ -3,14 +3,14 @@ - + diff --git a/dbscripts/xml/upgrade.xml b/dbscripts/xml/upgrade.xml index 97c465f5429..8f492b5cde2 100644 --- a/dbscripts/xml/upgrade.xml +++ b/dbscripts/xml/upgrade.xml @@ -3,15 +3,15 @@ - + @@ -39,9 +39,8 @@ - - + @@ -84,6 +83,10 @@ + + + + @@ -205,6 +208,10 @@ + + + + diff --git a/dbscripts/xml/upgrade/3.2.0_update.xml b/dbscripts/xml/upgrade/3.2.0_update.xml index 1e83a92e916..9de30428cd0 100644 --- a/dbscripts/xml/upgrade/3.2.0_update.xml +++ b/dbscripts/xml/upgrade/3.2.0_update.xml @@ -17,7 +17,7 @@ - ALTER TABLE journal_settings ALTER COLUMN setting_type DROP NOT NULL + ALTER TABLE journal_settings ALTER COLUMN setting_type DROP NOT NULL INSERT INTO journal_settings (setting_name, setting_value, journal_id) SELECT 'enableOai', '1', j.journal_id FROM journals j WHERE NOT EXISTS (SELECT js.setting_name FROM journal_settings js WHERE js.setting_name = 'enableOai' AND js.journal_id = j.journal_id) diff --git a/dbscripts/xml/upgrade/3.2.0_versioning.xml b/dbscripts/xml/upgrade/3.2.0_versioning.xml index ba2cf2e98de..0fa95207b0a 100644 --- a/dbscripts/xml/upgrade/3.2.0_versioning.xml +++ b/dbscripts/xml/upgrade/3.2.0_versioning.xml @@ -221,6 +221,52 @@ AND plugin_name='urnpubidplugin' + + + UPDATE publications as p + SET status = 5 + WHERE ctid IN ( + SELECT p.ctid + FROM publications as p + LEFT JOIN publication_settings as pss ON p.publication_id = pss.publication_id + LEFT JOIN submissions as s ON p.submission_id = s.submission_id + WHERE pss.setting_name = 'issueId' + AND pss.setting_value IS NOT NULL + AND p.status = 1 + AND s.stage_id IN (4, 5) + ) + + + UPDATE "submissions" as "s" + SET "status" = 5 + WHERE "ctid" IN ( + SELECT "s"."ctid" + FROM "submissions" as "s" + LEFT JOIN "publications" as "p" ON "s"."submission_id" = "p"."submission_id" + LEFT JOIN "publication_settings" as "pss" ON "p"."publication_id" = "pss"."publication_id" + WHERE "pss"."setting_name" = 'issueId' + AND "pss"."setting_value" IS NOT NULL + AND "s"."status" = 1 + AND "s"."stage_id" IN (4,5) + ) + + + UPDATE publications as p + LEFT JOIN publication_settings as pss ON p.publication_id = pss.publication_id + LEFT JOIN submissions as s ON p.submission_id = s.submission_id + SET p.status = 5, s.status = 5 + WHERE pss.setting_name = 'issueId' + AND pss.setting_value IS NOT NULL + AND p.status = 1 + AND s.stage_id in (4, 5) + + diff --git a/dbscripts/xml/version.xml b/dbscripts/xml/version.xml index ada6e057be4..e76f53b83ba 100644 --- a/dbscripts/xml/version.xml +++ b/dbscripts/xml/version.xml @@ -3,8 +3,8 @@