first commit
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/core/APIResponse.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 APIResponse
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Extends the Response class in the Slim microframework.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use Slim\Http\Response;
|
||||
|
||||
class APIResponse extends Response
|
||||
{
|
||||
public const RESPONSE_CSV = 'text/csv';
|
||||
public const RESPONSE_TSV = 'text/tab-separated-values';
|
||||
|
||||
/**
|
||||
* CSV Response
|
||||
*
|
||||
* @param integer $maxRows The total amount of rows, that is provided within the onw X-Total-Count header field
|
||||
*/
|
||||
public function withCSV(array $rows, array $columns, int $maxRows, string $mimeType = self::RESPONSE_CSV): self
|
||||
{
|
||||
$separator = ',' ;
|
||||
if ($mimeType == self::RESPONSE_TSV) {
|
||||
$separator = "\t";
|
||||
}
|
||||
$fp = fopen('php://output', 'wt');
|
||||
//Add BOM (byte order mark) to fix UTF-8 in Excel
|
||||
fprintf($fp, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
||||
if (!empty($columns)) {
|
||||
fputcsv($fp, [''], $separator);
|
||||
fputcsv($fp, $columns, $separator);
|
||||
}
|
||||
foreach ($rows as $row) {
|
||||
fputcsv($fp, $row, $separator);
|
||||
}
|
||||
$csvData = stream_get_contents($fp);
|
||||
fclose($fp);
|
||||
$this->getBody()->rewind();
|
||||
$this->getBody()->write($csvData);
|
||||
$this->withStatus(200);
|
||||
return $this->withHeader('X-Total-Count', $maxRows)->withHeader('Content-Type', $mimeType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Response with an error message
|
||||
*
|
||||
* @param string $msg The message translation key
|
||||
* @param array $params Optional parameters to pass to the translation
|
||||
*
|
||||
* @return APIResponse
|
||||
*/
|
||||
public function withJsonError($msg, $params = null)
|
||||
{
|
||||
return $this->withJson(
|
||||
[
|
||||
'error' => $msg,
|
||||
'errorMessage' => __($msg, $params ?? []),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\APIResponse', '\APIResponse');
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/APIRouter.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 APIRouter
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Map HTTP requests to a REST API using the Slim microframework.
|
||||
*
|
||||
* Requests for [index.php]/api are intercepted for site-level API requests,
|
||||
* and requests for [index.php]/{contextPath}/api are intercepted for
|
||||
* context-level API requests.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use APP\core\Application;
|
||||
use Exception;
|
||||
use PKP\handler\APIHandler;
|
||||
use PKP\session\SessionManager;
|
||||
|
||||
class APIRouter extends PKPRouter
|
||||
{
|
||||
/**
|
||||
* Determines path info parts
|
||||
*
|
||||
*/
|
||||
protected function getPathInfoParts(): array
|
||||
{
|
||||
return explode('/', trim($_SERVER['PATH_INFO'] ?? '', '/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this router can route the given request.
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return bool true, if the router supports this request, otherwise false
|
||||
*/
|
||||
public function supports($request): bool
|
||||
{
|
||||
$pathInfoParts = $this->getPathInfoParts();
|
||||
|
||||
if (!is_null($pathInfoParts) && count($pathInfoParts) >= 2 && $pathInfoParts[1] == 'api') {
|
||||
// Context-specific API requests: [index.php]/{contextPath}/api
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the API version
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
$pathInfoParts = $this->getPathInfoParts();
|
||||
return Core::cleanFileVar($pathInfoParts[2] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity being requested
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntity()
|
||||
{
|
||||
$pathInfoParts = $this->getPathInfoParts();
|
||||
return Core::cleanFileVar($pathInfoParts[3] ?? '');
|
||||
}
|
||||
|
||||
//
|
||||
// Implement template methods from PKPRouter
|
||||
//
|
||||
/**
|
||||
* @copydoc PKPRouter::route()
|
||||
*/
|
||||
public function route($request)
|
||||
{
|
||||
// Ensure slim library is available
|
||||
require_once('lib/pkp/lib/vendor/autoload.php');
|
||||
|
||||
$sourceFile = sprintf('api/%s/%s/index.php', $this->getVersion(), $this->getEntity());
|
||||
|
||||
if (!file_exists($sourceFile)) {
|
||||
http_response_code('404');
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'error' => 'api.404.endpointNotFound',
|
||||
'errorMessage' => __('api.404.endpointNotFound'),
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!SessionManager::isDisabled()) {
|
||||
// Initialize session
|
||||
SessionManager::getManager();
|
||||
}
|
||||
|
||||
$handler = require('./' . $sourceFile);
|
||||
$this->setHandler($handler);
|
||||
$handler->getApp()->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the requested operation
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRequestedOp($request)
|
||||
{
|
||||
/** @var APIHandler */
|
||||
$handler = $this->getHandler();
|
||||
$container = $handler->getApp()->getContainer();
|
||||
$router = $container->get('router');
|
||||
$slimRequest = $handler->getSlimRequest();
|
||||
$routeInfo = $router->dispatch($slimRequest);
|
||||
if (isset($routeInfo[1])) {
|
||||
$route = $router->lookupRoute($routeInfo[1]);
|
||||
$callable = $route->getCallable();
|
||||
if (is_array($callable) && count($callable) == 2) {
|
||||
return $callable[1];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc PKPRouter::handleAuthorizationFailure()
|
||||
*/
|
||||
public function handleAuthorizationFailure(
|
||||
$request,
|
||||
$authorizationMessage,
|
||||
array $messageParams = []
|
||||
) {
|
||||
http_response_code('403');
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'error' => $authorizationMessage,
|
||||
'errorMessage' => __($authorizationMessage, $messageParams),
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc PKPRouter::url()
|
||||
*
|
||||
* @param null|mixed $newContext
|
||||
* @param null|mixed $endpoint
|
||||
* @param null|mixed $op
|
||||
* @param null|mixed $path
|
||||
* @param null|mixed $params
|
||||
* @param null|mixed $anchor
|
||||
*/
|
||||
public function url(
|
||||
PKPRequest $request,
|
||||
?string $newContext = null,
|
||||
$endpoint = null,
|
||||
$op = null,
|
||||
$path = null,
|
||||
$params = null,
|
||||
$anchor = null,
|
||||
$escape = false
|
||||
) {
|
||||
// APIHandlers do not understand $op, $path or $anchor. All routing is baked
|
||||
// into the $endpoint string. It only accepts a string as the $newContext,
|
||||
// since it relies on this when path info is disabled.
|
||||
if (!is_null($op) || !is_null($path) || !is_null($anchor) || !is_scalar($newContext)) {
|
||||
throw new Exception('APIRouter::url() should not be called with an op, path or anchor. If a new context is passed, the context path must be passed instead of the context object.');
|
||||
}
|
||||
|
||||
//
|
||||
// Base URL and Context
|
||||
//
|
||||
$baseUrlAndContext = $this->_urlGetBaseAndContext($request, $newContext);
|
||||
$baseUrl = array_shift($baseUrlAndContext);
|
||||
$context = array_shift($baseUrlAndContext);
|
||||
|
||||
//
|
||||
// Additional query parameters
|
||||
//
|
||||
$additionalParameters = $this->_urlGetAdditionalParameters($request, $params, $escape);
|
||||
|
||||
//
|
||||
// Assemble URL
|
||||
//
|
||||
$pathInfoArray = array_merge(
|
||||
$context,
|
||||
['api', Application::API_VERSION, $endpoint]
|
||||
);
|
||||
$queryParametersArray = $additionalParameters;
|
||||
|
||||
return $this->_urlFromParts($baseUrl, $pathInfoArray, $queryParametersArray, $anchor, $escape);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\APIRouter', '\APIRouter');
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/AppServiceProvider.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 AppServiceProvider
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Resolves requests for application classes such as the request handler
|
||||
* to support dependency injection
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Services;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use PKP\context\Context;
|
||||
use PKP\services\PKPSchemaService;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register application services
|
||||
*
|
||||
* Services registered on the app container here can be automatically
|
||||
* injected as dependencies to classes that are instantiated by the
|
||||
* app container.
|
||||
*
|
||||
* @see https://laravel.com/docs/8.x/container#automatic-injection
|
||||
* @see https://laravel.com/docs/8.x/providers#the-register-method
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton('maps', function ($app) {
|
||||
return new MapContainer();
|
||||
});
|
||||
$this->app->singleton(PKPSchemaService::class, function ($app) {
|
||||
return Services::get('schema');
|
||||
});
|
||||
$this->app->singleton(PKPRequest::class, function ($app) {
|
||||
return Application::get()->getRequest();
|
||||
});
|
||||
$this->app->bind(Context::class, function ($app) {
|
||||
return Application::get()->getRequest()->getContext();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/ArrayItemIterator.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 ArrayItemIterator
|
||||
*
|
||||
* @ingroup db
|
||||
*
|
||||
* @brief Provides paging and iteration for arrays.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
class ArrayItemIterator extends ItemIterator
|
||||
{
|
||||
/** @var array The array of contents of this iterator. */
|
||||
public $theArray;
|
||||
|
||||
/** @var int Number of items to iterate through on this page */
|
||||
public $itemsPerPage;
|
||||
|
||||
/** @var int The current page. */
|
||||
public $page;
|
||||
|
||||
/** @var int The total number of items. */
|
||||
public $count;
|
||||
|
||||
/** @var bool Whether or not the iterator was empty from the start */
|
||||
public $wasEmpty;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $theArray The array of items to iterate through
|
||||
* @param int $page the current page number
|
||||
* @param int $itemsPerPage Number of items to display per page
|
||||
*/
|
||||
public function __construct(&$theArray, $page = -1, $itemsPerPage = -1)
|
||||
{
|
||||
parent::__construct();
|
||||
if ($page >= 1 && $itemsPerPage >= 1) {
|
||||
$this->theArray = array_slice($theArray, ($page - 1) * $itemsPerPage, $itemsPerPage, true);
|
||||
$this->page = $page;
|
||||
} else {
|
||||
$this->theArray = & $theArray;
|
||||
$this->page = 1;
|
||||
$this->itemsPerPage = max(count($this->theArray), 1);
|
||||
}
|
||||
$this->count = count($theArray);
|
||||
$this->itemsPerPage = $itemsPerPage;
|
||||
$this->wasEmpty = count($this->theArray) == 0;
|
||||
reset($this->theArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method: Generate an iterator from an array and rangeInfo object.
|
||||
*
|
||||
* @param array $theArray
|
||||
* @param object $theRange
|
||||
*/
|
||||
public function &fromRangeInfo(&$theArray, &$theRange)
|
||||
{
|
||||
if ($theRange && $theRange->isValid()) {
|
||||
$theIterator = new ArrayItemIterator($theArray, $theRange->getPage(), $theRange->getCount());
|
||||
} else {
|
||||
$theIterator = new ArrayItemIterator($theArray);
|
||||
}
|
||||
return $theIterator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next item in the iterator.
|
||||
*
|
||||
* @return ?object
|
||||
*/
|
||||
public function &next()
|
||||
{
|
||||
if (!is_array($this->theArray)) {
|
||||
$value = null;
|
||||
return $value;
|
||||
}
|
||||
$value = current($this->theArray);
|
||||
if (next($this->theArray) === false) {
|
||||
$this->theArray = null;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next item in the iterator, with key.
|
||||
*
|
||||
* @return array (key, value)
|
||||
*/
|
||||
public function nextWithKey()
|
||||
{
|
||||
$key = key($this->theArray);
|
||||
$value = $this->next();
|
||||
return [$key, $value];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not this iterator represents the first page
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function atFirstPage()
|
||||
{
|
||||
return $this->page == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not this iterator represents the last page
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function atLastPage()
|
||||
{
|
||||
return ($this->page * $this->itemsPerPage) + 1 > $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current page number
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPage()
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total count of items
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCount()
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of pages
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPageCount()
|
||||
{
|
||||
return max(1, ceil($this->count / $this->itemsPerPage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean indicating whether or not we've reached the end of results
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function eof()
|
||||
{
|
||||
return (($this->theArray == null) || (count($this->theArray) == 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean indicating whether or not this iterator was empty from the beginning
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function wasEmpty()
|
||||
{
|
||||
return $this->wasEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this iterator to an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->theArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this iterator as an associative array.
|
||||
*/
|
||||
public function toAssociativeArray()
|
||||
{
|
||||
return $this->theArray;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\ArrayItemIterator', '\ArrayItemIterator');
|
||||
}
|
||||
@@ -0,0 +1,513 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @defgroup core Core
|
||||
* Core web application concerns such as routing, dispatching, etc.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file classes/core/Core.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 Core
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Class containing system-wide functions.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use Exception;
|
||||
use PKP\cache\CacheManager;
|
||||
use PKP\cache\FileCache;
|
||||
use PKP\config\Config;
|
||||
use SplFileInfo;
|
||||
|
||||
define('PKP_LIB_PATH', 'lib/pkp');
|
||||
define('COUNTER_USER_AGENTS_FILE', Core::getBaseDir() . '/' . PKP_LIB_PATH . '/lib/counterBots/generated/COUNTER_Robots_list.txt');
|
||||
|
||||
class Core
|
||||
{
|
||||
/** @var array The regular expressions that will find a bot user agent */
|
||||
public static $botRegexps = [];
|
||||
|
||||
/**
|
||||
* Get the path to the base installation directory.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getBaseDir()
|
||||
{
|
||||
static $baseDir;
|
||||
return $baseDir ??= dirname(INDEX_FILE_LOCATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a value to be used in a file path.
|
||||
* Removes any characters except alphanumeric characters, underscores, and dashes.
|
||||
*
|
||||
* @param string $var
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function cleanFileVar($var)
|
||||
{
|
||||
return cleanFileVar($var);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current date in ISO (YYYY-MM-DD HH:MM:SS) format.
|
||||
*
|
||||
* @param int $ts optional, use specified timestamp instead of current time
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getCurrentDate($ts = null)
|
||||
{
|
||||
return date('Y-m-d H:i:s', $ts ?? time());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return *nix timestamp with microseconds (in units of seconds).
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public static function microtime()
|
||||
{
|
||||
[$usec, $sec] = explode(' ', microtime());
|
||||
return (float)$sec + (float)$usec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the server platform is Windows.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isWindows()
|
||||
{
|
||||
return strtolower_codesafe(substr(PHP_OS, 0, 3)) == 'win';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if a PHP module is enabled.
|
||||
*
|
||||
* @param string $moduleName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function checkGeneralPHPModule($moduleName)
|
||||
{
|
||||
if (extension_loaded($moduleName)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the passed user agent for a bot.
|
||||
*
|
||||
* @param string $userAgent
|
||||
* @param string $botRegexpsFile An alternative file with regular
|
||||
* expressions to find bots inside user agent strings.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isUserAgentBot($userAgent, $botRegexpsFile = COUNTER_USER_AGENTS_FILE)
|
||||
{
|
||||
static $botRegexps;
|
||||
Registry::set('currentUserAgentsFile', $botRegexpsFile);
|
||||
|
||||
if (!isset($botRegexps[$botRegexpsFile])) {
|
||||
$botFileCacheId = md5($botRegexpsFile);
|
||||
$cacheManager = CacheManager::getManager();
|
||||
/** @var FileCache */
|
||||
$cache = $cacheManager->getCache('core', $botFileCacheId, ['Core', '_botFileListCacheMiss'], CACHE_TYPE_FILE);
|
||||
$botRegexps[$botRegexpsFile] = $cache->getContents();
|
||||
}
|
||||
|
||||
foreach ($botRegexps[$botRegexpsFile] as $regexp) {
|
||||
// make the search case insensitive
|
||||
$regexp .= 'i';
|
||||
if (PKPString::regexp_match($regexp, $userAgent)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get context path present into the passed
|
||||
* url information.
|
||||
*
|
||||
* @param string $urlInfo Full url or just path info.
|
||||
*/
|
||||
public static function getContextPath(string $urlInfo): string
|
||||
{
|
||||
$contextPaths = explode('/', trim($urlInfo, '/'), 2);
|
||||
return self::cleanFileVar($contextPaths[0] ?: 'index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the page present into
|
||||
* the passed url information. It expects that urls
|
||||
* were built using the system.
|
||||
*
|
||||
* @param string $urlInfo Full url or just path info.
|
||||
* @param bool $isPathInfo Tell if the
|
||||
* passed url info string is a path info or not.
|
||||
* @param array $userVars (optional) Pass GET variables
|
||||
* if needed (for testing only).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getPage($urlInfo, $isPathInfo, $userVars = [])
|
||||
{
|
||||
$page = Core::_getUrlComponents($urlInfo, $isPathInfo, 0, 'page', $userVars);
|
||||
return Core::cleanFileVar(is_null($page) ? '' : $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the operation present into
|
||||
* the passed url information. It expects that urls
|
||||
* were built using the system.
|
||||
*
|
||||
* @param string $urlInfo Full url or just path info.
|
||||
* @param bool $isPathInfo Tell if the
|
||||
* passed url info string is a path info or not.
|
||||
* @param array $userVars (optional) Pass GET variables
|
||||
* if needed (for testing only).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getOp($urlInfo, $isPathInfo, $userVars = [])
|
||||
{
|
||||
$operation = Core::_getUrlComponents($urlInfo, $isPathInfo, 1, 'op', $userVars);
|
||||
return Core::cleanFileVar(empty($operation) ? 'index' : $operation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments present into
|
||||
* the passed url information (not GET/POST arguments,
|
||||
* only arguments appended to the URL separated by "/").
|
||||
* It expects that urls were built using the system.
|
||||
*
|
||||
* @param string $urlInfo Full url or just path info.
|
||||
* @param bool $isPathInfo Tell if the
|
||||
* passed url info string is a path info or not.
|
||||
* @param array $userVars (optional) Pass GET variables
|
||||
* if needed (for testing only).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getArgs($urlInfo, $isPathInfo, $userVars = [])
|
||||
{
|
||||
return Core::_getUrlComponents($urlInfo, $isPathInfo, 2, 'path', $userVars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove base url from the passed url, if any.
|
||||
* Also, if true, checks for the context path in
|
||||
* url and if it's missing, tries to add it.
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return string|bool The url without base url,
|
||||
* false if it was not possible to remove it.
|
||||
*/
|
||||
public static function removeBaseUrl($url)
|
||||
{
|
||||
[$baseUrl, $contextPath] = Core::_getBaseUrlAndPath($url);
|
||||
|
||||
if (!$baseUrl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove base url from url, if any.
|
||||
$url = str_replace($baseUrl, '', $url);
|
||||
|
||||
// If url doesn't have the entire protocol and host part,
|
||||
// remove any possible base url path from url.
|
||||
$baseUrlPath = parse_url($baseUrl, PHP_URL_PATH);
|
||||
if ($baseUrlPath == $url) {
|
||||
// Access to the base url, no context, the entire
|
||||
// url is part of the base url and we can return empty.
|
||||
$url = '';
|
||||
} else {
|
||||
// Handle case where index.php was removed by rewrite rules,
|
||||
// and we have base url followed by the args.
|
||||
if (strpos($url, $baseUrlPath . '?') === 0) {
|
||||
$replacement = '?'; // Url path replacement.
|
||||
$baseSystemEscapedPath = preg_quote($baseUrlPath . '?', '/');
|
||||
} else {
|
||||
$replacement = '/'; // Url path replacement.
|
||||
$baseSystemEscapedPath = preg_quote($baseUrlPath . '/', '/');
|
||||
}
|
||||
$url = preg_replace('/^' . $baseSystemEscapedPath . '/', $replacement, $url);
|
||||
|
||||
// Remove possible index.php page from url.
|
||||
$url = str_replace('/index.php', '', $url);
|
||||
}
|
||||
|
||||
if ($contextPath) {
|
||||
// We found the contextPath using the base_url
|
||||
// config file settings. Check if the url starts
|
||||
// with the context path, if not, prepend it.
|
||||
if (strpos($url, '/' . $contextPath . '/') !== 0) {
|
||||
$url = '/' . $contextPath . $url;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any possible trailing slashes.
|
||||
$url = rtrim($url, '/');
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get the base url and, if configuration
|
||||
* is set to use base url override, context
|
||||
* path for the passed url.
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return array With two elements, base url and context path.
|
||||
*/
|
||||
protected static function _getBaseUrlAndPath($url)
|
||||
{
|
||||
$baseUrl = false;
|
||||
$contextPath = false;
|
||||
|
||||
// Check for override base url settings.
|
||||
$contextBaseUrls = Config::getContextBaseUrls();
|
||||
|
||||
if (empty($contextBaseUrls)) {
|
||||
$baseUrl = Config::getVar('general', 'base_url');
|
||||
} else {
|
||||
// We are just interested in context base urls, remove the index one.
|
||||
if (isset($contextBaseUrls['index'])) {
|
||||
unset($contextBaseUrls['index']);
|
||||
}
|
||||
|
||||
// Arrange them in length order, so we make sure
|
||||
// we get the correct one, in case there's an overlaping
|
||||
// of contexts, eg.:
|
||||
// base_url[context1] = http://somesite.com/
|
||||
// base_url[context2] = http://somesite.com/context2
|
||||
$sortedBaseUrls = array_combine($contextBaseUrls, array_map('strlen', $contextBaseUrls));
|
||||
arsort($sortedBaseUrls);
|
||||
|
||||
foreach (array_keys($sortedBaseUrls) as $workingBaseUrl) {
|
||||
$urlHost = parse_url($url, PHP_URL_HOST);
|
||||
if (is_null($urlHost)) {
|
||||
// Check the base url without the host part.
|
||||
$baseUrlHost = parse_url($workingBaseUrl, PHP_URL_HOST);
|
||||
if (is_null($baseUrlHost)) {
|
||||
break;
|
||||
}
|
||||
$baseUrlToSearch = substr($workingBaseUrl, strpos($workingBaseUrl, $baseUrlHost) + strlen($baseUrlHost));
|
||||
// Base url with only host part, add trailing slash
|
||||
// so it can be checked below.
|
||||
if (!$baseUrlToSearch) {
|
||||
$baseUrlToSearch = '/';
|
||||
}
|
||||
} else {
|
||||
$baseUrlToSearch = $workingBaseUrl;
|
||||
}
|
||||
|
||||
$baseUrlCheck = Core::_checkBaseUrl($baseUrlToSearch, $url);
|
||||
if (is_null($baseUrlCheck)) {
|
||||
// Can't decide. Stop searching.
|
||||
break;
|
||||
} elseif ($baseUrlCheck === true) {
|
||||
$contextPath = array_search($workingBaseUrl, $contextBaseUrls);
|
||||
$baseUrl = $workingBaseUrl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we still have no base URL, this may be a situation where we have an install with some customized URLs, and some not.
|
||||
// Return the default base URL.
|
||||
|
||||
if (!$baseUrl) {
|
||||
$baseUrl = Config::getVar('general', 'base_url');
|
||||
}
|
||||
|
||||
return [$baseUrl, $contextPath];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the passed base url is part of
|
||||
* the passed url, based on the context base url
|
||||
* configuration. Both parameters can represent
|
||||
* full url (host plus path) or just the path,
|
||||
* but they have to be consistent.
|
||||
*
|
||||
* @param string $baseUrl Full base url
|
||||
* or just it's path info.
|
||||
* @param string $url Full url or just it's
|
||||
* path info.
|
||||
*
|
||||
* @return ?bool
|
||||
*/
|
||||
protected static function _checkBaseUrl($baseUrl, $url)
|
||||
{
|
||||
// Check if both base url and url have host
|
||||
// component or not.
|
||||
$baseUrlHasHost = (bool) parse_url($baseUrl, PHP_URL_HOST);
|
||||
$urlHasHost = (bool) parse_url($url, PHP_URL_HOST);
|
||||
if ($baseUrlHasHost !== $urlHasHost) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$contextBaseUrls = & Config::getContextBaseUrls();
|
||||
|
||||
// If the base url is found inside the passed url,
|
||||
// then we might found the right context path.
|
||||
if (strpos($url, $baseUrl) === 0) {
|
||||
if (strpos($url, '/index.php') == strlen($baseUrl) - 1) {
|
||||
// index.php appears right after the base url,
|
||||
// no more possible paths.
|
||||
return true;
|
||||
} else {
|
||||
// Still have to check if there is no other context
|
||||
// base url that combined with it's context path is
|
||||
// equal to this base url. If it exists, we can't
|
||||
// tell which base url is contained in url.
|
||||
foreach ($contextBaseUrls as $contextPath => $workingBaseUrl) {
|
||||
$urlToCheck = $workingBaseUrl . '/' . $contextPath;
|
||||
if (!$baseUrlHasHost) {
|
||||
$urlToCheck = parse_url($urlToCheck, PHP_URL_PATH);
|
||||
}
|
||||
if ($baseUrl == $urlToCheck) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bot list file cache miss fallback.
|
||||
* (WARNING: This function appears to be used externally, hence public despite _ prefix.)
|
||||
*
|
||||
* @param FileCache $cache
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function _botFileListCacheMiss($cache)
|
||||
{
|
||||
$id = $cache->getCacheId();
|
||||
$filteredBotRegexps = array_filter(
|
||||
file(Registry::get('currentUserAgentsFile')),
|
||||
function ($regexp) {
|
||||
$regexp = trim($regexp);
|
||||
return !empty($regexp) && $regexp[0] != '#';
|
||||
}
|
||||
);
|
||||
$botRegexps = array_map(
|
||||
function ($regexp) {
|
||||
$delimiter = '/';
|
||||
$regexp = trim($regexp);
|
||||
if (strpos($regexp, $delimiter) !== 0) {
|
||||
// Make sure delimiters are in place.
|
||||
$regexp = $delimiter . $regexp . $delimiter;
|
||||
}
|
||||
return $regexp;
|
||||
},
|
||||
$filteredBotRegexps
|
||||
);
|
||||
$cache->setEntireCache($botRegexps);
|
||||
return $botRegexps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get passed variable value inside the passed url.
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $varName
|
||||
* @param array $userVars
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private static function _getUserVar($url, $varName, $userVars = [])
|
||||
{
|
||||
parse_str((string) parse_url($url, PHP_URL_QUERY), $userVarsFromUrl);
|
||||
return $userVarsFromUrl[$varName] ?? $userVars[$varName] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get url components (page, operation and args)
|
||||
* based on the passed offset.
|
||||
*
|
||||
* @param string $urlInfo
|
||||
* @param string $isPathInfo
|
||||
* @param int $offset
|
||||
* @param string $varName
|
||||
* @param array $userVars (optional) GET variables
|
||||
* (only for testing).
|
||||
*
|
||||
* @return mixed array|string|null
|
||||
*/
|
||||
private static function _getUrlComponents($urlInfo, $isPathInfo, $offset, $varName = '', $userVars = [])
|
||||
{
|
||||
$component = null;
|
||||
|
||||
$isArrayComponent = false;
|
||||
if ($varName == 'path') {
|
||||
$isArrayComponent = true;
|
||||
}
|
||||
|
||||
$vars = explode('/', trim($urlInfo ?? '', '/'));
|
||||
if (count($vars) > $offset + 1) {
|
||||
if ($isArrayComponent) {
|
||||
$component = array_slice($vars, $offset + 1);
|
||||
} else {
|
||||
$component = $vars[$offset + 1];
|
||||
}
|
||||
}
|
||||
|
||||
if ($isArrayComponent) {
|
||||
if (empty($component)) {
|
||||
$component = [];
|
||||
} elseif (!is_array($component)) {
|
||||
$component = [$component];
|
||||
}
|
||||
}
|
||||
|
||||
return $component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the class name from the given file path.
|
||||
*
|
||||
* @param SplFileInfo $file info about a file extract class name from
|
||||
*
|
||||
* @return string fully qualified class name
|
||||
*
|
||||
* @see Finder
|
||||
*/
|
||||
public static function classFromFile(SplFileInfo $file): string
|
||||
{
|
||||
$libPath = realpath(base_path(PKP_LIB_PATH));
|
||||
$isLib = str_starts_with($file->getRealPath(), $libPath);
|
||||
$className = str_replace($isLib ? $libPath : realpath(base_path()), '', $file->getRealPath());
|
||||
// Drop the "classes" from the path (we don't use it on the namespaces) and the extension
|
||||
$className = preg_replace('#^[\\\\/]classes|\.php$#', '', $className);
|
||||
// Include the base namespace and replace the directory separator by the namespace separator
|
||||
$className = str_replace('/', '\\', '/' . ($isLib ? 'PKP' : 'APP') . $className);
|
||||
|
||||
return class_exists($className)
|
||||
? $className
|
||||
: throw new Exception("Failed to map the file \"{$file->getRealPath()}\" to a full qualified class name");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/DataObject.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 DataObject
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @see Core
|
||||
*
|
||||
* @brief Any class with an associated DAO should extend this class.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\db\DAO;
|
||||
use PKP\db\DAORegistry;
|
||||
use \PKP\filter\FilterDAO;
|
||||
use PKP\facades\Locale;
|
||||
|
||||
/**
|
||||
* @template T of EntityDAO|DAO
|
||||
*/
|
||||
class DataObject
|
||||
{
|
||||
/** @var array Array of object data */
|
||||
public $_data = [];
|
||||
|
||||
/** @var bool whether this objects loads meta-data adapters from the database */
|
||||
public $_hasLoadableAdapters = false;
|
||||
|
||||
/** @var array an array of meta-data extraction adapters (one per supported schema) */
|
||||
public $_metadataExtractionAdapters = [];
|
||||
|
||||
/** @var bool whether extraction adapters have already been loaded from the database */
|
||||
public $_extractionAdaptersLoaded = false;
|
||||
|
||||
/** @var array an array of meta-data injection adapters (one per supported schema) */
|
||||
public $_metadataInjectionAdapters = [];
|
||||
|
||||
/** @var bool whether injection adapters have already been loaded from the database */
|
||||
public $_injectionAdaptersLoaded = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Getters and Setters
|
||||
//
|
||||
/**
|
||||
* Get a piece of data for this object, localized to the current
|
||||
* locale if possible.
|
||||
*/
|
||||
public function getLocalizedData(string $key, string $preferredLocale = null, string &$selectedLocale = null): mixed
|
||||
{
|
||||
foreach ($this->getLocalePrecedence($preferredLocale) as $locale) {
|
||||
$value = & $this->getData($key, $locale);
|
||||
if (!empty($value)) {
|
||||
$selectedLocale = $locale;
|
||||
return $value;
|
||||
}
|
||||
unset($value);
|
||||
}
|
||||
|
||||
// Fallback: Get the first available piece of data.
|
||||
$data = $this->getData($key, null);
|
||||
foreach ((array) $data as $locale => $dataValue) {
|
||||
if (!empty($dataValue)) {
|
||||
$selectedLocale = $locale;
|
||||
return $dataValue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the locale precedence order for object in the following order
|
||||
*
|
||||
* 1. Preferred Locale if provided
|
||||
* 2. User's current local
|
||||
* 3. Object's default locale if set
|
||||
* 4. Context's primary locale if context available
|
||||
* 5. Site's primary locale
|
||||
*/
|
||||
public function getLocalePrecedence(string $preferredLocale = null): array
|
||||
{
|
||||
$request = Application::get()->getRequest();
|
||||
|
||||
return array_unique(
|
||||
array_filter([
|
||||
$preferredLocale ?? Locale::getLocale(),
|
||||
$this->getDefaultLocale(),
|
||||
$request->getContext()?->getPrimaryLocale(),
|
||||
$request->getSite()->getPrimaryLocale(),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default locale for object
|
||||
*/
|
||||
public function getDefaultLocale(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a data variable.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $locale (optional)
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function &getData($key, $locale = null)
|
||||
{
|
||||
if (is_null($locale)) {
|
||||
if (array_key_exists($key, $this->_data)) {
|
||||
return $this->_data[$key];
|
||||
}
|
||||
} elseif (array_key_exists($locale, (array) ($this->_data[$key] ?? []))) {
|
||||
return $this->_data[$key][$locale];
|
||||
}
|
||||
$nullVar = null;
|
||||
return $nullVar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a new or existing data variable.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value can be either a single value or
|
||||
* an array of of localized values in the form:
|
||||
* array(
|
||||
* 'fr_FR' => 'en français',
|
||||
* 'en' => 'in English',
|
||||
* ...
|
||||
* )
|
||||
* @param string $locale (optional) non-null for a single
|
||||
* localized value. Null for a non-localized value or
|
||||
* when setting all locales at once (see comment for
|
||||
* $value parameter)
|
||||
*/
|
||||
public function setData($key, $value, $locale = null)
|
||||
{
|
||||
if (is_null($locale)) {
|
||||
// This is either a non-localized value or we're passing in all locales at once.
|
||||
$this->_data[$key] = $value;
|
||||
return;
|
||||
}
|
||||
// Set a single localized value.
|
||||
if (!is_null($value)) {
|
||||
if (isset($this->_data[$key]) && !is_array($this->_data[$key])) {
|
||||
$this->_data[$key] = [];
|
||||
}
|
||||
$this->_data[$key][$locale] = $value;
|
||||
return;
|
||||
}
|
||||
// If the value is null, remove the entry.
|
||||
if (array_key_exists($key, $this->_data)) {
|
||||
if (array_key_exists($locale, (array) $this->_data[$key])) {
|
||||
unset($this->_data[$key][$locale]);
|
||||
}
|
||||
// Was this the last entry for the data variable?
|
||||
if (empty($this->_data[$key])) {
|
||||
unset($this->_data[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset an element of the data object.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $locale (optional) non-null for a single
|
||||
* localized value. Null for a non-localized value or
|
||||
* when unsetting all locales at once.
|
||||
*/
|
||||
public function unsetData($key, $locale = null)
|
||||
{
|
||||
if (is_null($locale)) {
|
||||
unset($this->_data[$key]);
|
||||
} else {
|
||||
unset($this->_data[$key][$locale]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a value exists for a given data variable.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $locale (optional)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasData($key, $locale = null)
|
||||
{
|
||||
return is_null($locale) ? array_key_exists($key, $this->_data) : array_key_exists($locale, (array) ($this->_data[$key] ?? []));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array with all data variables.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function &getAllData()
|
||||
{
|
||||
return $this->_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all data variables at once.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function setAllData($data)
|
||||
{
|
||||
$this->_data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ID of object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->getData('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ID of object.
|
||||
*
|
||||
* @param int $id
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
$this->setData('id', $id);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// MetadataProvider interface implementation
|
||||
//
|
||||
/**
|
||||
* Set whether the object has loadable meta-data adapters
|
||||
*
|
||||
* @param bool $hasLoadableAdapters
|
||||
*/
|
||||
public function setHasLoadableAdapters($hasLoadableAdapters)
|
||||
{
|
||||
$this->_hasLoadableAdapters = $hasLoadableAdapters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the object has loadable meta-data adapters
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getHasLoadableAdapters()
|
||||
{
|
||||
return $this->_hasLoadableAdapters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a meta-data adapter that will be supported
|
||||
* by this application entity. Only one adapter per schema
|
||||
* can be added.
|
||||
*
|
||||
* @param \PKP\metadata\MetadataDataObjectAdapter $metadataAdapter
|
||||
*/
|
||||
public function addSupportedMetadataAdapter($metadataAdapter)
|
||||
{
|
||||
$metadataSchemaName = $metadataAdapter->getMetadataSchemaName();
|
||||
assert(!empty($metadataSchemaName));
|
||||
|
||||
// NB: Some adapters are injectors and extractors at the same time,
|
||||
// notably the meta-data description dummy adapter that converts
|
||||
// from/to a meta-data description. That's why we have to check
|
||||
// input and output type separately.
|
||||
|
||||
// Is this a meta-data extractor?
|
||||
$inputType = $metadataAdapter->getInputType();
|
||||
if ($inputType->checkType($this)) {
|
||||
if (!isset($this->_metadataExtractionAdapters[$metadataSchemaName])) {
|
||||
$this->_metadataExtractionAdapters[$metadataSchemaName] = $metadataAdapter;
|
||||
}
|
||||
}
|
||||
|
||||
// Is this a meta-data injector?
|
||||
$outputType = $metadataAdapter->getOutputType();
|
||||
if ($outputType->checkType($this)) {
|
||||
if (!isset($this->_metadataInjectionAdapters[$metadataSchemaName])) {
|
||||
$this->_metadataInjectionAdapters[$metadataSchemaName] = $metadataAdapter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all adapters for the given meta-data schema
|
||||
* (if it exists).
|
||||
*
|
||||
* @param string $metadataSchemaName fully qualified class name
|
||||
*
|
||||
* @return bool true if an adapter was removed, otherwise false.
|
||||
*/
|
||||
public function removeSupportedMetadataAdapter($metadataSchemaName)
|
||||
{
|
||||
$result = false;
|
||||
if (isset($this->_metadataExtractionAdapters[$metadataSchemaName])) {
|
||||
unset($this->_metadataExtractionAdapters[$metadataSchemaName]);
|
||||
$result = true;
|
||||
}
|
||||
if (isset($this->_metadataInjectionAdapters[$metadataSchemaName])) {
|
||||
unset($this->_metadataInjectionAdapters[$metadataSchemaName]);
|
||||
$result = true;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all meta-data extraction adapters that
|
||||
* support this data object. This includes adapters
|
||||
* loaded from the database.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSupportedExtractionAdapters()
|
||||
{
|
||||
// Load meta-data adapters from the database.
|
||||
if ($this->getHasLoadableAdapters() && !$this->_extractionAdaptersLoaded) {
|
||||
$filterDao = DAORegistry::getDAO('FilterDAO'); /** @var FilterDAO $filterDao */
|
||||
$loadedAdapters = $filterDao->getObjectsByTypeDescription('class::%', 'metadata::%', $this);
|
||||
foreach ($loadedAdapters as $loadedAdapter) {
|
||||
$this->addSupportedMetadataAdapter($loadedAdapter);
|
||||
}
|
||||
$this->_extractionAdaptersLoaded = true;
|
||||
}
|
||||
|
||||
return $this->_metadataExtractionAdapters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all meta-data injection adapters that
|
||||
* support this data object. This includes adapters
|
||||
* loaded from the database.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSupportedInjectionAdapters()
|
||||
{
|
||||
// Load meta-data adapters from the database.
|
||||
if ($this->getHasLoadableAdapters() && !$this->_injectionAdaptersLoaded) {
|
||||
$filterDao = DAORegistry::getDAO('FilterDAO'); /** @var FilterDAO $filterDao */
|
||||
$loadedAdapters = $filterDao->getObjectsByTypeDescription('metadata::%', 'class::%', $this, false);
|
||||
foreach ($loadedAdapters as $loadedAdapter) {
|
||||
$this->addSupportedMetadataAdapter($loadedAdapter);
|
||||
}
|
||||
$this->_injectionAdaptersLoaded = true;
|
||||
}
|
||||
|
||||
return $this->_metadataInjectionAdapters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all supported meta-data schemas
|
||||
* which are supported by extractor adapters.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSupportedMetadataSchemas()
|
||||
{
|
||||
$supportedMetadataSchemas = [];
|
||||
$extractionAdapters = $this->getSupportedExtractionAdapters();
|
||||
foreach ($extractionAdapters as $metadataAdapter) {
|
||||
$supportedMetadataSchemas[] = $metadataAdapter->getMetadataSchema();
|
||||
}
|
||||
return $supportedMetadataSchemas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the names of meta-data
|
||||
* properties of this data object.
|
||||
*
|
||||
* @param bool $translated if true, return localized field
|
||||
* names, otherwise return additional field names.
|
||||
*/
|
||||
public function getMetadataFieldNames($translated = true)
|
||||
{
|
||||
// Create a list of all possible meta-data field names
|
||||
$metadataFieldNames = [];
|
||||
$extractionAdapters = $this->getSupportedExtractionAdapters();
|
||||
foreach ($extractionAdapters as $metadataAdapter) {
|
||||
// Add the field names from the current adapter
|
||||
$metadataFieldNames = array_merge(
|
||||
$metadataFieldNames,
|
||||
$metadataAdapter->getDataObjectMetadataFieldNames($translated)
|
||||
);
|
||||
}
|
||||
return array_unique($metadataFieldNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the names of meta-data
|
||||
* properties that need to be persisted
|
||||
* (i.e. that have data).
|
||||
*
|
||||
* @param bool $translated if true, return localized field
|
||||
* names, otherwise return additional field names.
|
||||
*
|
||||
* @return array an array of field names
|
||||
*/
|
||||
public function getSetMetadataFieldNames($translated = true)
|
||||
{
|
||||
// Retrieve a list of all possible meta-data field names
|
||||
$metadataFieldNameCandidates = $this->getMetadataFieldNames($translated);
|
||||
|
||||
// Only retain those fields that have data
|
||||
$metadataFieldNames = [];
|
||||
foreach ($metadataFieldNameCandidates as $metadataFieldNameCandidate) {
|
||||
if ($this->hasData($metadataFieldNameCandidate)) {
|
||||
$metadataFieldNames[] = $metadataFieldNameCandidate;
|
||||
}
|
||||
}
|
||||
return $metadataFieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the names of translated meta-data
|
||||
* properties that need to be persisted.
|
||||
*
|
||||
* @return array an array of field names
|
||||
*/
|
||||
public function getLocaleMetadataFieldNames()
|
||||
{
|
||||
return $this->getMetadataFieldNames(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the names of additional meta-data
|
||||
* properties that need to be persisted.
|
||||
*
|
||||
* @return array an array of field names
|
||||
*/
|
||||
public function getAdditionalMetadataFieldNames()
|
||||
{
|
||||
return $this->getMetadataFieldNames(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject a meta-data description into this
|
||||
* data object.
|
||||
*
|
||||
* @param \PKP\metadata\MetadataDescription $metadataDescription
|
||||
*
|
||||
* @return bool true on success, otherwise false
|
||||
*/
|
||||
public function injectMetadata($metadataDescription)
|
||||
{
|
||||
$dataObject = null;
|
||||
$metadataSchemaName = $metadataDescription->getMetadataSchemaName();
|
||||
$injectionAdapters = $this->getSupportedInjectionAdapters();
|
||||
if (isset($injectionAdapters[$metadataSchemaName])) {
|
||||
// Get the meta-data adapter that supports the
|
||||
// given meta-data description's schema.
|
||||
$metadataAdapter = $injectionAdapters[$metadataSchemaName]; /** @var \PKP\metadata\MetadataDataObjectAdapter $metadataAdapter */
|
||||
|
||||
// Pass in a reference to the data object which
|
||||
// the filter will use to update the current instance
|
||||
// of the data object.
|
||||
$metadataAdapter->setTargetDataObject($this);
|
||||
|
||||
// Use adapter filter to convert from a meta-data
|
||||
// description to a data object.
|
||||
$dataObject = $metadataAdapter->execute($metadataDescription);
|
||||
}
|
||||
return $dataObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a meta-data description from this
|
||||
* data object.
|
||||
*
|
||||
* @param \PKP\metadata\MetadataSchema $metadataSchema
|
||||
*
|
||||
* @return $metadataDescription MetadataDescription
|
||||
*/
|
||||
public function extractMetadata($metadataSchema)
|
||||
{
|
||||
$metadataDescription = null;
|
||||
$metadataSchemaName = $metadataSchema->getClassName();
|
||||
$extractionAdapters = $this->getSupportedExtractionAdapters();
|
||||
if (isset($extractionAdapters[$metadataSchemaName])) {
|
||||
// Get the meta-data adapter that supports the
|
||||
// given meta-data description's schema.
|
||||
$metadataAdapter = $extractionAdapters[$metadataSchemaName];
|
||||
|
||||
// Use adapter filter to convert from a data object
|
||||
// to a meta-data description.
|
||||
$metadataDescription = $metadataAdapter->execute($this);
|
||||
}
|
||||
return $metadataDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DAO class for this object.
|
||||
*
|
||||
* @return T
|
||||
*/
|
||||
public function getDAO()
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\DataObject', '\DataObject');
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/Dispatcher.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 Dispatcher
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Class dispatching HTTP requests to handlers.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use APP\core\Services;
|
||||
use PKP\config\Config;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\plugins\PluginRegistry;
|
||||
use PKP\services\PKPSchemaService;
|
||||
|
||||
class Dispatcher
|
||||
{
|
||||
/** @var PKPApplication */
|
||||
public $_application;
|
||||
|
||||
/** @var array an array of Router implementation class names */
|
||||
public $_routerNames = [];
|
||||
|
||||
/** @var array an array of Router instances */
|
||||
public $_routerInstances = [];
|
||||
|
||||
/** @var PKPRouter */
|
||||
public $_router;
|
||||
|
||||
/** @var PKPRequest Used for a callback hack - NOT GENERALLY SET. */
|
||||
public $_requestCallbackHack;
|
||||
|
||||
/**
|
||||
* Get the application
|
||||
*
|
||||
* @return PKPApplication
|
||||
*/
|
||||
public function &getApplication()
|
||||
{
|
||||
return $this->_application;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the application
|
||||
*
|
||||
* @param PKPApplication $application
|
||||
*/
|
||||
public function setApplication($application)
|
||||
{
|
||||
$this->_application = $application;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the router names
|
||||
*
|
||||
* @return array an array of Router names
|
||||
*/
|
||||
public function &getRouterNames()
|
||||
{
|
||||
return $this->_routerNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a router name.
|
||||
*
|
||||
* NB: Routers will be called in the order that they
|
||||
* have been added to the dispatcher. The first router
|
||||
* that supports the request will be called. The last
|
||||
* router should always be a "catch-all" router that
|
||||
* supports all types of requests.
|
||||
*
|
||||
* NB: Routers must be part of the core package
|
||||
* to be accepted by this dispatcher implementation.
|
||||
*
|
||||
* @param string $routerName a class name of a router
|
||||
* to be given the chance to route the request.
|
||||
* NB: These are class names and not instantiated objects. We'll
|
||||
* use lazy instantiation to reduce the performance/memory impact
|
||||
* to a minimum.
|
||||
* @param string $shortcut a shortcut name for the router
|
||||
* to be used for quick router instance retrieval.
|
||||
*/
|
||||
public function addRouterName($routerName, $shortcut)
|
||||
{
|
||||
assert(is_array($this->_routerNames) && is_string($routerName));
|
||||
$this->_routerNames[$shortcut] = $routerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the correct router for this request. Then
|
||||
* let the router dispatch the request to the appropriate
|
||||
* handler method.
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*/
|
||||
public function dispatch($request)
|
||||
{
|
||||
// Make sure that we have at least one router configured
|
||||
$routerNames = $this->getRouterNames();
|
||||
assert(count($routerNames) > 0);
|
||||
|
||||
// Go through all configured routers by priority
|
||||
// and find out whether one supports the incoming request
|
||||
/** @var PKPRouter */
|
||||
$router = null;
|
||||
foreach ($routerNames as $shortcut => $routerCandidateName) {
|
||||
$routerCandidate = & $this->_instantiateRouter($routerCandidateName, $shortcut);
|
||||
|
||||
// Does this router support the current request?
|
||||
if ($routerCandidate->supports($request)) {
|
||||
// Inject router and dispatcher into request
|
||||
$request->setRouter($routerCandidate);
|
||||
$request->setDispatcher($this);
|
||||
|
||||
// We've found our router and can go on
|
||||
// to handle the request.
|
||||
$router = & $routerCandidate;
|
||||
$this->_router = & $router;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// None of the router handles this request? This is a development-time
|
||||
// configuration error.
|
||||
if (is_null($router)) {
|
||||
fatalError('None of the configured routers supports this request.');
|
||||
}
|
||||
|
||||
// Can we serve a cached response?
|
||||
if ($router->isCacheable($request)) {
|
||||
$this->_requestCallbackHack = & $request;
|
||||
if (Config::getVar('cache', 'web_cache')) {
|
||||
if ($this->_displayCached($router, $request)) {
|
||||
exit;
|
||||
} // Success
|
||||
ob_start([$this, '_cacheContent']);
|
||||
}
|
||||
} else {
|
||||
if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') {
|
||||
header('HTTP/1.0 403 Forbidden');
|
||||
echo '403: Forbidden<br><br>Pre-fetching not allowed.';
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
PluginRegistry::loadCategory('generic', true);
|
||||
PluginRegistry::loadCategory('pubIds', true);
|
||||
|
||||
Hook::call('Dispatcher::dispatch', [$request]);
|
||||
|
||||
// Reload the context after generic plugins have loaded so that changes to
|
||||
// the context schema can take place
|
||||
$contextSchema = Services::get('schema')->get(PKPSchemaService::SCHEMA_CONTEXT, true);
|
||||
$request->getRouter()->getContext($request, true);
|
||||
|
||||
$router->route($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a handler request URL into PKPApplication.
|
||||
*
|
||||
* @param PKPRequest $request the request to be routed
|
||||
* @param string $shortcut the short name of the router that should be used to construct the URL
|
||||
* @param mixed $newContext Optional contextual paths
|
||||
* @param string $handler Optional name of the handler to invoke
|
||||
* @param string $op Optional name of operation to invoke
|
||||
* @param mixed $path Optional string or array of args to pass to handler
|
||||
* @param array $params Optional set of name => value pairs to pass as user parameters
|
||||
* @param string $anchor Optional name of anchor to add to URL
|
||||
* @param bool $escape Whether or not to escape ampersands for this URL; default false.
|
||||
*
|
||||
* @return string the URL
|
||||
*/
|
||||
public function url(
|
||||
PKPRequest $request,
|
||||
string $shortcut,
|
||||
?string $newContext = null,
|
||||
$handler = null,
|
||||
$op = null,
|
||||
$path = null,
|
||||
$params = null,
|
||||
$anchor = null,
|
||||
$escape = false
|
||||
) {
|
||||
// Instantiate the requested router
|
||||
assert(isset($this->_routerNames[$shortcut]));
|
||||
$routerName = $this->_routerNames[$shortcut];
|
||||
$router = & $this->_instantiateRouter($routerName, $shortcut);
|
||||
|
||||
return $router->url($request, $newContext, $handler, $op, $path, $params, $anchor, $escape);
|
||||
}
|
||||
|
||||
//
|
||||
// Private helper methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Instantiate a router
|
||||
*
|
||||
* @param string $routerName
|
||||
* @param string $shortcut
|
||||
*/
|
||||
public function &_instantiateRouter($routerName, $shortcut)
|
||||
{
|
||||
if (!isset($this->_routerInstances[$shortcut])) {
|
||||
// Instantiate the router
|
||||
$router = new $routerName();
|
||||
if (!$router instanceof \PKP\core\PKPRouter) {
|
||||
throw new \Exception('Cannot instantiate requested router. Routers must belong to the core package and be of type "PKPRouter".');
|
||||
}
|
||||
$router->setApplication($this->_application);
|
||||
$router->setDispatcher($this);
|
||||
|
||||
// Save the router instance for later re-use
|
||||
$this->_routerInstances[$shortcut] = & $router;
|
||||
}
|
||||
|
||||
return $this->_routerInstances[$shortcut];
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the request contents from cache.
|
||||
*
|
||||
* @param PKPRouter $router
|
||||
*/
|
||||
public function _displayCached($router, $request)
|
||||
{
|
||||
$filename = $router->getCacheFilename($request);
|
||||
if (!file_exists($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow a caching proxy to work its magic if possible
|
||||
$ifModifiedSince = $request->getIfModifiedSince();
|
||||
if ($ifModifiedSince !== null && $ifModifiedSince >= filemtime($filename)) {
|
||||
header('HTTP/1.1 304 Not Modified');
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = file_get_contents($filename);
|
||||
$i = strpos($data, ':');
|
||||
$time = substr($data, 0, $i);
|
||||
$contents = substr($data, $i + 1);
|
||||
|
||||
if (time() > $time + Config::getVar('cache', 'web_cache_hours') * 60 * 60) {
|
||||
return false;
|
||||
}
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
echo $contents;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache content as a local file.
|
||||
*
|
||||
* @param string $contents
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function _cacheContent($contents)
|
||||
{
|
||||
assert($this->_router instanceof \PKP\core\PKPRouter);
|
||||
if ($contents == '') {
|
||||
return $contents;
|
||||
} // Do not cache empties
|
||||
$filename = $this->_router->getCacheFilename($this->_requestCallbackHack);
|
||||
$fp = fopen($filename, 'w');
|
||||
if ($fp) {
|
||||
fwrite($fp, time() . ':' . $contents);
|
||||
fclose($fp);
|
||||
}
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a 404 error (page not found).
|
||||
*/
|
||||
public static function handle404()
|
||||
{
|
||||
header('HTTP/1.0 404 Not Found');
|
||||
fatalError('404 Not Found');
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\Dispatcher', '\Dispatcher');
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/core/EntityDAO.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 EntityDAO
|
||||
*
|
||||
* @brief A base class for DAOs that read and write an entity to the database
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\db\DAO;
|
||||
use PKP\services\PKPSchemaService;
|
||||
|
||||
/**
|
||||
* @template T of DataObject
|
||||
*/
|
||||
abstract class EntityDAO
|
||||
{
|
||||
/** @var string One of the \PKP\services\PKPSchemaService::SCHEMA_... constants */
|
||||
public $schema;
|
||||
|
||||
/** @var string The name of the primary table for this entity */
|
||||
public $table;
|
||||
|
||||
/** @var string The name of the settings table for this entity */
|
||||
public $settingsTable;
|
||||
|
||||
/** @var string The column name for the object id in primary and settings tables */
|
||||
public $primaryKeyColumn;
|
||||
|
||||
/** @var array Maps schema properties for the primary table to their column names */
|
||||
public $primaryTableColumns = [];
|
||||
|
||||
/**
|
||||
* @var array Map schema properties to the primary table
|
||||
*
|
||||
* An array mapping the property names of an entity to the
|
||||
* correct column in the database table.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* [
|
||||
* 'id' => 'announcement_id',
|
||||
* 'datePosted' => 'date_posted',
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Only include properties stored in self::$table. Properties
|
||||
* stored in self::$settingsTable do not need to be mapped.
|
||||
*/
|
||||
public $primaryKeyColumns;
|
||||
|
||||
/**
|
||||
* @var DAO An instance of PKP\db\DAO
|
||||
*
|
||||
* This provides access to a few methods that are
|
||||
* still shared with the deprecated DAOs.
|
||||
*
|
||||
* @deprecated 3.4
|
||||
*/
|
||||
public $deprecatedDao;
|
||||
|
||||
/** @var PKPSchemaService<T> $schemaService */
|
||||
protected $schemaService;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(PKPSchemaService $schemaService)
|
||||
{
|
||||
$this->deprecatedDao = new DAO();
|
||||
$this->schemaService = $schemaService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DataObject
|
||||
*
|
||||
* @return T
|
||||
*/
|
||||
public function newDataObject()
|
||||
{
|
||||
throw new Exception('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a row from the database query into a DataObject
|
||||
*
|
||||
* @return T
|
||||
*/
|
||||
public function fromRow(object $row): DataObject
|
||||
{
|
||||
$schema = $this->schemaService->get($this->schema);
|
||||
|
||||
$object = $this->newDataObject();
|
||||
|
||||
foreach ($this->primaryTableColumns as $propName => $column) {
|
||||
if (property_exists($row, $column)) {
|
||||
$object->setData(
|
||||
$propName,
|
||||
$this->convertFromDB($row->{$column}, $schema->properties->{$propName}->type, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->settingsTable) {
|
||||
$rows = DB::table($this->settingsTable)
|
||||
->where($this->primaryKeyColumn, '=', $row->{$this->primaryKeyColumn})
|
||||
->get();
|
||||
|
||||
$rows->each(function ($row) use ($object, $schema) {
|
||||
if (!empty($schema->properties->{$row->setting_name})) {
|
||||
$object->setData(
|
||||
$row->setting_name,
|
||||
$this->convertFromDB(
|
||||
$row->setting_value,
|
||||
$schema->properties->{$row->setting_name}->type
|
||||
),
|
||||
empty($row->locale) ? null : $row->locale
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an object into the database
|
||||
* @param T $object
|
||||
*/
|
||||
protected function _insert(DataObject $object): int
|
||||
{
|
||||
$schemaService = $this->schemaService;
|
||||
$schema = $schemaService->get($this->schema);
|
||||
$sanitizedProps = $schemaService->sanitize($this->schema, $object->_data);
|
||||
|
||||
$primaryDbProps = $this->getPrimaryDbProps($object);
|
||||
|
||||
if (empty($primaryDbProps)) {
|
||||
throw new Exception('Tried to insert ' . get_class($object) . ' without any properties for the ' . $this->table . ' table.');
|
||||
}
|
||||
|
||||
DB::table($this->table)->insert($primaryDbProps);
|
||||
$object->setId((int) DB::getPdo()->lastInsertId());
|
||||
|
||||
// Add additional properties to settings table if they exist
|
||||
if ($this->settingsTable && count($sanitizedProps) !== count($primaryDbProps)) {
|
||||
foreach ($schema->properties as $propName => $propSchema) {
|
||||
if (!isset($sanitizedProps[$propName]) || array_key_exists($propName, $this->primaryTableColumns)) {
|
||||
continue;
|
||||
}
|
||||
if (!empty($propSchema->multilingual)) {
|
||||
foreach ($sanitizedProps[$propName] as $localeKey => $localeValue) {
|
||||
DB::table($this->settingsTable)->insert([
|
||||
$this->primaryKeyColumn => $object->getId(),
|
||||
'locale' => $localeKey,
|
||||
'setting_name' => $propName,
|
||||
'setting_value' => $this->convertToDB($localeValue, $schema->properties->{$propName}->type),
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
DB::table($this->settingsTable)->insert([
|
||||
$this->primaryKeyColumn => $object->getId(),
|
||||
'setting_name' => $propName,
|
||||
'setting_value' => $this->convertToDB($sanitizedProps[$propName], $schema->properties->{$propName}->type),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $object->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an object in the database
|
||||
* @param T $object
|
||||
*/
|
||||
protected function _update(DataObject $object)
|
||||
{
|
||||
$schemaService = $this->schemaService;
|
||||
$schema = $schemaService->get($this->schema);
|
||||
$sanitizedProps = $schemaService->sanitize($this->schema, $object->_data);
|
||||
|
||||
$primaryDbProps = $this->getPrimaryDbProps($object);
|
||||
|
||||
DB::table($this->table)
|
||||
->where($this->primaryKeyColumn, '=', $object->getId())
|
||||
->update($primaryDbProps);
|
||||
|
||||
if ($this->settingsTable) {
|
||||
$deleteSettings = [];
|
||||
foreach ($schema->properties as $propName => $propSchema) {
|
||||
if (array_key_exists($propName, $this->primaryTableColumns)) {
|
||||
continue;
|
||||
} elseif (!isset($sanitizedProps[$propName])) {
|
||||
$deleteSettings[] = $propName;
|
||||
continue;
|
||||
}
|
||||
if (!empty($propSchema->multilingual)) {
|
||||
foreach ($sanitizedProps[$propName] as $localeKey => $localeValue) {
|
||||
// Delete rows with a null value
|
||||
if (is_null($localeValue)) {
|
||||
DB::table($this->settingsTable)
|
||||
->where($this->primaryKeyColumn, '=', $object->getId())
|
||||
->where('setting_name', '=', $propName)
|
||||
->where('locale', '=', $localeKey)
|
||||
->delete();
|
||||
} else {
|
||||
DB::table($this->settingsTable)
|
||||
->updateOrInsert(
|
||||
[
|
||||
$this->primaryKeyColumn => $object->getId(),
|
||||
'locale' => $localeKey,
|
||||
'setting_name' => $propName,
|
||||
],
|
||||
[
|
||||
'setting_value' => $this->convertToDB($localeValue, $schema->properties->{$propName}->type),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DB::table($this->settingsTable)
|
||||
->updateOrInsert(
|
||||
[
|
||||
$this->primaryKeyColumn => $object->getId(),
|
||||
'locale' => '',
|
||||
'setting_name' => $propName,
|
||||
],
|
||||
[
|
||||
'setting_value' => $this->convertToDB($sanitizedProps[$propName], $schema->properties->{$propName}->type),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($deleteSettings)) {
|
||||
DB::table($this->settingsTable)
|
||||
->where($this->primaryKeyColumn, '=', $object->getId())
|
||||
->whereIn('setting_name', $deleteSettings)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an object from the database
|
||||
* @param T $object
|
||||
*/
|
||||
protected function _delete(DataObject $object)
|
||||
{
|
||||
$this->deleteById($object->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an object from the database by its id
|
||||
*/
|
||||
public function deleteById(int $id)
|
||||
{
|
||||
DB::table($this->table)
|
||||
->where($this->primaryKeyColumn, '=', $id)
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare data to be inserted into the primary table
|
||||
*
|
||||
* Compiles the properties of a DataObject into a key/value
|
||||
* array that maps them to the primary table columns.
|
||||
*
|
||||
* @see $this->primaryTableColumns
|
||||
* @param T $object
|
||||
*/
|
||||
protected function getPrimaryDbProps(DataObject $object): array
|
||||
{
|
||||
$schema = $this->schemaService->get($this->schema);
|
||||
$sanitizedProps = $this->schemaService->sanitize($this->schema, $object->_data);
|
||||
|
||||
$primaryDbProps = [];
|
||||
foreach ($this->primaryTableColumns as $propName => $columnName) {
|
||||
if ($propName !== 'id' && array_key_exists($propName, $sanitizedProps)) {
|
||||
$primaryDbProps[$columnName] = $this->convertToDB($sanitizedProps[$propName] ?? null, $schema->properties->{$propName}->type, true);
|
||||
// Convert empty string values for DATETIME columns into null values
|
||||
// because an empty string can not be saved to a DATETIME column
|
||||
if ($primaryDbProps[$columnName] === ''
|
||||
&& isset($schema->properties->{$propName}->validation)
|
||||
&& (
|
||||
in_array('date_format:Y-m-d H:i:s', $schema->properties->{$propName}->validation)
|
||||
|| in_array('date_format:Y-m-d', $schema->properties->{$propName}->validation)
|
||||
)
|
||||
) {
|
||||
$primaryDbProps[$columnName] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $primaryDbProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc DAO::convertFromDB()
|
||||
*/
|
||||
protected function convertFromDB($value, string $type, bool $nullable = false)
|
||||
{
|
||||
return $this->deprecatedDao->convertFromDB($value, $type, $nullable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc DAO::convertToDB()
|
||||
*/
|
||||
protected function convertToDB($value, string $type, bool $nullable = false)
|
||||
{
|
||||
return $this->deprecatedDao->convertToDB($value, $type, $nullable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/EventServiceProvider.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 EventServiceProvider
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Registers Events Service Provider and boots data on events and their listeners
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use DateInterval;
|
||||
use Illuminate\Foundation\Events\DiscoverEvents;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as LaravelEventServiceProvider;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use SplFileInfo;
|
||||
|
||||
class EventServiceProvider extends LaravelEventServiceProvider
|
||||
{
|
||||
/** Max lifetime for the event discovery cache */
|
||||
protected const MAX_CACHE_LIFETIME = '1 day';
|
||||
|
||||
/**
|
||||
* @copydoc \Illuminate\Foundation\Support\Providers\EventServiceProvider::getEvents()
|
||||
*/
|
||||
public function getEvents()
|
||||
{
|
||||
$expiration = DateInterval::createFromDateString(static::MAX_CACHE_LIFETIME);
|
||||
$events = Cache::remember(static::getCacheKey(), $expiration, fn () => $this->discoveredEvents());
|
||||
|
||||
return array_merge_recursive(
|
||||
$events,
|
||||
$this->listens()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc \Illuminate\Foundation\Support\Providers\EventServiceProvider::shouldDiscoverEvents()
|
||||
*/
|
||||
public function shouldDiscoverEvents()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc \Illuminate\Foundation\Support\Providers\EventServiceProvider::discoverEvents()
|
||||
*/
|
||||
public function discoverEvents()
|
||||
{
|
||||
// Adapt classes naming convention
|
||||
$discoverEvents = new class () extends DiscoverEvents {
|
||||
/**
|
||||
* @copydoc \Illuminate\Foundation\Events\DiscoverEvents::classFromFile()
|
||||
*/
|
||||
protected static function classFromFile(SplFileInfo $file, $basePath): string
|
||||
{
|
||||
return Core::classFromFile($file);
|
||||
}
|
||||
};
|
||||
|
||||
return collect($this->discoverEventsWithin())
|
||||
->reject(function ($directory) {
|
||||
return !is_dir($directory);
|
||||
})
|
||||
->reduce(function ($discovered, $directory) use ($discoverEvents) {
|
||||
return array_merge_recursive(
|
||||
$discovered,
|
||||
$discoverEvents::within($directory, base_path())
|
||||
);
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc \Illuminate\Foundation\Support\Providers\EventServiceProvider::discoverEventsWithin()
|
||||
*/
|
||||
protected function discoverEventsWithin()
|
||||
{
|
||||
return [
|
||||
$this->app->basePath('lib/pkp/classes/observers/listeners'),
|
||||
$this->app->basePath('classes/observers/listeners'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the event cache
|
||||
*/
|
||||
public static function clearCache(): void
|
||||
{
|
||||
Cache::forget(static::getCacheKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a unique and static key to store the event cache
|
||||
*/
|
||||
private static function getCacheKey(): string
|
||||
{
|
||||
return __METHOD__ . static::MAX_CACHE_LIFETIME;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/ExportableTrait.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 ExportableTrait
|
||||
*
|
||||
* @ingroup db
|
||||
*
|
||||
* @brief Implements the __set_state magic method, for classes that have a parameterless constructor, in order to recover classes exported with "var_export"
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
trait ExportableTrait
|
||||
{
|
||||
/**
|
||||
* Generic __set_state implementation
|
||||
*/
|
||||
public static function __set_state(array $data)
|
||||
{
|
||||
$object = new static();
|
||||
foreach ($data as $key => $value) {
|
||||
$object->$key = $value;
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/ItemIterator.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 ItemIterator
|
||||
*
|
||||
* @ingroup db
|
||||
*
|
||||
* @brief Generic iterator class; needs to be overloaded by subclasses
|
||||
* providing specific implementations.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
/**
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
*/
|
||||
class ItemIterator
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next item in the iterator.
|
||||
*
|
||||
* @return TValue
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next item with key.
|
||||
*
|
||||
* @return array<Tkey,TValue>
|
||||
*/
|
||||
public function nextWithKey()
|
||||
{
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this iterator represents the first page of a set.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function atFirstPage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this iterator represents the last page of a set.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function atLastPage()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the page number of a set that this iterator represents.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPage()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of items in the set.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCount()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of pages in the set.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPageCount()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean indicating whether or not we've reached the end of results
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function eof()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean indicating whether or not this iterator was empty from the beginning
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function wasEmpty()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this iterator to an array.
|
||||
*
|
||||
* @return TValue[]
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\ItemIterator', '\ItemIterator');
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/JSONMessage.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 JSONMessage
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Class to represent a JSON (Javascript Object Notation) message.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
class JSONMessage
|
||||
{
|
||||
/** @var string The status of an event (e.g. false if form validation fails). */
|
||||
public $_status;
|
||||
|
||||
/** @var mixed The message to be delivered back to the calling script. */
|
||||
public $_content;
|
||||
|
||||
/** @var string ID for DOM element that will be replaced. */
|
||||
public $_elementId;
|
||||
|
||||
/** @var array List of JS events generated on the server side. */
|
||||
public $_events;
|
||||
|
||||
/** @var array Set of additional attributes for special cases. */
|
||||
public $_additionalAttributes;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param bool $status The status of an event (e.g. false if form validation fails).
|
||||
* @param mixed $content The message to be delivered back to the calling script.
|
||||
* @param string $elementId The DOM element to be replaced.
|
||||
* @param array $additionalAttributes Additional data to be returned.
|
||||
*/
|
||||
public function __construct($status = true, $content = '', $elementId = '0', $additionalAttributes = null)
|
||||
{
|
||||
// Set internal state.
|
||||
$this->setStatus($status);
|
||||
$this->setContent($content);
|
||||
$this->setElementId($elementId);
|
||||
if (isset($additionalAttributes)) {
|
||||
$this->setAdditionalAttributes($additionalAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
return $this->_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status string
|
||||
*
|
||||
* @param string $status
|
||||
*/
|
||||
public function setStatus($status)
|
||||
{
|
||||
assert(is_bool($status));
|
||||
$this->_status = $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content string
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content data
|
||||
*
|
||||
*/
|
||||
public function setContent($content)
|
||||
{
|
||||
$this->_content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the elementId string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getElementId()
|
||||
{
|
||||
return $this->_elementId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the elementId string
|
||||
*
|
||||
* @param string $elementId
|
||||
*/
|
||||
public function setElementId($elementId)
|
||||
{
|
||||
assert(is_string($elementId) || is_numeric($elementId));
|
||||
$this->_elementId = $elementId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the event to trigger with this JSON message
|
||||
*
|
||||
* @param string $eventName
|
||||
* @param null|mixed $eventData
|
||||
*/
|
||||
public function setEvent($eventName, $eventData = null)
|
||||
{
|
||||
assert(is_string($eventName));
|
||||
|
||||
// Construct the even as an associative array.
|
||||
$event = ['name' => $eventName];
|
||||
if (!is_null($eventData)) {
|
||||
$event['data'] = $eventData;
|
||||
}
|
||||
|
||||
$this->_events[] = $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a global event to trigger with this JSON message
|
||||
*
|
||||
* This is a wrapper for the setEvent method.
|
||||
*
|
||||
* Global events are triggered on the global event router instead of being
|
||||
* triggered directly on the handler. They are intended for broadcasting
|
||||
* updates from one handler to other handlers.
|
||||
*
|
||||
* @param string $eventName
|
||||
* @param array $eventData Global event data must be an assoc array
|
||||
*/
|
||||
public function setGlobalEvent($eventName, $eventData = [])
|
||||
{
|
||||
assert(is_array($eventData));
|
||||
$eventData['isGlobalEvent'] = true;
|
||||
$this->setEvent($eventName, $eventData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the events to trigger with this JSON message
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getEvents()
|
||||
{
|
||||
return $this->_events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the additionalAttributes array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdditionalAttributes()
|
||||
{
|
||||
return $this->_additionalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the additionalAttributes array
|
||||
*
|
||||
* @param array $additionalAttributes
|
||||
*/
|
||||
public function setAdditionalAttributes($additionalAttributes)
|
||||
{
|
||||
assert(is_array($additionalAttributes));
|
||||
$this->_additionalAttributes = $additionalAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a JSON string to use for AJAX communication
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getString()
|
||||
{
|
||||
// Construct an associative array that contains all information we require.
|
||||
$jsonObject = [
|
||||
'status' => $this->getStatus(),
|
||||
'content' => $this->getContent(),
|
||||
'elementId' => $this->getElementId(),
|
||||
'events' => $this->getEvents(),
|
||||
];
|
||||
if (is_array($this->getAdditionalAttributes())) {
|
||||
foreach ($this->getAdditionalAttributes() as $key => $value) {
|
||||
$jsonObject[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Encode the object.
|
||||
$json = json_encode($jsonObject);
|
||||
|
||||
if ($json === false) {
|
||||
error_log(json_last_error_msg());
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\JSONMessage', '\JSONMessage');
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/MailServiceProvider.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 MailServiceProvider
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Registers Laravel's Mailer service without support for markup rendering, such as blade or markdown templates
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use Illuminate\Mail\MailManager;
|
||||
use Illuminate\Mail\MailServiceProvider as IlluminateMailService;
|
||||
use InvalidArgumentException;
|
||||
use PKP\mail\Mailer;
|
||||
use PKP\mail\transport\PHPMailerTransport;
|
||||
use Symfony\Component\Mailer\Transport\SendmailTransport;
|
||||
|
||||
class MailServiceProvider extends IlluminateMailService
|
||||
{
|
||||
/**
|
||||
* Register mailer excluding markdown renderer
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->registerIlluminateMailer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc \Illuminate\Mail\MailServiceProvider::registerIlluminateMailer()
|
||||
*/
|
||||
public function registerIlluminateMailer(): void
|
||||
{
|
||||
$this->app->singleton('mail.manager', function ($app) {
|
||||
return new class ($app) extends MailManager {
|
||||
/**
|
||||
* @see MailManager::resolve()
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function resolve($name): Mailer
|
||||
{
|
||||
$config = $this->getConfig($name);
|
||||
|
||||
if (is_null($config)) {
|
||||
throw new InvalidArgumentException("Mailer [{$name}] is not defined.");
|
||||
}
|
||||
|
||||
// Override Illuminate mailer construction to remove unsupported view
|
||||
$mailer = new Mailer(
|
||||
$name,
|
||||
$this->createSymfonyTransport($config),
|
||||
$this->app['events']
|
||||
);
|
||||
|
||||
if ($this->app->bound('queue')) {
|
||||
$mailer->setQueue($this->app['queue']);
|
||||
}
|
||||
|
||||
return $mailer;
|
||||
}
|
||||
|
||||
/*
|
||||
* Override sendmail transport construction to allow default path
|
||||
*/
|
||||
protected function createSendmailTransport(array $config): SendmailTransport
|
||||
{
|
||||
$path = $config['path'] ?? $this->app['config']->get('mail.sendmail');
|
||||
return $path ? new SendmailTransport($path) : new SendmailTransport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport to send with mail() function by PHPMailer
|
||||
*/
|
||||
protected function createPHPMailerTransport(): PHPMailerTransport
|
||||
{
|
||||
return new PHPMailerTransport();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
$this->app->bind('mailer', function ($app) {
|
||||
return $app->make('mail.manager')->mailer();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc \Illuminate\Mail\MailServiceProvider::provides()
|
||||
*/
|
||||
public function provides(): array
|
||||
{
|
||||
return
|
||||
[
|
||||
'mail.manager',
|
||||
'mailer',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\MailServiceProvider', '\MailServiceProvider');
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/MapsContainer.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 MapContainer
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief This service provider allows Map classes to be instantiated and
|
||||
* applies any extensions to the map registered by plugins.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use PKP\core\maps\Base;
|
||||
|
||||
class MapContainer
|
||||
{
|
||||
/** @var array Key/value map that stores extensions to maps registered by plugins */
|
||||
protected array $extensions = [];
|
||||
|
||||
public function extend(string $map, callable $callback)
|
||||
{
|
||||
if (isset($this->extensions[$map])) {
|
||||
$this->extensions[$map][] = $callback;
|
||||
}
|
||||
$this->extensions[$map] = [$callback];
|
||||
}
|
||||
|
||||
public function getMap(string $class, array $dependencies = []): Base
|
||||
{
|
||||
return app($class, $dependencies);
|
||||
}
|
||||
|
||||
public function withExtensions(string $class, array $dependencies = []): Base
|
||||
{
|
||||
$map = $this->getMap($class, $dependencies);
|
||||
foreach ($this->extensions as $name => $extensions) {
|
||||
if (is_a($map, $name)) {
|
||||
foreach ($extensions as $extension) {
|
||||
$map->extend($extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,796 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/PKPApplication.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 PKPApplication
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Class describing this application.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Request;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Database\Events\QueryExecuted;
|
||||
use Illuminate\Database\MySqlConnection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\config\Config;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\facades\Locale;
|
||||
use PKP\security\Role;
|
||||
use PKP\session\SessionManager;
|
||||
use PKP\site\VersionDAO;
|
||||
use PKP\submission\RepresentationDAOInterface;
|
||||
|
||||
interface iPKPApplicationInfoProvider
|
||||
{
|
||||
/**
|
||||
* Get the top-level context DAO.
|
||||
*/
|
||||
public static function getContextDAO();
|
||||
|
||||
/**
|
||||
* Get the representation DAO.
|
||||
*/
|
||||
public static function getRepresentationDAO(): RepresentationDAOInterface;
|
||||
|
||||
/**
|
||||
* Get a SubmissionSearchIndex instance.
|
||||
*/
|
||||
public static function getSubmissionSearchIndex();
|
||||
|
||||
/**
|
||||
* Get a SubmissionSearchDAO instance.
|
||||
*/
|
||||
public static function getSubmissionSearchDAO();
|
||||
|
||||
/**
|
||||
* Get the stages used by the application.
|
||||
*/
|
||||
public static function getApplicationStages();
|
||||
|
||||
/**
|
||||
* Get the file directory array map used by the application.
|
||||
* should return array('context' => ..., 'submission' => ...)
|
||||
*/
|
||||
public static function getFileDirectories();
|
||||
|
||||
/**
|
||||
* Returns the context type for this application.
|
||||
*/
|
||||
public static function getContextAssocType();
|
||||
}
|
||||
|
||||
abstract class PKPApplication implements iPKPApplicationInfoProvider
|
||||
{
|
||||
public const PHP_REQUIRED_VERSION = '8.0.2';
|
||||
|
||||
// Constant used to distinguish between editorial and author workflows
|
||||
public const WORKFLOW_TYPE_EDITORIAL = 'editorial';
|
||||
public const WORKFLOW_TYPE_AUTHOR = 'author';
|
||||
|
||||
public const API_VERSION = 'v1';
|
||||
|
||||
public const ROUTE_COMPONENT = 'component';
|
||||
public const ROUTE_PAGE = 'page';
|
||||
public const ROUTE_API = 'api';
|
||||
|
||||
public const CONTEXT_SITE = 0;
|
||||
public const CONTEXT_ID_NONE = 0;
|
||||
public const CONTEXT_ID_ALL = '_';
|
||||
public const REVIEW_ROUND_NONE = 0;
|
||||
|
||||
public const ASSOC_TYPE_PRODUCTION_ASSIGNMENT = 0x0000202;
|
||||
public const ASSOC_TYPE_SUBMISSION_FILE = 0x0000203;
|
||||
public const ASSOC_TYPE_REVIEW_RESPONSE = 0x0000204;
|
||||
public const ASSOC_TYPE_REVIEW_ASSIGNMENT = 0x0000205;
|
||||
public const ASSOC_TYPE_SUBMISSION_EMAIL_LOG_ENTRY = 0x0000206;
|
||||
public const ASSOC_TYPE_WORKFLOW_STAGE = 0x0000207;
|
||||
public const ASSOC_TYPE_NOTE = 0x0000208;
|
||||
public const ASSOC_TYPE_REPRESENTATION = 0x0000209;
|
||||
public const ASSOC_TYPE_ANNOUNCEMENT = 0x000020A;
|
||||
public const ASSOC_TYPE_REVIEW_ROUND = 0x000020B;
|
||||
public const ASSOC_TYPE_SUBMISSION_FILES = 0x000020F;
|
||||
public const ASSOC_TYPE_PLUGIN = 0x0000211;
|
||||
public const ASSOC_TYPE_SECTION = 0x0000212;
|
||||
public const ASSOC_TYPE_CATEGORY = 0x000020D;
|
||||
public const ASSOC_TYPE_USER = 0x0001000; // This value used because of bug #6068
|
||||
public const ASSOC_TYPE_USER_GROUP = 0x0100002;
|
||||
public const ASSOC_TYPE_CITATION = 0x0100003;
|
||||
public const ASSOC_TYPE_AUTHOR = 0x0100004;
|
||||
public const ASSOC_TYPE_EDITOR = 0x0100005;
|
||||
public const ASSOC_TYPE_USER_ROLES = 0x0100007;
|
||||
public const ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES = 0x0100008;
|
||||
public const ASSOC_TYPE_SUBMISSION = 0x0100009;
|
||||
public const ASSOC_TYPE_QUERY = 0x010000a;
|
||||
public const ASSOC_TYPE_QUEUED_PAYMENT = 0x010000b;
|
||||
public const ASSOC_TYPE_PUBLICATION = 0x010000c;
|
||||
public const ASSOC_TYPE_ACCESSIBLE_FILE_STAGES = 0x010000d;
|
||||
public const ASSOC_TYPE_NONE = 0x010000e;
|
||||
public const ASSOC_TYPE_DECISION_TYPE = 0x010000f;
|
||||
|
||||
// Constant used in UsageStats for submission files that are not full texts
|
||||
public const ASSOC_TYPE_SUBMISSION_FILE_COUNTER_OTHER = 0x0000213;
|
||||
|
||||
public $enabledProducts = [];
|
||||
public $allProducts;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (!defined('PKP_STRICT_MODE')) {
|
||||
define('PKP_STRICT_MODE', (bool) Config::getVar('general', 'strict'));
|
||||
class_alias('\PKP\config\Config', '\Config');
|
||||
class_alias('\PKP\core\Registry', '\Registry');
|
||||
class_alias('\PKP\core\Core', '\Core');
|
||||
class_alias('\PKP\cache\CacheManager', '\CacheManager');
|
||||
class_alias('\PKP\handler\PKPHandler', '\PKPHandler');
|
||||
class_alias('\PKP\payment\QueuedPayment', '\QueuedPayment'); // QueuedPayment instances may be serialized
|
||||
}
|
||||
|
||||
// If not in strict mode, globally expose constants on this class.
|
||||
if (!PKP_STRICT_MODE) {
|
||||
foreach ([
|
||||
'WORKFLOW_TYPE_EDITORIAL', 'WORKFLOW_TYPE_AUTHOR', 'PHP_REQUIRED_VERSION',
|
||||
'API_VERSION',
|
||||
'ROUTE_COMPONENT', 'ROUTE_PAGE', 'ROUTE_API',
|
||||
'CONTEXT_SITE', 'CONTEXT_ID_NONE', 'CONTEXT_ID_ALL', 'REVIEW_ROUND_NONE',
|
||||
|
||||
'ASSOC_TYPE_PRODUCTION_ASSIGNMENT',
|
||||
'ASSOC_TYPE_SUBMISSION_FILE',
|
||||
'ASSOC_TYPE_REVIEW_RESPONSE',
|
||||
'ASSOC_TYPE_REVIEW_ASSIGNMENT',
|
||||
'ASSOC_TYPE_SUBMISSION_EMAIL_LOG_ENTRY',
|
||||
'ASSOC_TYPE_WORKFLOW_STAGE',
|
||||
'ASSOC_TYPE_NOTE',
|
||||
'ASSOC_TYPE_REPRESENTATION',
|
||||
'ASSOC_TYPE_ANNOUNCEMENT',
|
||||
'ASSOC_TYPE_REVIEW_ROUND',
|
||||
'ASSOC_TYPE_SUBMISSION_FILES',
|
||||
'ASSOC_TYPE_PLUGIN',
|
||||
'ASSOC_TYPE_SECTION',
|
||||
'ASSOC_TYPE_CATEGORY',
|
||||
'ASSOC_TYPE_USER',
|
||||
'ASSOC_TYPE_USER_GROUP',
|
||||
'ASSOC_TYPE_CITATION',
|
||||
'ASSOC_TYPE_AUTHOR',
|
||||
'ASSOC_TYPE_EDITOR',
|
||||
'ASSOC_TYPE_USER_ROLES',
|
||||
'ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES',
|
||||
'ASSOC_TYPE_SUBMISSION',
|
||||
'ASSOC_TYPE_QUERY',
|
||||
'ASSOC_TYPE_QUEUED_PAYMENT',
|
||||
'ASSOC_TYPE_PUBLICATION',
|
||||
'ASSOC_TYPE_ACCESSIBLE_FILE_STAGES',
|
||||
'ASSOC_TYPE_SUBMISSION_FILE_COUNTER_OTHER',
|
||||
] as $constantName) {
|
||||
if (!defined($constantName)) {
|
||||
define($constantName, constant('self::' . $constantName));
|
||||
}
|
||||
}
|
||||
if (!class_exists('\PKPApplication')) {
|
||||
class_alias('\PKP\core\PKPApplication', '\PKPApplication');
|
||||
}
|
||||
}
|
||||
|
||||
ini_set('display_errors', Config::getVar('debug', 'display_errors', ini_get('display_errors')));
|
||||
if (!static::isInstalled()) {
|
||||
SessionManager::disable();
|
||||
}
|
||||
|
||||
Registry::set('application', $this);
|
||||
|
||||
$microTime = Core::microtime();
|
||||
Registry::set('system.debug.startTime', $microTime);
|
||||
|
||||
$this->initializeLaravelContainer();
|
||||
PKPString::initialize();
|
||||
|
||||
// Load default locale files
|
||||
Locale::registerPath(BASE_SYS_DIR . '/lib/pkp/locale');
|
||||
|
||||
if (static::isInstalled() && !static::isUpgrading()) {
|
||||
$versionDao = DAORegistry::getDAO('VersionDAO'); /** @var VersionDAO $versionDao */
|
||||
$appVersion = $versionDao->getCurrentVersion()->getVersionString();
|
||||
Registry::set('appVersion', $appVersion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Laravel container and register service providers
|
||||
*/
|
||||
public function initializeLaravelContainer(): void
|
||||
{
|
||||
// Ensure multiple calls to this function don't cause trouble
|
||||
static $containerInitialized = false;
|
||||
if ($containerInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
$containerInitialized = true;
|
||||
|
||||
// Initialize Laravel's container and set it globally
|
||||
$laravelContainer = new PKPContainer();
|
||||
$laravelContainer->registerConfiguredProviders();
|
||||
|
||||
$this->initializeTimeZone();
|
||||
|
||||
if (Config::getVar('database', 'debug')) {
|
||||
DB::listen(fn (QueryExecuted $query) => error_log("Database query\n{$query->sql}\n" . json_encode($query->bindings)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the internal time zone for the database and PHP.
|
||||
*/
|
||||
protected function initializeTimeZone(): void
|
||||
{
|
||||
$timeZone = null;
|
||||
// Loads the time zone from the configuration file
|
||||
if ($setting = Config::getVar('general', 'time_zone')) {
|
||||
try {
|
||||
$timeZone = (new DateTimeZone($setting))->getName();
|
||||
} catch (Exception $e) {
|
||||
$setting = strtolower($setting);
|
||||
foreach (DateTimeZone::listIdentifiers() as $identifier) {
|
||||
// Backward compatibility identification
|
||||
if ($setting == strtolower(preg_replace(['/^\w+\//', '/_/'], ['', ' '], $identifier))) {
|
||||
$timeZone = $identifier;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set the default timezone
|
||||
date_default_timezone_set($timeZone ?: ini_get('date.timezone') ?: 'UTC');
|
||||
|
||||
// Synchronize the database time zone
|
||||
if (Application::isInstalled()) {
|
||||
// Retrieve the current offset
|
||||
$offset = (new DateTime())->format('P');
|
||||
$statement = DB::connection() instanceof MySqlConnection
|
||||
? "SET time_zone = '{$offset}'"
|
||||
: "SET TIME ZONE INTERVAL '{$offset}' HOUR TO MINUTE";
|
||||
DB::statement($statement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc PKPApplication::get()
|
||||
*
|
||||
* @deprecated Use PKPApplication::get() instead.
|
||||
*/
|
||||
public static function getApplication()
|
||||
{
|
||||
return self::get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current application object
|
||||
*
|
||||
* @return Application
|
||||
*/
|
||||
public static function get()
|
||||
{
|
||||
return Registry::get('application');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique site ID
|
||||
*/
|
||||
public function getUUID(): string
|
||||
{
|
||||
$site = $this->getRequest()->getSite();
|
||||
$uniqueSiteId = $site->getUniqueSiteID();
|
||||
if (!strlen((string) $uniqueSiteId)) {
|
||||
$uniqueSiteId = PKPString::generateUUID();
|
||||
$site->setUniqueSiteID($uniqueSiteId);
|
||||
/** @var SiteDAO */
|
||||
$siteDao = DAORegistry::getDAO('SiteDAO');
|
||||
$siteDao->updateObject($site);
|
||||
}
|
||||
return $uniqueSiteId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a HTTP client implementation.
|
||||
*
|
||||
* @return Client
|
||||
*/
|
||||
public function getHttpClient()
|
||||
{
|
||||
$application = Application::get();
|
||||
$userAgent = $application->getName() . '/';
|
||||
if (static::isInstalled() && !static::isUpgrading()) {
|
||||
/** @var \PKP\site\VersionDAO */
|
||||
$versionDao = DAORegistry::getDAO('VersionDAO');
|
||||
$currentVersion = $versionDao->getCurrentVersion();
|
||||
$userAgent .= $currentVersion->getVersionString();
|
||||
} else {
|
||||
$userAgent .= '?';
|
||||
}
|
||||
|
||||
return new Client([
|
||||
'proxy' => [
|
||||
'http' => Config::getVar('proxy', 'http_proxy', null),
|
||||
'https' => Config::getVar('proxy', 'https_proxy', null),
|
||||
],
|
||||
'headers' => [
|
||||
'User-Agent' => $userAgent,
|
||||
],
|
||||
'allow_redirects' => ['strict' => true],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request implementation singleton
|
||||
*
|
||||
* @return Request
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
$request = & Registry::get('request', true, null); // Ref req'd
|
||||
|
||||
if (is_null($request)) {
|
||||
// Implicitly set request by ref in the registry
|
||||
$request = new Request();
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dispatcher implementation singleton
|
||||
*
|
||||
* @return Dispatcher
|
||||
*/
|
||||
public function getDispatcher()
|
||||
{
|
||||
$dispatcher = & Registry::get('dispatcher', true, null); // Ref req'd
|
||||
if (is_null($dispatcher)) {
|
||||
// Implicitly set dispatcher by ref in the registry
|
||||
$dispatcher = new Dispatcher();
|
||||
|
||||
// Inject dependency
|
||||
$dispatcher->setApplication(PKPApplication::get());
|
||||
|
||||
// Inject router configuration
|
||||
$dispatcher->addRouterName('\PKP\core\APIRouter', self::ROUTE_API);
|
||||
$dispatcher->addRouterName('\PKP\core\PKPComponentRouter', self::ROUTE_COMPONENT);
|
||||
$dispatcher->addRouterName('\APP\core\PageRouter', self::ROUTE_PAGE);
|
||||
}
|
||||
|
||||
return $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* This executes the application by delegating the
|
||||
* request to the dispatcher.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
// Dispatch the request to the correct handler
|
||||
$dispatcher = $this->getDispatcher();
|
||||
$dispatcher->dispatch($this->getRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the symbolic name of this application
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getName()
|
||||
{
|
||||
return 'pkp-lib';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the locale key for the name of this application.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getNameKey();
|
||||
|
||||
/**
|
||||
* Get the name of the context for this application
|
||||
*/
|
||||
abstract public function getContextName(): string;
|
||||
|
||||
/**
|
||||
* Get the URL to the XML descriptor for the current version of this
|
||||
* application.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getVersionDescriptorUrl();
|
||||
|
||||
/**
|
||||
* This function retrieves all enabled product versions once
|
||||
* from the database and caches the result for further
|
||||
* access.
|
||||
*
|
||||
* @param string $category
|
||||
* @param int $mainContextId Optional ID of the top-level context
|
||||
* (e.g. Journal, Conference, Press) to query for enabled products
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getEnabledProducts($category = null, $mainContextId = null)
|
||||
{
|
||||
if (is_null($mainContextId)) {
|
||||
$request = $this->getRequest();
|
||||
$router = $request->getRouter();
|
||||
|
||||
$mainContextId = $router->getContext($request)?->getId() ?? self::CONTEXT_SITE;
|
||||
}
|
||||
if (!isset($this->enabledProducts[$mainContextId])) {
|
||||
$versionDao = DAORegistry::getDAO('VersionDAO'); /** @var \PKP\site\VersionDAO $versionDao */
|
||||
$this->enabledProducts[$mainContextId] = $versionDao->getCurrentProducts($mainContextId);
|
||||
}
|
||||
|
||||
if (is_null($category)) {
|
||||
return $this->enabledProducts[$mainContextId];
|
||||
} elseif (isset($this->enabledProducts[$mainContextId][$category])) {
|
||||
return $this->enabledProducts[$mainContextId][$category];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of plugin categories for this application.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getPluginCategories();
|
||||
|
||||
/**
|
||||
* Return the current version of the application.
|
||||
*
|
||||
* @return \PKP\site\Version
|
||||
*/
|
||||
public function getCurrentVersion()
|
||||
{
|
||||
$currentVersion = $this->getEnabledProducts('core');
|
||||
assert(count($currentVersion)) == 1;
|
||||
return $currentVersion[$this->getName()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the map of DAOName => full.class.Path for this application.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDAOMap()
|
||||
{
|
||||
return [
|
||||
'AccessKeyDAO' => 'PKP\security\AccessKeyDAO',
|
||||
'AnnouncementDAO' => 'PKP\announcement\AnnouncementDAO',
|
||||
'AnnouncementTypeDAO' => 'PKP\announcement\AnnouncementTypeDAO',
|
||||
'CitationDAO' => 'PKP\citation\CitationDAO',
|
||||
'ControlledVocabDAO' => 'PKP\controlledVocab\ControlledVocabDAO',
|
||||
'ControlledVocabEntryDAO' => 'PKP\controlledVocab\ControlledVocabEntryDAO',
|
||||
'DataObjectTombstoneDAO' => 'PKP\tombstone\DataObjectTombstoneDAO',
|
||||
'DataObjectTombstoneSettingsDAO' => 'PKP\tombstone\DataObjectTombstoneSettingsDAO',
|
||||
'FilterDAO' => 'PKP\filter\FilterDAO',
|
||||
'FilterGroupDAO' => 'PKP\filter\FilterGroupDAO',
|
||||
'GenreDAO' => 'PKP\submission\GenreDAO',
|
||||
'InterestDAO' => 'PKP\user\InterestDAO',
|
||||
'InterestEntryDAO' => 'PKP\user\InterestEntryDAO',
|
||||
'LibraryFileDAO' => 'PKP\context\LibraryFileDAO',
|
||||
'NavigationMenuDAO' => 'PKP\navigationMenu\NavigationMenuDAO',
|
||||
'NavigationMenuItemDAO' => 'PKP\navigationMenu\NavigationMenuItemDAO',
|
||||
'NavigationMenuItemAssignmentDAO' => 'PKP\navigationMenu\NavigationMenuItemAssignmentDAO',
|
||||
'NoteDAO' => 'PKP\note\NoteDAO',
|
||||
'NotificationDAO' => 'PKP\notification\NotificationDAO',
|
||||
'NotificationSettingsDAO' => 'PKP\notification\NotificationSettingsDAO',
|
||||
'NotificationSubscriptionSettingsDAO' => 'PKP\notification\NotificationSubscriptionSettingsDAO',
|
||||
'PluginGalleryDAO' => 'PKP\plugins\PluginGalleryDAO',
|
||||
'PluginSettingsDAO' => 'PKP\plugins\PluginSettingsDAO',
|
||||
'PublicationDAO' => 'APP\publication\PublicationDAO',
|
||||
'QueuedPaymentDAO' => 'PKP\payment\QueuedPaymentDAO',
|
||||
'ReviewAssignmentDAO' => 'PKP\submission\reviewAssignment\ReviewAssignmentDAO',
|
||||
'ReviewFilesDAO' => 'PKP\submission\ReviewFilesDAO',
|
||||
'ReviewFormDAO' => 'PKP\reviewForm\ReviewFormDAO',
|
||||
'ReviewFormElementDAO' => 'PKP\reviewForm\ReviewFormElementDAO',
|
||||
'ReviewFormResponseDAO' => 'PKP\reviewForm\ReviewFormResponseDAO',
|
||||
'ReviewRoundDAO' => 'PKP\submission\reviewRound\ReviewRoundDAO',
|
||||
'RoleDAO' => 'PKP\security\RoleDAO',
|
||||
'ScheduledTaskDAO' => 'PKP\scheduledTask\ScheduledTaskDAO',
|
||||
'SessionDAO' => 'PKP\session\SessionDAO',
|
||||
'SiteDAO' => 'PKP\site\SiteDAO',
|
||||
'StageAssignmentDAO' => 'PKP\stageAssignment\StageAssignmentDAO',
|
||||
'SubEditorsDAO' => 'PKP\context\SubEditorsDAO',
|
||||
'SubmissionAgencyDAO' => 'PKP\submission\SubmissionAgencyDAO',
|
||||
'SubmissionAgencyEntryDAO' => 'PKP\submission\SubmissionAgencyEntryDAO',
|
||||
'SubmissionCommentDAO' => 'PKP\submission\SubmissionCommentDAO',
|
||||
'SubmissionDisciplineDAO' => 'PKP\submission\SubmissionDisciplineDAO',
|
||||
'SubmissionDisciplineEntryDAO' => 'PKP\submission\SubmissionDisciplineEntryDAO',
|
||||
'SubmissionEmailLogDAO' => 'PKP\log\SubmissionEmailLogDAO',
|
||||
'QueryDAO' => 'PKP\query\QueryDAO',
|
||||
'SubmissionLanguageDAO' => 'PKP\submission\SubmissionLanguageDAO',
|
||||
'SubmissionLanguageEntryDAO' => 'PKP\submission\SubmissionLanguageEntryDAO',
|
||||
'SubmissionKeywordDAO' => 'PKP\submission\SubmissionKeywordDAO',
|
||||
'SubmissionKeywordEntryDAO' => 'PKP\submission\SubmissionKeywordEntryDAO',
|
||||
'SubmissionSubjectDAO' => 'PKP\submission\SubmissionSubjectDAO',
|
||||
'SubmissionSubjectEntryDAO' => 'PKP\submission\SubmissionSubjectEntryDAO',
|
||||
'TemporaryFileDAO' => 'PKP\file\TemporaryFileDAO',
|
||||
'TemporaryInstitutionsDAO' => 'PKP\statistics\TemporaryInstitutionsDAO',
|
||||
'UserStageAssignmentDAO' => 'PKP\user\UserStageAssignmentDAO',
|
||||
'VersionDAO' => 'PKP\site\VersionDAO',
|
||||
'WorkflowStageDAO' => 'PKP\workflow\WorkflowStageDAO',
|
||||
'XMLDAO' => 'PKP\db\XMLDAO',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fully-qualified (e.g. page.name.ClassNameDAO) name of the
|
||||
* given DAO.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQualifiedDAOName($name)
|
||||
{
|
||||
$map = & Registry::get('daoMap', true, $this->getDAOMap()); // Ref req'd
|
||||
if (isset($map[$name])) {
|
||||
return $map[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mapping of license URL to license locale key for common
|
||||
* creative commons licenses.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getCCLicenseOptions()
|
||||
{
|
||||
return [
|
||||
'https://creativecommons.org/licenses/by-nc-nd/4.0' => 'submission.license.cc.by-nc-nd4',
|
||||
'https://creativecommons.org/licenses/by-nc/4.0' => 'submission.license.cc.by-nc4',
|
||||
'https://creativecommons.org/licenses/by-nc-sa/4.0' => 'submission.license.cc.by-nc-sa4',
|
||||
'https://creativecommons.org/licenses/by-nd/4.0' => 'submission.license.cc.by-nd4',
|
||||
'https://creativecommons.org/licenses/by/4.0' => 'submission.license.cc.by4',
|
||||
'https://creativecommons.org/licenses/by-sa/4.0' => 'submission.license.cc.by-sa4'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Creative Commons license badge associated with a given
|
||||
* license URL.
|
||||
*
|
||||
* @param ?string $ccLicenseURL URL to creative commons license
|
||||
* @param ?string $locale Optional locale to return badge in
|
||||
*
|
||||
* @return ?string HTML code for CC license
|
||||
*/
|
||||
public function getCCLicenseBadge($ccLicenseURL, $locale = null)
|
||||
{
|
||||
if (!$ccLicenseURL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$licenseKeyMap = [
|
||||
'|http[s]?://(www\.)?creativecommons.org/licenses/by-nc-nd/4.0[/]?|' => 'submission.license.cc.by-nc-nd4.footer',
|
||||
'|http[s]?://(www\.)?creativecommons.org/licenses/by-nc/4.0[/]?|' => 'submission.license.cc.by-nc4.footer',
|
||||
'|http[s]?://(www\.)?creativecommons.org/licenses/by-nc-sa/4.0[/]?|' => 'submission.license.cc.by-nc-sa4.footer',
|
||||
'|http[s]?://(www\.)?creativecommons.org/licenses/by-nd/4.0[/]?|' => 'submission.license.cc.by-nd4.footer',
|
||||
'|http[s]?://(www\.)?creativecommons.org/licenses/by/4.0[/]?|' => 'submission.license.cc.by4.footer',
|
||||
'|http[s]?://(www\.)?creativecommons.org/licenses/by-sa/4.0[/]?|' => 'submission.license.cc.by-sa4.footer',
|
||||
'|http[s]?://(www\.)?creativecommons.org/licenses/by-nc-nd/3.0[/]?|' => 'submission.license.cc.by-nc-nd3.footer',
|
||||
'|http[s]?://(www\.)?creativecommons.org/licenses/by-nc/3.0[/]?|' => 'submission.license.cc.by-nc3.footer',
|
||||
'|http[s]?://(www\.)?creativecommons.org/licenses/by-nc-sa/3.0[/]?|' => 'submission.license.cc.by-nc-sa3.footer',
|
||||
'|http[s]?://(www\.)?creativecommons.org/licenses/by-nd/3.0[/]?|' => 'submission.license.cc.by-nd3.footer',
|
||||
'|http[s]?://(www\.)?creativecommons.org/licenses/by/3.0[/]?|' => 'submission.license.cc.by3.footer',
|
||||
'|http[s]?://(www\.)?creativecommons.org/licenses/by-sa/3.0[/]?|' => 'submission.license.cc.by-sa3.footer'
|
||||
];
|
||||
|
||||
$locale ??= Locale::getLocale();
|
||||
foreach ($licenseKeyMap as $pattern => $key) {
|
||||
if (preg_match($pattern, $ccLicenseURL)) {
|
||||
return __($key, [], $locale);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mapping of role keys and i18n key names.
|
||||
*
|
||||
* @param bool $contextOnly If false, also returns site-level roles (Site admin)
|
||||
* @param array|null $roleIds Only return role names of these IDs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getRoleNames($contextOnly = false, $roleIds = null)
|
||||
{
|
||||
$siteRoleNames = [Role::ROLE_ID_SITE_ADMIN => 'user.role.siteAdmin'];
|
||||
$appRoleNames = [
|
||||
Role::ROLE_ID_MANAGER => 'user.role.manager',
|
||||
Role::ROLE_ID_SUB_EDITOR => 'user.role.subEditor',
|
||||
Role::ROLE_ID_ASSISTANT => 'user.role.assistant',
|
||||
Role::ROLE_ID_AUTHOR => 'user.role.author',
|
||||
Role::ROLE_ID_REVIEWER => 'user.role.reviewer',
|
||||
Role::ROLE_ID_READER => 'user.role.reader',
|
||||
];
|
||||
$roleNames = $contextOnly ? $appRoleNames : $siteRoleNames + $appRoleNames;
|
||||
if (!empty($roleIds)) {
|
||||
$roleNames = array_intersect_key($roleNames, array_flip($roleIds));
|
||||
}
|
||||
|
||||
return $roleNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mapping of roles allowed to access particular workflows
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getWorkflowTypeRoles()
|
||||
{
|
||||
return [
|
||||
self::WORKFLOW_TYPE_EDITORIAL => [Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT],
|
||||
self::WORKFLOW_TYPE_AUTHOR => [Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_AUTHOR],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of a workflow stage
|
||||
*
|
||||
* @param int $stageId One of the WORKFLOW_STAGE_* constants
|
||||
*/
|
||||
public static function getWorkflowStageName(int $stageId): string
|
||||
{
|
||||
return match ($stageId) {
|
||||
WORKFLOW_STAGE_ID_SUBMISSION => 'submission.submission',
|
||||
WORKFLOW_STAGE_ID_INTERNAL_REVIEW => 'workflow.review.internalReview',
|
||||
WORKFLOW_STAGE_ID_EXTERNAL_REVIEW => 'workflow.review.externalReview',
|
||||
WORKFLOW_STAGE_ID_EDITING => 'submission.editorial',
|
||||
WORKFLOW_STAGE_ID_PRODUCTION => 'submission.production',
|
||||
default => new Exception('Name requested for an unrecognized stage id.')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hex color (#000000) of a workflow stage
|
||||
*
|
||||
* @param int $stageId One of the WORKFLOW_STAGE_* constants
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getWorkflowStageColor($stageId)
|
||||
{
|
||||
switch ($stageId) {
|
||||
case WORKFLOW_STAGE_ID_SUBMISSION: return '#d00a0a';
|
||||
case WORKFLOW_STAGE_ID_INTERNAL_REVIEW: return '#e05c14';
|
||||
case WORKFLOW_STAGE_ID_EXTERNAL_REVIEW: return '#e08914';
|
||||
case WORKFLOW_STAGE_ID_EDITING: return '#006798';
|
||||
case WORKFLOW_STAGE_ID_PRODUCTION: return '#00b28d';
|
||||
}
|
||||
throw new Exception('Color requested for an unrecognized stage id.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human-readable version of the max file upload size
|
||||
*/
|
||||
public static function getReadableMaxFileSize(): string
|
||||
{
|
||||
return strtoupper(UPLOAD_MAX_FILESIZE) . 'B';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the max upload size to an integer in MBs
|
||||
*/
|
||||
public static function getIntMaxFileMBs(): int
|
||||
{
|
||||
$size = (int) UPLOAD_MAX_FILESIZE;
|
||||
$unit = strtolower(substr(UPLOAD_MAX_FILESIZE, -1));
|
||||
// No suffix fallbacks to "byte"
|
||||
match (ctype_alpha($unit) ? $unit : 'b') {
|
||||
'g' => $size <<= 10,
|
||||
'm' => null,
|
||||
'k' => $size >>= 10,
|
||||
'b' => $size >>= 20,
|
||||
default => error_log(sprintf('Invalid value for the PHP configuration upload_max_filesize "%s"', UPLOAD_MAX_FILESIZE))
|
||||
};
|
||||
return floor($size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the supported metadata setting names for this application
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getMetadataFields()
|
||||
{
|
||||
return [
|
||||
'coverage',
|
||||
'languages',
|
||||
'rights',
|
||||
'source',
|
||||
'subjects',
|
||||
'type',
|
||||
'disciplines',
|
||||
'keywords',
|
||||
'agencies',
|
||||
'citations',
|
||||
'dataAvailability',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves whether the application is installed
|
||||
*/
|
||||
public static function isInstalled(): bool
|
||||
{
|
||||
return !!Config::getVar('general', 'installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves whether the application is running an upgrade
|
||||
*/
|
||||
public static function isUpgrading(): bool
|
||||
{
|
||||
return defined('RUNNING_UPGRADE');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves whether the application is under maintenance (not installed or being upgraded)
|
||||
*/
|
||||
public static function isUnderMaintenance(): bool
|
||||
{
|
||||
return !static::isInstalled() || static::isUpgrading();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals the application is undergoing an upgrade
|
||||
*/
|
||||
public static function upgrade(): void
|
||||
{
|
||||
// Constant kept for backwards compatibility
|
||||
if (!defined('RUNNING_UPGRADE')) {
|
||||
define('RUNNING_UPGRADE', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the property name for a section id
|
||||
*
|
||||
* In OMP, the section is referred to as a series and the
|
||||
* property name is different.
|
||||
*/
|
||||
public static function getSectionIdPropName(): string
|
||||
{
|
||||
return 'sectionId';
|
||||
}
|
||||
}
|
||||
|
||||
define('REALLY_BIG_NUMBER', 10000);
|
||||
define('UPLOAD_MAX_FILESIZE', trim(ini_get('upload_max_filesize')));
|
||||
|
||||
define('WORKFLOW_STAGE_ID_PUBLISHED', 0); // FIXME? See bug #6463.
|
||||
define('WORKFLOW_STAGE_ID_SUBMISSION', 1);
|
||||
define('WORKFLOW_STAGE_ID_INTERNAL_REVIEW', 2);
|
||||
define('WORKFLOW_STAGE_ID_EXTERNAL_REVIEW', 3);
|
||||
define('WORKFLOW_STAGE_ID_EDITING', 4);
|
||||
define('WORKFLOW_STAGE_ID_PRODUCTION', 5);
|
||||
|
||||
/* TextArea insert tag variable types used to change their display when selected */
|
||||
define('INSERT_TAG_VARIABLE_TYPE_PLAIN_TEXT', 'PLAIN_TEXT');
|
||||
@@ -0,0 +1,534 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/PKPComponentRouter.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 PKPComponentRouter
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Class mapping an HTTP request to a component handler operation.
|
||||
*
|
||||
* We are using an RPC style URL-to-endpoint mapping. Our approach follows
|
||||
* a simple "convention-over-configuration" paradigm. If necessary the
|
||||
* router can be subclassed to implement more complex URL-to-endpoint mappings.
|
||||
*
|
||||
* For servers with path info enabled the component URL has the following elements:
|
||||
*
|
||||
* .../index.php/context1/context2/$$$call$$$/path/to/handler-class/operation-name?arg1=...&arg2=...
|
||||
*
|
||||
* where "$$$call$$$" is a non-mutable literal string and "path/to" is
|
||||
* by convention the directory path below the "controllers" folder leading to the
|
||||
* component. The next element ("handler-class" in this example) will be mapped to a
|
||||
* component class file by "camelizing" the string to "HandlerClassHandler" and adding
|
||||
* ".php" to the end. The "operation-name" is transformed to "operationName"
|
||||
* and represents the name of the handler method to be called. Finally "arg1", "arg2",
|
||||
* etc. are parameters to be passed along to the handler method.
|
||||
*
|
||||
* For servers with path info disabled the component URL looks like this:
|
||||
*
|
||||
* .../index.php?component=path.to.handler-class&op=operation-name&arg1=...&arg2=...
|
||||
*
|
||||
* The router will sanitize the request URL to a certain amount to make sure that
|
||||
* random code inclusions are prevented. User authorization and parameter validation
|
||||
* are however not the router's concern. These must be implemented on handler level.
|
||||
*
|
||||
* NB: Component and operation names may only contain a-z, 0-9 and hyphens. Numbers
|
||||
* are not allowed at the beginning of a name or after a hyphen.
|
||||
*
|
||||
* NB: Component handlers must implement an initialize() method that will be called
|
||||
* before the request is routed. The initialization method must enforce authorization
|
||||
* and request validation.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use Exception;
|
||||
use PKP\config\Config;
|
||||
use PKP\plugins\Hook;
|
||||
|
||||
// The string to be found in the URL to mark this request as a component request
|
||||
define('COMPONENT_ROUTER_PATHINFO_MARKER', '$$$call$$$');
|
||||
|
||||
// The parameter to be found in the query string for servers with path info disabled
|
||||
define('COMPONENT_ROUTER_PARAMETER_MARKER', 'component');
|
||||
|
||||
// This is the maximum directory depth allowed within the component directory. Set
|
||||
// it to something reasonable to avoid DoS or overflow attacks
|
||||
define('COMPONENT_ROUTER_PARTS_MAXDEPTH', 9);
|
||||
|
||||
// This is the maximum/minimum length of the name of a sub-directory or
|
||||
// handler class name.
|
||||
define('COMPONENT_ROUTER_PARTS_MAXLENGTH', 50);
|
||||
define('COMPONENT_ROUTER_PARTS_MINLENGTH', 2);
|
||||
|
||||
class PKPComponentRouter extends PKPRouter
|
||||
{
|
||||
//
|
||||
// Internal state cache variables
|
||||
// NB: Please do not access directly but
|
||||
// only via their respective getters/setters
|
||||
//
|
||||
/** @var string the requested component handler */
|
||||
public $_component;
|
||||
/** @var string the requested operation */
|
||||
public $_op;
|
||||
/** @var array the rpc service endpoint parts from the request */
|
||||
public $_rpcServiceEndpointParts = false;
|
||||
/** @var callable the rpc service endpoint the request was routed to */
|
||||
public $_rpcServiceEndpoint = false;
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether this router can route the given request.
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return bool true, if the router supports this request, otherwise false
|
||||
*/
|
||||
public function supports($request): bool
|
||||
{
|
||||
// See whether this looks like a component router request.
|
||||
// NOTE: this is prone to false positives i.e. when a class
|
||||
// name cannot be matched, but this laxity permits plugins to
|
||||
// extend the system by registering against the
|
||||
// LoadComponentHandler hook.
|
||||
return $this->_retrieveServiceEndpointParts($request) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the requested component from the request.
|
||||
*
|
||||
* NB: This can be a component that not actually exists
|
||||
* in the code base.
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return string the requested component or an empty string
|
||||
* if none can be found.
|
||||
*/
|
||||
public function getRequestedComponent($request)
|
||||
{
|
||||
if (is_null($this->_component)) {
|
||||
$this->_component = '';
|
||||
|
||||
// Retrieve the service endpoint parts from the request.
|
||||
if (is_null($rpcServiceEndpointParts = $this->_getValidatedServiceEndpointParts($request))) {
|
||||
// Endpoint parts cannot be found in the request
|
||||
return '';
|
||||
}
|
||||
|
||||
// Pop off the operation part
|
||||
array_pop($rpcServiceEndpointParts);
|
||||
|
||||
// Construct the fully qualified component class name from the rest of it.
|
||||
$handlerClassName = PKPString::camelize(array_pop($rpcServiceEndpointParts), PKPString::CAMEL_CASE_HEAD_UP) . 'Handler';
|
||||
|
||||
// camelize remaining endpoint parts
|
||||
$camelizedRpcServiceEndpointParts = [];
|
||||
foreach ($rpcServiceEndpointParts as $part) {
|
||||
$camelizedRpcServiceEndpointParts[] = PKPString::camelize($part, PKPString::CAMEL_CASE_HEAD_DOWN);
|
||||
}
|
||||
$handlerPackage = implode('.', $camelizedRpcServiceEndpointParts);
|
||||
|
||||
$this->_component = $handlerPackage . '.' . $handlerClassName;
|
||||
}
|
||||
|
||||
return $this->_component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the requested operation from the request
|
||||
*
|
||||
* NB: This can be an operation that not actually
|
||||
* exists in the requested component.
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return string the requested operation or an empty string
|
||||
* if none can be found.
|
||||
*/
|
||||
public function getRequestedOp($request)
|
||||
{
|
||||
if (is_null($this->_op)) {
|
||||
$this->_op = '';
|
||||
|
||||
// Retrieve the service endpoint parts from the request.
|
||||
if (is_null($rpcServiceEndpointParts = $this->_getValidatedServiceEndpointParts($request))) {
|
||||
// Endpoint parts cannot be found in the request
|
||||
return '';
|
||||
}
|
||||
|
||||
// Pop off the operation part
|
||||
$this->_op = PKPString::camelize(array_pop($rpcServiceEndpointParts), PKPString::CAMEL_CASE_HEAD_DOWN);
|
||||
}
|
||||
|
||||
return $this->_op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the (validated) RPC service endpoint from the request.
|
||||
* If no such RPC service endpoint can be constructed then the method
|
||||
* returns null.
|
||||
*
|
||||
* @param PKPRequest $request the request to be routed
|
||||
*
|
||||
* @return callable|array|null an array with the handler instance
|
||||
* and the handler operation to be called by call_user_func().
|
||||
*/
|
||||
public function &getRpcServiceEndpoint($request)
|
||||
{
|
||||
if ($this->_rpcServiceEndpoint === false) {
|
||||
// We have not yet resolved this request. Mark the
|
||||
// state variable so that we don't try again next
|
||||
// time.
|
||||
$this->_rpcServiceEndpoint = $nullVar = null;
|
||||
|
||||
// Retrieve requested component operation
|
||||
$op = $this->getRequestedOp($request);
|
||||
assert(!empty($op));
|
||||
|
||||
//
|
||||
// Component Handler
|
||||
//
|
||||
// Retrieve requested component handler
|
||||
$component = $this->getRequestedComponent($request);
|
||||
$componentInstance = null;
|
||||
$allowedPackages = null;
|
||||
|
||||
// Give plugins a chance to intervene
|
||||
if (!Hook::call('LoadComponentHandler', [&$component, &$op, &$componentInstance])) {
|
||||
if (empty($component)) {
|
||||
return $nullVar;
|
||||
}
|
||||
|
||||
// Construct the component handler file name and test its existence.
|
||||
$component = 'controllers.' . $component;
|
||||
$componentFileNamePart = str_replace('.', '/', $component);
|
||||
switch (true) {
|
||||
case file_exists("{$componentFileNamePart}.php"):
|
||||
$className = 'APP\\' . strtr($componentFileNamePart, '/', '\\');
|
||||
$componentInstance = new $className();
|
||||
break;
|
||||
|
||||
case file_exists("{$componentFileNamePart}.inc.php"):
|
||||
// This behaviour is DEPRECATED as of 3.4.0.
|
||||
break;
|
||||
|
||||
case file_exists(PKP_LIB_PATH . "/{$componentFileNamePart}.php"):
|
||||
$className = 'PKP\\' . strtr($componentFileNamePart, '/', '\\');
|
||||
$componentInstance = new $className();
|
||||
break;
|
||||
|
||||
case file_exists(PKP_LIB_PATH . "/{$componentFileNamePart}.inc.php"):
|
||||
// This behaviour is DEPRECATED as of 3.4.0.
|
||||
$component = 'lib.pkp.' . $component;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Request to non-existent handler
|
||||
return $nullVar;
|
||||
}
|
||||
|
||||
// We expect the handler to be part of one
|
||||
// of the following packages:
|
||||
$allowedPackages = [
|
||||
'controllers',
|
||||
'lib.pkp.controllers'
|
||||
];
|
||||
}
|
||||
|
||||
// A handler at least needs to implement the
|
||||
// following methods:
|
||||
$requiredMethods = [
|
||||
$op, 'authorize', 'validate', 'initialize'
|
||||
];
|
||||
|
||||
if (!$componentInstance) {
|
||||
$componentInstance = instantiate($component, 'PKPHandler', $allowedPackages, $requiredMethods);
|
||||
}
|
||||
if (!is_object($componentInstance)) {
|
||||
return $nullVar;
|
||||
}
|
||||
$this->setHandler($componentInstance);
|
||||
|
||||
//
|
||||
// Callable service endpoint
|
||||
//
|
||||
// Construct the callable array
|
||||
$this->_rpcServiceEndpoint = [$componentInstance, $op];
|
||||
}
|
||||
|
||||
return $this->_rpcServiceEndpoint;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement template methods from PKPRouter
|
||||
//
|
||||
/**
|
||||
* @copydoc PKPRouter::route()
|
||||
*/
|
||||
public function route($request)
|
||||
{
|
||||
// Determine the requested service endpoint.
|
||||
$rpcServiceEndpoint = & $this->getRpcServiceEndpoint($request);
|
||||
|
||||
// Retrieve RPC arguments from the request.
|
||||
$args = $request->getUserVars();
|
||||
assert(is_array($args));
|
||||
|
||||
// Remove the caller-parameter (if present)
|
||||
if (isset($args[COMPONENT_ROUTER_PARAMETER_MARKER])) {
|
||||
unset($args[COMPONENT_ROUTER_PARAMETER_MARKER]);
|
||||
}
|
||||
|
||||
// Authorize, validate and initialize the request
|
||||
$this->_authorizeInitializeAndCallRequest($rpcServiceEndpoint, $request, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc PKPRouter::url()
|
||||
*
|
||||
* @param null|mixed $newContext
|
||||
* @param null|mixed $component
|
||||
* @param null|mixed $op
|
||||
* @param null|mixed $path
|
||||
* @param null|mixed $params
|
||||
* @param null|mixed $anchor
|
||||
*/
|
||||
public function url(
|
||||
$request,
|
||||
$newContext = null,
|
||||
$component = null,
|
||||
$op = null,
|
||||
$path = null,
|
||||
$params = null,
|
||||
$anchor = null,
|
||||
$escape = false
|
||||
) {
|
||||
if (!is_null($path)) {
|
||||
throw new Exception('Path must be null when calling PKPComponentRouter::url()');
|
||||
}
|
||||
|
||||
//
|
||||
// Base URL and Context
|
||||
//
|
||||
[$baseUrl, $context] = $this->_urlGetBaseAndContext($request, $newContext);
|
||||
|
||||
//
|
||||
// Component and Operation
|
||||
//
|
||||
// We only support component/op retrieval from the request
|
||||
// if this request is a component request.
|
||||
$currentRequestIsAComponentRequest = $request->getRouter() instanceof self;
|
||||
if ($currentRequestIsAComponentRequest) {
|
||||
if (empty($component)) {
|
||||
$component = $this->getRequestedComponent($request);
|
||||
}
|
||||
if (empty($op)) {
|
||||
$op = $this->getRequestedOp($request);
|
||||
}
|
||||
}
|
||||
assert(!empty($component) && !empty($op));
|
||||
|
||||
// Encode the component and operation
|
||||
$componentParts = explode('.', $component);
|
||||
$componentName = array_pop($componentParts);
|
||||
assert(substr($componentName, -7) == 'Handler');
|
||||
$componentName = PKPString::uncamelize(substr($componentName, 0, -7));
|
||||
|
||||
// uncamelize the component parts
|
||||
$uncamelizedComponentParts = [];
|
||||
foreach ($componentParts as $part) {
|
||||
$uncamelizedComponentParts[] = PKPString::uncamelize($part);
|
||||
}
|
||||
array_push($uncamelizedComponentParts, $componentName);
|
||||
$opName = PKPString::uncamelize($op);
|
||||
|
||||
//
|
||||
// Additional query parameters
|
||||
//
|
||||
$additionalParameters = $this->_urlGetAdditionalParameters($request, $params, $escape);
|
||||
|
||||
//
|
||||
// Anchor
|
||||
//
|
||||
$anchor = (empty($anchor) ? '' : '#' . rawurlencode($anchor));
|
||||
|
||||
//
|
||||
// Assemble URL
|
||||
//
|
||||
// Context, page, operation and additional path go into the path info.
|
||||
$pathInfoArray = array_merge(
|
||||
$context,
|
||||
[COMPONENT_ROUTER_PATHINFO_MARKER],
|
||||
$uncamelizedComponentParts,
|
||||
[$opName]
|
||||
);
|
||||
|
||||
// Query parameters
|
||||
$queryParametersArray = $additionalParameters;
|
||||
|
||||
return $this->_urlFromParts($baseUrl, $pathInfoArray, $queryParametersArray, $anchor, $escape);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc PKPRouter::handleAuthorizationFailure()
|
||||
*/
|
||||
public function handleAuthorizationFailure(
|
||||
$request,
|
||||
$authorizationMessage,
|
||||
array $messageParams = []
|
||||
) {
|
||||
$translatedAuthorizationMessage = __($authorizationMessage, $messageParams);
|
||||
|
||||
// Add the router name and operation if show_stacktrace is enabled.
|
||||
if (Config::getVar('debug', 'show_stacktrace')) {
|
||||
$url = $request->getRequestUrl();
|
||||
$queryString = $request->getQueryString();
|
||||
if ($queryString) {
|
||||
$queryString = '?' . $queryString;
|
||||
}
|
||||
$translatedAuthorizationMessage .= ' [' . $url . $queryString . ']';
|
||||
}
|
||||
// Return a JSON error message.
|
||||
return new JSONMessage(false, $translatedAuthorizationMessage);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods
|
||||
//
|
||||
/**
|
||||
* Get the (validated) RPC service endpoint parts from the request.
|
||||
* If no such RPC service endpoint parts can be retrieved
|
||||
* then the method returns null.
|
||||
*
|
||||
* @param PKPRequest $request the request to be routed
|
||||
*
|
||||
* @return ?array a string array with the RPC service endpoint
|
||||
* parts as values.
|
||||
*/
|
||||
public function _getValidatedServiceEndpointParts($request)
|
||||
{
|
||||
if ($this->_rpcServiceEndpointParts === false) {
|
||||
// Mark the internal state variable so this
|
||||
// will not be called again.
|
||||
$this->_rpcServiceEndpointParts = null;
|
||||
|
||||
// Retrieve service endpoint parts from the request.
|
||||
if (is_null($rpcServiceEndpointParts = $this->_retrieveServiceEndpointParts($request))) {
|
||||
// This is not an RPC request
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate the service endpoint parts.
|
||||
if (is_null($rpcServiceEndpointParts = $this->_validateServiceEndpointParts($rpcServiceEndpointParts))) {
|
||||
// Invalid request
|
||||
return null;
|
||||
}
|
||||
|
||||
// Assign the validated service endpoint parts
|
||||
$this->_rpcServiceEndpointParts = $rpcServiceEndpointParts;
|
||||
}
|
||||
|
||||
return $this->_rpcServiceEndpointParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to retrieve a (non-validated) array with the service
|
||||
* endpoint parts from the request. See the classdoc for the
|
||||
* URL patterns supported here.
|
||||
*
|
||||
* @param PKPRequest $request the request to be routed
|
||||
*
|
||||
* @return ?array an array of (non-validated) service endpoint
|
||||
* parts or null if the request is not an RPC request.
|
||||
*/
|
||||
public function _retrieveServiceEndpointParts($request)
|
||||
{
|
||||
if (!isset($_SERVER['PATH_INFO'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$pathInfoParts = explode('/', trim($_SERVER['PATH_INFO'], '/'));
|
||||
|
||||
// We expect at least the context + the component
|
||||
// router marker + 3 component parts (path, handler, operation)
|
||||
$application = $this->getApplication();
|
||||
if (count($pathInfoParts) < 5) {
|
||||
// This path info is too short to be an RPC request
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check the component router marker
|
||||
if ($pathInfoParts[1] != COMPONENT_ROUTER_PATHINFO_MARKER) {
|
||||
// This is not an RPC request
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove context and component marker from the array
|
||||
$rpcServiceEndpointParts = array_slice($pathInfoParts, 2);
|
||||
|
||||
return $rpcServiceEndpointParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method pre-validates the service endpoint parts before
|
||||
* we try to convert them to a file/method name. This also
|
||||
* converts all parts to lower case.
|
||||
*
|
||||
* @param array $rpcServiceEndpointParts
|
||||
*
|
||||
* @return ?array the validated service endpoint parts or null if validation
|
||||
* does not succeed.
|
||||
*/
|
||||
public function _validateServiceEndpointParts($rpcServiceEndpointParts)
|
||||
{
|
||||
// Do we have data at all?
|
||||
if (is_null($rpcServiceEndpointParts) || empty($rpcServiceEndpointParts)
|
||||
|| !is_array($rpcServiceEndpointParts)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We require at least three parts: component directory, handler
|
||||
// and method name.
|
||||
if (count($rpcServiceEndpointParts) < 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check that the array dimensions remain within sane limits.
|
||||
if (count($rpcServiceEndpointParts) > COMPONENT_ROUTER_PARTS_MAXDEPTH) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate the individual endpoint parts.
|
||||
foreach ($rpcServiceEndpointParts as $key => $rpcServiceEndpointPart) {
|
||||
// Make sure that none of the elements exceeds the length limit.
|
||||
$partLen = strlen($rpcServiceEndpointPart);
|
||||
if ($partLen > COMPONENT_ROUTER_PARTS_MAXLENGTH
|
||||
|| $partLen < COMPONENT_ROUTER_PARTS_MINLENGTH) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Service endpoint URLs are case insensitive.
|
||||
$rpcServiceEndpointParts[$key] = strtolower_codesafe($rpcServiceEndpointPart);
|
||||
|
||||
// We only allow letters, numbers and the hyphen.
|
||||
if (!PKPString::regexp_match('/^[a-z0-9-]*$/', $rpcServiceEndpointPart)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $rpcServiceEndpointParts;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\PKPComponentRouter', '\PKPComponentRouter');
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/PKPContainer.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 PKPContainer
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Bootstraps Laravel services, application-level parts and creates bindings
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use APP\core\AppServiceProvider;
|
||||
use Exception;
|
||||
use Illuminate\Config\Repository;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Contracts\Console\Kernel as KernelContract;
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler;
|
||||
use Illuminate\Events\EventServiceProvider as LaravelEventServiceProvider;
|
||||
use Illuminate\Foundation\Console\Kernel;
|
||||
use Illuminate\Log\LogServiceProvider;
|
||||
use Illuminate\Queue\Failed\DatabaseFailedJobProvider;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use PKP\config\Config;
|
||||
use PKP\i18n\LocaleServiceProvider;
|
||||
use PKP\proxy\ProxyParser;
|
||||
use Throwable;
|
||||
|
||||
class PKPContainer extends Container
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @brief the base path of the application, needed for base_path helper
|
||||
*/
|
||||
protected $basePath;
|
||||
|
||||
/**
|
||||
* @brief Create own container instance, initialize bindings
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->basePath = BASE_SYS_DIR;
|
||||
$this->settingProxyForStreamContext();
|
||||
$this->registerBaseBindings();
|
||||
$this->registerCoreContainerAliases();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bind the current container and set it globally
|
||||
* let helpers, facades and services know to which container refer to
|
||||
*/
|
||||
protected function registerBaseBindings()
|
||||
{
|
||||
static::setInstance($this);
|
||||
$this->instance('app', $this);
|
||||
$this->instance(Container::class, $this);
|
||||
$this->instance('path', $this->basePath);
|
||||
$this->singleton(ExceptionHandler::class, function () {
|
||||
return new class () implements ExceptionHandler {
|
||||
public function shouldReport(Throwable $e)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function report(Throwable $e)
|
||||
{
|
||||
error_log((string) $e->getTraceAsString());
|
||||
}
|
||||
|
||||
public function render($request, Throwable $e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function renderForConsole($output, Throwable $e)
|
||||
{
|
||||
echo (string) $e;
|
||||
}
|
||||
};
|
||||
});
|
||||
$this->singleton(
|
||||
KernelContract::class,
|
||||
Kernel::class
|
||||
);
|
||||
|
||||
$this->singleton('pkpJobQueue', function ($app) {
|
||||
return new PKPQueueProvider($app);
|
||||
});
|
||||
|
||||
$this->singleton(
|
||||
'queue.failer',
|
||||
function ($app) {
|
||||
return new DatabaseFailedJobProvider(
|
||||
$app['db'],
|
||||
config('queue.failed.database'),
|
||||
config('queue.failed.table')
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Facade::setFacadeApplication($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Register used service providers within the container
|
||||
*/
|
||||
public function registerConfiguredProviders()
|
||||
{
|
||||
// Load main settings, this should be done before registering services, e.g., it's used by Database Service
|
||||
$this->loadConfiguration();
|
||||
$this->register(new \Illuminate\Cache\CacheServiceProvider($this));
|
||||
$this->register(new \Illuminate\Filesystem\FilesystemServiceProvider($this));
|
||||
$this->register(new \ElcoBvg\Opcache\ServiceProvider($this));
|
||||
$this->register(new LaravelEventServiceProvider($this));
|
||||
$this->register(new EventServiceProvider($this));
|
||||
$this->register(new LogServiceProvider($this));
|
||||
$this->register(new \Illuminate\Database\DatabaseServiceProvider($this));
|
||||
$this->register(new \Illuminate\Bus\BusServiceProvider($this));
|
||||
$this->register(new PKPQueueProvider($this));
|
||||
$this->register(new MailServiceProvider($this));
|
||||
$this->register(new AppServiceProvider($this));
|
||||
$this->register(new LocaleServiceProvider($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Illuminate\Support\ServiceProvider $provider
|
||||
*
|
||||
* @brief Simplified service registration
|
||||
*/
|
||||
public function register($provider)
|
||||
{
|
||||
$provider->register();
|
||||
$provider->callBootingCallbacks();
|
||||
if (method_exists($provider, 'boot')) {
|
||||
$this->call([$provider, 'boot']);
|
||||
}
|
||||
|
||||
// If there are bindings / singletons set as properties on the provider we
|
||||
// will spin through them and register them with the application, which
|
||||
// serves as a convenience layer while registering a lot of bindings.
|
||||
if (property_exists($provider, 'bindings')) {
|
||||
foreach ($provider->bindings as $key => $value) {
|
||||
$this->bind($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
if (property_exists($provider, 'singletons')) {
|
||||
foreach ($provider->singletons as $key => $value) {
|
||||
$key = is_int($key) ? $value : $key;
|
||||
$this->singleton($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$provider->callBootedCallbacks();
|
||||
|
||||
$this->app->bind('request', fn () => PKPApplication::get()->getRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bind aliases with contracts
|
||||
*/
|
||||
public function registerCoreContainerAliases()
|
||||
{
|
||||
foreach ([
|
||||
'app' => [self::class, \Illuminate\Contracts\Container\Container::class, \Psr\Container\ContainerInterface::class],
|
||||
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
|
||||
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
|
||||
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class, \Psr\SimpleCache\CacheInterface::class],
|
||||
'cache.psr6' => [\Psr\Cache\CacheItemPoolInterface::class],
|
||||
'db' => [\Illuminate\Database\DatabaseManager::class, \Illuminate\Database\ConnectionResolverInterface::class],
|
||||
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
|
||||
'files' => [\Illuminate\Filesystem\Filesystem::class],
|
||||
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
|
||||
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
|
||||
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
|
||||
'maps' => [MapContainer::class, MapContainer::class],
|
||||
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
|
||||
'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
|
||||
'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class],
|
||||
'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
|
||||
'log' => [\Illuminate\Log\LogManager::class, \Psr\Log\LoggerInterface::class],
|
||||
] as $key => $aliases) {
|
||||
foreach ($aliases as $alias) {
|
||||
$this->alias($key, $alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bind and load container configurations
|
||||
* usage from Facade, see Illuminate\Support\Facades\Config
|
||||
*/
|
||||
protected function loadConfiguration()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
// Database connection
|
||||
$driver = 'mysql';
|
||||
|
||||
if (substr(strtolower(Config::getVar('database', 'driver')), 0, 8) === 'postgres') {
|
||||
$driver = 'pgsql';
|
||||
}
|
||||
|
||||
$items['database']['default'] = $driver;
|
||||
$items['database']['connections'][$driver] = [
|
||||
'driver' => $driver,
|
||||
'host' => Config::getVar('database', 'host'),
|
||||
'database' => Config::getVar('database', 'name'),
|
||||
'username' => Config::getVar('database', 'username'),
|
||||
'port' => Config::getVar('database', 'port'),
|
||||
'unix_socket' => Config::getVar('database', 'unix_socket'),
|
||||
'password' => Config::getVar('database', 'password'),
|
||||
'charset' => Config::getVar('i18n', 'connection_charset', 'utf8'),
|
||||
'collation' => Config::getVar('database', 'collation', 'utf8_general_ci'),
|
||||
];
|
||||
|
||||
// Queue connection
|
||||
$items['queue']['default'] = 'database';
|
||||
$items['queue']['connections']['sync']['driver'] = 'sync';
|
||||
$items['queue']['connections']['database'] = [
|
||||
'driver' => 'database',
|
||||
'table' => 'jobs',
|
||||
'queue' => 'default',
|
||||
'retry_after' => 240,
|
||||
'after_commit' => true,
|
||||
];
|
||||
$items['queue']['failed'] = [
|
||||
'driver' => 'database',
|
||||
'database' => $driver,
|
||||
'table' => 'failed_jobs',
|
||||
];
|
||||
|
||||
// Logging
|
||||
$items['logging']['channels']['errorlog'] = [
|
||||
'driver' => 'errorlog',
|
||||
'level' => 'debug',
|
||||
];
|
||||
|
||||
// Mail Service
|
||||
$items['mail']['mailers']['sendmail'] = [
|
||||
'transport' => 'sendmail',
|
||||
'path' => Config::getVar('email', 'sendmail_path'),
|
||||
];
|
||||
$items['mail']['mailers']['smtp'] = [
|
||||
'transport' => 'smtp',
|
||||
'host' => Config::getVar('email', 'smtp_server'),
|
||||
'port' => Config::getVar('email', 'smtp_port'),
|
||||
'encryption' => Config::getVar('email', 'smtp_auth'),
|
||||
'username' => Config::getVar('email', 'smtp_username'),
|
||||
'password' => Config::getVar('email', 'smtp_password'),
|
||||
'verify_peer' => !Config::getVar('email', 'smtp_suppress_cert_check'),
|
||||
];
|
||||
$items['mail']['mailers']['log'] = [
|
||||
'transport' => 'log',
|
||||
'channel' => 'errorlog',
|
||||
];
|
||||
$items['mail']['mailers']['phpmailer'] = [
|
||||
'transport' => 'phpmailer',
|
||||
];
|
||||
|
||||
$items['mail']['default'] = static::getDefaultMailer();
|
||||
|
||||
// Cache configuration
|
||||
$items['cache'] = [
|
||||
'default' => 'opcache',
|
||||
'stores' => [
|
||||
'opcache' => [
|
||||
'driver' => 'opcache',
|
||||
'path' => Core::getBaseDir() . '/cache/opcache'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// Create instance and bind to use globally
|
||||
$this->instance('config', new Repository($items));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path appended to the base path
|
||||
*
|
||||
* @brief see Illuminate\Foundation\Application::basePath
|
||||
*/
|
||||
public function basePath($path = '')
|
||||
{
|
||||
return $this->basePath . ($path ? "/{$path}" : $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path appended to the path
|
||||
*
|
||||
* @brief alias of basePath(), Laravel app path differs from installation path
|
||||
*/
|
||||
public function path($path = '')
|
||||
{
|
||||
return $this->basePath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves default mailer driver depending on the configuration
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static function getDefaultMailer(): string
|
||||
{
|
||||
$default = Config::getVar('general', 'sandbox', false)
|
||||
? 'log'
|
||||
: Config::getVar('email', 'default');
|
||||
|
||||
if (!$default) {
|
||||
throw new Exception('Mailer driver isn\'t specified in the application\'s config');
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting a proxy on the stream_context_set_default when configuration [proxy] is filled
|
||||
*/
|
||||
protected function settingProxyForStreamContext(): void
|
||||
{
|
||||
$proxy = new ProxyParser();
|
||||
|
||||
if ($httpProxy = Config::getVar('proxy', 'http_proxy')) {
|
||||
$proxy->parseFQDN($httpProxy);
|
||||
}
|
||||
|
||||
if ($httpsProxy = Config::getVar('proxy', 'https_proxy')) {
|
||||
$proxy->parseFQDN($httpsProxy);
|
||||
}
|
||||
|
||||
if ($proxy->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* `Connection close` here its to avoid slowness. More info at https://www.php.net/manual/en/context.http.php#114867
|
||||
* `request_fulluri` its related to avoid proxy errors. More info at https://www.php.net/manual/en/context.http.php#110449
|
||||
*/
|
||||
$opts = [
|
||||
'http' => [
|
||||
'protocol_version' => 1.1,
|
||||
'header' => [
|
||||
'Connection: close',
|
||||
],
|
||||
'proxy' => $proxy->getProxy(),
|
||||
'request_fulluri' => true,
|
||||
],
|
||||
];
|
||||
|
||||
if ($proxy->getAuth()) {
|
||||
$opts['http']['header'][] = 'Proxy-Authorization: Basic ' . $proxy->getAuth();
|
||||
}
|
||||
|
||||
$context = stream_context_create($opts);
|
||||
stream_context_set_default($opts);
|
||||
libxml_set_streams_context($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override Laravel method; always false.
|
||||
* Prevents the undefined method error when the Log Manager tries to determine the driver
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function runningUnitTests()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the application is currently down for maintenance.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDownForMaintenance()
|
||||
{
|
||||
return PKPApplication::isUnderMaintenance();
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\PKPContainer', '\PKPContainer');
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
<?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');
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file classes/core/PKPQueueDatabaseConnector.php
|
||||
*
|
||||
* Copyright (c) 2014-2023 Simon Fraser University
|
||||
* Copyright (c) 2000-2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PKPQueueDatabaseConnector
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Registers the database queue connector
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use Illuminate\Queue\Connectors\DatabaseConnector as IlluminateQueueDatabaseConnector;
|
||||
use Illuminate\Queue\DatabaseQueue;
|
||||
use PKP\config\Config;
|
||||
|
||||
class PKPQueueDatabaseConnector extends IlluminateQueueDatabaseConnector
|
||||
{
|
||||
/**
|
||||
* Establish a queue connection.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Queue\Queue
|
||||
*/
|
||||
public function connect(array $config)
|
||||
{
|
||||
return new DatabaseQueue(
|
||||
$this->connections->connection($config['connection'] ?? null),
|
||||
$config['table'],
|
||||
Config::getVar('queues', 'default_queue', 'queue'),
|
||||
$config['retry_after'] ?? 60,
|
||||
$config['after_commit'] ?? null
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file classes/core/PKPQueueProvider.php
|
||||
*
|
||||
* Copyright (c) 2014-2023 Simon Fraser University
|
||||
* Copyright (c) 2000-2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PKPQueueProvider
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Registers Events Service Provider and boots data on events and their listeners
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use APP\core\Application;
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Queue\Events\JobFailed;
|
||||
use Illuminate\Queue\QueueServiceProvider as IlluminateQueueServiceProvider;
|
||||
use Illuminate\Queue\Worker;
|
||||
use Illuminate\Queue\WorkerOptions;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use PKP\config\Config;
|
||||
use PKP\job\models\Job as PKPJobModel;
|
||||
use PKP\queue\JobRunner;
|
||||
use PKP\queue\WorkerConfiguration;
|
||||
|
||||
class PKPQueueProvider extends IlluminateQueueServiceProvider
|
||||
{
|
||||
/**
|
||||
* Specific queue to target to run the associated jobs
|
||||
*/
|
||||
protected ?string $queue = null;
|
||||
|
||||
/**
|
||||
* Set a specific queue to target to run the associated jobs
|
||||
*/
|
||||
public function forQueue(string $queue): self
|
||||
{
|
||||
$this->queue = $queue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a job model builder instance to query the jobs table
|
||||
*/
|
||||
public function getJobModelBuilder(): Builder
|
||||
{
|
||||
return PKPJobModel::isAvailable()
|
||||
->nonEmptyQueue()
|
||||
->when($this->queue, fn ($query) => $query->onQueue($this->queue))
|
||||
->when(is_null($this->queue), fn ($query) => $query->notQueue(PKPJobModel::TESTING_QUEUE))
|
||||
->notExceededAttempts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the worker options object
|
||||
*/
|
||||
public function getWorkerOptions(array $options = []): WorkerOptions
|
||||
{
|
||||
return new WorkerOptions(
|
||||
...array_values(WorkerConfiguration::withOptions($options)->getWorkerOptions())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the queue worker via an infinite loop daemon
|
||||
*/
|
||||
public function runJobsViaDaemon(string $connection, string $queue, array $workerOptions = []): void
|
||||
{
|
||||
$worker = PKPContainer::getInstance()['queue.worker']; /** @var \Illuminate\Queue\Worker $worker */
|
||||
|
||||
$worker
|
||||
->setCache(app()->get('cache.store'))
|
||||
->daemon(
|
||||
$connection,
|
||||
$queue,
|
||||
$this->getWorkerOptions($workerOptions)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the queue worker to process queue the jobs
|
||||
*/
|
||||
public function runJobInQueue(): void
|
||||
{
|
||||
$job = $this->getJobModelBuilder()->limit(1)->first();
|
||||
|
||||
if ($job === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$laravelContainer = PKPContainer::getInstance();
|
||||
|
||||
$laravelContainer['queue.worker']->runNextJob(
|
||||
'database',
|
||||
$job->queue ?? Config::getVar('queues', 'default_queue', 'queue'),
|
||||
$this->getWorkerOptions()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
if (Config::getVar('queues', 'job_runner', true)) {
|
||||
register_shutdown_function(function () {
|
||||
// As this runs at the current request's end but the 'register_shutdown_function' registered
|
||||
// at the service provider's registration time at application initial bootstrapping,
|
||||
// need to check the maintenance status within the 'register_shutdown_function'
|
||||
if (Application::get()->isUnderMaintenance()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Config::getVar('general', 'sandbox', false)) {
|
||||
error_log(__('admin.cli.tool.jobs.sandbox.message'));
|
||||
return;
|
||||
}
|
||||
|
||||
(new JobRunner($this))
|
||||
->withMaxExecutionTimeConstrain()
|
||||
->withMaxJobsConstrain()
|
||||
->withMaxMemoryConstrain()
|
||||
->withEstimatedTimeToProcessNextJobConstrain()
|
||||
->processJobs();
|
||||
});
|
||||
}
|
||||
|
||||
Queue::failing(function (JobFailed $event) {
|
||||
error_log($event->exception->__toString());
|
||||
|
||||
app('queue.failer')->log(
|
||||
$event->connectionName,
|
||||
$event->job->getQueue(),
|
||||
$event->job->getRawBody(),
|
||||
json_encode([
|
||||
'message' => $event->exception->getMessage(),
|
||||
'code' => $event->exception->getCode(),
|
||||
'file' => $event->exception->getFile(),
|
||||
'line' => $event->exception->getLine(),
|
||||
'trace' => $event->exception->getTrace(),
|
||||
])
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the database queue connector.
|
||||
*
|
||||
* @param \Illuminate\Queue\QueueManager $manager
|
||||
*/
|
||||
protected function registerDatabaseConnector($manager)
|
||||
{
|
||||
$manager->addConnector('database', function () {
|
||||
return new PKPQueueDatabaseConnector($this->app['db']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the queue worker.
|
||||
*
|
||||
*/
|
||||
protected function registerWorker()
|
||||
{
|
||||
$this->app->singleton('queue.worker', function ($app) {
|
||||
$isDownForMaintenance = function () {
|
||||
return $this->app->isDownForMaintenance();
|
||||
};
|
||||
|
||||
$resetScope = function () use ($app) {
|
||||
if (method_exists($app['db'], 'getConnections')) {
|
||||
foreach ($app['db']->getConnections() as $connection) {
|
||||
$connection->resetTotalQueryDuration();
|
||||
$connection->allowQueryDurationHandlersToRunAgain();
|
||||
}
|
||||
}
|
||||
|
||||
$app->forgetScopedInstances();
|
||||
|
||||
return Facade::clearResolvedInstances();
|
||||
};
|
||||
|
||||
return new Worker(
|
||||
$app['queue'],
|
||||
$app['events'],
|
||||
$app[ExceptionHandler::class],
|
||||
$isDownForMaintenance,
|
||||
$resetScope
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,884 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/PKPRequest.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 PKPRequest
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Class providing operations associated with HTTP requests.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\file\PublicFileManager;
|
||||
use PKP\config\Config;
|
||||
use PKP\context\Context;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\handler\APIHandler;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\security\Validation;
|
||||
use PKP\session\Session;
|
||||
use PKP\session\SessionManager;
|
||||
use PKP\site\Site;
|
||||
use PKP\site\SiteDAO;
|
||||
use PKP\user\User;
|
||||
|
||||
class PKPRequest
|
||||
{
|
||||
//
|
||||
// Internal state - please do not reference directly
|
||||
//
|
||||
/** @var PKPRouter router instance used to route this request */
|
||||
public $_router = null;
|
||||
|
||||
/** @var Dispatcher dispatcher instance used to dispatch this request */
|
||||
public $_dispatcher = null;
|
||||
|
||||
/** @var array the request variables cache (GET/POST) */
|
||||
public $_requestVars = null;
|
||||
|
||||
/** @var string request base path */
|
||||
public $_basePath;
|
||||
|
||||
/** @var string request path */
|
||||
public $_requestPath;
|
||||
|
||||
/** @var bool true if restful URLs are enabled in the config */
|
||||
public $_isRestfulUrlsEnabled;
|
||||
|
||||
/** @var string server host */
|
||||
public $_serverHost;
|
||||
|
||||
/** @var string request protocol */
|
||||
public $_protocol;
|
||||
|
||||
/** @var bool bot flag */
|
||||
public $_isBot;
|
||||
|
||||
/** @var string user agent */
|
||||
public $_userAgent;
|
||||
|
||||
|
||||
/**
|
||||
* get the router instance
|
||||
*
|
||||
* @return PKPRouter
|
||||
*/
|
||||
public function &getRouter()
|
||||
{
|
||||
return $this->_router;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the router instance
|
||||
*
|
||||
* @param PKPRouter $router
|
||||
*/
|
||||
public function setRouter($router)
|
||||
{
|
||||
$this->_router = $router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the dispatcher
|
||||
*
|
||||
* @param Dispatcher $dispatcher
|
||||
*/
|
||||
public function setDispatcher($dispatcher)
|
||||
{
|
||||
$this->_dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dispatcher
|
||||
*
|
||||
* @return Dispatcher
|
||||
*/
|
||||
public function &getDispatcher()
|
||||
{
|
||||
if (! $this->_dispatcher) {
|
||||
$application = Application::get();
|
||||
|
||||
$this->setDispatcher($application->getDispatcher());
|
||||
}
|
||||
|
||||
return $this->_dispatcher;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform an HTTP redirect to an absolute or relative (to base system URL) URL.
|
||||
*
|
||||
* @param string $url (exclude protocol for local redirects)
|
||||
*/
|
||||
public function redirectUrl($url)
|
||||
{
|
||||
if (Hook::call('Request::redirect', [&$url])) {
|
||||
return;
|
||||
}
|
||||
|
||||
header("Location: {$url}");
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request an HTTP redirect via JSON to be used from components.
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return JSONMessage
|
||||
*/
|
||||
public function redirectUrlJson($url)
|
||||
{
|
||||
$json = new JSONMessage(true);
|
||||
$json->setEvent('redirectRequested', $url);
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the current URL, forcing the HTTPS protocol to be used.
|
||||
*/
|
||||
public function redirectSSL()
|
||||
{
|
||||
// Note that we are intentionally skipping PKP processing of REQUEST_URI and QUERY_STRING for a protocol redirect
|
||||
// This processing is deferred to the redirected (target) URI
|
||||
$url = 'https://' . $this->getServerHost() . $_SERVER['REQUEST_URI'];
|
||||
$queryString = $_SERVER['QUERY_STRING'];
|
||||
if (!empty($queryString)) {
|
||||
$url .= "?{$queryString}";
|
||||
}
|
||||
$this->redirectUrl($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the current URL, forcing the HTTP protocol to be used.
|
||||
*/
|
||||
public function redirectNonSSL()
|
||||
{
|
||||
// Note that we are intentionally skipping PKP processing of REQUEST_URI and QUERY_STRING for a protocol redirect
|
||||
// This processing is deferred to the redirected (target) URI
|
||||
$url = 'http://' . $this->getServerHost() . $_SERVER['REQUEST_URI'];
|
||||
$queryString = $_SERVER['QUERY_STRING'];
|
||||
if (!empty($queryString)) {
|
||||
$url .= "?{$queryString}";
|
||||
}
|
||||
$this->redirectUrl($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IF_MODIFIED_SINCE date (as a numerical timestamp) if available
|
||||
*
|
||||
* @return ?int
|
||||
*/
|
||||
public function getIfModifiedSince()
|
||||
{
|
||||
if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
||||
return null;
|
||||
}
|
||||
return strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base URL of the request (excluding script).
|
||||
*
|
||||
* @param bool $allowProtocolRelative True iff protocol-relative URLs are allowed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseUrl($allowProtocolRelative = false)
|
||||
{
|
||||
$serverHost = $this->getServerHost(false);
|
||||
if ($serverHost !== false) {
|
||||
// Auto-detection worked.
|
||||
if ($allowProtocolRelative) {
|
||||
$baseUrl = '//' . $this->getServerHost() . $this->getBasePath();
|
||||
} else {
|
||||
$baseUrl = $this->getProtocol() . '://' . $this->getServerHost() . $this->getBasePath();
|
||||
}
|
||||
} else {
|
||||
// Auto-detection didn't work (e.g. this is a command-line call); use configuration param
|
||||
$baseUrl = Config::getVar('general', 'base_url');
|
||||
}
|
||||
Hook::call('Request::getBaseUrl', [&$baseUrl]);
|
||||
return $baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base path of the request (excluding trailing slash).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBasePath()
|
||||
{
|
||||
if (!isset($this->_basePath)) {
|
||||
// Strip the PHP filename off of the script's executed path
|
||||
// We expect the SCRIPT_NAME to look like /path/to/file.php
|
||||
// If the SCRIPT_NAME ends in /, assume this is the directory and the script's actual name
|
||||
// is masked as the DirectoryIndex
|
||||
// If the SCRIPT_NAME ends in neither / or .php, assume the the script's actual name is masked
|
||||
// and we need to avoid stripping the terminal directory
|
||||
$path = preg_replace('#/[^/]*$#', '', $_SERVER['SCRIPT_NAME'] . (substr($_SERVER['SCRIPT_NAME'], -1) == '/' || preg_match('#.php$#i', $_SERVER['SCRIPT_NAME']) ? '' : '/'));
|
||||
|
||||
// Encode characters which need to be encoded in a URL.
|
||||
// Simply using rawurlencode() doesn't work because it
|
||||
// also encodes characters which are valid in a URL (i.e. @, $).
|
||||
$parts = explode('/', $path);
|
||||
foreach ($parts as $i => $part) {
|
||||
$pieces = array_map([$this, 'encodeBasePathFragment'], str_split($part));
|
||||
$parts[$i] = implode('', $pieces);
|
||||
}
|
||||
$this->_basePath = implode('/', $parts);
|
||||
|
||||
if ($this->_basePath == '/' || $this->_basePath == '\\') {
|
||||
$this->_basePath = '';
|
||||
}
|
||||
Hook::call('Request::getBasePath', [&$this->_basePath]);
|
||||
}
|
||||
|
||||
return $this->_basePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function for getBasePath() to correctly encode (or not encode)
|
||||
* a basepath fragment.
|
||||
*
|
||||
* @param string $fragment
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function encodeBasePathFragment($fragment)
|
||||
{
|
||||
if (!preg_match('/[A-Za-z0-9-._~!$&\'()*+,;=:@]/', $fragment)) {
|
||||
return rawurlencode($fragment);
|
||||
}
|
||||
return $fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated
|
||||
*
|
||||
* @see PKPPageRouter::getIndexUrl()
|
||||
*/
|
||||
public function getIndexUrl()
|
||||
{
|
||||
static $indexUrl;
|
||||
|
||||
if (!isset($indexUrl)) {
|
||||
$indexUrl = $this->_delegateToRouter('getIndexUrl');
|
||||
|
||||
// Call legacy hook
|
||||
Hook::call('Request::getIndexUrl', [&$indexUrl]);
|
||||
}
|
||||
|
||||
return $indexUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete URL to this page, including parameters.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCompleteUrl()
|
||||
{
|
||||
static $completeUrl;
|
||||
|
||||
if (!isset($completeUrl)) {
|
||||
$completeUrl = $this->getRequestUrl();
|
||||
$queryString = $this->getQueryString();
|
||||
if (!empty($queryString)) {
|
||||
$completeUrl .= "?{$queryString}";
|
||||
}
|
||||
Hook::call('Request::getCompleteUrl', [&$completeUrl]);
|
||||
}
|
||||
|
||||
return $completeUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete URL of the request.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRequestUrl()
|
||||
{
|
||||
static $requestUrl;
|
||||
|
||||
if (!isset($requestUrl)) {
|
||||
$requestUrl = $this->getProtocol() . '://' . $this->getServerHost() . $this->getRequestPath();
|
||||
Hook::call('Request::getRequestUrl', [&$requestUrl]);
|
||||
}
|
||||
|
||||
return $requestUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete set of URL parameters to the current request.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQueryString()
|
||||
{
|
||||
static $queryString;
|
||||
|
||||
if (!isset($queryString)) {
|
||||
$queryString = $_SERVER['QUERY_STRING'] ?? '';
|
||||
Hook::call('Request::getQueryString', [&$queryString]);
|
||||
}
|
||||
|
||||
return $queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete set of URL parameters to the current request as an
|
||||
* associative array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getQueryArray()
|
||||
{
|
||||
$queryString = $this->getQueryString();
|
||||
$queryArray = [];
|
||||
|
||||
if (isset($queryString)) {
|
||||
parse_str($queryString, $queryArray);
|
||||
}
|
||||
|
||||
return $queryArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the completed path of the request.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRequestPath()
|
||||
{
|
||||
if (!isset($this->_requestPath)) {
|
||||
if ($this->isRestfulUrlsEnabled()) {
|
||||
$this->_requestPath = $this->getBasePath();
|
||||
} else {
|
||||
$this->_requestPath = $_SERVER['SCRIPT_NAME'] ?? '';
|
||||
}
|
||||
|
||||
$this->_requestPath .= $_SERVER['PATH_INFO'] ?? '';
|
||||
Hook::call('Request::getRequestPath', [&$this->_requestPath]);
|
||||
}
|
||||
return $this->_requestPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server hostname in the request.
|
||||
*
|
||||
* @param string $default Default hostname (defaults to localhost)
|
||||
* @param bool $includePort Whether to include non-standard port number; default true
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getServerHost($default = null, $includePort = true)
|
||||
{
|
||||
if ($default === null) {
|
||||
$default = 'localhost';
|
||||
}
|
||||
|
||||
if (!isset($this->_serverHost)) {
|
||||
$this->_serverHost = $_SERVER['HTTP_X_FORWARDED_HOST']
|
||||
?? ($_SERVER['HTTP_HOST']
|
||||
?? ($_SERVER['SERVER_NAME']
|
||||
?? $default));
|
||||
// in case of multiple host entries in the header (e.g. multiple reverse proxies) take the first entry
|
||||
$this->_serverHost = strtok($this->_serverHost, ',');
|
||||
Hook::call('Request::getServerHost', [&$this->_serverHost, &$default, &$includePort]);
|
||||
}
|
||||
if (!$includePort) {
|
||||
// Strip the port number, if one is included. (#3912)
|
||||
return preg_replace("/:\d*$/", '', $this->_serverHost);
|
||||
}
|
||||
return $this->_serverHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the protocol used for the request (HTTP or HTTPS).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProtocol()
|
||||
{
|
||||
if (!isset($this->_protocol)) {
|
||||
$this->_protocol = (!isset($_SERVER['HTTPS']) || strtolower_codesafe($_SERVER['HTTPS']) != 'on') ? 'http' : 'https';
|
||||
Hook::call('Request::getProtocol', [&$this->_protocol]);
|
||||
}
|
||||
return $this->_protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request method
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRequestMethod()
|
||||
{
|
||||
return ($_SERVER['REQUEST_METHOD'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the request is a POST request
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPost()
|
||||
{
|
||||
return ($this->getRequestMethod() == 'POST');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the request is a GET request
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isGet()
|
||||
{
|
||||
return ($this->getRequestMethod() == 'GET');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a CSRF token is present and correct.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkCSRF()
|
||||
{
|
||||
$session = $this->getSession();
|
||||
return $this->getUserVar('csrfToken') == $session->getCSRFToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remote IP address of the current request.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteAddr()
|
||||
{
|
||||
$ipaddr = & Registry::get('remoteIpAddr'); // Reference required.
|
||||
if (is_null($ipaddr)) {
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) &&
|
||||
Config::getVar('general', 'trust_x_forwarded_for', true) &&
|
||||
preg_match_all('/([0-9.a-fA-F:]+)/', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
|
||||
} elseif (isset($_SERVER['REMOTE_ADDR']) &&
|
||||
preg_match_all('/([0-9.a-fA-F:]+)/', $_SERVER['REMOTE_ADDR'], $matches)) {
|
||||
} elseif (preg_match_all('/([0-9.a-fA-F:]+)/', getenv('REMOTE_ADDR'), $matches)) {
|
||||
} else {
|
||||
$ipaddr = '';
|
||||
}
|
||||
|
||||
if (!isset($ipaddr)) {
|
||||
// If multiple addresses are listed, take the last. (Supports ipv6.)
|
||||
$ipaddr = $matches[0][count($matches[0]) - 1];
|
||||
}
|
||||
Hook::call('Request::getRemoteAddr', [&$ipaddr]);
|
||||
}
|
||||
return $ipaddr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remote domain of the current request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteDomain()
|
||||
{
|
||||
static $remoteDomain;
|
||||
if (!isset($remoteDomain)) {
|
||||
$remoteDomain = null;
|
||||
$remoteDomain = @getHostByAddr($this->getRemoteAddr());
|
||||
Hook::call('Request::getRemoteDomain', [&$remoteDomain]);
|
||||
}
|
||||
return $remoteDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user agent of the current request.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserAgent()
|
||||
{
|
||||
if (!isset($this->_userAgent)) {
|
||||
if (isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||
$this->_userAgent = $_SERVER['HTTP_USER_AGENT'];
|
||||
}
|
||||
if (!isset($this->_userAgent) || empty($this->_userAgent)) {
|
||||
$this->_userAgent = getenv('HTTP_USER_AGENT');
|
||||
}
|
||||
if (!isset($this->_userAgent) || $this->_userAgent == false) {
|
||||
$this->_userAgent = '';
|
||||
}
|
||||
Hook::call('Request::getUserAgent', [&$this->_userAgent]);
|
||||
}
|
||||
return $this->_userAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user agent is a bot or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isBot()
|
||||
{
|
||||
if (!isset($this->_isBot)) {
|
||||
$userAgent = $this->getUserAgent();
|
||||
$this->_isBot = Core::isUserAgentBot($userAgent);
|
||||
}
|
||||
return $this->_isBot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the HTTP_DNT (Do Not Track) is set
|
||||
*/
|
||||
public function getDoNotTrack(): bool
|
||||
{
|
||||
return (array_key_exists('HTTP_DNT', $_SERVER) && ((int) $_SERVER['HTTP_DNT'] === 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if RESTFUL_URLS is enabled.
|
||||
*/
|
||||
public function isRestfulUrlsEnabled()
|
||||
{
|
||||
if (!isset($this->_isRestfulUrlsEnabled)) {
|
||||
$this->_isRestfulUrlsEnabled = Config::getVar('general', 'restful_urls') ? true : false;
|
||||
}
|
||||
return $this->_isRestfulUrlsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get site data.
|
||||
*
|
||||
*/
|
||||
public function getSite(): ?Site
|
||||
{
|
||||
$site = & Registry::get('site', true, null);
|
||||
/** @var SiteDAO */
|
||||
$siteDao = DAORegistry::getDAO('SiteDAO');
|
||||
return $site ??= $siteDao->getSite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user session associated with the current request.
|
||||
*/
|
||||
public function getSession(): Session
|
||||
{
|
||||
$session = & Registry::get('session', true, null);
|
||||
return $session ??= SessionManager::getManager()->getUserSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user associated with the current request.
|
||||
*/
|
||||
public function getUser(): ?User
|
||||
{
|
||||
$user = & Registry::get('user', true, null);
|
||||
if ($user) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
// Attempt to load user from API token
|
||||
if (($handler = $this->getRouter()->getHandler())
|
||||
&& ($token = $handler->getApiToken())
|
||||
&& ($apiUser = Repo::user()->getByApiKey($token))
|
||||
&& $apiUser->getData('apiKeyEnabled')
|
||||
) {
|
||||
return $user = $apiUser;
|
||||
}
|
||||
|
||||
// Attempts to retrieve a logged user
|
||||
if (Validation::isLoggedIn()) {
|
||||
$user = SessionManager::getManager()->getUserSession()->getUser();
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a GET/POST variable.
|
||||
*/
|
||||
public function getUserVar($key)
|
||||
{
|
||||
// special treatment for APIRouter. APIHandler gets to fetch parameter first
|
||||
$router = $this->getRouter();
|
||||
if ($router instanceof \PKP\core\APIRouter && (!is_null($handler = $router->getHandler()))) {
|
||||
/** @var APIHandler */
|
||||
$handler = $router->getHandler();
|
||||
$value = $handler->getParameter($key);
|
||||
if (!is_null($value)) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Get all vars (already cleaned)
|
||||
$vars = $this->getUserVars();
|
||||
return $vars[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all GET/POST variables as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function &getUserVars()
|
||||
{
|
||||
$this->_requestVars ??= array_map(fn ($s) => is_string($s) ? trim($s) : $s, array_merge($_GET, $_POST));
|
||||
return $this->_requestVars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a GET/POST variable generated using the Smarty
|
||||
* html_select_date and/or html_select_time function.
|
||||
*
|
||||
* @param string $prefix
|
||||
* @param int $defaultDay
|
||||
* @param int $defaultMonth
|
||||
* @param int $defaultYear
|
||||
* @param int $defaultHour
|
||||
* @param int $defaultMinute
|
||||
* @param int $defaultSecond
|
||||
*
|
||||
* @return ?int Linux timestamp
|
||||
*/
|
||||
public function getUserDateVar($prefix, $defaultDay = null, $defaultMonth = null, $defaultYear = null, $defaultHour = 0, $defaultMinute = 0, $defaultSecond = 0)
|
||||
{
|
||||
$monthPart = $this->getUserVar($prefix . 'Month');
|
||||
$dayPart = $this->getUserVar($prefix . 'Day');
|
||||
$yearPart = $this->getUserVar($prefix . 'Year');
|
||||
$hourPart = $this->getUserVar($prefix . 'Hour');
|
||||
$minutePart = $this->getUserVar($prefix . 'Minute');
|
||||
$secondPart = $this->getUserVar($prefix . 'Second');
|
||||
|
||||
switch ($this->getUserVar($prefix . 'Meridian')) {
|
||||
case 'pm':
|
||||
if (is_numeric($hourPart) && $hourPart != 12) {
|
||||
$hourPart += 12;
|
||||
}
|
||||
break;
|
||||
case 'am':
|
||||
default:
|
||||
// Do nothing.
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($dayPart)) {
|
||||
$dayPart = $defaultDay;
|
||||
}
|
||||
if (empty($monthPart)) {
|
||||
$monthPart = $defaultMonth;
|
||||
}
|
||||
if (empty($yearPart)) {
|
||||
$yearPart = $defaultYear;
|
||||
}
|
||||
if (empty($hourPart)) {
|
||||
$hourPart = $defaultHour;
|
||||
}
|
||||
if (empty($minutePart)) {
|
||||
$minutePart = $defaultMinute;
|
||||
}
|
||||
if (empty($secondPart)) {
|
||||
$secondPart = $defaultSecond;
|
||||
}
|
||||
|
||||
if (empty($monthPart) || empty($dayPart) || empty($yearPart)) {
|
||||
return null;
|
||||
}
|
||||
return mktime($hourPart, $minutePart, $secondPart, $monthPart, $dayPart, $yearPart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a cookie variable.
|
||||
*/
|
||||
public function getCookieVar($key)
|
||||
{
|
||||
if (isset($_COOKIE[$key])) {
|
||||
return $_COOKIE[$key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie variable.
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $expire (optional)
|
||||
*/
|
||||
public function setCookieVar($key, $value, $expire = 0)
|
||||
{
|
||||
$basePath = $this->getBasePath();
|
||||
if (!$basePath) {
|
||||
$basePath = '/';
|
||||
}
|
||||
|
||||
setcookie($key, $value, $expire, $basePath);
|
||||
$_COOKIE[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the specified page within a PKP Application.
|
||||
* Shorthand for a common call to $request->redirect($dispatcher->url($request, PKPApplication::ROUTE_PAGE, ...)).
|
||||
*
|
||||
* @param mixed $context The optional contextual paths
|
||||
* @param string $page The name of the op to redirect to.
|
||||
* @param string $op optional The name of the op to redirect to.
|
||||
* @param mixed $path string or array containing path info for redirect.
|
||||
* @param array $params Map of name => value pairs for additional parameters
|
||||
* @param string $anchor Name of desired anchor on the target page
|
||||
*/
|
||||
public function redirect($context = null, $page = null, $op = null, $path = null, $params = null, $anchor = null)
|
||||
{
|
||||
$dispatcher = $this->getDispatcher();
|
||||
$this->redirectUrl($dispatcher->url($this, PKPApplication::ROUTE_PAGE, $context, $page, $op, $path, $params, $anchor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current "context" (press/journal/etc) object.
|
||||
*
|
||||
* @see PKPPageRouter::getContext()
|
||||
*/
|
||||
public function getContext(): ?Context
|
||||
{
|
||||
return $this->_delegateToRouter('getContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated
|
||||
*
|
||||
* @see PKPPageRouter::getRequestedPage()
|
||||
*/
|
||||
public function getRequestedPage()
|
||||
{
|
||||
return $this->_delegateToRouter('getRequestedPage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated
|
||||
*
|
||||
* @see PKPPageRouter::getRequestedOp()
|
||||
*/
|
||||
public function getRequestedOp()
|
||||
{
|
||||
return $this->_delegateToRouter('getRequestedOp');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated
|
||||
*
|
||||
* @see PKPPageRouter::getRequestedArgs()
|
||||
*/
|
||||
public function getRequestedArgs()
|
||||
{
|
||||
return $this->_delegateToRouter('getRequestedArgs');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated
|
||||
*
|
||||
* @see PKPPageRouter::url()
|
||||
*
|
||||
* @param null|mixed $context
|
||||
* @param null|mixed $page
|
||||
* @param null|mixed $op
|
||||
* @param null|mixed $path
|
||||
* @param null|mixed $params
|
||||
* @param null|mixed $anchor
|
||||
*/
|
||||
public function url(
|
||||
$context = null,
|
||||
$page = null,
|
||||
$op = null,
|
||||
$path = null,
|
||||
$params = null,
|
||||
$anchor = null,
|
||||
$escape = false
|
||||
) {
|
||||
return $this->_delegateToRouter(
|
||||
'url',
|
||||
$context,
|
||||
$page,
|
||||
$op,
|
||||
$path,
|
||||
$params,
|
||||
$anchor,
|
||||
$escape
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to the public file uploads directory
|
||||
*/
|
||||
public function getPublicFilesUrl(?Context $context = null): string
|
||||
{
|
||||
$publicFileManager = new PublicFileManager();
|
||||
|
||||
return join('/', [
|
||||
$this->getBaseUrl(),
|
||||
$context
|
||||
? $publicFileManager->getContextFilesPath($context->getId())
|
||||
: $publicFileManager->getSiteFilesPath()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method exists to maintain backwards compatibility
|
||||
* with calls to methods that have been factored into the
|
||||
* Router implementations.
|
||||
*
|
||||
* It delegates the call to the router and returns the result.
|
||||
*
|
||||
* NB: This method is protected and may not be used by
|
||||
* external classes. It should also only be used in legacy
|
||||
* methods.
|
||||
*
|
||||
* @return mixed depends on the called method
|
||||
*/
|
||||
public function &_delegateToRouter($method)
|
||||
{
|
||||
// This call is deprecated. We don't trigger a
|
||||
// deprecation error, though, as there are so
|
||||
// many instances of this error that it has a
|
||||
// performance impact and renders the error
|
||||
// log virtually useless when deprecation
|
||||
// warnings are switched on.
|
||||
// FIXME: Fix enough instances of this error so that
|
||||
// we can put a deprecation warning in here.
|
||||
$router = $this->getRouter();
|
||||
|
||||
if (is_null($router)) {
|
||||
assert(false);
|
||||
$nullValue = null;
|
||||
return $nullValue;
|
||||
}
|
||||
|
||||
// Construct the method call
|
||||
$callable = [$router, $method];
|
||||
|
||||
// Get additional parameters but replace
|
||||
// the first parameter (currently the
|
||||
// method to be called) with the request
|
||||
// as all router methods required the request
|
||||
// as their first parameter.
|
||||
$parameters = func_get_args();
|
||||
$parameters[0] = & $this;
|
||||
|
||||
$returner = call_user_func_array($callable, $parameters);
|
||||
return $returner;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\PKPRequest', '\PKPRequest');
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/PKPRouter.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 PKPRouter
|
||||
*
|
||||
* @see PKPPageRouter
|
||||
* @see PKPComponentRouter
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Basic router class that has functionality common to all routers.
|
||||
*
|
||||
* NB: All handlers provide the common basic workflow. The router
|
||||
* calls the following methods in the given order.
|
||||
* 1) constructor:
|
||||
* Handlers should establish a mapping of remote
|
||||
* operations to roles that may access them. They do
|
||||
* so by calling PKPHandler::addRoleAssignment().
|
||||
* 2) authorize():
|
||||
* Authorizes the request, among other things based
|
||||
* on the result of the role assignment created
|
||||
* during object instantiation. If authorization fails
|
||||
* then die with a fatal error or execute the "call-
|
||||
* on-deny" advice if one has been defined in the
|
||||
* authorization policy that denied access.
|
||||
* 3) validate():
|
||||
* Let the handler execute non-fatal data integrity
|
||||
* checks (FIXME: currently only for component handlers).
|
||||
* Please make sure that data integrity checks that can
|
||||
* lead to denial of access are being executed in the
|
||||
* authorize() step via authorization policies and not
|
||||
* here.
|
||||
* 4) initialize():
|
||||
* Let the handler initialize its internal state based
|
||||
* on authorized and valid data. Authorization and integrity
|
||||
* checks should be kept out of here to get a clear separation
|
||||
* of concerns.
|
||||
* 5) execution:
|
||||
* Executes the requested handler operation. The mapping
|
||||
* of requests to operations depends on the router
|
||||
* implementation (see the class doc of specific router
|
||||
* implementations for more details).
|
||||
* 6) client response:
|
||||
* Handlers should return a string value that will then be
|
||||
* returned to the client as a response. Handler operations
|
||||
* should not output the response directly to the client so
|
||||
* that we can run filter operations on the output if required.
|
||||
* Outputting text from handler operations to the client
|
||||
* is possible but deprecated.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use APP\core\Application;
|
||||
use Exception;
|
||||
use PKP\config\Config;
|
||||
use PKP\context\Context;
|
||||
use PKP\context\ContextDAO;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\handler\PKPHandler;
|
||||
use PKP\plugins\Hook;
|
||||
|
||||
abstract class PKPRouter
|
||||
{
|
||||
//
|
||||
// Internal state cache variables
|
||||
// NB: Please do not access directly but
|
||||
// only via their respective getters/setters
|
||||
//
|
||||
protected Application $_application;
|
||||
protected Dispatcher $_dispatcher;
|
||||
protected ?string $_contextPath = null;
|
||||
public ?Context $_context = null;
|
||||
public ?PKPHandler $_handler = null;
|
||||
/** @var string */
|
||||
public $_indexUrl;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* get the application
|
||||
*/
|
||||
public function getApplication(): Application
|
||||
{
|
||||
return $this->_application;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the application
|
||||
*/
|
||||
public function setApplication(Application $application)
|
||||
{
|
||||
$this->_application = $application;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the dispatcher
|
||||
*/
|
||||
public function getDispatcher(): \PKP\core\Dispatcher
|
||||
{
|
||||
return $this->_dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the dispatcher
|
||||
*/
|
||||
public function setDispatcher(\PKP\core\Dispatcher $dispatcher)
|
||||
{
|
||||
$this->_dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the handler object for later retrieval.
|
||||
*/
|
||||
public function setHandler(PKPHandler $handler)
|
||||
{
|
||||
$this->_handler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the handler object.
|
||||
*/
|
||||
public function getHandler(): ?PKPHandler
|
||||
{
|
||||
return $this->_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this router can route the given request.
|
||||
*/
|
||||
public function supports(PKPRequest $request): bool
|
||||
{
|
||||
// Default implementation returns always true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not this request is cacheable
|
||||
*/
|
||||
public function isCacheable(PKPRequest $request): bool
|
||||
{
|
||||
// Default implementation returns always false
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic method to return a context path (e.g. a Press or a Journal path)
|
||||
*/
|
||||
public function getRequestedContextPath(PKPRequest $request): string
|
||||
{
|
||||
// Determine the context path
|
||||
if ($this->_contextPath === null) {
|
||||
$this->_contextPath = Core::getContextPath($_SERVER['PATH_INFO'] ?? '');
|
||||
|
||||
Hook::call('Router::getRequestedContextPath', [&$this->_contextPath]);
|
||||
}
|
||||
return $this->_contextPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Generic call to a context defining object (e.g. a Journal, Press, or Server)
|
||||
*
|
||||
* @param PKPRequest $request the request to be routed
|
||||
* @param bool $forceReload (optional) Reset a context even if it's already been loaded
|
||||
*
|
||||
* @return Context
|
||||
*/
|
||||
public function getContext($request, $forceReload = false)
|
||||
{
|
||||
if ($forceReload || !isset($this->_context)) {
|
||||
// Retrieve the requested context path (this validates the path)
|
||||
$path = $this->getRequestedContextPath($request);
|
||||
|
||||
// Resolve the path to the context
|
||||
if ($path === 'index' || $path === '' || $path === Application::CONTEXT_ID_ALL) {
|
||||
$this->_context = null;
|
||||
} else {
|
||||
// FIXME: Can't just use Application::get()->getContextDAO() without test breakage
|
||||
/** @var ContextDAO */
|
||||
$contextDao = DAORegistry::getDAO(ucfirst(Application::get()->getContextName()) . 'DAO');
|
||||
|
||||
// Retrieve the context from the DAO (by path)
|
||||
$this->_context = $contextDao->getByPath($path);
|
||||
|
||||
// If the context couldn't be retrieved, it's a 404 error.
|
||||
if (!$this->_context) {
|
||||
$this->getDispatcher()?->handle404();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to the index script.
|
||||
*
|
||||
* @param PKPRequest $request the request to be routed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIndexUrl($request)
|
||||
{
|
||||
if (!isset($this->_indexUrl)) {
|
||||
if ($request->isRestfulUrlsEnabled()) {
|
||||
$this->_indexUrl = $request->getBaseUrl();
|
||||
} else {
|
||||
$this->_indexUrl = $request->getBaseUrl() . '/index.php';
|
||||
}
|
||||
Hook::call('Router::getIndexUrl', [&$this->_indexUrl]);
|
||||
}
|
||||
|
||||
return $this->_indexUrl;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Protected template methods to be implemented by sub-classes.
|
||||
//
|
||||
/**
|
||||
* Determine the filename to use for a local cache file.
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheFilename($request)
|
||||
{
|
||||
throw new Exception('Unimplemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes a given request to a handler operation
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
*/
|
||||
abstract public function route($request);
|
||||
|
||||
/**
|
||||
* Build a handler request URL into PKPApplication.
|
||||
*
|
||||
* @param PKPRequest $request the request to be routed
|
||||
* @param mixed $newContext Optional contextual paths
|
||||
* @param string $handler Optional name of the handler to invoke
|
||||
* @param string $op Optional name of operation to invoke
|
||||
* @param mixed $path Optional string or array of args to pass to handler
|
||||
* @param array $params Optional set of name => value pairs to pass as user parameters
|
||||
* @param string $anchor Optional name of anchor to add to URL
|
||||
* @param bool $escape Whether or not to escape ampersands, square brackets, etc. for this URL; default false.
|
||||
*
|
||||
* @return string the URL
|
||||
*/
|
||||
abstract public function url(
|
||||
PKPRequest $request,
|
||||
?string $newContext = null,
|
||||
$handler = null,
|
||||
$op = null,
|
||||
$path = null,
|
||||
$params = null,
|
||||
$anchor = null,
|
||||
$escape = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Handle an authorization failure.
|
||||
*
|
||||
* @param PKPRequest $request
|
||||
* @param string $authorizationMessage a translation key with the authorization
|
||||
* failure message.
|
||||
*/
|
||||
abstract public function handleAuthorizationFailure(
|
||||
$request,
|
||||
$authorizationMessage,
|
||||
array $messageParams = []
|
||||
);
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods
|
||||
//
|
||||
/**
|
||||
* This is the method that implements the basic
|
||||
* life-cycle of a handler request:
|
||||
* 1) authorization
|
||||
* 2) validation
|
||||
* 3) initialization
|
||||
* 4) execution
|
||||
* 5) client response
|
||||
*
|
||||
* @param callable|array $serviceEndpoint the handler operation
|
||||
* @param PKPRequest $request
|
||||
* @param array $args
|
||||
* @param bool $validate whether or not to execute the
|
||||
* validation step.
|
||||
*/
|
||||
public function _authorizeInitializeAndCallRequest(&$serviceEndpoint, $request, &$args, $validate = true)
|
||||
{
|
||||
$dispatcher = $this->getDispatcher();
|
||||
|
||||
// It's conceivable that a call has gotten this far without
|
||||
// actually being callable, e.g. a component has been named
|
||||
// that does not exist and that no plugin has registered.
|
||||
if (!is_callable($serviceEndpoint)) {
|
||||
$dispatcher->handle404();
|
||||
}
|
||||
|
||||
// Pass the dispatcher to the handler.
|
||||
$serviceEndpoint[0]->setDispatcher($dispatcher);
|
||||
|
||||
// Authorize the request.
|
||||
$roleAssignments = $serviceEndpoint[0]->getRoleAssignments();
|
||||
assert(is_array($roleAssignments));
|
||||
if ($serviceEndpoint[0]->authorize($request, $args, $roleAssignments)) {
|
||||
// Execute class-wide data integrity checks.
|
||||
if ($validate) {
|
||||
$serviceEndpoint[0]->validate($request, $args);
|
||||
}
|
||||
|
||||
// Let the handler initialize itself.
|
||||
$serviceEndpoint[0]->initialize($request, $args);
|
||||
|
||||
// Call the service endpoint.
|
||||
$result = call_user_func($serviceEndpoint, $args, $request);
|
||||
} else {
|
||||
// Authorization failed - try to retrieve a user
|
||||
// message.
|
||||
$authorizationMessage = $serviceEndpoint[0]->getLastAuthorizationMessage();
|
||||
|
||||
// Set a generic authorization message if no
|
||||
// specific authorization message was set.
|
||||
if ($authorizationMessage == '') {
|
||||
$authorizationMessage = 'user.authorization.accessDenied';
|
||||
}
|
||||
|
||||
// Handle the authorization failure.
|
||||
$result = $this->handleAuthorizationFailure($request, $authorizationMessage);
|
||||
}
|
||||
|
||||
// Return the result of the operation to the client.
|
||||
if (is_string($result)) {
|
||||
echo $result;
|
||||
} elseif ($result instanceof \PKP\core\JSONMessage) {
|
||||
header('Content-Type: application/json');
|
||||
echo $result->getString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the base URL and add the context part of the URL.
|
||||
*
|
||||
* The new URL will be based on the current request's context
|
||||
* if no new context is given.
|
||||
*
|
||||
* The base URL for a given primary context can be overridden
|
||||
* in the config file using the 'base_url[context]' syntax in the
|
||||
* config file's 'general' section.
|
||||
*
|
||||
* @param PKPRequest $request the request to be routed
|
||||
* @param mixed $newContext (optional) context that differs from
|
||||
* the current request's context
|
||||
*
|
||||
* @return array An array consisting of the base url as the first
|
||||
* entry and the context as the remaining entries.
|
||||
*/
|
||||
public function _urlGetBaseAndContext($request, ?string $newContext = null)
|
||||
{
|
||||
if (isset($newContext)) {
|
||||
// A new context has been set so use it.
|
||||
$contextValue = rawurlencode($newContext);
|
||||
} else {
|
||||
// No new context has been set so determine
|
||||
// the current request's context
|
||||
$contextObject = $this->getContext($request);
|
||||
$contextValue = $contextObject?->getPath() ?? 'index';
|
||||
}
|
||||
|
||||
// Check whether the base URL is overridden.
|
||||
if ($overriddenBaseUrl = Config::getVar('general', "base_url[{$contextValue}]")) {
|
||||
return [$overriddenBaseUrl, []];
|
||||
}
|
||||
return [$this->getIndexUrl($request), [$contextValue]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the additional parameters part of the URL.
|
||||
*
|
||||
* @param PKPRequest $request the request to be routed
|
||||
* @param array $params (optional) the parameter list to be
|
||||
* transformed to a url part.
|
||||
* @param bool $escape (optional) Whether or not to escape structural elements
|
||||
*
|
||||
* @return array the encoded parameters or an empty array
|
||||
* if no parameters were given.
|
||||
*/
|
||||
public function _urlGetAdditionalParameters($request, $params = null, $escape = true)
|
||||
{
|
||||
$additionalParameters = [];
|
||||
if (!empty($params)) {
|
||||
assert(is_array($params));
|
||||
foreach ($params as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $element) {
|
||||
$additionalParameters[] = $key . ($escape ? '%5B%5D=' : '[]=') . rawurlencode($element);
|
||||
}
|
||||
} else {
|
||||
$additionalParameters[] = $key . '=' . rawurlencode($value ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $additionalParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid URL from parts.
|
||||
*
|
||||
* @param string $baseUrl the protocol, domain and initial path/parameters, no anchors allowed here
|
||||
* @param array $pathInfoArray strings to be concatenated as path info
|
||||
* @param array $queryParametersArray strings to be concatenated as query string
|
||||
* @param ?string $anchor an additional anchor
|
||||
* @param bool $escape whether to escape ampersands
|
||||
*
|
||||
* @return string the URL
|
||||
*/
|
||||
public function _urlFromParts(string $baseUrl, array $pathInfoArray = [], array $queryParametersArray = [], ?string $anchor = '', bool $escape = false)
|
||||
{
|
||||
// Parse the base url
|
||||
$baseUrlParts = parse_url($baseUrl);
|
||||
assert(isset($baseUrlParts['host']) && !isset($baseUrlParts['fragment']));
|
||||
|
||||
// Reconstruct the base url without path and query
|
||||
$baseUrl = (isset($baseUrlParts['scheme']) ? $baseUrlParts['scheme'] . ':' : '') . '//';
|
||||
if (isset($baseUrlParts['user'])) {
|
||||
$baseUrl .= $baseUrlParts['user'];
|
||||
if (isset($baseUrlParts['pass'])) {
|
||||
$baseUrl .= ':' . $baseUrlParts['pass'];
|
||||
}
|
||||
$baseUrl .= '@';
|
||||
}
|
||||
$baseUrl .= $baseUrlParts['host'];
|
||||
if (isset($baseUrlParts['port'])) {
|
||||
$baseUrl .= ':' . $baseUrlParts['port'];
|
||||
}
|
||||
$baseUrl .= '/';
|
||||
|
||||
// Add path info from the base URL to the path info array (if any).
|
||||
if (isset($baseUrlParts['path'])) {
|
||||
$pathInfoArray = array_merge(explode('/', trim($baseUrlParts['path'], '/')), $pathInfoArray);
|
||||
}
|
||||
|
||||
// Add query parameters from the base URL to the query parameter array (if any).
|
||||
if (isset($baseUrlParts['query'])) {
|
||||
$queryParametersArray = array_merge(explode('&', $baseUrlParts['query']), $queryParametersArray);
|
||||
}
|
||||
|
||||
// Expand path info
|
||||
$pathInfo = implode('/', $pathInfoArray);
|
||||
|
||||
// Expand query parameters
|
||||
$amp = $escape ? '&' : '&';
|
||||
$queryParameters = implode($amp, $queryParametersArray);
|
||||
$queryParameters = empty($queryParameters) ? '' : '?' . $queryParameters;
|
||||
|
||||
// Assemble and return the final URL
|
||||
return $baseUrl . $pathInfo . $queryParameters . $anchor;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\PKPRouter', '\PKPRouter');
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/PKPServices.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 PKPServices
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @see Core
|
||||
*
|
||||
* @brief Pimple Dependency Injection Container.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use APP\core\Services;
|
||||
use Pimple\Container;
|
||||
|
||||
abstract class PKPServices
|
||||
{
|
||||
/** @var Container Pimple Dependency Injection Container */
|
||||
private static $instance = null;
|
||||
|
||||
protected $container = null;
|
||||
|
||||
/**
|
||||
* private constructor
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
$this->container = new Container();
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* container initialization
|
||||
*/
|
||||
abstract protected function init();
|
||||
|
||||
/**
|
||||
* A static method to register a service
|
||||
*/
|
||||
public static function register(\Pimple\ServiceProviderInterface $service)
|
||||
{
|
||||
self::_instance()->container->register($service);
|
||||
}
|
||||
|
||||
/**
|
||||
* A static method to get a service
|
||||
*
|
||||
* @param string $service
|
||||
*/
|
||||
public static function get($service)
|
||||
{
|
||||
return self::_instance()->_getFromContainer($service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the instance of the container
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
private static function _instance()
|
||||
{
|
||||
if (is_null(self::$instance)) {
|
||||
self::$instance = new Services();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the service from an instanced container.
|
||||
*
|
||||
* @param string $service
|
||||
*/
|
||||
private function _getFromContainer($service)
|
||||
{
|
||||
return $this->container[$service];
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\PKPServices', '\PKPServices');
|
||||
}
|
||||
@@ -0,0 +1,599 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/PKPString.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 PKPString
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief String manipulation wrapper class.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use HTMLPurifier;
|
||||
use HTMLPurifier_Config;
|
||||
use PKP\config\Config;
|
||||
use Stringy\Stringy;
|
||||
|
||||
class PKPString
|
||||
{
|
||||
/** @var int Camel case for class names */
|
||||
public const CAMEL_CASE_HEAD_UP = 1;
|
||||
|
||||
/** @var int Camel case for method names */
|
||||
public const CAMEL_CASE_HEAD_DOWN = 2;
|
||||
|
||||
/**
|
||||
* Perform initialization required for the string wrapper library.
|
||||
*/
|
||||
public static function initialize()
|
||||
{
|
||||
static $isInitialized;
|
||||
if (!$isInitialized) {
|
||||
if (self::hasMBString()) {
|
||||
// Set up default encoding
|
||||
mb_internal_encoding('utf-8');
|
||||
ini_set('default_charset', 'utf-8');
|
||||
}
|
||||
$isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if server has the mbstring library.
|
||||
*
|
||||
* @return bool Returns true iff the server supports mbstring functions.
|
||||
*/
|
||||
public static function hasMBString()
|
||||
{
|
||||
static $hasMBString;
|
||||
if (isset($hasMBString)) {
|
||||
return $hasMBString;
|
||||
}
|
||||
|
||||
// If string overloading is active, it will break many of the
|
||||
// native implementations. mbstring.func_overload must be set
|
||||
// to 0, 1 or 4 in php.ini (string overloading disabled).
|
||||
// Note: Overloading has been deprecated on PHP 7.2
|
||||
if (ini_get('mbstring.func_overload') && defined('MB_OVERLOAD_STRING')) {
|
||||
$hasMBString = false;
|
||||
} else {
|
||||
$hasMBString = extension_loaded('mbstring') &&
|
||||
function_exists('mb_strlen') &&
|
||||
function_exists('mb_strpos') &&
|
||||
function_exists('mb_strrpos') &&
|
||||
function_exists('mb_substr') &&
|
||||
function_exists('mb_strtolower') &&
|
||||
function_exists('mb_strtoupper') &&
|
||||
function_exists('mb_substr_count') &&
|
||||
function_exists('mb_send_mail');
|
||||
}
|
||||
return $hasMBString;
|
||||
}
|
||||
|
||||
//
|
||||
// Wrappers for basic string manipulation routines.
|
||||
//
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/strlen
|
||||
*
|
||||
* @param string $string Input string
|
||||
*
|
||||
* @return int String length
|
||||
*/
|
||||
public static function strlen($string)
|
||||
{
|
||||
return Stringy::create($string)->length();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/strpos
|
||||
*
|
||||
* @param string $haystack Input haystack to search
|
||||
* @param string $needle Input needle to search for
|
||||
* @param int $offset Offset at which to begin searching
|
||||
*
|
||||
* @return int Position of needle within haystack
|
||||
*/
|
||||
public static function strpos($haystack, $needle, $offset = 0)
|
||||
{
|
||||
return Stringy::create($haystack)->indexOf($needle, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/strrpos
|
||||
*
|
||||
* @param string $haystack Haystack to search
|
||||
* @param string $needle Needle to search haystack for
|
||||
*
|
||||
* @return int Last index of Needle in Haystack
|
||||
*/
|
||||
public static function strrpos($haystack, $needle)
|
||||
{
|
||||
return Stringy::create($haystack)->indexOfLast($needle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/substr
|
||||
*
|
||||
* @param string $string Subject to extract substring from
|
||||
* @param int $start Position to start from
|
||||
* @param int $length Length to extract, or false for entire string from start position
|
||||
*
|
||||
* @return string Substring of $string
|
||||
*/
|
||||
public static function substr($string, $start, $length = null)
|
||||
{
|
||||
return (string) Stringy::create($string)->substr($start, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/strtolower
|
||||
*
|
||||
* @param string $string Input string
|
||||
*
|
||||
* @return string Lower case version of input string
|
||||
*/
|
||||
public static function strtolower($string)
|
||||
{
|
||||
return (string) Stringy::create($string)->toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/strtoupper
|
||||
*
|
||||
* @param string $string Input string
|
||||
*
|
||||
* @return string Upper case version of input string
|
||||
*/
|
||||
public static function strtoupper($string)
|
||||
{
|
||||
return (string) Stringy::create($string)->toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/ucfirst
|
||||
*
|
||||
* @param string $string Input string
|
||||
*
|
||||
* @return string ucfirst version of input string
|
||||
*/
|
||||
public static function ucfirst($string)
|
||||
{
|
||||
return (string) Stringy::create($string)->upperCaseFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/substr_count
|
||||
*
|
||||
* @param string $haystack Input string to search
|
||||
* @param string $needle String to search within $haystack for
|
||||
*
|
||||
* @return int Count of number of times $needle appeared in $haystack
|
||||
*/
|
||||
public static function substr_count($haystack, $needle)
|
||||
{
|
||||
return Stringy::create($haystack)->countSubstr($needle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/encode_mime_header
|
||||
*
|
||||
* @param string $string Input MIME header to encode.
|
||||
*
|
||||
* @return string Encoded MIME header.
|
||||
*/
|
||||
public static function encode_mime_header($string)
|
||||
{
|
||||
static::initialize();
|
||||
return static::hasMBString()
|
||||
? mb_encode_mimeheader($string, mb_internal_encoding(), 'B', Core::isWindows() ? "\r\n" : "\n")
|
||||
: $string;
|
||||
}
|
||||
|
||||
//
|
||||
// Wrappers for PCRE-compatible regular expression routines.
|
||||
// See the php.net documentation for usage.
|
||||
//
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/preg_quote
|
||||
*
|
||||
* @param string $string String to quote
|
||||
* @param string $delimiter Delimiter for regular expression
|
||||
*
|
||||
* @return string Quoted equivalent of $string
|
||||
*/
|
||||
public static function regexp_quote($string, $delimiter = '/')
|
||||
{
|
||||
return preg_quote($string, $delimiter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/preg_grep
|
||||
*
|
||||
* @param string $pattern Regular expression
|
||||
* @param array $input Input
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function regexp_grep($pattern, $input)
|
||||
{
|
||||
return preg_grep($pattern . 'u', $input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/preg_match
|
||||
*
|
||||
* @param string $pattern Regular expression
|
||||
* @param string $subject String to apply regular expression to
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function regexp_match($pattern, $subject)
|
||||
{
|
||||
return preg_match($pattern . 'u', $subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/preg_match_get
|
||||
*
|
||||
* @param string $pattern Regular expression
|
||||
* @param string $subject String to apply regular expression to
|
||||
* @param array $matches Reference to receive matches
|
||||
*
|
||||
* @return int|boolean Returns 1 if the pattern matches given subject, 0 if it does not, or FALSE if an error occurred.
|
||||
*/
|
||||
public static function regexp_match_get($pattern, $subject, &$matches)
|
||||
{
|
||||
return preg_match($pattern . 'u', $subject, $matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/preg_match_all
|
||||
*
|
||||
* @param string $pattern Regular expression
|
||||
* @param string $subject String to apply regular expression to
|
||||
* @param array $matches Reference to receive matches
|
||||
*
|
||||
* @return int|boolean Returns number of full matches of given subject, or FALSE if an error occurred.
|
||||
*/
|
||||
public static function regexp_match_all($pattern, $subject, &$matches)
|
||||
{
|
||||
return preg_match_all($pattern . 'u', $subject, $matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/preg_replace
|
||||
*
|
||||
* @param string $pattern Regular expression
|
||||
* @param string $replacement String to replace matches in $subject with
|
||||
* @param string $subject String to apply regular expression to
|
||||
* @param int $limit Number of replacements to perform, maximum, or -1 for no limit.
|
||||
*/
|
||||
public static function regexp_replace($pattern, $replacement, $subject, $limit = -1)
|
||||
{
|
||||
return preg_replace($pattern . 'u', (string) $replacement, (string) $subject, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/preg_replace_callback
|
||||
*
|
||||
* @param string $pattern Regular expression
|
||||
* @param callable $callback PHP callback to generate content to replace matches with
|
||||
* @param string $subject String to apply regular expression to
|
||||
* @param int $limit Number of replacements to perform, maximum, or -1 for no limit.
|
||||
*/
|
||||
public static function regexp_replace_callback($pattern, $callback, $subject, $limit = -1)
|
||||
{
|
||||
return preg_replace_callback($pattern . 'u', $callback, $subject, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/preg_split
|
||||
*
|
||||
* @param string $pattern Regular expression
|
||||
* @param string $subject String to apply regular expression to
|
||||
* @param int $limit Number of times to match; -1 for unlimited
|
||||
*
|
||||
* @return array Resulting string segments
|
||||
*/
|
||||
public static function regexp_split($pattern, $subject, $limit = -1)
|
||||
{
|
||||
return preg_split($pattern . 'u', $subject, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/mime_content_type
|
||||
*
|
||||
* @param string $filename Filename to test.
|
||||
* @param string $suggestedExtension Suggested file extension (used for common misconfigurations)
|
||||
*
|
||||
* @return string Detected MIME type
|
||||
*/
|
||||
public static function mime_content_type($filename, $suggestedExtension = '')
|
||||
{
|
||||
$result = null;
|
||||
|
||||
if (function_exists('finfo_open')) {
|
||||
$fi = & Registry::get('fileInfo', true, null);
|
||||
if ($fi === null) {
|
||||
$fi = finfo_open(FILEINFO_MIME, Config::getVar('finfo', 'mime_database_path'));
|
||||
}
|
||||
if ($fi !== false) {
|
||||
$result = strtok(finfo_file($fi, $filename), ' ;');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$result && function_exists('mime_content_type')) {
|
||||
$result = mime_content_type($filename);
|
||||
// mime_content_type appears to return a charset
|
||||
// (erroneously?) in recent versions of PHP5
|
||||
if (($i = strpos($result, ';')) !== false) {
|
||||
$result = trim(substr($result, 0, $i));
|
||||
}
|
||||
}
|
||||
|
||||
if (!$result) {
|
||||
// Fall back on an external "file" tool
|
||||
$f = escapeshellarg($filename);
|
||||
$result = trim(`file --brief --mime $f`);
|
||||
// Make sure we just return the mime type.
|
||||
if (($i = strpos($result, ';')) !== false) {
|
||||
$result = trim(substr($result, 0, $i));
|
||||
}
|
||||
}
|
||||
|
||||
// Check ambiguous mimetypes against extension
|
||||
$exploded = explode('.', $filename);
|
||||
$ext = array_pop($exploded);
|
||||
if ($suggestedExtension) {
|
||||
$ext = $suggestedExtension;
|
||||
}
|
||||
|
||||
$ambiguities = self::getAmbiguousExtensionsMap();
|
||||
if (isset($ambiguities[strtolower($ext . ':' . $result)])) {
|
||||
$result = $ambiguities[strtolower($ext . ':' . $result)];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*
|
||||
* @brief overrides for ambiguous mime types returned by finfo
|
||||
* SUGGESTED_EXTENSION:DETECTED_MIME_TYPE => OVERRIDE_MIME_TYPE
|
||||
*/
|
||||
public static function getAmbiguousExtensionsMap()
|
||||
{
|
||||
return [
|
||||
'html:text/xml' => 'text/html',
|
||||
'css:text/x-c' => 'text/css',
|
||||
'css:text/plain' => 'text/css',
|
||||
'csv:text/plain' => 'text/csv',
|
||||
'js:text/plain' => 'text/javascript',
|
||||
'xlsx:application/zip' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'xltx:application/zip' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
|
||||
'potx:application/zip' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
|
||||
'ppsx:application/zip' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
|
||||
'pptx:application/zip' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'sldx:application/zip' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
|
||||
'docm:application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'application/vnd.ms-word.document.macroEnabled.12',
|
||||
'docx:application/zip' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'dotx:application/zip' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
||||
'wma:video/x-ms-asf' => 'audio/x-ms-wma',
|
||||
'wmv:video/x-ms-asf' => 'video/x-ms-wmv',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip unsafe HTML from the input text. Covers XSS attacks like scripts,
|
||||
* onclick(...) attributes, javascript: urls, and special characters.
|
||||
*
|
||||
* @param string $input input string
|
||||
* @param string $configKey The config section key['allowed_html', 'allowed_title_html']
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function stripUnsafeHtml($input, $configKey = 'allowed_html')
|
||||
{
|
||||
static $purifier;
|
||||
if (!isset($purifier)) {
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$config->set('Core.Encoding', 'utf-8');
|
||||
$config->set('HTML.Doctype', 'HTML 4.01 Transitional');
|
||||
$config->set('HTML.Allowed', Config::getVar('security', $configKey));
|
||||
$config->set('Cache.SerializerPath', 'cache');
|
||||
$purifier = new HTMLPurifier($config);
|
||||
}
|
||||
return $purifier->purify((string) $input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert limited HTML into a string.
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function html2text($html)
|
||||
{
|
||||
$html = self::regexp_replace('/<[\/]?p>/', "\n", $html);
|
||||
$html = self::regexp_replace('/<li>/', '• ', $html);
|
||||
$html = self::regexp_replace('/<\/li>/', "\n", $html);
|
||||
$html = self::regexp_replace('/<br[ ]?[\/]?>/', "\n", $html);
|
||||
$html = html_entity_decode(strip_tags($html), ENT_COMPAT, 'UTF-8');
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins two title string fragments (in $fields) either with a
|
||||
* space or a colon.
|
||||
*
|
||||
* @param array $fields
|
||||
*
|
||||
* @return string the joined string
|
||||
*/
|
||||
public static function concatTitleFields($fields)
|
||||
{
|
||||
// Set the characters that will avoid the use of
|
||||
// a semicolon between title and subtitle.
|
||||
$avoidColonChars = ['?', '!', '/', '&'];
|
||||
|
||||
// if the first field ends in a character in $avoidColonChars,
|
||||
// concat with a space, otherwise use a colon.
|
||||
// Check for any of these characters in
|
||||
// the last position of current full title value.
|
||||
if (in_array(substr($fields[0], -1, 1), $avoidColonChars)) {
|
||||
$fullTitle = join(' ', $fields);
|
||||
} else {
|
||||
$fullTitle = join(': ', $fields);
|
||||
}
|
||||
|
||||
return $fullTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform "handler-class" to "HandlerClass"
|
||||
* and "my-op" to "myOp".
|
||||
*
|
||||
* @param string $string input string
|
||||
* @param int $type which kind of camel case?
|
||||
*
|
||||
* @return string the string in camel case
|
||||
*/
|
||||
public static function camelize($string, $type = self::CAMEL_CASE_HEAD_UP)
|
||||
{
|
||||
assert($type == static::CAMEL_CASE_HEAD_UP || $type == static::CAMEL_CASE_HEAD_DOWN);
|
||||
|
||||
// Transform "handler-class" to "HandlerClass" and "my-op" to "MyOp"
|
||||
$string = implode(array_map('ucfirst_codesafe', explode('-', $string)));
|
||||
|
||||
// Transform "MyOp" to "myOp"
|
||||
if ($type == static::CAMEL_CASE_HEAD_DOWN) {
|
||||
$string = strtolower_codesafe(substr($string, 0, 1)) . substr($string, 1);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform "HandlerClass" to "handler-class"
|
||||
* and "myOp" to "my-op".
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function uncamelize($string)
|
||||
{
|
||||
assert(!empty($string));
|
||||
|
||||
// Transform "myOp" to "MyOp"
|
||||
$string = ucfirst_codesafe($string);
|
||||
|
||||
// Insert hyphens between words and return the string in lowercase
|
||||
$words = [];
|
||||
self::regexp_match_all('/[A-Z][a-z0-9]*/', $string, $words);
|
||||
assert(isset($words[0]) && !empty($words[0]) && strlen(implode('', $words[0])) == strlen($string));
|
||||
return strtolower_codesafe(implode('-', $words[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new UUID (version 4)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generateUUID()
|
||||
{
|
||||
$charid = strtoupper(md5(uniqid(random_int(0, PHP_INT_MAX), true)));
|
||||
$hyphen = '-';
|
||||
$uuid = substr($charid, 0, 8) . $hyphen
|
||||
. substr($charid, 8, 4) . $hyphen
|
||||
. '4' . substr($charid, 13, 3) . $hyphen
|
||||
. strtoupper(dechex(hexdec(ord(substr($charid, 16, 1))) % 4 + 8)) . substr($charid, 17, 3) . $hyphen
|
||||
. substr($charid, 20, 12);
|
||||
return $uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mapping from strftime to DateTime::format formatting equivalents.
|
||||
* Old format: https://www.php.net/manual/en/function.strftime.php
|
||||
* New format: https://www.php.net/manual/en/datetime.format.php
|
||||
*
|
||||
* Introduced in 3.4.0; remove this function (and calls to it) after this is distributed
|
||||
* in an LTS release.
|
||||
*/
|
||||
public static function getStrftimeConversion(): array
|
||||
{
|
||||
return [
|
||||
'%%' => '%', '%h' => 'M', '%d' => 'd', '%a' => 'D',
|
||||
'%e' => 'j', '%A' => 'l', '%u' => 'N', '%w' => 'w',
|
||||
'%U' => 'W', '%B' => 'F', '%m' => 'm', '%b' => 'M',
|
||||
'%Y' => 'Y', '%y' => 'y', '%P' => 'a', '%p' => 'A',
|
||||
'%l' => 'g', '%k' => 'G', '%I' => 'h', '%H' => 'H',
|
||||
'%M' => 'i', '%S' => 's', '%Z' => 'T',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert any strftime-based datetime formatting into DateTime::format equivalent.
|
||||
* Passes through any strings that are already in the new format without modification.
|
||||
* Old format: https://www.php.net/manual/en/function.strftime.php
|
||||
* New format: https://www.php.net/manual/en/datetime.format.php
|
||||
*
|
||||
* Introduced in 3.4.0; remove this function (and calls to it) after this is distributed
|
||||
* in an LTS release.
|
||||
*/
|
||||
public static function convertStrftimeFormat(string $format): string
|
||||
{
|
||||
// Following the lead of Smarty's date_format modifier, check the
|
||||
// format string for "%" characters. If found, attempt to convert.
|
||||
// We don't expect date/time formats to contain other uses of %.
|
||||
if (strstr($format, '%')) {
|
||||
if (Config::getVar('debug', 'deprecation_warnings')) {
|
||||
trigger_error('Deprecated use of strftime-based date format.');
|
||||
}
|
||||
$format = strtr($format, self::getStrftimeConversion());
|
||||
}
|
||||
return $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches each symbol of PHP date format string
|
||||
* to jQuery Datepicker widget date format.
|
||||
*
|
||||
* @param string $phpFormat
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function dateformatPHP2JQueryDatepicker($phpFormat)
|
||||
{
|
||||
return str_replace(
|
||||
['d', 'j', 'l', 'm', 'n', 'F', 'Y'],
|
||||
['dd', 'd', 'DD', 'mm', 'm', 'MM', 'yy'],
|
||||
$phpFormat
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the word count of a string
|
||||
*/
|
||||
public static function getWordCount(string $str): int
|
||||
{
|
||||
return count(preg_split('/\s+/', trim(str_replace(' ', ' ', strip_tags($str)))));
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\PKPString', '\PKPString');
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/Registry.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 Registry
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Maintains a static table of keyed references.
|
||||
* Used for storing/accessing single instance objects and values.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
class Registry
|
||||
{
|
||||
/**
|
||||
* Get a static reference to the registry data structure.
|
||||
*
|
||||
*/
|
||||
public static function &_getRegistry(): array
|
||||
{
|
||||
static $registry = [];
|
||||
return $registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of an item in the registry (optionally setting a default).
|
||||
*
|
||||
* @param bool $createIfEmpty Whether or not to create an entry if none exists
|
||||
* @param mixed $default If $createIfEmpty, this value will be used as a default
|
||||
*/
|
||||
public static function &get(string $key, bool $createIfEmpty = false, mixed $default = null): mixed
|
||||
{
|
||||
$registry = & self::_getRegistry();
|
||||
|
||||
if (isset($registry[$key])) {
|
||||
return $registry[$key];
|
||||
}
|
||||
if ($createIfEmpty) {
|
||||
self::set($key, $default);
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of an item in the registry.
|
||||
*/
|
||||
public static function set(string $key, mixed &$value): void
|
||||
{
|
||||
$registry = & self::_getRegistry();
|
||||
$registry[$key] = & $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the registry.
|
||||
*/
|
||||
public static function delete(string $key): void
|
||||
{
|
||||
$registry = & self::_getRegistry();
|
||||
if (isset($registry[$key])) {
|
||||
unset($registry[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the registry of all contents.
|
||||
*/
|
||||
public static function clear(): void
|
||||
{
|
||||
$registry = & self::_getRegistry();
|
||||
foreach (array_keys($registry) as $key) {
|
||||
unset($registry[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/core/RuntimeEnvironment.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 RuntimeEnvironment
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Class that describes a runtime environment.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use PKP\config\Config;
|
||||
|
||||
class RuntimeEnvironment
|
||||
{
|
||||
/** @var string */
|
||||
public $_phpVersionMin;
|
||||
|
||||
/** @var string */
|
||||
public $_phpVersionMax;
|
||||
|
||||
/** @var array */
|
||||
public $_phpExtensions;
|
||||
|
||||
/** @var array */
|
||||
public $_externalPrograms;
|
||||
|
||||
public function __construct($phpVersionMin = PKPApplication::PHP_REQUIRED_VERSION, $phpVersionMax = null, $phpExtensions = [], $externalPrograms = [])
|
||||
{
|
||||
$this->_phpVersionMin = $phpVersionMin;
|
||||
$this->_phpVersionMax = $phpVersionMax;
|
||||
$this->_phpExtensions = $phpExtensions;
|
||||
$this->_externalPrograms = $externalPrograms;
|
||||
}
|
||||
|
||||
//
|
||||
// Setters and Getters
|
||||
//
|
||||
/**
|
||||
* Get the min required PHP version
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPhpVersionMin()
|
||||
{
|
||||
return $this->_phpVersionMin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the max required PHP version
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPhpVersionMax()
|
||||
{
|
||||
return $this->_phpVersionMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required PHP extensions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPhpExtensions()
|
||||
{
|
||||
return $this->_phpExtensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required external programs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getExternalPrograms()
|
||||
{
|
||||
return $this->_externalPrograms;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Checks whether the current runtime environment is
|
||||
* compatible with the specified parameters.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCompatible()
|
||||
{
|
||||
// Check PHP version
|
||||
if (!is_null($this->_phpVersionMin) && version_compare(PHP_VERSION, $this->_phpVersionMin) < 0) {
|
||||
return false;
|
||||
}
|
||||
if (!is_null($this->_phpVersionMax) && version_compare(PHP_VERSION, $this->_phpVersionMax) > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check PHP extensions
|
||||
foreach ($this->_phpExtensions as $requiredExtension) {
|
||||
if (!extension_loaded($requiredExtension)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check external programs
|
||||
foreach ($this->_externalPrograms as $requiredProgram) {
|
||||
$externalProgram = Config::getVar('cli', $requiredProgram);
|
||||
if (!file_exists($externalProgram)) {
|
||||
return false;
|
||||
}
|
||||
if (function_exists('is_executable')) {
|
||||
if (!is_executable($externalProgram)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compatibility check was successful
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\RuntimeEnvironment', '\RuntimeEnvironment');
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/SoftDeleteTrait.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class SoftDeleteTrait
|
||||
*
|
||||
* @ingroup core
|
||||
*
|
||||
* @brief Implements the methods for soft deletion that can be used in entity daos that support it
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
trait SoftDeleteTrait
|
||||
{
|
||||
/**
|
||||
* Soft delete an object from the database
|
||||
*/
|
||||
protected function _softDelete(DataObject $object): void
|
||||
{
|
||||
$this->softDeleteById($object->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft delete an object from the database by its id
|
||||
*/
|
||||
public function softDeleteById(int $id): void
|
||||
{
|
||||
DB::table($this->table)
|
||||
->where($this->primaryKeyColumn, '=', $id)
|
||||
->update(['deleted_at' => Core::getCurrentDate()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/VirtualArrayIterator.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 VirtualArrayIterator
|
||||
*
|
||||
* @ingroup db
|
||||
*
|
||||
* @brief Provides paging and iteration for "virtual" arrays -- arrays for which only
|
||||
* the current "page" is available, but are much bigger in entirety.
|
||||
*/
|
||||
|
||||
namespace PKP\core;
|
||||
|
||||
use PKP\db\DBResultRange;
|
||||
|
||||
class VirtualArrayIterator extends ItemIterator
|
||||
{
|
||||
/** @var array The array of contents of this iterator. */
|
||||
public $theArray;
|
||||
|
||||
/** @var int Number of items to iterate through on this page */
|
||||
public $itemsPerPage;
|
||||
|
||||
/** @var int The current page. */
|
||||
public $page;
|
||||
|
||||
/** @var int The total number of items. */
|
||||
public $count;
|
||||
|
||||
/** @var bool Whether or not the iterator was empty from the start */
|
||||
public $wasEmpty;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $theArray The array of items to iterate through
|
||||
* @param int $totalItems The total number of items in the virtual "larger" array
|
||||
* @param int $page the current page number
|
||||
* @param int $itemsPerPage Number of items to display per page
|
||||
*/
|
||||
public function __construct($theArray, $totalItems, $page = -1, $itemsPerPage = -1)
|
||||
{
|
||||
parent::__construct();
|
||||
if ($page >= 1 && $itemsPerPage >= 1) {
|
||||
$this->page = $page;
|
||||
} else {
|
||||
$this->page = 1;
|
||||
$this->itemsPerPage = max(count($this->theArray), 1);
|
||||
}
|
||||
$this->theArray = $theArray;
|
||||
$this->count = $totalItems;
|
||||
$this->itemsPerPage = $itemsPerPage;
|
||||
$this->wasEmpty = count($this->theArray) == 0;
|
||||
reset($this->theArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory Method.
|
||||
* Extracts the appropriate page items from the whole array and
|
||||
* calls the constructor.
|
||||
*
|
||||
* @param array $wholeArray The whole array of items
|
||||
* @param DBResultRange $rangeInfo The number of items per page
|
||||
*
|
||||
* @return object VirtualArrayIterator
|
||||
*/
|
||||
public static function factory($wholeArray, $rangeInfo)
|
||||
{
|
||||
if ($rangeInfo->isValid()) {
|
||||
$slicedArray = array_slice($wholeArray, $rangeInfo->getCount() * ($rangeInfo->getPage() - 1), $rangeInfo->getCount(), true);
|
||||
}
|
||||
return new VirtualArrayIterator($slicedArray, count($wholeArray), $rangeInfo->getPage(), $rangeInfo->getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next item in the iterator.
|
||||
*
|
||||
* @return ?object
|
||||
*/
|
||||
public function &next()
|
||||
{
|
||||
if (!is_array($this->theArray)) {
|
||||
$nullVar = null;
|
||||
return $nullVar;
|
||||
}
|
||||
$value = current($this->theArray);
|
||||
if (next($this->theArray) == null) {
|
||||
$this->theArray = null;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next item in the iterator, with key.
|
||||
*
|
||||
* @return array (key, value)
|
||||
*/
|
||||
public function nextWithKey()
|
||||
{
|
||||
$key = key($this->theArray);
|
||||
$value = $this->next();
|
||||
return [$key, $value];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether or not this iterator is for the first page of a sequence
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function atFirstPage()
|
||||
{
|
||||
return $this->page == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether or not this iterator is for the last page of a sequence
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function atLastPage()
|
||||
{
|
||||
return ($this->page * $this->itemsPerPage) + 1 > $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the page number that this iterator represents
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPage()
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of items in the virtual array
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCount()
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of pages in the virtual array
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPageCount()
|
||||
{
|
||||
return max(1, ceil($this->count / $this->itemsPerPage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean indicating whether or not we've reached the end of results
|
||||
* Note: This implementation requires that next() be called before every eof() will
|
||||
* function properly (except the first call).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function eof()
|
||||
{
|
||||
return (($this->theArray == null) || (count($this->theArray) == 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean indicating whether or not this iterator was empty from the beginning
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function wasEmpty()
|
||||
{
|
||||
return $this->wasEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the iterator into an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function &toArray()
|
||||
{
|
||||
return $this->theArray;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\core\VirtualArrayIterator', '\VirtualArrayIterator');
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @file classes/core/exceptions/StoreTemporaryFileException.php
|
||||
*
|
||||
* Copyright (c) 2014-2023 Simon Fraser University
|
||||
* Copyright (c) 2000-2023 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class StoreTemporaryFileException
|
||||
*
|
||||
* @brief Use this exception when an error is encountered while moving
|
||||
* a temporary file to a permanent place.
|
||||
*/
|
||||
|
||||
namespace PKP\core\exceptions;
|
||||
|
||||
use Exception;
|
||||
use PKP\core\DataObject;
|
||||
use PKP\file\TemporaryFile;
|
||||
use PKP\user\User;
|
||||
|
||||
class StoreTemporaryFileException extends Exception
|
||||
{
|
||||
public function __construct(public TemporaryFile $temporaryFile, public string $targetPath, public ?User $user, public ?DataObject $dataObject)
|
||||
{
|
||||
$message = `Unable to store temporary file {$temporaryFile->getFilePath()} in {$targetPath}.`;
|
||||
if ($user) {
|
||||
$message .= ` File was uploaded by {$temporaryFile->getUserId()}. The current user is {$user->getId()}.`;
|
||||
}
|
||||
if ($dataObject) {
|
||||
$class = get_class($dataObject);
|
||||
$message .= ` Handling {$class} id {$dataObject->getId()}.`;
|
||||
}
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/core/interfaces/CollectorInterface.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 CollectorInterface
|
||||
*
|
||||
* @brief An interface describing the methods an Collector class must implement.
|
||||
*/
|
||||
|
||||
namespace PKP\core\interfaces;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
interface CollectorInterface
|
||||
{
|
||||
/**
|
||||
* Get the configured query builder
|
||||
*
|
||||
* This returns an instance of Laravel's query builder. Use this
|
||||
* to execute queries on the entity's table that do not already
|
||||
* have a query method.
|
||||
*
|
||||
* The following example shows how to use this method after applying
|
||||
* query conditions. In this example, the query is used to get
|
||||
* only the date of the last three announcements:
|
||||
*
|
||||
* ```php
|
||||
* $dates = Repo::announcement()
|
||||
* ->filterByContextIds([$contextId])
|
||||
* ->getQueryBuilder()
|
||||
* ->limit(3)
|
||||
* ->pluck('date_posted');
|
||||
* ```
|
||||
*
|
||||
* See: https://laravel.com/docs/8.x/queries
|
||||
*/
|
||||
public function getQueryBuilder(): Builder;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/core/maps/Base.php
|
||||
*
|
||||
* Copyright (c) 2014-2020 Simon Fraser University
|
||||
* Copyright (c) 2000-2020 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class Base
|
||||
*
|
||||
* @brief A base class for creating extensible maps of any kind.
|
||||
*/
|
||||
|
||||
namespace PKP\core\maps;
|
||||
|
||||
abstract class Base
|
||||
{
|
||||
/** @var array Callbacks that should be applied to each object in the map. */
|
||||
protected $extensions = [];
|
||||
|
||||
/**
|
||||
* Extend the map with a custom callback function
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $map->extend(function($output, $input, $map) {
|
||||
* $output['example'] = $input->example;
|
||||
* return $output;
|
||||
* })
|
||||
*
|
||||
*/
|
||||
public function extend(callable $cb): self
|
||||
{
|
||||
$this->extensions[] = $cb;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run extensions applied to this map
|
||||
*
|
||||
* Run the callback functions registered with extend.
|
||||
*
|
||||
* @param mixed $output The output the object is being mapped to
|
||||
* @param mixed $input The object that is being mapped from
|
||||
*/
|
||||
protected function withExtensions($output, $input)
|
||||
{
|
||||
foreach ($this->extensions as $extension) {
|
||||
$output = call_user_func($extension, $output, $input, $this);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/core/maps/Schema.php
|
||||
*
|
||||
* Copyright (c) 2014-2020 Simon Fraser University
|
||||
* Copyright (c) 2000-2020 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class schema
|
||||
*
|
||||
* @brief A base class for mapping objects to their schema properties
|
||||
*/
|
||||
|
||||
namespace PKP\core\maps;
|
||||
|
||||
use Illuminate\Support\Enumerable;
|
||||
use PKP\context\Context;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\services\PKPSchemaService;
|
||||
|
||||
abstract class Schema extends Base
|
||||
{
|
||||
public PKPRequest $request;
|
||||
|
||||
public ?Context $context;
|
||||
|
||||
public PKPSchemaService $schemaService;
|
||||
|
||||
/** The collection of objects being mapped. Null if only one item is being mapped. */
|
||||
public Enumerable $collection;
|
||||
|
||||
/** The property names for a summary of this entity according to its schema */
|
||||
public array $summaryProps;
|
||||
|
||||
/** The property names of this entity according to its schema */
|
||||
public array $props;
|
||||
|
||||
/** The name of the schema for this entity. One of the \PKP\services\PKPSchemaService::SCHEMA_... constants */
|
||||
public string $schema;
|
||||
|
||||
public function __construct(PKPRequest $request, ?Context $context, PKPSchemaService $schemaService)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->context = $context;
|
||||
$this->schemaService = $schemaService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the property names of this entity according to its schema
|
||||
*/
|
||||
protected function getProps(): array
|
||||
{
|
||||
return $this->props ?? $this->schemaService->getFullProps($this->schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the property names for a summary of this entity according to its schema
|
||||
*/
|
||||
protected function getSummaryProps(): array
|
||||
{
|
||||
return $this->summaryProps ?? $this->schemaService->getSummaryProps($this->schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to an object in the REST API
|
||||
*/
|
||||
protected function getApiUrl(string $route, $contextPath = PKPApplication::CONTEXT_ID_ALL): string
|
||||
{
|
||||
return $this->request->getDispatcher()->url(
|
||||
$this->request,
|
||||
PKPApplication::ROUTE_API,
|
||||
$contextPath,
|
||||
$route,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/core/traits/EntityWithParent.php
|
||||
*
|
||||
* Copyright (c) 2022 Simon Fraser University
|
||||
* Copyright (c) 2022 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class EntityWithParent
|
||||
*
|
||||
* @ingroup core_traits
|
||||
*
|
||||
* @brief A trait for DAO classes that can be used with entities that have a parent entity. For example, a Submission always has a parent Context.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace PKP\core\traits;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PKP\core\DataObject;
|
||||
|
||||
/**
|
||||
* @template T of DataObject
|
||||
*/
|
||||
trait EntityWithParent
|
||||
{
|
||||
/**
|
||||
* Get the parent object ID column name
|
||||
*/
|
||||
abstract public function getParentColumn(): string;
|
||||
|
||||
/**
|
||||
* @copydoc EntityDAO::fromRow()
|
||||
*
|
||||
* @return T
|
||||
*/
|
||||
abstract public function fromRow(object $row): DataObject;
|
||||
|
||||
/**
|
||||
* Check if an object exists.
|
||||
*
|
||||
* Optionally, pass the ID of a parent entity to check if the object
|
||||
* exists and is assigned to that parent.
|
||||
*/
|
||||
public function exists(int $id, int $parentId = null): bool
|
||||
{
|
||||
return DB::table($this->table)
|
||||
->where($this->primaryKeyColumn, '=', $id)
|
||||
->when($parentId !== null, fn (Builder $query) => $query->where($this->getParentColumn(), $parentId))
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an object.
|
||||
*
|
||||
* Optionally, pass the ID of a parent entity to only get an object
|
||||
* if it exists and is assigned to that parent.
|
||||
*
|
||||
* @return ?T
|
||||
*/
|
||||
public function get(int $id, int $parentId = null): ?DataObject
|
||||
{
|
||||
$row = DB::table($this->table)
|
||||
->where($this->primaryKeyColumn, $id)
|
||||
->when($parentId !== null, fn (Builder $query) => $query->where($this->getParentColumn(), $parentId))
|
||||
->first();
|
||||
return $row ? $this->fromRow($row) : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user