[ Mini Kiebo ]
Server: Windows NT DESKTOP-5B8S0D4 6.2 build 9200 (Windows 8 Professional Edition) i586
Path:
D:
/
Backup
/
05122024
/
htdocs
/
jurnal-kesmas
/
v1
/
lib
/
pkp
/
classes
/
template
/
[
Home
]
File: PKPTemplateManager.php
<?php /** * @defgroup template Template * Implements template management. */ /** * @file classes/template/PKPTemplateManager.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 PKPTemplateManager * * @ingroup template * * @brief Class for accessing the underlying template engine. * Currently integrated with Smarty (from http://smarty.php.net/). */ namespace PKP\template; use APP\core\Application; use APP\core\PageRouter; use APP\core\Request; require_once('./lib/pkp/lib/vendor/smarty/smarty/libs/plugins/modifier.escape.php'); // Seems to be needed? use APP\core\Services; use APP\facades\Repo; use APP\file\PublicFileManager; use APP\notification\Notification; use APP\submission\Submission; use APP\template\TemplateManager; use Exception; use Less_Parser; use PKP\cache\CacheManager; use PKP\config\Config; use PKP\context\Context; use PKP\controllers\listbuilder\ListbuilderHandler; use PKP\core\Core; use PKP\core\JSONMessage; use PKP\core\PKPApplication; use PKP\core\PKPRequest; use PKP\core\PKPString; use PKP\core\Registry; use PKP\db\DAORegistry; use PKP\facades\Locale; use PKP\file\FileManager; use PKP\form\FormBuilderVocabulary; use PKP\linkAction\LinkAction; use PKP\linkAction\request\NullAction; use PKP\navigationMenu\NavigationMenuDAO; use PKP\notification\NotificationDAO; use PKP\plugins\Hook; use PKP\plugins\PluginRegistry; use PKP\plugins\ThemePlugin; use PKP\security\Role; use PKP\security\Validation; use PKP\session\SessionManager; use PKP\site\VersionDAO; use PKP\submission\GenreDAO; use Smarty; use Smarty_Internal_Template; /* This definition is required by Smarty */ define('SMARTY_DIR', Core::getBaseDir() . '/lib/pkp/lib/vendor/smarty/smarty/libs/'); class PKPTemplateManager extends Smarty { public const CACHEABILITY_NO_CACHE = 'no-cache'; public const CACHEABILITY_NO_STORE = 'no-store'; public const CACHEABILITY_PUBLIC = 'public'; public const CACHEABILITY_MUST_REVALIDATE = 'must-revalidate'; public const CACHEABILITY_PROXY_REVALIDATE = 'proxy-revalidate'; public const STYLE_SEQUENCE_CORE = 0; public const STYLE_SEQUENCE_NORMAL = 10; public const STYLE_SEQUENCE_LATE = 15; public const STYLE_SEQUENCE_LAST = 20; public const CSS_FILENAME_SUFFIX = 'css'; public const PAGE_WIDTH_NARROW = 'narrow'; public const PAGE_WIDTH_NORMAL = 'normal'; public const PAGE_WIDTH_WIDE = 'wide'; public const PAGE_WIDTH_FULL = 'full'; /** @var array of URLs to stylesheets */ private $_styleSheets = []; /** @var array of URLs to javascript files */ private $_javaScripts = []; /** @var array of HTML head content to output */ private $_htmlHeaders = []; /** @var array Key/value list of constants to expose in the JS interface */ private $_constants = []; /** @var array Key/value list of locale keys to expose in the JS interface */ private $_localeKeys = []; /** @var array Initial state data to be managed by the page's Vue.js component */ protected $_state = []; /** @var string Type of cacheability (Cache-Control). */ private $_cacheability; /** @var object The form builder vocabulary class. */ private $_fbv; /** @var PKPRequest */ private $_request; /** @var string[] */ private array $headers = []; /** @var bool Track whether its backend page */ private bool $isBackendPage = false; /** * Constructor. * Initialize template engine and assign basic template variables. */ public function __construct() { parent::__construct(); // Set up Smarty configuration $baseDir = Core::getBaseDir(); $cachePath = CacheManager::getFileCachePath(); $this->compile_dir = "{$cachePath}/t_compile"; $this->config_dir = "{$cachePath}/t_config"; $this->cache_dir = "{$cachePath}/t_cache"; $this->_cacheability = self::CACHEABILITY_NO_STORE; // Safe default // Register the template resources. $this->registerResource('core', new PKPTemplateResource($coreTemplateDir = 'lib/pkp/templates')); $this->registerResource('app', new PKPTemplateResource(['templates', $coreTemplateDir])); $this->default_resource_type = 'app'; $this->error_reporting = E_ALL & ~E_NOTICE & ~E_WARNING; } /** * Initialize the template manager. * * @param PKPRequest $request */ public function initialize($request) { assert($request instanceof PKPRequest); $this->_request = $request; $locale = Locale::getLocale(); $application = Application::get(); $router = $request->getRouter(); assert($router instanceof \PKP\core\PKPRouter); $currentContext = $request->getContext(); $this->assign([ 'defaultCharset' => 'utf-8', 'baseUrl' => $request->getBaseUrl(), 'currentContext' => $currentContext, 'currentLocale' => $locale, 'currentLocaleLangDir' => Locale::getMetadata($locale)?->isRightToLeft() ? 'rtl' : 'ltr', 'applicationName' => __($application->getNameKey()), ]); // Assign date and time format if ($currentContext) { $this->assign([ 'dateFormatShort' => PKPString::convertStrftimeFormat($currentContext->getLocalizedDateFormatShort()), 'dateFormatLong' => PKPString::convertStrftimeFormat($currentContext->getLocalizedDateFormatLong()), 'datetimeFormatShort' => PKPString::convertStrftimeFormat($currentContext->getLocalizedDateTimeFormatShort()), 'datetimeFormatLong' => PKPString::convertStrftimeFormat($currentContext->getLocalizedDateTimeFormatLong()), 'timeFormat' => PKPString::convertStrftimeFormat($currentContext->getLocalizedTimeFormat()), 'displayPageHeaderTitle' => $currentContext->getLocalizedData('name'), 'displayPageHeaderLogo' => $currentContext->getLocalizedData('pageHeaderLogoImage'), 'displayPageHeaderLogoAltText' => $currentContext->getLocalizedData('pageHeaderLogoImageAltText'), ]); } else { $this->assign([ 'dateFormatShort' => PKPString::convertStrftimeFormat(Config::getVar('general', 'date_format_short')), 'dateFormatLong' => PKPString::convertStrftimeFormat(Config::getVar('general', 'date_format_long')), 'datetimeFormatShort' => PKPString::convertStrftimeFormat(Config::getVar('general', 'datetime_format_short')), 'datetimeFormatLong' => PKPString::convertStrftimeFormat(Config::getVar('general', 'datetime_format_long')), 'timeFormat' => PKPString::convertStrftimeFormat(Config::getVar('general', 'time_format')), ]); } if (Application::isInstalled() && !$currentContext) { $site = $request->getSite(); $this->assign([ 'displayPageHeaderTitle' => $site->getLocalizedTitle(), 'displayPageHeaderLogo' => $site->getLocalizedData('pageHeaderTitleImage'), ]); } // Assign meta tags if ($currentContext) { $favicon = $currentContext->getLocalizedFavicon(); if (!empty($favicon)) { $publicFileManager = new PublicFileManager(); $faviconDir = $request->getBaseUrl() . '/' . $publicFileManager->getContextFilesPath($currentContext->getId()); $this->addHeader('favicon', '<link rel="icon" href="' . $faviconDir . '/' . $favicon['uploadName'] . '">', ['contexts' => ['frontend', 'backend']]); } } if (Application::isInstalled()) { $activeTheme = null; $contextOrSite = $currentContext ? $currentContext : $request->getSite(); $allThemes = PluginRegistry::getPlugins('themes'); foreach ($allThemes as $theme) { if ($contextOrSite->getData('themePluginPath') === $theme->getDirName()) { $activeTheme = $theme; break; } } $this->assign(['activeTheme' => $activeTheme]); } if ($router instanceof \PKP\core\PKPPageRouter) { $this->assign([ 'requestedPage' => $router->getRequestedPage($request), 'requestedOp' => $router->getRequestedOp($request), ]); // A user-uploaded stylesheet if ($currentContext) { $contextStyleSheet = $currentContext->getData('styleSheet'); if ($contextStyleSheet) { $publicFileManager = new PublicFileManager(); $this->addStyleSheet( 'contextStylesheet', $request->getBaseUrl() . '/' . $publicFileManager->getContextFilesPath($currentContext->getId()) . '/' . $contextStyleSheet['uploadName'] . '?d=' . urlencode($contextStyleSheet['dateUploaded']), ['priority' => self::STYLE_SEQUENCE_LATE] ); } } // Register recaptcha on relevant pages if (Config::getVar('captcha', 'recaptcha')) { $contexts = []; if (Config::getVar('captcha', 'captcha_on_register')) { array_push($contexts, 'frontend-user-register', 'frontend-user-registerUser'); } if (Config::getVar('captcha', 'captcha_on_login')) { array_push($contexts, 'frontend-login-index', 'frontend-login-signIn'); } if (count($contexts)) { $this->addJavaScript( 'recaptcha', 'https://www.google.com/recaptcha/api.js?hl=' . substr(Locale::getLocale(), 0, 2), [ 'contexts' => $contexts, ] ); } } // Register meta tags if (Application::isInstalled()) { if (($request->getRequestedPage() == '' || $request->getRequestedPage() == 'index') && $currentContext && $currentContext->getLocalizedData('searchDescription')) { $this->addHeader('searchDescription', '<meta name="description" content="' . $currentContext->getLocalizedData('searchDescription') . '">'); } $this->addHeader( 'generator', '<meta name="generator" content="' . __($application->getNameKey()) . ' ' . $application->getCurrentVersion()->getVersionString(false) . '">', [ 'contexts' => ['frontend', 'backend'], ] ); if ($currentContext) { $customHeaders = $currentContext->getLocalizedData('customHeaders'); if (!empty($customHeaders)) { $this->addHeader('customHeaders', $customHeaders); } } } if ($currentContext && !$currentContext->getEnabled()) { $this->addHeader( 'noindex', '<meta name="robots" content="noindex,nofollow">', [ 'contexts' => ['frontend', 'backend'], ] ); } // Register Navigation Menus $nmService = Services::get('navigationMenu'); if (Application::isInstalled()) { Hook::add('LoadHandler', [$nmService, '_callbackHandleCustomNavigationMenuItems']); } } // Register custom functions $this->registerPlugin('modifier', 'intval', 'intval'); $this->registerPlugin('modifier', 'json_encode', 'json_encode'); $this->registerPlugin('modifier', 'uniqid', 'uniqid'); $this->registerPlugin('modifier', 'substr', 'substr'); $this->registerPlugin('modifier', 'strstr', 'strstr'); $this->registerPlugin('modifier', 'strval', 'strval'); $this->registerPlugin('modifier', 'array_key_first', 'array_key_first'); $this->registerPlugin('modifier', 'array_values', 'array_values'); $this->registerPlugin('modifier', 'fatalError', 'fatalError'); $this->registerPlugin('modifier', 'translate', [$this, 'smartyTranslateModifier']); $this->registerPlugin('modifier', 'strip_unsafe_html', '\PKP\core\PKPString::stripUnsafeHtml'); $this->registerPlugin('modifier', 'parse_url', 'parse_url'); $this->registerPlugin('modifier', 'parse_str', 'parse_str'); $this->registerPlugin('modifier', 'strtok', 'strtok'); $this->registerPlugin('modifier', 'array_pop', 'array_pop'); $this->registerPlugin('modifier', 'array_keys', 'array_keys'); $this->registerPlugin('modifier', 'String_substr', '\PKP\core\PKPString::substr'); $this->registerPlugin('modifier', 'dateformatPHP2JQueryDatepicker', '\PKP\core\PKPString::dateformatPHP2JQueryDatepicker'); $this->registerPlugin('modifier', 'to_array', [$this, 'smartyToArray']); $this->registerPlugin('modifier', 'compare', [$this, 'smartyCompare']); $this->registerPlugin('modifier', 'concat', [$this, 'smartyConcat']); $this->registerPlugin('modifier', 'strtotime', [$this, 'smartyStrtotime']); $this->registerPlugin('modifier', 'explode', [$this, 'smartyExplode']); $this->registerPlugin('modifier', 'escape', [$this, 'smartyEscape']); $this->registerPlugin('function', 'csrf', [$this, 'smartyCSRF']); $this->registerPlugin('function', 'translate', [$this, 'smartyTranslate']); $this->registerPlugin('function', 'null_link_action', [$this, 'smartyNullLinkAction']); $this->registerPlugin('function', 'help', [$this, 'smartyHelp']); $this->registerPlugin('function', 'flush', [$this, 'smartyFlush']); $this->registerPlugin('function', 'call_hook', [$this, 'smartyCallHook']); $this->registerPlugin('function', 'html_options_translate', [$this, 'smartyHtmlOptionsTranslate']); $this->registerPlugin('block', 'iterate', [$this, 'smartyIterate']); $this->registerPlugin('function', 'page_links', [$this, 'smartyPageLinks']); $this->registerPlugin('function', 'page_info', [$this, 'smartyPageInfo']); $this->registerPlugin('function', 'pluck_files', [$this, 'smartyPluckFiles']); $this->registerPlugin('function', 'locale_direction', [$this, 'smartyLocaleDirection']); $this->registerPlugin('function', 'html_select_date_a11y', [$this, 'smartyHtmlSelectDateA11y']); $this->registerPlugin('function', 'title', [$this, 'smartyTitle']); $this->registerPlugin('function', 'url', [$this, 'smartyUrl']); // load stylesheets/scripts/headers from a given context $this->registerPlugin('function', 'load_stylesheet', [$this, 'smartyLoadStylesheet']); $this->registerPlugin('function', 'load_script', [$this, 'smartyLoadScript']); $this->registerPlugin('function', 'load_header', [$this, 'smartyLoadHeader']); // load NavigationMenu Areas from context $this->registerPlugin('function', 'load_menu', [$this, 'smartyLoadNavigationMenuArea']); // Load form builder vocabulary $fbv = $this->getFBV(); $this->registerPlugin('block', 'fbvFormSection', [$fbv, 'smartyFBVFormSection']); $this->registerPlugin('block', 'fbvFormArea', [$fbv, 'smartyFBVFormArea']); $this->registerPlugin('function', 'fbvFormButtons', [$fbv, 'smartyFBVFormButtons']); $this->registerPlugin('function', 'fbvElement', [$fbv, 'smartyFBVElement']); $this->registerPlugin('function', 'fieldLabel', [$fbv, 'smartyFieldLabel']); $this->assign('fbvStyles', $fbv->getStyles()); // ajax load into a div or any element $this->registerPlugin('function', 'load_url_in_el', [$this, 'smartyLoadUrlInEl']); $this->registerPlugin('function', 'load_url_in_div', [$this, 'smartyLoadUrlInDiv']); // Always pass these ListBuilder constants to the browser // because a ListBuilder may be loaded in an ajax request // and won't have an opportunity to pass its constants to // the template manager. This is not a recommended practice, // but these are the only constants from a controller that are // required on the frontend. We can remove them once the // ListBuilderHandler is no longer needed. $this->setConstants([ 'LISTBUILDER_SOURCE_TYPE_TEXT' => ListbuilderHandler::LISTBUILDER_SOURCE_TYPE_TEXT, 'LISTBUILDER_SOURCE_TYPE_SELECT' => ListbuilderHandler::LISTBUILDER_SOURCE_TYPE_SELECT, 'LISTBUILDER_OPTGROUP_LABEL' => ListbuilderHandler::LISTBUILDER_OPTGROUP_LABEL, ]); /** * Kludge to make sure no code that tries to connect to the * database is executed (e.g., when loading installer pages). */ if (!SessionManager::isDisabled()) { $this->assign([ 'isUserLoggedIn' => Validation::isLoggedIn(), 'isUserLoggedInAs' => (bool) Validation::loggedInAs(), 'itemsPerPage' => Config::getVar('interface', 'items_per_page'), 'numPageLinks' => Config::getVar('interface', 'page_links'), 'siteTitle' => $request->getSite()->getLocalizedData('title'), ]); $user = $request->getUser(); if ($user) { /** @var NotificationDAO */ $notificationDao = DAORegistry::getDAO('NotificationDAO'); $this->assign([ 'currentUser' => $user, // Assign the user name to be used in the sitenav 'loggedInUsername' => $user->getUsername(), // Assign a count of unread tasks 'unreadNotificationCount' => $notificationDao->getNotificationCount(false, $user->getId(), null, Notification::NOTIFICATION_LEVEL_TASK), ]); } } if (Application::isInstalled()) { // Respond to the sidebar hook if ($currentContext) { $this->assign('hasSidebar', !empty($currentContext->getData('sidebar'))); } else { $this->assign('hasSidebar', !empty($request->getSite()->getData('sidebar'))); } Hook::add('Templates::Common::Sidebar', [$this, 'displaySidebar']); // Clear the cache whenever the active theme is changed Hook::add('Context::edit', [$this, 'clearThemeTemplateCache']); Hook::add('Site::edit', [$this, 'clearThemeTemplateCache']); } } /** * Flag the page as cacheable (or not). * * @param string $cacheability optional */ public function setCacheability($cacheability = self::CACHEABILITY_PUBLIC) { $this->_cacheability = $cacheability; } /** * Compile a LESS stylesheet * * @param string $name Unique name for this LESS stylesheet * @param string $lessFile Path to the LESS file to compile * @param array $args Optional arguments. Supports: * 'baseUrl': Base URL to use when rewriting URLs in the LESS file. * 'addLess': Array of additional LESS files to parse before compiling * * @return string Compiled CSS styles */ public function compileLess($name, $lessFile, $args = []) { $less = new Less_Parser([ 'relativeUrls' => false, 'compress' => true, ]); $request = $this->_request; // Allow plugins to intervene Hook::call('PageHandler::compileLess', [&$less, &$lessFile, &$args, $name, $request]); // Read the stylesheet $less->parseFile($lessFile); // Add extra LESS files before compiling if (isset($args['addLess']) && is_array($args['addLess'])) { foreach ($args['addLess'] as $addless) { $less->parseFile($addless); } } // Add extra LESS variables before compiling if (isset($args['addLessVariables'])) { foreach ((array) $args['addLessVariables'] as $addlessVariables) { $less->parse($addlessVariables); } } // Set the @baseUrl variable $baseUrl = !empty($args['baseUrl']) ? $args['baseUrl'] : $request->getBaseUrl(true); $less->parse("@baseUrl: '{$baseUrl}';"); return $less->getCSS(); } /** * Save LESS styles to a cached file * * @param string $path File path to save the compiled styles * @param string $styles CSS styles compiled from the LESS * * @return bool success/failure */ public function cacheLess($path, $styles) { if (file_put_contents($path, $styles) === false) { error_log("Unable to write \"{$path}\"."); return false; } return true; } /** * Retrieve the file path for a cached LESS file * * @param string $name Unique name for the LESS file * * @return string Path to the less file or false if not found */ public function getCachedLessFilePath($name) { $cacheDirectory = CacheManager::getFileCachePath(); $context = $this->_request->getContext(); $contextId = $context instanceof Context ? $context->getId() : 0; return "{$cacheDirectory}/{$contextId}-{$name}.css"; } /** * Register a stylesheet with the style handler * * @param string $name Unique name for the stylesheet * @param string $style The stylesheet to be included. Should be a URL * or, if the `inline` argument is included, stylesheet data to be output. * @param array $args Key/value array defining display details * `priority` int The order in which to print this stylesheet. * Default: STYLE_SEQUENCE_NORMAL * `contexts` string|array Where the stylesheet should be loaded. * Default: array('frontend') * `inline` bool Whether the $stylesheet value should be output directly as * stylesheet data. Used to pass backend data to the scripts. */ public function addStyleSheet($name, $style, $args = []) { $args = array_merge( [ 'priority' => self::STYLE_SEQUENCE_NORMAL, 'contexts' => ['frontend'], 'inline' => false, ], $args ); $args['contexts'] = (array) $args['contexts']; foreach ($args['contexts'] as $context) { $this->_styleSheets[$context][$args['priority']][$name] = [ 'style' => $style, 'inline' => $args['inline'], ]; } } /** * Register a script with the script handler * * @param string $name Unique name for the script * @param string $script The script to be included. Should be a URL or, if * the `inline` argument is included, script data to be output. * @param array $args Key/value array defining display details * `priority` int The order in which to print this script. * Default: STYLE_SEQUENCE_NORMAL * `contexts` string|array Where the script should be loaded. * Default: array('frontend') * `inline` bool Whether the $script value should be output directly as * script data. Used to pass backend data to the scripts. */ public function addJavaScript($name, $script, $args = []) { $args = array_merge( [ 'priority' => self::STYLE_SEQUENCE_NORMAL, 'contexts' => ['frontend'], 'inline' => false, ], $args ); $args['contexts'] = (array) $args['contexts']; foreach ($args['contexts'] as $context) { $this->_javaScripts[$context][$args['priority']][$name] = [ 'script' => $script, 'inline' => $args['inline'], ]; } } /** * Add a page-specific item to the <head>. * * @param string $name Unique name for the header * @param string $header The header to be included. * @param array $args Key/value array defining display details * `priority` int The order in which to print this header. * Default: STYLE_SEQUENCE_NORMAL * `contexts` string|array Where the header should be loaded. * Default: array('frontend') */ public function addHeader($name, $header, $args = []) { $args = array_merge( [ 'priority' => self::STYLE_SEQUENCE_NORMAL, 'contexts' => ['frontend'], ], $args ); $args['contexts'] = (array) $args['contexts']; foreach ($args['contexts'] as $context) { $this->_htmlHeaders[$context][$args['priority']][$name] = [ 'header' => $header, ]; } } /** * Set constants to be exposed in JavaScript at pkp.const.<constant> * * @param array $names Array mapping constant names to values */ public function setConstants($names) { foreach ($names as $name => $value) { $this->_constants[$name] = $value; } } /** * Set locale keys to be exposed in JavaScript at pkp.localeKeys.<key> * * @param array $keys Array of locale keys */ public function setLocaleKeys($keys) { foreach ($keys as $key) { if (!array_key_exists($key, $this->_localeKeys)) { $this->_localeKeys[$key] = __($key); } } } /** * Get a piece of the state data * */ public function getState(string $key) { return array_key_exists($key, $this->_state) ? $this->_state[$key] : null; } /** * Set initial state data to be managed by the Vue.js component on this page * * @param array $data */ public function setState($data) { $this->_state = array_merge($this->_state, $data); } /** * Register all files required by the core JavaScript library */ public function registerJSLibrary() { $baseUrl = $this->_request->getBaseUrl(); $localeChecks = [Locale::getLocale(), strtolower(substr(Locale::getLocale(), 0, 2))]; // Common $args array used for all our core JS files $args = [ 'priority' => self::STYLE_SEQUENCE_CORE, 'contexts' => 'backend', ]; // Load jQuery validate separately because it can not be linted // properly by our build script $this->addJavaScript( 'jqueryValidate', $baseUrl . '/lib/pkp/js/lib/jquery/plugins/validate/jquery.validate.min.js', $args ); $jqvLocalePath = 'lib/pkp/js/lib/jquery/plugins/validate/localization/messages_'; foreach ($localeChecks as $localeCheck) { if (file_exists($jqvLocalePath . $localeCheck . '.js')) { $this->addJavaScript('jqueryValidateLocale', $baseUrl . '/' . $jqvLocalePath . $localeCheck . '.js', $args); } } $this->addJavaScript( 'plUpload', $baseUrl . '/lib/pkp/lib/vendor/moxiecode/plupload/js/plupload.full.min.js', $args ); $this->addJavaScript( 'jQueryPlUpload', $baseUrl . '/lib/pkp/lib/vendor/moxiecode/plupload/js/jquery.ui.plupload/jquery.ui.plupload.js', $args ); $plLocalePath = 'lib/pkp/lib/vendor/moxiecode/plupload/js/i18n/'; foreach ($localeChecks as $localeCheck) { if (file_exists($plLocalePath . $localeCheck . '.js')) { $this->addJavaScript('plUploadLocale', $baseUrl . '/' . $plLocalePath . $localeCheck . '.js', $args); } } // Load new component library bundle $this->addJavaScript( 'pkpApp', $baseUrl . '/js/build.js', [ 'priority' => self::STYLE_SEQUENCE_LATE, 'contexts' => ['backend'] ] ); // Load minified file if it exists if (Config::getVar('general', 'enable_minified')) { $this->addJavaScript( 'pkpLib', $baseUrl . '/js/pkp.min.js', [ 'priority' => self::STYLE_SEQUENCE_CORE, 'contexts' => ['backend'] ] ); return; } // Otherwise retrieve and register all script files $minifiedScripts = array_filter(array_map('trim', file('registry/minifiedScripts.txt')), function ($s) { return strlen($s) && $s[0] != '#'; // Exclude empty and commented (#) lines }); foreach ($minifiedScripts as $key => $script) { $this->addJavaScript('pkpLib' . $key, "{$baseUrl}/{$script}", $args); } } /** * Register JavaScript data used by the core JS library * * This function registers script data that is required by the core JS * library. This data is queued after jQuery but before the pkp-lib * framework, allowing dynamic data to be passed to the framework. It is * intended to be used for passing constants and locale strings, but plugins * may also take advantage of a hook to include data required by their own * scripts, when integrating with the pkp-lib framework. */ public function registerJSLibraryData() { $context = $this->_request->getContext(); // Instantiate the namespace $output = '$.pkp = $.pkp || {};'; $app_data = [ 'currentLocale' => Locale::getLocale(), 'primaryLocale' => Locale::getPrimaryLocale(), 'baseUrl' => $this->_request->getBaseUrl(), 'contextPath' => isset($context) ? $context->getPath() : '', 'apiBasePath' => '/api/v1', 'restfulUrlsEnabled' => Config::getVar('general', 'restful_urls') ? true : false, 'tinyMceContentCSS' => $this->_request->getBaseUrl() . '/plugins/generic/tinymce/styles/content.css', 'tinyMceOneLineContentCSS' => $this->_request->getBaseUrl() . '/plugins/generic/tinymce/styles/content_oneline.css', ]; // Add an array of rtl languages (right-to-left) if (Application::isInstalled() && !SessionManager::isDisabled()) { $allLocales = []; if ($context) { $allLocales = array_merge( $context->getSupportedLocales() ?? [], $context->getSupportedFormLocales() ?? [], $context->getSupportedSubmissionLocales() ?? [] ); } else { $allLocales = $this->_request->getSite()->getSupportedLocales(); } $allLocales = array_unique($allLocales); $rtlLocales = array_filter($allLocales, fn (string $locale) => Locale::getMetadata($locale)?->isRightToLeft()); $app_data['rtlLocales'] = array_values($rtlLocales); } $output .= '$.pkp.app = ' . json_encode($app_data) . ';'; // Load exposed constants $output .= '$.pkp.cons = ' . json_encode($this->_constants) . ';'; // Allow plugins to load data within their own namespace $output .= '$.pkp.plugins = {};'; $this->addJavaScript( 'pkpLibData', $output, [ 'priority' => self::STYLE_SEQUENCE_CORE, 'contexts' => 'backend', 'inline' => true, ] ); } /** * Set up the template requirements for editorial backend pages */ public function setupBackendPage() { $this->isBackendPage = true; $request = Application::get()->getRequest(); $dispatcher = $request->getDispatcher(); /** @var PageRouter */ $router = $request->getRouter(); if (empty($this->getTemplateVars('pageComponent'))) { $this->assign('pageComponent', 'Page'); } $this->setConstants([ 'REALLY_BIG_NUMBER' => REALLY_BIG_NUMBER, 'UPLOAD_MAX_FILESIZE' => UPLOAD_MAX_FILESIZE, 'WORKFLOW_STAGE_ID_PUBLISHED' => WORKFLOW_STAGE_ID_PUBLISHED, 'WORKFLOW_STAGE_ID_SUBMISSION' => WORKFLOW_STAGE_ID_SUBMISSION, 'WORKFLOW_STAGE_ID_INTERNAL_REVIEW' => WORKFLOW_STAGE_ID_INTERNAL_REVIEW, 'WORKFLOW_STAGE_ID_EXTERNAL_REVIEW' => WORKFLOW_STAGE_ID_EXTERNAL_REVIEW, 'WORKFLOW_STAGE_ID_EDITING' => WORKFLOW_STAGE_ID_EDITING, 'WORKFLOW_STAGE_ID_PRODUCTION' => WORKFLOW_STAGE_ID_PRODUCTION, 'INSERT_TAG_VARIABLE_TYPE_PLAIN_TEXT' => INSERT_TAG_VARIABLE_TYPE_PLAIN_TEXT, 'ROLE_ID_MANAGER' => Role::ROLE_ID_MANAGER, 'ROLE_ID_SITE_ADMIN' => Role::ROLE_ID_SITE_ADMIN, 'ROLE_ID_AUTHOR' => Role::ROLE_ID_AUTHOR, 'ROLE_ID_REVIEWER' => Role::ROLE_ID_REVIEWER, 'ROLE_ID_ASSISTANT' => Role::ROLE_ID_ASSISTANT, 'ROLE_ID_READER' => Role::ROLE_ID_READER, 'ROLE_ID_SUB_EDITOR' => Role::ROLE_ID_SUB_EDITOR, 'ROLE_ID_SUBSCRIPTION_MANAGER' => Role::ROLE_ID_SUBSCRIPTION_MANAGER, 'STATUS_QUEUED' => Submission::STATUS_QUEUED, 'STATUS_PUBLISHED' => Submission::STATUS_PUBLISHED, 'STATUS_DECLINED' => Submission::STATUS_DECLINED, 'STATUS_SCHEDULED' => Submission::STATUS_SCHEDULED, ]); // Common locale keys available in the browser for every page $this->setLocaleKeys([ 'common.attachFiles', 'common.cancel', 'common.clearSearch', 'common.close', 'common.commaListSeparator', 'common.confirm', 'common.delete', 'common.edit', 'common.editItem', 'common.error', 'common.filter', 'common.filterAdd', 'common.filterRemove', 'common.insertContent', 'common.loading', 'common.no', 'common.noItemsFound', 'common.none', 'common.ok', 'common.order', 'common.orderUp', 'common.orderDown', 'common.pageNumber', 'common.pagination.goToPage', 'common.pagination.label', 'common.pagination.next', 'common.pagination.previous', 'common.remove', 'common.required', 'common.save', 'common.saving', 'common.search', 'common.selectWithName', 'common.unknownError', 'common.uploadedBy', 'common.uploadedByAndWhen', 'common.view', 'list.viewLess', 'list.viewMore', 'common.viewWithName', 'common.yes', 'form.dataHasChanged', 'form.errorA11y', 'form.errorGoTo', 'form.errorMany', 'form.errorOne', 'form.errors', 'form.multilingualLabel', 'form.multilingualProgress', 'form.saved', 'help.help', 'navigation.backTo', 'validator.required' ]); // Set up the document type icons $documentTypeIcons = [ FileManager::DOCUMENT_TYPE_DEFAULT => 'file-o', FileManager::DOCUMENT_TYPE_AUDIO => 'file-audio-o', FileManager::DOCUMENT_TYPE_EPUB => 'file-text-o', FileManager::DOCUMENT_TYPE_EXCEL => 'file-excel-o', FileManager::DOCUMENT_TYPE_HTML => 'file-code-o', FileManager::DOCUMENT_TYPE_IMAGE => 'file-image-o', FileManager::DOCUMENT_TYPE_PDF => 'file-pdf-o', FileManager::DOCUMENT_TYPE_WORD => 'file-word-o', FileManager::DOCUMENT_TYPE_VIDEO => 'file-video-o', FileManager::DOCUMENT_TYPE_ZIP => 'file-archive-o', FileManager::DOCUMENT_TYPE_URL => 'external-link', ]; $this->addJavaScript( 'documentTypeIcons', 'pkp.documentTypeIcons = ' . json_encode($documentTypeIcons) . ';', [ 'priority' => self::STYLE_SEQUENCE_LAST, 'contexts' => 'backend', 'inline' => true, ] ); // Register the jQuery script $min = Config::getVar('general', 'enable_minified') ? '.min' : ''; $this->addJavaScript( 'jquery', $request->getBaseUrl() . '/lib/pkp/lib/vendor/components/jquery/jquery' . $min . '.js', [ 'priority' => self::STYLE_SEQUENCE_CORE, 'contexts' => 'backend', ] ); $this->addJavaScript( 'jqueryUI', $request->getBaseUrl() . '/lib/pkp/lib/vendor/components/jqueryui/jquery-ui' . $min . '.js', [ 'priority' => self::STYLE_SEQUENCE_CORE, 'contexts' => 'backend', ] ); // Register the pkp-lib JS library $this->registerJSLibraryData(); $this->registerJSLibrary(); // FontAwesome - http://fontawesome.io/ $this->addStyleSheet( 'fontAwesome', $request->getBaseUrl() . '/lib/pkp/styles/fontawesome/fontawesome.css', [ 'priority' => self::STYLE_SEQUENCE_CORE, 'contexts' => 'backend', ] ); // Stylesheet compiled from Vue.js single-file components $this->addStyleSheet( 'build', $request->getBaseUrl() . '/styles/build.css', [ 'priority' => self::STYLE_SEQUENCE_CORE, 'contexts' => 'backend', ] ); // The legacy stylesheet for the backend $this->addStyleSheet( 'pkpLib', $dispatcher->url($request, PKPApplication::ROUTE_COMPONENT, null, 'page.PageHandler', 'css'), [ 'priority' => self::STYLE_SEQUENCE_CORE, 'contexts' => 'backend', ] ); // Set up required state properties $this->setState([ 'menu' => [], 'tinyMCE' => [ 'skinUrl' => $this->getTinyMceSkinUrl($request), ], ]); /** * Kludge to make sure no code that tries to connect to the * database is executed (e.g., when loading installer pages). */ if (Application::isInstalled() && !SessionManager::isDisabled()) { if ($request->getUser()) { // Get a count of unread tasks $notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */ $unreadTasksCount = (int) $notificationDao->getNotificationCount(false, $request->getUser()->getId(), null, Notification::NOTIFICATION_LEVEL_TASK); // Get a URL to load the tasks grid $tasksUrl = $request->getDispatcher()->url($request, PKPApplication::ROUTE_COMPONENT, null, 'page.PageHandler', 'tasks'); // Load system notifications in SiteHandler.js $notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */ $notificationsCount = count($notificationDao->getByUserId($request->getUser()->getId(), Notification::NOTIFICATION_LEVEL_TRIVIAL)->toArray()); // Load context switcher $isAdmin = in_array(Role::ROLE_ID_SITE_ADMIN, $this->getTemplateVars('userRoles')); if ($isAdmin) { $args = []; } else { $args = ['userId' => $request->getUser()->getId()]; } $availableContexts = Services::get('context')->getManySummary($args); if ($request->getContext()) { $availableContexts = array_filter($availableContexts, function ($context) use ($request) { return $context->id !== $request->getContext()->getId(); }); } // Admins should switch to the same page on another context where possible $requestedOp = $request->getRequestedOp() === 'index' ? null : $request->getRequestedOp(); $isSwitchable = $isAdmin && in_array($request->getRequestedPage(), [ 'submissions', 'manageIssues', 'management', 'payment', 'stats', ]); foreach ($availableContexts as $availableContext) { // Site admins redirected to the same page. Everyone else to submission lists if ($isSwitchable) { $availableContext->url = $dispatcher->url($request, PKPApplication::ROUTE_PAGE, $availableContext->urlPath, $request->getRequestedPage(), $requestedOp, $request->getRequestedArgs()); } else { $availableContext->url = $dispatcher->url($request, PKPApplication::ROUTE_PAGE, $availableContext->urlPath, 'submissions'); } } // Create main navigation menu $userRoles = (array) $router->getHandler()->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES); $menu = []; if ($request->getContext()) { if (count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT, Role::ROLE_ID_REVIEWER, Role::ROLE_ID_AUTHOR], $userRoles))) { $menu['submissions'] = [ 'name' => __('navigation.submissions'), 'url' => $router->url($request, null, 'submissions'), 'isCurrent' => $router->getRequestedPage($request) === 'submissions', ]; } elseif (count($userRoles) === 1 && in_array(Role::ROLE_ID_READER, $userRoles)) { $menu['submit'] = [ 'name' => __('author.submit'), 'url' => $router->url($request, null, 'submission'), 'isCurrent' => $router->getRequestedPage($request) === 'submission', ]; } if (count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN], $userRoles))) { if ($request->getContext()->getData('enableAnnouncements')) { $menu['announcements'] = [ 'name' => __('announcement.announcements'), 'url' => $router->url($request, null, 'management', 'settings', 'announcements'), 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('announcements', (array) $router->getRequestedArgs($request)), ]; } if ($request->getContext()->getData(Context::SETTING_ENABLE_DOIS) && !empty($request->getContext()->getData(Context::SETTING_ENABLED_DOI_TYPES))) { $menu['dois'] = [ 'name' => __('doi.manager.displayName'), 'url' => $router->url($request, null, 'dois'), 'isCurrent' => $request->getRequestedPage() === 'dois', ]; } if ($request->getContext()->isInstitutionStatsEnabled($request->getSite())) { $menu['institutions'] = [ 'name' => __('institution.institutions'), 'url' => $router->url($request, null, 'management', 'settings', 'institutions'), 'isCurrent' => $request->getRequestedPage() === 'management' && in_array('institutions', (array) $request->getRequestedArgs()), ]; } $menu['settings'] = [ 'name' => __('navigation.settings'), 'submenu' => [ 'context' => [ 'name' => __('context.context'), 'url' => $router->url($request, null, 'management', 'settings', 'context'), 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('context', (array) $router->getRequestedArgs($request)), ], 'website' => [ 'name' => __('manager.website'), 'url' => $router->url($request, null, 'management', 'settings', 'website'), 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('website', (array) $router->getRequestedArgs($request)), ], 'workflow' => [ 'name' => __('manager.workflow'), 'url' => $router->url($request, null, 'management', 'settings', 'workflow'), 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('workflow', (array) $router->getRequestedArgs($request)), ], 'distribution' => [ 'name' => __('manager.distribution'), 'url' => $router->url($request, null, 'management', 'settings', 'distribution'), 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('distribution', (array) $router->getRequestedArgs($request)), ], 'access' => [ 'name' => __('navigation.access'), 'url' => $router->url($request, null, 'management', 'settings', 'access'), 'isCurrent' => $router->getRequestedPage($request) === 'management' && in_array('access', (array) $router->getRequestedArgs($request)), ] ] ]; } if (count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR], $userRoles))) { $menu['statistics'] = [ 'name' => __('navigation.tools.statistics'), 'submenu' => [ 'publications' => [ 'name' => __('common.publications'), 'url' => $router->url($request, null, 'stats', 'publications', 'publications'), 'isCurrent' => $router->getRequestedPage($request) === 'stats' && $router->getRequestedOp($request) === 'publications', ], 'context' => [ 'name' => __('context.context'), 'url' => $router->url($request, null, 'stats', 'context', 'context'), 'isCurrent' => $router->getRequestedPage($request) === 'stats' && $router->getRequestedOp($request) === 'context', ], 'editorial' => [ 'name' => __('stats.editorialActivity'), 'url' => $router->url($request, null, 'stats', 'editorial', 'editorial'), 'isCurrent' => $router->getRequestedPage($request) === 'stats' && $router->getRequestedOp($request) === 'editorial', ], 'users' => [ 'name' => __('manager.users'), 'url' => $router->url($request, null, 'stats', 'users', 'users'), 'isCurrent' => $router->getRequestedPage($request) === 'stats' && $router->getRequestedOp($request) === 'users', ], 'counterR5' => [ 'name' => __('manager.statistics.counterR5'), 'url' => $router->url($request, null, 'stats', 'counterR5', 'counterR5'), 'isCurrent' => $router->getRequestedPage($request) === 'stats' && $router->getRequestedOp($request) === 'counterR5', ] ] ]; if (count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN], $userRoles))) { $menu['statistics']['submenu'] += [ 'reports' => [ 'name' => __('manager.statistics.reports'), 'url' => $router->url($request, null, 'stats', 'reports'), 'isCurrent' => $router->getRequestedPage($request) === 'stats' && $router->getRequestedOp($request) === 'reports', ] ]; } } if (count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN], $userRoles))) { $menu['tools'] = [ 'name' => __('navigation.tools'), 'url' => $router->url($request, null, 'management', 'tools'), 'isCurrent' => $router->getRequestedPage($request) === 'management' && $router->getRequestedOp($request) === 'tools', ]; } if (in_array(Role::ROLE_ID_SITE_ADMIN, $userRoles)) { $menu['admin'] = [ 'name' => __('navigation.admin'), 'url' => $router->url($request, 'index', 'admin'), 'isCurrent' => $router->getRequestedPage($request) === 'admin', ]; } } $this->setState([ 'menu' => $menu, 'tasksUrl' => $tasksUrl, 'unreadTasksCount' => $unreadTasksCount, ]); $this->assign([ 'availableContexts' => $availableContexts, 'hasSystemNotifications' => $notificationsCount > 0, ]); } } Hook::call('TemplateManager::setupBackendPage'); } /** * @copydoc Smarty::fetch() * * @param null|mixed $template * @param null|mixed $cache_id * @param null|mixed $compile_id * @param null|mixed $parent */ public function fetch($template = null, $cache_id = null, $compile_id = null, $parent = null) { // If no compile ID was assigned, get one. if (!$compile_id) { $compile_id = $this->getCompileId($template); } // Give hooks an opportunity to override $result = null; if (Hook::call('TemplateManager::fetch', [$this, $template, $cache_id, $compile_id, &$result])) { return $result; } return parent::fetch($template, $cache_id, $compile_id, $parent); } /** * Fetch content via AJAX and add it to the DOM, wrapped in a container element. * * @param string $id ID to use for the generated container element. * @param string $url URL to fetch the contents from. * @param string $element Element to use for container. * * @return JSONMessage The JSON-encoded result. */ public function fetchAjax($id, $url, $element = 'div') { return new JSONMessage(true, $this->smartyLoadUrlInEl( [ 'url' => $url, 'id' => $id, 'el' => $element, ], $this )); } /** * Calculate a compile ID for a resource. * * @param string $resourceName Resource name. * * @return string */ public function getCompileId($resourceName) { if (Application::isInstalled()) { $context = $this->_request->getContext(); if ($context instanceof Context) { $resourceName .= $context->getData('themePluginPath'); } } return sha1($resourceName); } /** * Returns the template results as a JSON message. * * @param string $template Template filename (or Smarty resource name) * @param bool $status * * @return JSONMessage JSON object */ public function fetchJson($template, $status = true) { return new JSONMessage($status, $this->fetch($template)); } /** * @copydoc Smarty::display() * * @param null|mixed $template * @param null|mixed $cache_id * @param null|mixed $compile_id * @param null|mixed $parent */ public function display($template = null, $cache_id = null, $compile_id = null, $parent = null) { if($this->isBackendPage) { $this->unregisterPlugin('modifier', 'escape'); /** prevent {{ JS }} injection */ $this->registerPlugin('modifier', 'escape', function ($string, $esc_type = 'html', $char_set = 'ISO-8859-1') { $result = $string; if($esc_type === 'html') { $result = $this->smartyEscape($result, $esc_type, $char_set); $result = str_replace('{{', '<span v-pre>{{</span>', $result); $result = str_replace('}}', '<span v-pre>}}</span>', $result); return $result; } return $this->smartyEscape($result, $esc_type, $char_set); }); $this->unregisterPlugin('modifier', 'strip_unsafe_html'); /** prevent {{ JS }} injection */ $this->registerPlugin('modifier', 'strip_unsafe_html', function ($input, $configKey = 'allowed_html') { $result = \PKP\core\PKPString::stripUnsafeHtml($input, $configKey); $result = str_replace('{{', '<span v-pre>{{</span>', $result); $result = str_replace('}}', '<span v-pre>}}</span>', $result); return $result; }); } // Output global constants and locale keys used in new component library $output = ''; if (!empty($this->_constants)) { $output .= 'pkp.const = ' . json_encode($this->_constants) . ';'; } if (!empty($this->_localeKeys)) { $output .= 'pkp.localeKeys = ' . json_encode($this->_localeKeys) . ';'; } // Load current user data if (Application::isInstalled()) { $user = $this->_request->getUser(); if ($user) { $userGroups = Repo::userGroup()->userUserGroups($user->getId()); $userRoles = []; foreach ($userGroups as $userGroup) { $userRoles[] = (int) $userGroup->getRoleId(); } $currentUser = [ 'csrfToken' => $this->_request->getSession()->getCSRFToken(), 'id' => (int) $user->getId(), 'roles' => array_values(array_unique($userRoles)), ]; $output .= 'pkp.currentUser = ' . json_encode($currentUser) . ';'; } } $this->addJavaScript( 'pkpAppData', $output, [ 'priority' => self::STYLE_SEQUENCE_LATE, 'contexts' => ['backend'], 'inline' => true, ] ); // Give any hooks registered against the TemplateManager // the opportunity to modify behavior; otherwise, display // the template as usual. $output = null; if (Hook::call('TemplateManager::display', [$this, &$template, &$output])) { echo $output; return; } // Pass the initial state data for this page $this->assign('state', $this->_state); // Explicitly set the character encoding. Required in // case server is using Apache's AddDefaultCharset // directive (which can prevent browser auto-detection // of the proper character set). header('content-type: text/html; charset=utf-8'); header("cache-control: {$this->_cacheability}"); foreach ($this->headers as $header) { header($header); } // If no compile ID was assigned, get one. if (!$compile_id) { $compile_id = $this->getCompileId($template); } // Actually display the template. parent::display($template, $cache_id, $compile_id, $parent); } /** * Clear template compile and cache directories. */ public function clearTemplateCache() { $this->clearCompiledTemplate(); $this->clearAllCache(); } /** * Clear all compiled CSS files */ public function clearCssCache() { $cacheDirectory = CacheManager::getFileCachePath(); $files = scandir($cacheDirectory); array_map('unlink', glob(CacheManager::getFileCachePath() . '/*.' . self::CSS_FILENAME_SUFFIX)); } /** * Clear the cache when a context or site has changed it's active theme * * @param string $hookName * @param array $args [ * * @option Context|Site The new values * @option Context|Site The old values * @option array Key/value of params that were modified * @option Request * ] */ public function clearThemeTemplateCache($hookName, $args) { $newContextOrSite = $args[0]; $contextOrSite = $args[1]; if ($newContextOrSite->getData('themePluginPath') !== $contextOrSite->getData('themePluginPath')) { $this->clearTemplateCache(); $this->clearCssCache(); } } /** * Return an instance of the template manager. * * @param ?PKPRequest $request * * @return TemplateManager the template manager object */ public static function &getManager($request = null) { if (!isset($request)) { $request = Registry::get('request'); if (Config::getVar('debug', 'deprecation_warnings')) { throw new Exception('Deprecated call without request object.'); } } assert($request instanceof PKPRequest); $instance = & Registry::get('templateManager', true, null); // Reference required if ($instance === null) { $instance = new TemplateManager(); $themes = PluginRegistry::getPlugins('themes'); if (empty($themes)) { $themes = PluginRegistry::loadCategory('themes', true); } $instance->initialize($request); } return $instance; } /** * Return an instance of the Form Builder Vocabulary class. * * @return TemplateManager the template manager object */ public function getFBV() { if (!$this->_fbv) { $this->_fbv = new FormBuilderVocabulary(); } return $this->_fbv; } /** * Display the sidebar * * @param string $hookName * @param array $args [ * * @option array Params passed to the hook * @option Smarty * @option string The output * ] */ public function displaySidebar($hookName, $args) { $params = & $args[0]; $smarty = & $args[1]; $output = & $args[2]; if ($this->_request->getContext()) { $blocks = $this->_request->getContext()->getData('sidebar'); } else { $blocks = $this->_request->getSite()->getData('sidebar'); } if (empty($blocks)) { return false; } $plugins = PluginRegistry::loadCategory('blocks', true); if (empty($plugins)) { return false; } foreach ($blocks as $pluginName) { if (!empty($plugins[$pluginName])) { $output .= $plugins[$pluginName]->getContents($smarty, $this->_request); } } return false; } /** * Get the URL to the TinyMCE skin */ public function getTinyMceSkinUrl(Request $request): string { return $request->getBaseUrl() . '/lib/pkp/styles/tinymce'; } // // Custom template functions, modifiers, etc. // /** * Smarty usage: * Simple translation * {translate key="localization.key.name" [paramName="paramValue" ...]} * * Pluralized translation * {translate key="localization.key.name" count="10" [paramName="paramValue" ...]} * Custom Smarty function for translating localization keys. * Substitution works by replacing tokens like "{$foo}" with the value of the parameter named "foo" (if supplied). * * The params named "key", "count", "locale" and "params" are reserved. If you need to pass one of them as a translation variable specify them using the "params": * $smarty->assign('params', ['key' => "Golden key"]); * {translate key="pluralized.key" locale="en" count="10" params=$params} * * @param array $params associative array, must contain "key" parameter for string to translate plus zero or more named parameters for substitution. * Translation variables can be specified also as an optional associative array named "params". * @param Smarty $smarty * * @return string the localized string, including any parameter substitutions */ public function smartyTranslate(array $params, Smarty_Internal_Template $smarty): string { // Save reserved params before removing them $key = $params['key'] ?? ''; $count = $params['count'] ?? null; $locale = $params['locale'] ?? null; $variables = $params['params'] ?? []; // Remove reserved params unset($params['key'], $params['count'], $params['params'], $params['locale']); // Merge variables $variables = $params + $variables; // Decides between the simple/pluralized version return $count === null ? __($key, $variables, $locale) : __p($key, $count, $variables, $locale); } /** * Applies a translation modifier, where the value to be transformed is used as locale key * Simple translation * {$foo|translate} * * Passing variables for the translation * {$foo|translate:varFoo:valueFoo:varBar:valueBar} * * Pluralized translation with a different locale * {$foo|translate:count:123:locale:pt_BR} */ public function smartyTranslateModifier(): string { $params = func_get_args(); $key = array_shift($params); $variables = []; if (count($params)) { $name = null; foreach ($params as $i => $value) { if ($i % 2) { $variables[$name] = $value; } else { $name = $value; } } } $count = $variables['count'] ?? null; $locale = $variables['locale'] ?? null; return $count === null ? __($key, $variables, $locale) : __p($key, $count, $variables, $locale); } /** * Smarty usage: {null_link_action id="linkId" key="localization.key.name" image="imageClassName"} * * Custom Smarty function for displaying a null link action; these will * typically be attached and handled in Javascript. * * @param Smarty $smarty * * @return string the HTML for the generated link action */ public function smartyNullLinkAction($params, $smarty) { assert(isset($params['id'])); $id = $params['id']; $key = $params['key'] ?? null; $hoverTitle = isset($params['hoverTitle']) ? true : false; $image = $params['image'] ?? null; $translate = isset($params['translate']) ? false : true; $key = $translate ? __($key) : $key; $this->assign('action', new LinkAction( $id, new NullAction(), $key, $image )); $this->assign('hoverTitle', $hoverTitle); return $this->fetch('linkAction/linkAction.tpl'); } /** * Smarty usage: {help file="someFile" section="someSection" textKey="some.text.key"} * * Custom Smarty function for displaying a context-sensitive help link. * * @param Smarty $smarty * * @return string the HTML for the generated link action */ public function smartyHelp($params, $smarty) { assert(isset($params['file'])); $params = array_merge( [ 'file' => null, // The name of the Markdown file 'section' => null, // The (optional) anchor within the Markdown file 'textKey' => 'help.help', // An (optional) locale key for the link 'text' => null, // An (optional) literal text for the link 'class' => null, // An (optional) CSS class string for the link ], $params ); $this->assign([ 'helpFile' => $params['file'], 'helpSection' => $params['section'], 'helpTextKey' => $params['textKey'], 'helpText' => $params['text'], 'helpClass' => $params['class'], ]); return $this->fetch('common/helpLink.tpl'); } /** * Smarty usage: {html_options_translate ...} * For parameter usage, see http://smarty.php.net/manual/en/language.function.html.options.php * * Identical to Smarty's "html_options" function except option values are translated from i18n keys. * * @param array $params * @param Smarty $smarty */ public function smartyHtmlOptionsTranslate($params, $smarty) { if (isset($params['options'])) { if (isset($params['translateValues'])) { // Translate values AND output $newOptions = []; foreach ($params['options'] as $k => $v) { $newOptions[__($k)] = __($v); } $params['options'] = $newOptions; } else { // Just translate output $params['options'] = array_map('__', $params['options']); } } if (isset($params['output'])) { $params['output'] = array_map('__', $params['output']); } if (isset($params['values']) && isset($params['translateValues'])) { $params['values'] = array_map('__', $params['values']); } require_once('lib/pkp/lib/vendor/smarty/smarty/libs/plugins/function.html_options.php'); /** @var Smarty_Internal_Template $smarty */ return smarty_function_html_options($params, $smarty); } /** * Iterator function for looping through objects extending the * ItemIterator class. * Parameters: * - from: Name of template variable containing iterator * - item: Name of template variable to receive each item * - key: (optional) Name of variable to receive index of current item */ public function smartyIterate($params, $content, $smarty, &$repeat) { $iterator = $smarty->getTemplateVars($params['from']); if (isset($params['key'])) { if (empty($content)) { $smarty->assign($params['key'], 1); } else { $smarty->assign($params['key'], $smarty->getTemplateVars($params['key']) + 1); } } // If the iterator is empty, we're finished. if (!$iterator || $iterator->eof()) { if (!$repeat) { return $content; } $repeat = false; return ''; } $repeat = true; if (isset($params['key'])) { [$key, $value] = $iterator->nextWithKey(); $smarty->assign($params['item'], $value); $smarty->assign($params['key'], $key); } else { $smarty->assign($params['item'], $iterator->next()); } return $content; } /** * Display page information for a listing of items that has been * divided onto multiple pages. * Usage: * {page_info from=$myIterator} */ public function smartyPageInfo($params, $smarty) { $iterator = $params['iterator']; if (isset($params['itemsPerPage'])) { $itemsPerPage = $params['itemsPerPage']; } else { $itemsPerPage = $smarty->getTemplateVars('itemsPerPage'); if (!is_numeric($itemsPerPage)) { $itemsPerPage = 25; } } $page = $iterator->getPage(); $pageCount = $iterator->getPageCount(); $itemTotal = $iterator->getCount(); if ($pageCount < 1) { return ''; } $from = (($page - 1) * $itemsPerPage) + 1; $to = min($itemTotal, $page * $itemsPerPage); return __('navigation.items', [ 'from' => ($to === 0 ? 0 : $from), 'to' => $to, 'total' => $itemTotal ]); } /** * Flush the output buffer. This is useful in cases where Smarty templates * are calling functions that take a while to execute so that they can display * a progress indicator or a message stating that the operation may take a while. */ public function smartyFlush($params, $smarty) { $smarty->flush(); } public function flush() { while (ob_get_level()) { ob_end_flush(); } flush(); } /** * Call hooks from a template. */ public function smartyCallHook($params, $smarty) { $output = null; Hook::call($params['name'], [&$params, $smarty, &$output]); return $output; } /** * Generate a URL into a PKPApp. * * @param object $smarty * Available parameters: * - router: which router to use * - context * - page * - component * - op * - path (array) * - anchor * - escape (default to true unless otherwise specified) * - params: parameters to include in the URL if available as an array */ public function smartyUrl($parameters, $smarty) { if (!isset($parameters['context'])) { // Extract the variables named in $paramList, and remove them // from the parameters array. Variables remaining in params will be // passed along to Request::url as extra parameters. $contextName = Application::get()->getContextName(); if (isset($parameters[$contextName])) { $context = $parameters[$contextName]; unset($parameters[$contextName]); } else { $context = null; } $parameters['context'] = $context; } // Extract the reserved variables named in $paramList, and remove them // from the parameters array. Variables remaining in parameters will be passed // along to Request::url as extra parameters. $params = $router = $page = $component = $anchor = $escape = $op = $path = null; $paramList = ['params', 'router', 'context', 'page', 'component', 'op', 'path', 'anchor', 'escape']; foreach ($paramList as $parameter) { if (isset($parameters[$parameter])) { $$parameter = $parameters[$parameter]; } else { $$parameter = null; } unset($parameters[$parameter]); } // Merge parameters specified in the {url paramName=paramValue} format with // those optionally supplied in {url params=$someAssociativeArray} format $parameters = array_merge($parameters, (array) $params); // Set the default router if (is_null($router)) { if ($this->_request->getRouter() instanceof \PKP\core\PKPComponentRouter) { $router = PKPApplication::ROUTE_COMPONENT; } else { $router = PKPApplication::ROUTE_PAGE; } } // Check the router $dispatcher = Application::get()->getDispatcher(); $routerShortcuts = array_keys($dispatcher->getRouterNames()); assert(in_array($router, $routerShortcuts)); // Identify the handler switch ($router) { case PKPApplication::ROUTE_PAGE: $handler = $page; break; case PKPApplication::ROUTE_COMPONENT: $handler = $component; break; default: // Unknown router type assert(false); } // Let the dispatcher create the url return $dispatcher->url($this->_request, $router, $context, $handler, $op, $path, $parameters, $anchor, !isset($escape) || $escape); } /** * Generate the <title> tag for a page * * Usage: {title value="Journal Settings"} * * @param array $parameters * @param object $smarty * Available parameters: * - router: which router to use * - context * - page * - component * - op * - path (array) * - anchor * - escape (default to true unless otherwise specified) * - params: parameters to include in the URL if available as an array */ public function smartyTitle($parameters, $smarty) { $page = $parameters['value'] ?? ''; if ($smarty->getTemplateVars('currentContext')) { $siteTitle = $smarty->getTemplateVars('currentContext')->getLocalizedData('name'); } elseif ($smarty->getTemplateVars('siteTitle')) { $siteTitle = $smarty->getTemplateVars('siteTitle'); } else { $siteTitle = __('common.software'); } if (empty($parameters['value'])) { return $siteTitle; } return $parameters['value'] . __('common.titleSeparator') . $siteTitle; } /** * Display page links for a listing of items that has been * divided onto multiple pages. * Usage: * {page_links * name="nameMustMatchGetRangeInfoCall" * iterator=$myIterator * additional_param=myAdditionalParameterValue * } */ public function smartyPageLinks($params, $smarty) { $iterator = $params['iterator']; $name = $params['name']; if (isset($params['params']) && is_array($params['params'])) { $extraParams = $params['params']; unset($params['params']); $params = array_merge($params, $extraParams); } if (isset($params['anchor'])) { $anchor = $params['anchor']; unset($params['anchor']); } else { $anchor = null; } if (isset($params['all_extra'])) { $allExtra = ' ' . $params['all_extra']; unset($params['all_extra']); } else { $allExtra = ''; } unset($params['iterator']); unset($params['name']); $numPageLinks = $smarty->getTemplateVars('numPageLinks'); if (!is_numeric($numPageLinks)) { $numPageLinks = 10; } $page = $iterator->getPage(); $pageCount = $iterator->getPageCount(); $pageBase = max($page - floor($numPageLinks / 2), 1); $paramName = $name . 'Page'; if ($pageCount <= 1) { return ''; } $value = ''; $router = $this->_request->getRouter(); $requestedArgs = null; if ($router instanceof PageRouter) { $requestedArgs = $router->getRequestedArgs($this->_request); } if ($page > 1) { $params[$paramName] = 1; $value .= '<a href="' . $this->_request->url(null, null, null, $requestedArgs, $params, $anchor) . '"' . $allExtra . '><<</a> '; $params[$paramName] = $page - 1; $value .= '<a href="' . $this->_request->url(null, null, null, $requestedArgs, $params, $anchor) . '"' . $allExtra . '><</a> '; } for ($i = $pageBase; $i < min($pageBase + $numPageLinks, $pageCount + 1); $i++) { if ($i == $page) { $value .= "<strong>{$i}</strong> "; } else { $params[$paramName] = $i; $value .= '<a href="' . $this->_request->url(null, null, null, $requestedArgs, $params, $anchor) . '"' . $allExtra . '>' . $i . '</a> '; } } if ($page < $pageCount) { $params[$paramName] = $page + 1; $value .= '<a href="' . $this->_request->url(null, null, null, $requestedArgs, $params, $anchor) . '"' . $allExtra . '>></a> '; $params[$paramName] = $pageCount; $value .= '<a href="' . $this->_request->url(null, null, null, $requestedArgs, $params, $anchor) . '"' . $allExtra . '>>></a> '; } return $value; } /** * Convert the parameters of a function to an array. */ public function smartyToArray() { return func_get_args(); } /** * Concatenate the parameters and return the result. */ public function smartyConcat() { $args = func_get_args(); return implode('', $args); } /** * Compare the parameters. * * @param mixed $a Parameter A * @param mixed $a Parameter B * @param bool $strict True iff a strict (===) compare should be used * @param bool $invert True iff the output should be inverted */ public function smartyCompare($a, $b, $strict = false, $invert = false) { $result = $strict ? $a === $b : $a == $b; return $invert ? !$result : $result; } /** * Convert a string to a numeric time. */ public function smartyStrtotime($string) { return strtotime($string); } /** * Split the supplied string by the supplied separator. */ public function smartyExplode($string, $separator) { return explode($separator, $string); } /** * Override the built-in smarty escape modifier to * add the jqselector escaping method. */ public function smartyEscape($string, $esc_type = 'html', $char_set = 'ISO-8859-1') { $pattern = "/(:|\.|\[|\]|,|=|@)/"; $replacement = '\\\\\\\$1'; switch ($esc_type) { // Because jQuery uses CSS syntax for selecting elements // some characters are interpreted as CSS notation. // In order to tell jQuery to treat these characters literally rather // than as CSS notation, they must be escaped by placing two backslashes // in front of them. case 'jqselector': $result = smarty_modifier_escape($string, 'html', $char_set); $result = preg_replace($pattern, $replacement, $result); return $result; case 'jsid': $result = smarty_modifier_escape($string, 'javascript', $char_set); $result = preg_replace($pattern, $replacement, $result); return $result; default: return smarty_modifier_escape($string, $esc_type, $char_set); } } /** * Smarty usage: {load_url_in_el el="htmlElement" id="someHtmlId" url="http://the.url.to.be.loaded.into.the.grid"} * * Custom Smarty function for loading a URL via AJAX into any HTML element * * @param array $params associative array * @param Smarty $smarty * * @return string of HTML/Javascript */ public function smartyLoadUrlInEl($params, $smarty) { // Required Params if (!isset($params['el'])) { throw new Exception('el parameter is missing from load_url_in_el'); } if (!isset($params['url'])) { throw new Exception('url parameter is missing from load_url_in_el'); } if (!isset($params['id'])) { throw new Exception('id parameter is missing from load_url_in_el'); } $this->assign([ 'inEl' => $params['el'], 'inElUrl' => $params['url'], 'inElElId' => $params['id'], 'inElClass' => $params['class'] ?? null, 'inVueEl' => $params['inVueEl'] ?? null, 'refreshOn' => $params['refreshOn'] ?? null, ]); if (isset($params['placeholder'])) { $this->assign('inElPlaceholder', $params['placeholder']); } elseif (isset($params['loadMessageId'])) { $loadMessageId = $params['loadMessageId']; $this->assign('inElPlaceholder', __($loadMessageId, $params)); } else { $this->assign('inElPlaceholder', $this->fetch('common/loadingContainer.tpl')); } return $this->fetch('common/urlInEl.tpl'); } /** * Smarty usage: {load_url_in_div id="someHtmlId" url="http://the.url.to.be.loaded.into.the.grid"} * * Custom Smarty function for loading a URL via AJAX into a DIV. Convenience * wrapper for smartyLoadUrlInEl. * * @param array $params associative array * @param Smarty $smarty * * @return string of HTML/Javascript */ public function smartyLoadUrlInDiv($params, $smarty) { $params['el'] = 'div'; return $this->smartyLoadUrlInEl($params, $smarty); } /** * Smarty usage: {csrf} * * Custom Smarty function for inserting a CSRF token. * * @param array $params associative array * @param Smarty $smarty * * @return string of HTML */ public function smartyCSRF($params, $smarty) { $csrfToken = $this->_request->getSession()->getCSRFToken(); switch ($params['type'] ?? null) { case 'raw': return $csrfToken; case 'json': return json_encode($csrfToken); case 'html': default: return '<input type="hidden" name="csrfToken" value="' . htmlspecialchars($csrfToken) . '">'; } } /** * Smarty usage: {load_stylesheet context="frontend" stylesheets=$stylesheets} * * Custom Smarty function for printing stylesheets attached to a context. * * @param array $params associative array * @param Smarty $smarty * * @return string of HTML/Javascript */ public function smartyLoadStylesheet($params, $smarty) { if (empty($params['context'])) { $params['context'] = 'frontend'; } if (!SessionManager::isDisabled()) { $versionDao = DAORegistry::getDAO('VersionDAO'); /** @var VersionDAO $versionDao */ $appVersion = $versionDao->getCurrentVersion()->getVersionString(); } else { $appVersion = null; } $stylesheets = $this->getResourcesByContext($this->_styleSheets, $params['context']); ksort($stylesheets); $output = ''; foreach ($stylesheets as $priorityList) { foreach ($priorityList as $style) { if (!empty($style['inline'])) { $output .= '<style type="text/css">' . $style['style'] . '</style>'; } else { if ($appVersion && strpos($style['style'], '?') === false) { $style['style'] .= '?v=' . $appVersion; } $output .= '<link rel="stylesheet" href="' . $style['style'] . '" type="text/css" />'; } } } return $output; } /** * Inject default styles into a HTML galley * * Any styles assigned to the `htmlGalley` context will be injected into the * galley unless the galley already has an embedded CSS file. * * @param string $htmlContent The HTML file content * @param array $embeddedFiles Additional files embedded in this galley */ public function loadHtmlGalleyStyles($htmlContent, $embeddedFiles) { if (empty($htmlContent)) { return $htmlContent; } $hasEmbeddedStyle = false; foreach ($embeddedFiles as $embeddedFile) { if ($embeddedFile->getData('mimetype') === 'text/css') { $hasEmbeddedStyle = true; break; } } if ($hasEmbeddedStyle) { return $htmlContent; } $links = ''; $styles = $this->getResourcesByContext($this->_styleSheets, 'htmlGalley'); if (!empty($styles)) { ksort($styles); foreach ($styles as $priorityGroup) { foreach ($priorityGroup as $htmlStyle) { if (!empty($htmlStyle['inline'])) { $links .= '<style type="text/css">' . $htmlStyle['style'] . '</style>' . "\n"; } else { $links .= '<link rel="stylesheet" href="' . $htmlStyle['style'] . '" type="text/css">' . "\n"; } } } } return str_ireplace('<head>', '<head>' . "\n" . $links, $htmlContent); } /** * Smarty usage: {load_script context="backend" scripts=$scripts} * * Custom Smarty function for printing scripts attached to a context. * * @param array $params associative array * @param Smarty $smarty * * @return string of HTML/Javascript */ public function smartyLoadScript($params, $smarty) { if (empty($params['context'])) { $params['context'] = 'frontend'; } if (!SessionManager::isDisabled()) { $versionDao = DAORegistry::getDAO('VersionDAO'); /** @var VersionDAO $versionDao */ $appVersion = SessionManager::isDisabled() ? null : $versionDao->getCurrentVersion()->getVersionString(); } else { $appVersion = null; } $scripts = $this->getResourcesByContext($this->_javaScripts, $params['context']); ksort($scripts); $output = ''; foreach ($scripts as $priorityList) { foreach ($priorityList as $name => $data) { if ($data['inline']) { $output .= '<script type="text/javascript">' . $data['script'] . '</script>'; } else { if ($appVersion && strpos($data['script'], '?') === false) { $data['script'] .= '?v=' . $appVersion; } $output .= '<script src="' . $data['script'] . '" type="text/javascript"></script>'; } } } return $output; } /** * Smarty usage: {load_header context="frontend" headers=$headers} * * Custom Smarty function for printing scripts attached to a context. * * @param array $params associative array * @param Smarty $smarty * * @return string of HTML/Javascript */ public function smartyLoadHeader($params, $smarty) { if (empty($params['context'])) { $params['context'] = 'frontend'; } $headers = $this->getResourcesByContext($this->_htmlHeaders, $params['context']); ksort($headers); $output = ''; foreach ($headers as $priorityList) { foreach ($priorityList as $name => $data) { $output .= "\n" . $data['header']; } } return $output; } /** * Smarty usage: {load_menu name=$areaName path=$declaredMenuTemplatePath id=$id ulClass=$ulClass liClass=$liClass} * * Custom Smarty function for printing navigation menu areas attached to a context. * * @param array $params associative array * @param Smarty $smarty * * @return string of HTML/Javascript */ public function smartyLoadNavigationMenuArea($params, $smarty) { $areaName = $params['name']; $declaredMenuTemplatePath = $params['path'] ?? null; $currentContext = $this->_request->getContext(); $contextId = Application::CONTEXT_ID_NONE; if ($currentContext) { $contextId = $currentContext->getId(); } // Don't load menus for an area that's not registered by the active theme $themePlugins = PluginRegistry::getPlugins('themes'); if (empty($themePlugins)) { $themePlugins = PluginRegistry::loadCategory('themes', true); } /** @var ThemePlugin[] $themePlugins */ $activeThemeNavigationAreas = []; foreach ($themePlugins as $themePlugin) { if ($themePlugin->isActive()) { $areas = $themePlugin->getMenuAreas(); if (!in_array($areaName, $areas)) { return ''; } } } $menuTemplatePath = 'frontend/components/navigationMenu.tpl'; if (isset($declaredMenuTemplatePath)) { $menuTemplatePath = $declaredMenuTemplatePath; } $navigationMenuDao = DAORegistry::getDAO('NavigationMenuDAO'); /** @var NavigationMenuDAO $navigationMenuDao */ $output = ''; $navigationMenus = $navigationMenuDao->getByArea($contextId, $areaName)->toArray(); if (isset($navigationMenus[0])) { $navigationMenu = $navigationMenus[0]; Services::get('navigationMenu')->getMenuTree($navigationMenu); } $this->assign([ 'navigationMenu' => $navigationMenu, 'id' => $params['id'], 'ulClass' => $params['ulClass'] ?? '', 'liClass' => $params['liClass'] ?? '', ]); return $this->fetch($menuTemplatePath); } /** * Get resources assigned to a context * * A helper function which retrieves script, style and header assets * assigned to a particular context. * * @param array $resources Requested resources * @param string $context Requested context * * @return array Resources assigned to these contexts */ public function getResourcesByContext($resources, $context) { $matches = []; if (array_key_exists($context, $resources)) { $matches = $resources[$context]; } $page = $this->getTemplateVars('requestedPage'); $page = empty($page) ? 'index' : $page; $op = $this->getTemplateVars('requestedOp'); $op = empty($op) ? 'index' : $op; $contexts = [ join('-', [$context, $page]), join('-', [$context, $page, $op]), ]; foreach ($contexts as $context) { if (array_key_exists($context, $resources)) { foreach ($resources[$context] as $priority => $priorityList) { if (!array_key_exists($priority, $matches)) { $matches[$priority] = []; } $matches[$priority] = array_merge($matches[$priority], $resources[$context][$priority]); } $matches += $resources[$context]; } } return $matches; } /** * Smarty usage: {pluck_files files=$availableFiles by="chapter" value=$chapterId} * * Custom Smarty function for plucking files from the array of $availableFiles * related to a submission. Intended to be used on the frontend * * @param array $params associative array * @param Smarty $smarty */ public function smartyPluckFiles($params, $smarty) { // The variable to assign the result to. if (empty($params['assign'])) { error_log('Smarty: {pluck_files} function called without required `assign` param. Called in ' . __FILE__ . ':' . __LINE__); return; } // $params['files'] should be an array of SubmissionFile objects if (!is_array($params['files'])) { error_log('Smarty: {pluck_files} function called without required `files` param. Called in ' . __FILE__ . ':' . __LINE__); $smarty->assign($params['assign'], []); return; } // $params['by'] is one of an approved list of attributes to select by if (empty($params['by'])) { error_log('Smarty: {pluck_files} function called without required `by` param. Called in ' . __FILE__ . ':' . __LINE__); $smarty->assign($params['assign'], []); return; } // The approved list of `by` attributes // chapter Any files assigned to a chapter ID. A value of `any` will return files assigned to any chapter. A value of 0 will return files not assigned to chapter // publicationFormat Any files in a given publicationFormat ID // genre Any files with a genre ID (file genres are configurable but typically refer to Manuscript, Bibliography, etc) if (!in_array($params['by'], ['chapter','publicationFormat','fileExtension','genre'])) { error_log('Smarty: {pluck_files} function called without a valid `by` param. Called in ' . __FILE__ . ':' . __LINE__); $smarty->assign($params['assign'], []); return; } // The value to match against. See docs for `by` param if (!isset($params['value'])) { error_log('Smarty: {pluck_files} function called without required `value` param. Called in ' . __FILE__ . ':' . __LINE__); $smarty->assign($params['assign'], []); return; } $matching_files = []; $genreDao = DAORegistry::getDAO('GenreDAO'); /** @var GenreDAO $genreDao */ foreach ($params['files'] as $file) { switch ($params['by']) { case 'chapter': $genre = $genreDao->getById($file->getGenreId()); if (!$genre->getDependent() && method_exists($file, 'getChapterId')) { if ($params['value'] === 'any' && $file->getChapterId()) { $matching_files[] = $file; } elseif ($file->getChapterId() == $params['value']) { $matching_files[] = $file; } elseif ($params['value'] == 0 && !$file->getChapterId()) { $matching_files[] = $file; } } break; case 'publicationFormat': if ($file->getData('assocId') == $params['value']) { $matching_files[] = $file; } break; case 'genre': if ($file->getGenreId() == $params['value']) { $matching_files[] = $file; } break; } } $smarty->assign($params['assign'], $matching_files); } /** * Get the direction of a locale * * @param array $params * @param TemplateManager $smarty */ public function smartyLocaleDirection($params, $smarty) { $locale = empty($params['locale']) ? Locale::getLocale() : $params['locale']; return Locale::getMetadata($locale)?->isRightToLeft() ? 'rtl' : 'ltr'; } /** * Smarty usage: {html_select_date_a11y legend="Published After" prefix="dateFrom" time=$dateFrom start_year=$yearStart end_year=$yearEnd} * * Get a fieldset of select fields to select a date * * Mimics basic features of Smarty's html_select_date function but * gives each select field a label and returns all fields within * a fieldset in order to be accessible. * * @param array $params * @param TemplateManager $smarty * * @return string */ public function smartyHtmlSelectDateA11y($params, $smarty) { if (!isset($params['prefix'], $params['legend'], $params['start_year'], $params['end_year'])) { throw new Exception('You must provide a prefix, legend, start_year and end_year when using html_select_date_a11y.'); } $prefix = $params['prefix']; $legend = $params['legend']; $time = $params['time'] ?? ''; $startYear = $params['start_year']; $endYear = $params['end_year']; $yearEmpty = $params['year_empty'] ?? ''; $monthEmpty = $params['month_empty'] ?? ''; $dayEmpty = $params['day_empty'] ?? ''; $yearLabel = $params['year_label'] ?? __('common.year'); $monthLabel = $params['month_label'] ?? __('common.month'); $dayLabel = $params['day_label'] ?? __('common.day'); $years = []; $i = $startYear; while ($i <= $endYear) { $years[$i] = $i; $i++; } $months = []; for ($i = 1; $i <= 12; $i++) { $months[$i] = date('M', strtotime('2020-' . $i . '-01')); } $days = []; for ($i = 1; $i <= 31; $i++) { $days[$i] = $i; } $currentYear = $currentMonth = $currentDay = ''; if ($time) { $currentYear = (int) substr($time, 0, 4); $currentMonth = (int) substr($time, 5, 2); $currentDay = (int) substr($time, 8, 2); } $output = '<fieldset><legend>' . $legend . '</legend>'; $output .= '<label for="' . $prefix . 'Year">' . $yearLabel . '</label>'; $output .= '<select id="' . $prefix . 'Year" name="' . $prefix . 'Year">'; $output .= '<option>' . $yearEmpty . '</option>'; foreach ($years as $value => $label) { $selected = $currentYear === $value ? ' selected' : ''; $output .= '<option value="' . $value . '"' . $selected . '>' . $label . '</option>'; } $output .= '</select>'; $output .= '<label for="' . $prefix . 'Month">' . $monthLabel . '</label>'; $output .= '<select id="' . $prefix . 'Month" name="' . $prefix . 'Month">'; $output .= '<option>' . $monthEmpty . '</option>'; foreach ($months as $value => $label) { $selected = $currentMonth === $value ? ' selected' : ''; $output .= '<option value="' . $value . '"' . $selected . '>' . $label . '</option>'; } $output .= '</select>'; $output .= '<label for="' . $prefix . 'Day">' . $dayLabel . '</label>'; $output .= '<select id="' . $prefix . 'Day" name="' . $prefix . 'Day">'; $output .= '<option>' . $dayEmpty . '</option>'; foreach ($days as $value => $label) { $selected = $currentDay === $value ? ' selected' : ''; $output .= '<option value="' . $value . '"' . $selected . '>' . $label . '</option>'; } $output .= '</select>'; $output .= '</fieldset>'; return $output; } /** * Defines the HTTP headers which will be appended to the output once the display() method gets called * * @param string[] List of formatted headers (['header: content', ...]) */ public function setHeaders(array $headers): static { $this->headers = $headers; return $this; } /** * Retrieves the headers * * @return string[] */ public function getHeaders(): array { return $this->headers; } } if (!PKP_STRICT_MODE) { class_alias('\PKP\template\PKPTemplateManager', '\PKPTemplateManager'); foreach ([ 'CACHEABILITY_NO_CACHE', 'CACHEABILITY_NO_STORE', 'CACHEABILITY_PUBLIC', 'CACHEABILITY_MUST_REVALIDATE', 'CACHEABILITY_PROXY_REVALIDATE', 'STYLE_SEQUENCE_CORE', 'STYLE_SEQUENCE_NORMAL', 'STYLE_SEQUENCE_LATE', 'STYLE_SEQUENCE_LAST', 'CSS_FILENAME_SUFFIX', 'PAGE_WIDTH_NARROW', 'PAGE_WIDTH_NORMAL', 'PAGE_WIDTH_WIDE', 'PAGE_WIDTH_FULL', ] as $constantName) { define($constantName, constant('\PKPTemplateManager::' . $constantName)); } }