diff --git a/classes/components/forms/context/PKPAppearanceMastheadForm.php b/classes/components/forms/context/PKPAppearanceMastheadForm.php index 550cf3dd056..fc9fadc3855 100644 --- a/classes/components/forms/context/PKPAppearanceMastheadForm.php +++ b/classes/components/forms/context/PKPAppearanceMastheadForm.php @@ -10,7 +10,7 @@ * * @ingroup classes_controllers_form * - * @brief A form for defining in which order the masthead roles should be displayed. + * @brief A form for defining in which order the editorial masthead roles should be displayed. */ namespace PKP\components\forms\context; @@ -63,8 +63,8 @@ public function __construct($action, $locales, $context) } $this->addField(new FieldOptions('mastheadUserGroupIds', [ - 'label' => __('manager.setup.masthead'), - 'description' => __('manager.setup.masthead.description'), + 'label' => __('common.editorialMasthead'), + 'description' => __('manager.setup.editorialMasthead.description'), 'isOrderable' => true, 'value' => array_column($mastheadOptions, 'value'), 'options' => $mastheadOptions, diff --git a/classes/navigationMenu/NavigationMenuItem.php b/classes/navigationMenu/NavigationMenuItem.php index 2b4c72606da..943aea31598 100755 --- a/classes/navigationMenu/NavigationMenuItem.php +++ b/classes/navigationMenu/NavigationMenuItem.php @@ -23,6 +23,7 @@ class NavigationMenuItem extends \PKP\core\DataObject // Types for all default navigationMenuItems public const NMI_TYPE_ABOUT = 'NMI_TYPE_ABOUT'; public const NMI_TYPE_SUBMISSIONS = 'NMI_TYPE_SUBMISSIONS'; + public const NMI_TYPE_MASTHEAD = 'NMI_TYPE_MASTHEAD'; public const NMI_TYPE_EDITORIAL_TEAM = 'NMI_TYPE_EDITORIAL_TEAM'; public const NMI_TYPE_CONTACT = 'NMI_TYPE_CONTACT'; public const NMI_TYPE_ANNOUNCEMENTS = 'NMI_TYPE_ANNOUNCEMENTS'; @@ -311,6 +312,7 @@ class_alias('\PKP\navigationMenu\NavigationMenuItem', '\NavigationMenuItem'); foreach ([ 'NMI_TYPE_ABOUT', 'NMI_TYPE_SUBMISSIONS', + 'NMI_TYPE_MASTHEAD', 'NMI_TYPE_EDITORIAL_TEAM', 'NMI_TYPE_CONTACT', 'NMI_TYPE_ANNOUNCEMENTS', diff --git a/classes/services/PKPNavigationMenuService.php b/classes/services/PKPNavigationMenuService.php index 98e31823313..5280c06d621 100755 --- a/classes/services/PKPNavigationMenuService.php +++ b/classes/services/PKPNavigationMenuService.php @@ -58,6 +58,10 @@ public function getMenuItemTypes() 'description' => __('manager.navigationMenus.about.description'), 'conditionalWarning' => __('manager.navigationMenus.about.conditionalWarning'), ], + NavigationMenuItem::NMI_TYPE_MASTHEAD => [ + 'title' => __('common.editorialMasthead'), + 'description' => __('manager.navigationMenus.editorialMasthead.description'), + ], NavigationMenuItem::NMI_TYPE_EDITORIAL_TEAM => [ 'title' => __('about.editorialTeam'), 'description' => __('manager.navigationMenus.editorialTeam.description'), @@ -252,6 +256,16 @@ public function getDisplayStatus(&$navigationMenuItem, &$navigationMenu) null )); break; + case NavigationMenuItem::NMI_TYPE_MASTHEAD: + $navigationMenuItem->setUrl($dispatcher->url( + $request, + PKPApplication::ROUTE_PAGE, + null, + 'about', + 'editorialMasthead', + null + )); + break; case NavigationMenuItem::NMI_TYPE_EDITORIAL_TEAM: $navigationMenuItem->setUrl($dispatcher->url( $request, @@ -654,9 +668,9 @@ public function _callbackHandleCustomNavigationMenuItems($hookName, $args) { $request = Application::get()->getRequest(); - $page = & $args[0]; - $op = & $args[1]; - $handler = & $args[3]; + $page = &$args[0]; + $op = &$args[1]; + $handler = &$args[3]; // Construct a path to look for $path = $page; diff --git a/classes/submission/reviewAssignment/DAO.php b/classes/submission/reviewAssignment/DAO.php index 9cb13c89d13..fa2a06326dc 100644 --- a/classes/submission/reviewAssignment/DAO.php +++ b/classes/submission/reviewAssignment/DAO.php @@ -178,4 +178,23 @@ public function delete(ReviewAssignment $reviewAssignment) { parent::_delete($reviewAssignment); } + + /** + * Get IDs of the reviewers that have completed a reivew for the given context in the given year. + * + * @return Collection + */ + public function getReviewerIdsByCompletedYear(int $contextId, string $year): Collection + { + return DB::table($this->table) + ->whereIn( + 'submission_id', + fn (Builder $q) => $q + ->select('s.submission_id') + ->from('submissions as s') + ->where('s.context_id', $contextId) + ) + ->whereYear('date_completed', $year) + ->pluck('reviewer_id'); + } } diff --git a/classes/submission/reviewAssignment/Repository.php b/classes/submission/reviewAssignment/Repository.php index 21c6373e6d4..d21b2d9ea83 100644 --- a/classes/submission/reviewAssignment/Repository.php +++ b/classes/submission/reviewAssignment/Repository.php @@ -16,6 +16,7 @@ use APP\core\Application; use APP\core\Request; use APP\facades\Repo; +use Illuminate\Support\Collection; use PKP\context\Context; use PKP\db\DAORegistry; use PKP\notification\NotificationDAO; @@ -259,4 +260,14 @@ protected function updateReviewRoundStatus(ReviewAssignment $reviewAssignment): return false; } + + /** + * @copydoc DAO::getReviewerIdsByCompletedYear() + * + * @return Collection + */ + public function getReviewerIdsByCompletedYear(int $contextId, string $year): Collection + { + return $this->dao->getReviewerIdsByCompletedYear($contextId, $year); + } } diff --git a/locale/en/common.po b/locale/en/common.po index 9abce0630f8..a84eb299dd5 100644 --- a/locale/en/common.po +++ b/locale/en/common.po @@ -148,6 +148,15 @@ msgstr "Attach Files" msgid "common.attachSelected" msgstr "Attach Selected" +msgid "common.editorialMasthead" +msgstr "Editorial Masthead" + +msgid "common.editorialMasthead.peerReviewers" +msgstr "Peer Reviewers" + +msgid "common.editorialMasthead.peerReviewers.description" +msgstr "The editors express their appreciation of the reviewers for {$year} listed below." + msgid "common.name" msgstr "Name" diff --git a/locale/en/manager.po b/locale/en/manager.po index 576240236d9..9d43a95cbd9 100644 --- a/locale/en/manager.po +++ b/locale/en/manager.po @@ -1860,8 +1860,8 @@ msgstr "Yes, require submitting authors to upload one or more of these files." msgid "manager.setup.genres.submitRequired.no" msgstr "No, allow new submissions without these files." -msgid "manager.setup.masthead.description" -msgstr "Define the order of masthead roles for public display." +msgid "manager.setup.editorialMasthead.description" +msgstr "Define the order of editorial masthead roles for public display." msgid "manager.settings.wizard" msgstr "Settings Wizard" @@ -2429,6 +2429,9 @@ msgstr "" "This link will only be displayed if you have filled out the Editorial Team " "section under Settings > Journal." +msgid "manager.navigationMenus.editorialMasthead.description" +msgstr "Link to a page displaying all active editorial masthead services." + msgid "manager.navigationMenus.submissions.description" msgstr "Link to the page displaying submission instructions." diff --git a/pages/about/AboutContextHandler.php b/pages/about/AboutContextHandler.php index 3af77c27512..f372d3cfa8f 100644 --- a/pages/about/AboutContextHandler.php +++ b/pages/about/AboutContextHandler.php @@ -20,8 +20,12 @@ use APP\facades\Repo; use APP\handler\Handler; use APP\template\TemplateManager; +use DateTime; +use PKP\plugins\Hook; use PKP\security\authorization\ContextRequiredPolicy; use PKP\security\Role; +use PKP\userGroup\relationships\enums\UserUserGroupMastheadStatus; +use PKP\userGroup\relationships\UserUserGroup; class AboutContextHandler extends Handler { @@ -53,6 +57,84 @@ public function index($args, $request) $templateMgr->display('frontend/pages/about.tpl'); } + /** + * Display editorial masthead page. + * + * @param array $args + * @param \PKP\core\PKPRequest $request + * + * @hook AboutContextHandler::masthead [[$mastheadRoles, $mastheadUsers, $reviewers, $previousYear]] + * @hook AboutContextHandler::editorialMasthead [[$mastheadRoles, $mastheadUsers, $reviewers, $previousYear]] + */ + public function editorialMasthead($args, $request) + { + $context = $request->getContext(); + + $savedMastheadUserGroupIdsOrder = (array) $context->getData('mastheadUserGroupIds'); + + $collector = Repo::userGroup()->getCollector(); + $allMastheadUserGroups = $collector + ->filterByContextIds([$context->getId()]) + ->filterByMasthead(true) + ->orderBy($collector::ORDERBY_ROLE_ID) + ->getMany() + ->toArray(); + + // sort the masthead roles in their saved order for display + $mastheadRoles = array_replace(array_flip($savedMastheadUserGroupIdsOrder), $allMastheadUserGroups); + + $mastheadUsers = []; + foreach ($mastheadRoles as $mastheadUserGroup) { + // Get all users that are active in the given role + // and that have accepted to be displayed on the masthead for that role. + // No need to filter by context ID, because the user groups are already filtered so. + $users = Repo::user() + ->getCollector() + ->filterByUserGroupIds([$mastheadUserGroup->getId()]) + ->filterByUserUserGroupMastheadStatus(UserUserGroupMastheadStatus::STATUS_ON) + ->getMany(); + foreach ($users as $user) { + $userUserGroup = UserUserGroup::withUserId($user->getId()) + ->withUserGroupId($mastheadUserGroup->getId()) + ->withActive() + ->withMasthead() + ->first(); + if ($userUserGroup) { + $startDatetime = new DateTime($userUserGroup->dateStart); + $mastheadUsers[$mastheadUserGroup->getId()][$user->getId()] = [ + 'user' => $user, + 'dateStart' => $startDatetime->format('Y'), + ]; + } + } + } + + $previousYear = date('Y') - 1; + $reviewerIds = Repo::reviewAssignment()->getReviewerIdsByCompletedYear($context->getId(), $previousYear); + $reviewers = Repo::user() + ->getCollector() + ->filterByUserIds($reviewerIds->toArray()) + ->getMany() + ->all(); + + Hook::call('AboutContextHandler::editorialMasthead', [$mastheadRoles, $mastheadUsers, $reviewers, $previousYear]); + + // To come after https://github.com/pkp/pkp-lib/issues/9771 + // $orcidIcon = OrcidManager::getIcon(); + $orcidIcon = ''; + + $templateMgr = TemplateManager::getManager($request); + $this->setupTemplate($request); + $templateMgr->assign([ + 'mastheadRoles' => $mastheadRoles, + 'mastheadUsers' => $mastheadUsers, + 'reviewers' => $reviewers, + 'previousYear' => $previousYear, + 'orcidIcon' => $orcidIcon + ]); + $templateMgr->display('frontend/pages/editorialMasthead.tpl'); + } + /** * Display editorialTeam page. * diff --git a/pages/about/index.php b/pages/about/index.php index be7700da72d..8fdbf33b381 100644 --- a/pages/about/index.php +++ b/pages/about/index.php @@ -19,6 +19,7 @@ switch ($op) { case 'index': + case 'editorialMasthead': case 'editorialTeam': case 'submissions': case 'contact': diff --git a/templates/frontend/pages/editorialMasthead.tpl b/templates/frontend/pages/editorialMasthead.tpl new file mode 100644 index 00000000000..c8c184eb866 --- /dev/null +++ b/templates/frontend/pages/editorialMasthead.tpl @@ -0,0 +1,55 @@ +{** + * templates/frontend/pages/editorialMasthead.tpl + * + * Copyright (c) 2024 Simon Fraser University + * Copyright (c) 2024 John Willinsky + * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING. + * + * @brief Display journal's editorial masthead page. + * + *} +{include file="frontend/components/header.tpl" pageTitle="common.editorialMasthead"} + +
+ {include file="frontend/components/breadcrumbs.tpl" currentTitleKey="common.editorialMasthead"} + +

{translate key="common.editorialMasthead"}

+ {foreach from=$mastheadRoles item="mastheadRole"} + {if $mastheadRole->getRoleId() != \PKP\security\Role::ROLE_ID_REVIEWER} + {if array_key_exists($mastheadRole->getId(), $mastheadUsers)} +

{$mastheadRole->getLocalizedName()|escape}

+
    + {foreach from=$mastheadUsers[$mastheadRole->getId()] item="mastheadUser"} +
  • + {$mastheadUser['user']->getFullName()|escape}, + {$mastheadUser['user']->getLocalizedData('affiliation')|escape}, + {$mastheadUser['dateStart']} + {if $mastheadUser['user']->getData('orcid')} + + {if $mastheadUser['user']->getData('orcidAccessToken')} + {$orcidIcon} + {/if} + + {$mastheadUser['user']->getData('orcid')|escape} + + + {/if} +
  • + {/foreach} +
+ {/if} + {/if} + {/foreach} + + {if !empty($reviewers)} +

{translate key="common.editorialMasthead.peerReviewers"}

+

{translate key="common.editorialMasthead.peerReviewers.description" year=$previousYear}

+
    + {foreach from=$reviewers item="reviewer"} +
  • {$reviewer->getFullName()|escape}
  • + {/foreach} +
+ {/if} +
+ +{include file="frontend/components/footer.tpl"} diff --git a/templates/management/website.tpl b/templates/management/website.tpl index 82af05a0f1a..7205f8aabfa 100644 --- a/templates/management/website.tpl +++ b/templates/management/website.tpl @@ -41,7 +41,7 @@ @set="set" /> - +