diff --git a/.github/actions/tests.sh b/.github/actions/tests.sh index 2482793..1dd05e7 100755 --- a/.github/actions/tests.sh +++ b/.github/actions/tests.sh @@ -2,8 +2,4 @@ set -e -npx cypress run --spec "cypress/tests/data/10-Installation.spec.js,cypress/tests/data/20-CreateContext.spec.js" - -npx cypress run --headless --browser chrome --config '{"specPattern":["plugins/generic/customHeader/cypress/tests/functional/*.cy.js"]}' - - +npx cypress run --config specPattern=plugins/generic/customHeader/cypress/tests/functional diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b7589e8..71aca35 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -on: [push] +on: [push, pull_request] name: customHeader jobs: customHeader: @@ -43,12 +43,12 @@ jobs: - application: ops php-version: 8.2 database: pgsql - name: customHeader steps: - uses: pkp/pkp-github-actions@v1 with: - node_version: 20 - branch: main - repository: pkp - plugin: true + node_version: 20 + branch: main + repository: pkp + plugin: true + dataset_inject: true diff --git a/CustomHeaderPlugin.php b/CustomHeaderPlugin.php index 91a859c..bf27628 100644 --- a/CustomHeaderPlugin.php +++ b/CustomHeaderPlugin.php @@ -3,8 +3,8 @@ /** * @file CustomHeaderPlugin.php * - * Copyright (c) 2013-2023 Simon Fraser University - * Copyright (c) 2003-2023 John Willinsky + * Copyright (c) 2013-2025 Simon Fraser University + * Copyright (c) 2003-2025 John Willinsky * Distributed under the GNU GPL v3. For full terms see the file LICENSE. * * @brief CustomHeader plugin class @@ -12,145 +12,157 @@ namespace APP\plugins\generic\customHeader; -use PKP\plugins\GenericPlugin; +use APP\core\Application; +use Exception; use PKP\config\Config; -use PKP\plugins\Hook; +use PKP\core\JSONMessage; +use PKP\core\PKPApplication; use PKP\linkAction\LinkAction; use PKP\linkAction\request\AjaxModal; -use PKP\core\JSONMessage; -use APP\i18n\AppLocale; -use APP\template\TemplateManager; -use APP\core\Application; - -class CustomHeaderPlugin extends GenericPlugin { - /** @var bool Whether or not the header has been injected */ - var $injected = false; - - /** - * @copydoc Plugin::register() - */ - function register($category, $path, $mainContextId = null) { - $success = parent::register($category, $path, $mainContextId); - if (!Config::getVar('general', 'installed') || defined('RUNNING_UPGRADE')) return true; - if ($success && $this->getEnabled()) { - // Insert CustomHeader page tag to page header - Hook::add('TemplateManager::display', array($this, 'displayTemplateHook')); - // Insert custom script to the page footer - Hook::add('Templates::Common::Footer::PageFooter', array($this, 'insertFooter')); - } - return $success; - } - - /** - * Get the plugin display name. - * @return string - */ - function getDisplayName() { - return __('plugins.generic.customHeader.displayName'); - } - - /** - * Get the plugin description. - * @return string - */ - function getDescription() { - return __('plugins.generic.customHeader.description'); - } - - /** - * @copydoc Plugin::getActions() - */ - function getActions($request, $verb) { - $router = $request->getRouter(); - return array_merge( - $this->getEnabled()?array( - new LinkAction( - 'settings', - new AjaxModal( - $router->url($request, null, null, 'manage', null, array('verb' => 'settings', 'plugin' => $this->getName(), 'category' => 'generic')), - $this->getDisplayName() - ), - __('manager.plugins.settings'), - null - ), - ):array(), - parent::getActions($request, $verb) - ); - } - - /** - * @copydoc Plugin::manage() - */ - function manage($args, $request) { - switch ($request->getUserVar('verb')) { - case 'settings': - $context = $request->getContext(); - - $templateMgr = TemplateManager::getManager($request); - $form = new CustomHeaderSettingsForm($this, $context?$context->getId():CONTEXT_ID_NONE); - - if ($request->getUserVar('save')) { - $form->readInputData(); - if ($form->validate()) { - $form->execute(); - return new JSONMessage(true); - } - } else { - $form->initData(); - } - return new JSONMessage(true, $form->fetch($request)); - } - return parent::manage($args, $request); - } - - /** - * Register the CustomHeader script tag - * @param $hookName string - * @param $params array - */ - function displayTemplateHook($hookName, $params) { - if (!$this->injected) { - $this->injected = true; - $templateMgr =& $params[0]; - $request = Application::get()->getRequest(); - $context = $request->getContext(); - $templateMgr->addHeader('custom', $this->getSetting($context?$context->getId():CONTEXT_ID_NONE, 'content')); - $templateMgr->addHeader('custombackend', $this->getSetting($context?$context->getId():CONTEXT_ID_NONE, 'backendContent'), ['contexts' => ['backend']]); - } - return false; - } - - /** - * Add custom footer to the page - * - * @param $hookName string - * @param $params array - */ - function insertFooter($hookName, $params) { - $templateMgr =& $params[0]; - $output =& $params[2]; - $request = Application::get()->getRequest(); - $context = $request->getContext(); - - $output .= $this->getSetting($context?$context->getId():CONTEXT_ID_NONE, 'footerContent'); - - return false; - } - - /** - * This plugin can be used site-wide or in a specific context. The - * isSitePlugin check is used to grant access to different users, so this - * plugin must return true only if the user is currently in the site-wide - * context. - * - * @see PluginGridRow::_canEdit() - * @return boolean - */ - function isSitePlugin() { - return !(Application::get()->getRequest()->getContext()); - } -} +use PKP\plugins\GenericPlugin; +use PKP\plugins\Hook; -if (!PKP_STRICT_MODE) { - class_alias('\APP\plugins\generic\customHeader\CustomHeaderPlugin', '\CustomHeaderPlugin'); +class CustomHeaderPlugin extends GenericPlugin +{ + /** Whether the header has been injected */ + public bool $injected = false; + + /** + * @copydoc Plugin::register() + * @throws Exception + */ + public function register($category, $path, $mainContextId = null): bool + { + $success = parent::register($category, $path, $mainContextId); + if (!Config::getVar('general', 'installed') || defined('RUNNING_UPGRADE')) { + return true; + } + if ($success && $this->getEnabled()) { + // Insert CustomHeader page tag to page header + Hook::add('TemplateManager::display', array($this, 'displayTemplateHook')); + // Insert custom script to the page footer + Hook::add('Templates::Common::Footer::PageFooter', array($this, 'insertFooter')); + } + return $success; + } + + /** + * Get the plugin display name. + */ + public function getDisplayName(): string + { + return __('plugins.generic.customHeader.displayName'); + } + + /** + * Get the plugin description. + */ + public function getDescription(): string + { + return __('plugins.generic.customHeader.description'); + } + + /** + * @copydoc Plugin::getActions() + */ + public function getActions($request, $verb): array + { + $router = $request->getRouter(); + return array_merge( + $this->getEnabled() ? array( + new LinkAction( + 'settings', + new AjaxModal( + $router->url($request, null, null, 'manage', null, ['verb' => 'settings', 'plugin' => $this->getName(), 'category' => 'generic']), + $this->getDisplayName() + ), + __('manager.plugins.settings'), + null + ), + ) : array(), + parent::getActions($request, $verb) + ); + } + + /** + * @copydoc Plugin::manage() + * @throws Exception + */ + public function manage($args, $request) + { + switch ($request->getUserVar('verb')) { + case 'settings': + $context = $request->getContext(); + $form = new CustomHeaderSettingsForm( + $this, + $context ? $context->getId() : PKPApplication::SITE_CONTEXT_ID + ); + + if ($request->getUserVar('save')) { + $form->readInputData(); + if ($form->validate()) { + $form->execute(); + return new JSONMessage(true); + } + } else { + $form->initData(); + } + return new JSONMessage(true, $form->fetch($request)); + } + return parent::manage($args, $request); + } + + /** + * Register the CustomHeader script tag + */ + public function displayTemplateHook(string $hookName, array $params) + { + if (!$this->injected) { + $this->injected = true; + $templateMgr =& $params[0]; + $request = Application::get()->getRequest(); + $context = $request->getContext(); + $templateMgr->addHeader( + 'custom', + $this->getSetting($context ? $context->getId() : PKPApplication::SITE_CONTEXT_ID, 'content') + ); + $templateMgr->addHeader( + 'custombackend', + $this->getSetting( + $context ? $context->getId() : PKPApplication::SITE_CONTEXT_ID, + 'backendContent' + ), + ['contexts' => ['backend']] + ); + } + return false; + } + + /** + * Add custom footer to the page + */ + public function insertFooter(string $hookName, array $params) + { + $templateMgr =& $params[0]; + $output =& $params[2]; + $request = Application::get()->getRequest(); + $context = $request->getContext(); + + $output .= $this->getSetting($context ? $context->getId() : PKPApplication::SITE_CONTEXT_ID, 'footerContent'); + + return false; + } + + /** + * This plugin can be used site-wide or in a specific context. The + * isSitePlugin check is used to grant access to different users, so this + * plugin must return true only if the user is currently in the site-wide + * context. + * + * @see PluginGridRow::_canEdit() + */ + public function isSitePlugin(): bool + { + return !(Application::get()->getRequest()->getContext()); + } } diff --git a/CustomHeaderSettingsForm.php b/CustomHeaderSettingsForm.php index 1a0bba1..f3164d1 100644 --- a/CustomHeaderSettingsForm.php +++ b/CustomHeaderSettingsForm.php @@ -3,8 +3,8 @@ /** * @file CustomHeaderSettingsForm.php * - * Copyright (c) 2013-2023 Simon Fraser University - * Copyright (c) 2003-2023 John Willinsky + * Copyright (c) 2013-2025 Simon Fraser University + * Copyright (c) 2003-2025 John Willinsky * Distributed under the GNU GPL v3. For full terms see the file LICENSE. * * @brief Form for managers to modify custom header plugin settings @@ -12,91 +12,97 @@ namespace APP\plugins\generic\customHeader; -use PKP\form\Form; +use DOMDocument; use APP\core\Application; -use APP\template\TemplateManager; use APP\notification\NotificationManager; +use APP\template\TemplateManager; +use PKP\form\Form; +use PKP\form\validation\FormValidator; +use PKP\form\validation\FormValidatorCSRF; +use PKP\form\validation\FormValidatorCustom; +use PKP\form\validation\FormValidatorPost; -class CustomHeaderSettingsForm extends Form { - - /** @var int */ - var $_contextId; - - /** @var object */ - var $_plugin; +class CustomHeaderSettingsForm extends Form +{ + public int $contextId; + public CustomHeaderPlugin $plugin; - /** - * Constructor - * @param $plugin CustomHeaderPlugin - * @param $contextId int - */ - function __construct($plugin, $contextId) { - $this->_contextId = $contextId; - $this->_plugin = $plugin; + /** + * Constructor + */ + public function __construct(CustomHeaderPlugin $plugin, int $contextId) + { + $this->contextId = $contextId; + $this->plugin = $plugin; - parent::__construct($plugin->getTemplateResource('settingsForm.tpl')); + parent::__construct($plugin->getTemplateResource('settingsForm.tpl')); - $this->addCheck(new \PKP\form\validation\FormValidatorCustom($this, 'backendContent', FORM_VALIDATOR_OPTIONAL_VALUE, 'plugins.generic.customHeader.backendContent.error', function ($backendContent) { return $this->validateWellFormed($backendContent); })); - $this->addCheck(new \PKP\form\validation\FormValidatorPost($this)); - $this->addCheck(new \PKP\form\validation\FormValidatorCSRF($this)); - } + $this->addCheck(new FormValidatorCustom($this, 'backendContent', FormValidator::FORM_VALIDATOR_OPTIONAL_VALUE, 'plugins.generic.customHeader.backendContent.error', function ($backendContent) { + return $this->validateWellFormed($backendContent); + })); + $this->addCheck(new FormValidatorPost($this)); + $this->addCheck(new FormValidatorCSRF($this)); + } - /** - * Initialize form data. - */ - function initData() { - $this->_data = array( - 'content' => $this->_plugin->getSetting($this->_contextId, 'content'), - 'backendContent' => $this->_plugin->getSetting($this->_contextId, 'backendContent'), - 'footerContent' => $this->_plugin->getSetting($this->_contextId, 'footerContent') - ); - } + /** + * Initialize form data. + */ + public function initData(): void + { + $this->_data = array( + 'content' => $this->plugin->getSetting($this->contextId, 'content'), + 'backendContent' => $this->plugin->getSetting($this->contextId, 'backendContent'), + 'footerContent' => $this->plugin->getSetting($this->contextId, 'footerContent') + ); + } - /** - * Assign form data to user-submitted data. - */ - function readInputData() { - $this->readUserVars(array('content', 'backendContent', 'footerContent')); - } + /** + * Assign form data to user-submitted data. + */ + public function readInputData(): void + { + $this->readUserVars(array('content', 'backendContent', 'footerContent')); + } - /** - * Fetch the form. - * @copydoc Form::fetch() - */ - function fetch($request, $template = null, $display = false) { - $templateMgr = TemplateManager::getManager($request); - $templateMgr->assign('pluginName', $this->_plugin->getName()); - return parent::fetch($request, $template, $display); - } + /** + * Fetch the form. + * @copydoc Form::fetch() + */ + public function fetch($request, $template = null, $display = false): null|string + { + $templateMgr = TemplateManager::getManager($request); + $templateMgr->assign('pluginName', $this->plugin->getName()); + return parent::fetch($request, $template, $display); + } - /** - * Save settings. - */ - function execute(...$functionArgs) { - parent::execute(...$functionArgs); + /** + * Save settings. + */ + public function execute(...$functionArgs) + { + parent::execute(...$functionArgs); - $request = Application::get()->getRequest(); - $this->_plugin->updateSetting($this->_contextId, 'content', $this->getData('content'), 'string'); - $this->_plugin->updateSetting($this->_contextId, 'backendContent', $this->getData('backendContent'), 'string'); - $this->_plugin->updateSetting($this->_contextId, 'footerContent', $this->getData('footerContent'), 'string'); - $notificationManager = new NotificationManager(); - $notificationManager->createTrivialNotification($request->getUser()->getId(), NOTIFICATION_TYPE_SUCCESS); - } + $request = Application::get()->getRequest(); + $this->plugin->updateSetting($this->contextId, 'content', $this->getData('content'), 'string'); + $this->plugin->updateSetting($this->contextId, 'backendContent', $this->getData('backendContent'), 'string'); + $this->plugin->updateSetting($this->contextId, 'footerContent', $this->getData('footerContent'), 'string'); + $notificationManager = new NotificationManager(); + $notificationManager->createTrivialNotification($request->getUser()->getId()); + } - /** - * Validate that the input is well-formed XML - * We want to avoid breaking the whole HTML page with an unclosed HTML attribute quote or tag - * @param $input string - * @return boolean - */ - function validateWellFormed($input) { - $libxml_errors_setting = libxml_use_internal_errors(); - libxml_use_internal_errors(true); - libxml_clear_errors(); - $dom = new DOMDocument(); - $dom->loadHTML($input); - $isWellFormed = count(libxml_get_errors())==0; - libxml_use_internal_errors($libxml_errors_setting); - return $isWellFormed; - } + /** + * Validate that the input is well-formed XML + * We want to avoid breaking the whole HTML page with an unclosed HTML attribute quote or tag + */ + public function validateWellFormed(string $input): bool + { + $libxml_errors_setting = libxml_use_internal_errors(); + libxml_use_internal_errors(true); + libxml_clear_errors(); + $dom = new DOMDocument(); + $dom->loadHTML($input); + $isWellFormed = count(libxml_get_errors()) == 0; + libxml_use_internal_errors($libxml_errors_setting); + return $isWellFormed; + } } diff --git a/README.md b/README.md index 9157d18..4c6c1ec 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ Custom Header Plugin ==================== -| main |3.4.0 | 3.3.0 | -|--------- | --- | ---- | -| [](https://github.com/pkp/customHeader/actions/workflows/main.yml) | | [](https://github.com/pkp/customHeader/actions/workflows/stable-3_3_0.yml) | +| main | 3.4.0 | 3.3.0 | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [](https://github.com/pkp/customHeader/actions/workflows/main.yml) | | [](https://github.com/pkp/customHeader/actions/workflows/stable-3_3_0.yml) | This is a plugin adding custom headers content to the webpage. diff --git a/cypress/tests/functional/CustomHeader.cy.js b/cypress/tests/functional/CustomHeader.cy.js index 1538dd4..f4c3ea9 100644 --- a/cypress/tests/functional/CustomHeader.cy.js +++ b/cypress/tests/functional/CustomHeader.cy.js @@ -1,33 +1,36 @@ /** * @file cypress/tests/functional/CustomHeader.cy.js * - * Copyright (c) 2014-2023 Simon Fraser University - * Copyright (c) 2000-2023 John Willinsky + * Copyright (c) 2014-2025 Simon Fraser University + * Copyright (c) 2000-2025 John Willinsky * Distributed under the GNU GPL v3. For full terms see the file LICENSE. * */ -describe('Custom Header plugin tests', function() { - it('Creates and exercises a custom header', function() { - cy.login('admin', 'admin', 'publicknowledge'); +describe('Custom Header plugin tests', function () { + it('Creates and exercises a custom header', function () { + cy.login('admin', 'admin', 'publicknowledge'); - cy.get('.app__nav a').contains('Website').click(); - cy.get('button[id="plugins-button"]').click(); + cy.get('nav').contains('Settings').click(); + // Ensure submenu item click despite animation + cy.get('nav').contains('Website').click({ force: true }); + cy.get('button[id="plugins-button"]').click(); - // Find and enable the plugin - cy.get('input[id^="select-cell-customheaderplugin-enabled"]').click(); - cy.get('div:contains(\'The plugin "Custom Header Plugin" has been enabled.\')'); - cy.waitJQuery(); + // Find and enable the plugin + cy.get('input[id^="select-cell-customheaderplugin-enabled"]').click(); + cy.get('div:contains(\'The plugin "Custom Header Plugin" has been enabled.\')'); + cy.reload(); - cy.get('tr[id*="customheaderplugin"] a.show_extras').click(); - cy.get('a[id*="customheaderplugin-settings"]').click(); - cy.waitJQuery(2000); // Wait for form to settle - cy.get('textarea[id^="headerContent-"]').type('', {delay: 0}); - cy.get('textarea[id^="footerContent-"]').type('Tweets by pkp', {delay: 0}); - cy.get('form[id="customHeaderSettingsForm"] button:contains("OK")').click(); - cy.get('div:contains("Your changes have been saved.")'); + cy.get('tr[id="component-grid-settings-plugins-settingsplugingrid-category-generic-row-customheaderplugin"] a.show_extras').click(); + cy.get('a[id^="component-grid-settings-plugins-settingsplugingrid-category-generic-row-customheaderplugin-settings-button"]').click(); + cy.waitJQuery(2000); // Wait for form to settle + cy.get('textarea[id^="headerContent-"]').type('', {delay: 0}); + cy.get('textarea[id^="footerContent-"]').type('Tweets by pkp', {delay: 0}); + cy.get('textarea[id^="backendContent-"]').type('Notice: this is a test', {delay: 0}); + cy.get('form[id="customHeaderSettingsForm"] button:contains("OK")').click(); + cy.get('div:contains("Your changes have been saved.")'); - cy.visit(''); - cy.get('iframe[id*="twitter-widget"]'); - }); + cy.visit(''); + cy.get('iframe[id*="twitter-widget"]'); + }); }) diff --git a/templates/settingsForm.tpl b/templates/settingsForm.tpl index 572bb62..49c26e9 100644 --- a/templates/settingsForm.tpl +++ b/templates/settingsForm.tpl @@ -1,8 +1,8 @@ {** * templates/settingsForm.tpl * - * Copyright (c) 2013-2023 Simon Fraser University - * Copyright (c) 2003-2023 John Willinsky + * Copyright (c) 2013-2025 Simon Fraser University + * Copyright (c) 2003-2025 John Willinsky * Distributed under the GNU GPL v3. For full terms see the file LICENSE. * * Plugin settings @@ -15,7 +15,7 @@ $('#customHeaderSettingsForm').pkpHandler('$.pkp.controllers.form.AjaxFormHandler'); {rdelim}); -
-