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..cbfd1c25820 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.masthead'), + 'description' => __('manager.navigationMenus.masthead.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', + 'masthead', + 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 54a5f029d4e..292503ecab0 100644 --- a/classes/submission/reviewAssignment/DAO.php +++ b/classes/submission/reviewAssignment/DAO.php @@ -15,13 +15,14 @@ use APP\facades\Repo; use Illuminate\Database\Query\Builder; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\LazyCollection; use PKP\core\EntityDAO; -use Illuminate\Support\Collection; /** * @template T of ReviewAssignment + * * @extends EntityDAO */ class DAO extends EntityDAO @@ -81,7 +82,7 @@ public function exists(int $id, ?int $submissionId): bool { return DB::table($this->table) ->where($this->primaryKeyColumn, $id) - ->when($submissionId !== null, fn(Builder $query) => $query->where('submission_id', $submissionId)) + ->when($submissionId !== null, fn (Builder $query) => $query->where('submission_id', $submissionId)) ->exists(); } @@ -92,7 +93,7 @@ public function get(int $id, ?int $submissionId = null): ?ReviewAssignment { $row = DB::table($this->table) ->where($this->primaryKeyColumn, $id) - ->when($submissionId !== null, fn(Builder $query) => $query->where('submission_id', $submissionId)) + ->when($submissionId !== null, fn (Builder $query) => $query->where('submission_id', $submissionId)) ->first(); return $row ? $this->fromRow($row) : null; @@ -132,7 +133,7 @@ public function getMany(Collector $query): LazyCollection ->getQueryBuilder() ->get(); - return LazyCollection::make(function() use ($rows) { + return LazyCollection::make(function () use ($rows) { foreach ($rows as $row) { yield $row->review_id => $this->fromRow($row); } @@ -176,4 +177,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 getCompletedByYear(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..13d29be0bf8 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::getCompletedByYear() + * + * @return Collection + */ + public function getCompletedByYear(int $contextId, string $year): Collection + { + return $this->dao->getCompletedByYear($contextId, $year); + } } diff --git a/locale/en/common.po b/locale/en/common.po index 9abce0630f8..1193ccc060e 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.masthead" +msgstr "Masthead" + +msgid "common.masthead.peerReviewers" +msgstr "Peer Reviewers" + +msgid "common.masthead.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..6ace30c5052 100644 --- a/locale/en/manager.po +++ b/locale/en/manager.po @@ -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.masthead.description" +msgstr "Link to a page displaying all active 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..271dea21ac4 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 masthead page. + * + * @param array $args + * @param \PKP\core\PKPRequest $request + * + * @hook AboutContextHandler::masthead [[$mastheadRoles, $mastheadUsers, $reviewers, $previousYear]] + */ + public function masthead($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 mashead 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() + ->all(); + 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()->getCompletedByYear($context->getId(), $previousYear); + $reviewers = Repo::user() + ->getCollector() + ->filterByUserIds($reviewerIds->toArray()) + ->getMany() + ->all(); + + Hook::call('AboutContextHandler::masthead', [$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/masthead.tpl'); + } + /** * Display editorialTeam page. * diff --git a/pages/about/index.php b/pages/about/index.php index be7700da72d..a57df18f45b 100644 --- a/pages/about/index.php +++ b/pages/about/index.php @@ -19,6 +19,7 @@ switch ($op) { case 'index': + case 'masthead': case 'editorialTeam': case 'submissions': case 'contact': diff --git a/templates/frontend/pages/masthead.tpl b/templates/frontend/pages/masthead.tpl new file mode 100644 index 00000000000..3c384062d31 --- /dev/null +++ b/templates/frontend/pages/masthead.tpl @@ -0,0 +1,55 @@ +{** + * templates/frontend/pages/masthed.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 masthead page. + * + *} +{include file="frontend/components/header.tpl" pageTitle="common.masthead"} + +
+ {include file="frontend/components/breadcrumbs.tpl" currentTitleKey="common.masthead"} + +

{translate key="common.masthead"}

+ {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.masthead.peerReviewers"}

+

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

+
    + {foreach from=$reviewers item="reviewer"} +
  • {$reviewer->getFullName()|escape}
  • + {/foreach} +
+ {/if} +
+ +{include file="frontend/components/footer.tpl"}