<?php
/**
* @file classes/core/PKPPageRouter.php
*
* 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.
*
* @class PKPPageRouter
*
* @ingroup core
*
* @brief Class mapping an HTTP request to a handler or context.
*/
namespace PKP\core;
use APP\core\Application;
use APP\facades\Repo;
use PKP\facades\Locale;
use PKP\plugins\Hook;
use PKP\security\Role;
use PKP\security\Validation;
use PKP\session\SessionManager;
define('ROUTER_DEFAULT_PAGE', './pages/index/index.php');
define('ROUTER_DEFAULT_OP', 'index');
class PKPPageRouter extends PKPRouter
{
/** @var array pages that don't need an installed system to be displayed */
public $_installationPages = ['install', 'help', 'header', 'sidebar'];
//
// Internal state cache variables
// NB: Please do not access directly but
// only via their respective getters/setters
//
/** @var string the requested page */
public $_page;
/** @var string the requested operation */
public $_op;
/** @var string cache filename */
public $_cacheFilename;
/**
* get the installation pages
*
* @return array
*/
public function getInstallationPages()
{
return $this->_installationPages;
}
/**
* get the cacheable pages
*
* @return array
*/
public function getCacheablePages()
{
// Can be overridden by sub-classes.
return [];
}
/**
* Determine whether or not the request is cacheable.
*
* @param PKPRequest $request
* @param bool $testOnly required for unit test to
* bypass session check.
*
*/
public function isCacheable($request, $testOnly = false): bool
{
if (SessionManager::isDisabled() && !$testOnly) {
return false;
}
if (Application::isUnderMaintenance()) {
return false;
}
if (!empty($_POST) || Validation::isLoggedIn()) {
return false;
}
if (!empty($_GET)) {
return false;
}
if (in_array($this->getRequestedPage($request), $this->getCacheablePages())) {
return true;
}
return false;
}
/**
* Get the page requested in the URL.
*
* @param PKPRequest $request the request to be routed
*
* @return string the page path (under the "pages" directory)
*/
public function getRequestedPage($request)
{
if (!isset($this->_page)) {
$this->_page = $this->_getRequestedUrlParts(['Core', 'getPage'], $request);
}
return $this->_page;
}
/**
* Get the operation requested in the URL (assumed to exist in the requested page handler).
*
* @param PKPRequest $request the request to be routed
*
* @return string
*/
public function getRequestedOp($request)
{
if (!isset($this->_op)) {
$this->_op = $this->_getRequestedUrlParts(['Core', 'getOp'], $request);
}
return $this->_op;
}
/**
* Get the arguments requested in the URL.
*
* @param PKPRequest $request the request to be routed
*
* @return array
*/
public function getRequestedArgs($request)
{
return $this->_getRequestedUrlParts(['Core', 'getArgs'], $request);
}
/**
* Get the anchor (#anchor) requested in the URL
*
* @para $request PKPRequest the request to be routed
*
* @return string
*/
public function getRequestedAnchor($request)
{
$url = $request->getRequestUrl();
$parts = explode('#', $url);
if (count($parts) < 2) {
return '';
}
return $parts[1];
}
//
// Implement template methods from PKPRouter
//
/**
* @copydoc PKPRouter::getCacheFilename()
*/
public function getCacheFilename($request)
{
if (!isset($this->_cacheFilename)) {
$id = $_SERVER['PATH_INFO'] ?? 'index';
$id .= '-' . Locale::getLocale();
$path = Core::getBaseDir();
$this->_cacheFilename = $path . '/cache/wc-' . md5($id) . '.html';
}
return $this->_cacheFilename;
}
/**
* @copydoc PKPRouter::route()
*/
public function route($request)
{
// Determine the requested page and operation
$page = $this->getRequestedPage($request);
$op = $this->getRequestedOp($request);
// If the application has not yet been installed we only
// allow installer pages to be displayed.
if (!Application::isInstalled()) {
if (!in_array($page, $this->getInstallationPages())) {
// A non-installation page was called although
// the system is not yet installed. Redirect to
// the installation page.
$request->redirect('index', 'install');
}
}
// Redirect requests from logged-out users to a context which is not
// publicly enabled
if (!SessionManager::isDisabled()) {
$user = $request->getUser();
$currentContext = $request->getContext();
if ($currentContext && !$currentContext->getEnabled() && !$user instanceof \PKP\user\User) {
if ($page != 'login') {
$request->redirect(null, 'login');
}
}
}
// Determine the page index file. This file contains the
// logic to resolve a page to a specific handler class.
$sourceFile = sprintf('pages/%s/index.php', $page);
// If a hook has been registered to handle this page, give it the
// opportunity to load required resources and set the handler.
$handler = null;
if (!Hook::call('LoadHandler', [&$page, &$op, &$sourceFile, &$handler])) {
if (file_exists($sourceFile)) {
$result = require('./' . $sourceFile);
if (is_object($result)) {
$handler = $result;
}
} elseif (file_exists(PKP_LIB_PATH . "/{$sourceFile}")) {
$result = require('./' . PKP_LIB_PATH . "/{$sourceFile}");
if (is_object($result)) {
$handler = $result;
}
} elseif (empty($page)) {
require(ROUTER_DEFAULT_PAGE);
} else {
$dispatcher = $this->getDispatcher();
$dispatcher->handle404();
}
}
if (!SessionManager::isDisabled()) {
// Initialize session
SessionManager::getManager();
}
// Call the selected handler's index operation if
// no operation was defined in the request.
if (empty($op)) {
$op = ROUTER_DEFAULT_OP;
}
// Redirect to 404 if the operation doesn't exist
// for the handler.
$methods = [];
if ($handler) {
$methods = get_class_methods($handler);
} elseif (defined('HANDLER_CLASS')) {
// The use of HANDLER_CLASS is DEPRECATED with 3.4.0 pkp/pkp-lib#6019
$methods = get_class_methods(HANDLER_CLASS);
}
if (!in_array($op, $methods)) {
$dispatcher = $this->getDispatcher();
$dispatcher->handle404();
}
// Instantiate the handler class
if (!$handler) {
// The use of HANDLER_CLASS is DEPRECATED with 3.4.0 pkp/pkp-lib#6019
$handlerClass = HANDLER_CLASS;
$handler = new $handlerClass($request);
}
$this->setHandler($handler);
// Authorize and initialize the request but don't call the
// validate() method on page handlers.
// FIXME: We should call the validate() method for page
// requests also (last param = true in the below method
// call) once we've made sure that all validate() calls can
// be removed from handler operations without damage (i.e.
// they don't depend on actions being performed before the
// call to validate().
$args = $this->getRequestedArgs($request);
$serviceEndpoint = [$handler, $op];
$this->_authorizeInitializeAndCallRequest($serviceEndpoint, $request, $args, false);
}
/**
* @copydoc PKPRouter::url()
*
* @param null|mixed $newContext
* @param null|mixed $page
* @param null|mixed $op
* @param null|mixed $path
* @param null|mixed $params
* @param null|mixed $anchor
*/
public function url(
PKPRequest $request,
?string $newContext = null,
$page = null,
$op = null,
$path = null,
$params = null,
$anchor = null,
$escape = false
) {
//
// Base URL and Context
//
$baseUrlAndContext = $this->_urlGetBaseAndContext($request, $newContext);
$baseUrl = array_shift($baseUrlAndContext);
$context = array_shift($baseUrlAndContext);
//
// Additional path info
//
if (empty($path)) {
$additionalPath = [];
} else {
if (is_array($path)) {
$additionalPath = array_map('rawurlencode', $path);
} else {
$additionalPath = [rawurlencode($path)];
}
}
//
// Page and Operation
//
// Are we in a page request?
$currentRequestIsAPageRequest = $request->getRouter() instanceof \PKP\core\PKPPageRouter;
// Determine the operation
if ($op) {
// If an operation has been explicitly set then use it.
$op = rawurlencode($op);
} else {
// No operation has been explicitly set so let's determine a sensible
// default operation.
if (empty($newContext) && empty($page) && $currentRequestIsAPageRequest) {
// If we remain in the existing context and on the existing page then
// we will default to the current operation. We can only determine a
// current operation if the current request is a page request.
$op = $this->getRequestedOp($request);
} else {
// If a new context (or page) has been set then we'll default to the
// index operation within the new context (or on the new page).
if (empty($additionalPath)) {
// If no additional path is set we can simply leave the operation
// undefined which automatically defaults to the index operation
// but gives shorter (=nicer) URLs.
$op = null;
} else {
// If an additional path is set then we have to explicitly set the
// index operation to disambiguate the path info.
$op = 'index';
}
}
}
// Determine the page
if ($page) {
// If a page has been explicitly set then use it.
$page = rawurlencode($page);
} else {
// No page has been explicitly set so let's determine a sensible default page.
if (empty($newContext) && $currentRequestIsAPageRequest) {
// If we remain in the existing context then we will default to the current
// page. We can only determine a current page if the current request is a
// page request.
$page = $this->getRequestedPage($request);
} else {
// If a new context has been set then we'll default to the index page
// within the new context.
if (empty($op)) {
// If no explicit operation is set we can simply leave the page
// undefined which automatically defaults to the index page but gives
// shorter (=nicer) URLs.
$page = null;
} else {
// If an operation is set then we have to explicitly set the index
// page to disambiguate the path info.
$page = 'index';
}
}
}
//
// Additional query parameters
//
$additionalParameters = $this->_urlGetAdditionalParameters($request, $params, $escape);
//
// Anchor
//
$anchor = (empty($anchor) ? '' : '#' . preg_replace("/[^a-zA-Z0-9\-\_\/\.\~]/", '', $anchor));
//
// Assemble URL
//
// Context, page, operation and additional path go into the path info.
$pathInfoArray = $context;
if (!empty($page)) {
$pathInfoArray[] = $page;
if (!empty($op)) {
$pathInfoArray[] = $op;
}
}
$pathInfoArray = array_merge($pathInfoArray, $additionalPath);
// Query parameters
$queryParametersArray = $additionalParameters;
return $this->_urlFromParts($baseUrl, $pathInfoArray, $queryParametersArray, $anchor, $escape);
}
/**
* @copydoc PKPRouter::handleAuthorizationFailure()
*/
public function handleAuthorizationFailure(
$request,
$authorizationMessage,
array $messageParams = []
) {
// Redirect to the authorization denied page.
if (!$request->getUser()) {
Validation::redirectLogin();
}
$request->redirect(null, 'user', 'authorizationDenied', null, ['message' => $authorizationMessage]);
}
/**
* Redirect to user home page (or the user group home page if the user has one user group).
*/
public function redirectHome(PKPRequest $request)
{
$request->redirectUrl($this->getHomeUrl($request));
}
/**
* Get the user's "home" page URL (e.g. where they are sent after login).
*
* @param PKPRequest $request the request to be routed
*/
public function getHomeUrl($request)
{
$user = $request->getUser();
$userId = $user->getId();
if ($context = $this->getContext($request)) {
// If the user has no roles, or only one role and this is reader, go to "Index" page.
// Else go to "submissions" page
$userGroups = Repo::userGroup()->userUserGroups($userId, $context->getId());
if ($userGroups->isEmpty()
|| ($userGroups->count() == 1 && $userGroups->first()->getRoleId() == Role::ROLE_ID_READER)
) {
return $request->url(null, 'index');
}
return $request->url(null, 'submissions');
} else {
// The user is at the site context, check to see if they are
// only registered in one place w/ one role
$userGroups = Repo::userGroup()->userUserGroups($userId, \PKP\core\PKPApplication::CONTEXT_ID_NONE);
if ($userGroups->count() == 1) {
$firstUserGroup = $userGroups->first();
$contextDao = Application::getContextDAO();
$context = $contextDao->getById($firstUserGroup->getContextId());
if (!isset($context)) {
$request->redirect('index', 'index');
}
if ($firstUserGroup->getRoleId() == Role::ROLE_ID_READER) {
$request->redirect(null, 'index');
}
}
return $request->url('index', 'index');
}
}
//
// Private helper methods.
//
/**
* Retrieve part of the current requested
* url using the passed callback method.
*
* @param array $callback Core method to retrieve
* page, operation or arguments from url.
* @param PKPRequest $request
*
* @return array|string|null
*/
private function _getRequestedUrlParts($callback, &$request)
{
$url = null;
assert($request->getRouter() instanceof \PKP\core\PKPPageRouter);
if (isset($_SERVER['PATH_INFO'])) {
$url = $_SERVER['PATH_INFO'];
}
$userVars = $request->getUserVars();
return call_user_func_array($callback, [$url, true, $userVars]);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\core\PKPPageRouter', '\PKPPageRouter');
}
|