first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-06-08 17:09:23 -04:00
commit df3a033196
17887 changed files with 8637778 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
<?php
/**
* @file classes/plugins/BlockPlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class BlockPlugin
*
* @ingroup plugins
*
* @brief Abstract class for block plugins
*/
namespace PKP\plugins;
use PKP\core\PKPRequest;
abstract class BlockPlugin extends LazyLoadPlugin
{
//
// Override public methods from Plugin
//
/**
* Determine whether or not this plugin is currently enabled.
*
* @param int $contextId Context ID (journal/press)
*
* @return bool
*/
public function getEnabled($contextId = null)
{
return $this->getSetting(is_null($contextId) ? $this->getCurrentContextId() : $contextId, 'enabled');
}
/**
* Set whether or not this plugin is currently enabled.
*
* @param bool $enabled
* @param int $contextId Context ID (journal/press)
*/
public function setEnabled($enabled, $contextId = null)
{
$this->updateSetting(is_null($contextId) ? $this->getCurrentContextId() : $contextId, 'enabled', $enabled, 'bool');
}
/**
* Get the filename of the template block. (Default behavior may
* be overridden through some combination of this function and the
* getContents function.)
* Returning null from this function results in an empty display.
*
* @return string
*/
public function getBlockTemplateFilename()
{
return 'block.tpl';
}
/**
* Get the HTML contents for this block.
*
* @param object $templateMgr
* @param PKPRequest $request (Optional for legacy plugins)
*
* @return string
*/
public function getContents($templateMgr, $request = null)
{
$blockTemplateFilename = $this->getBlockTemplateFilename();
if ($blockTemplateFilename === null) {
return '';
}
return $templateMgr->fetch($this->getTemplateResource($blockTemplateFilename));
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\BlockPlugin', '\BlockPlugin');
}
+473
View File
@@ -0,0 +1,473 @@
<?php
/**
* @file classes/plugin/GalleryPlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class GalleryPlugin
*
* @ingroup plugins
*
* @brief Class describing a plugin in the Plugin Gallery.
*/
namespace PKP\plugins;
use PKP\core\Core;
use PKP\db\DAORegistry;
use PKP\site\Version;
use PKP\site\VersionDAO;
define('PLUGIN_GALLERY_STATE_AVAILABLE', 0);
define('PLUGIN_GALLERY_STATE_INCOMPATIBLE', 0);
define('PLUGIN_GALLERY_STATE_UPGRADABLE', 1);
define('PLUGIN_GALLERY_STATE_CURRENT', 2);
define('PLUGIN_GALLERY_STATE_NEWER', 3);
class GalleryPlugin extends \PKP\core\DataObject
{
/**
* Get the localized name of the plugin
*
* @param string $preferredLocale
*
* @return string
*/
public function getLocalizedName($preferredLocale = null)
{
return $this->getLocalizedData('name', $preferredLocale);
}
/**
* Set the name of the plugin
*
* @param string $name
* @param string $locale optional
*/
public function setName($name, $locale = null)
{
$this->setData('name', $name, $locale);
}
/**
* Get the name of the plugin
*
* @param string $locale optional
*
* @return string
*/
public function getName($locale = null)
{
return $this->getData('name', $locale);
}
/**
* Get the homepage for this plugin
*
* @return string
*/
public function getHomepage()
{
return $this->getData('homepage');
}
/**
* Set the homepage for this plugin
*
* @param string $homepage
*/
public function setHomepage($homepage)
{
$this->setData('homepage', $homepage);
}
/**
* Get the product (symbolic name) for this plugin
*
* @return string
*/
public function getProduct()
{
return $this->getData('product');
}
/**
* Set the product (symbolic name) for this plugin
*
* @param string $product
*/
public function setProduct($product)
{
$this->setData('product', $product);
}
/**
* Get the category for this plugin
*
* @return string
*/
public function getCategory()
{
return $this->getData('category');
}
/**
* Set the category for this plugin
*
* @param string $category
*/
public function setCategory($category)
{
$this->setData('category', $category);
}
/**
* Get the newest compatible version of this plugin
*
* @param bool $pad True iff returned version numbers should be
* padded to 4 terms, e.g. 1.0.0.0 instead of just 1.0
*
* @return string
*/
public function getVersion($pad = false)
{
$version = $this->getData('version');
if ($pad) {
// Ensure there are 4 terms (3 separators)
$separators = substr_count($version, '.');
if ($separators < 3) {
$version .= str_repeat('.0', 3 - $separators);
}
}
return $version;
}
/**
* Set the version for this plugin
*
* @param string $version
*/
public function setVersion($version)
{
$this->setData('version', $version);
}
/**
* Get the release date of this plugin
*
* @return int
*/
public function getDate()
{
return $this->getData('date');
}
/**
* Set the release date for this plugin
*
* @param int $date
*/
public function setDate($date)
{
$this->setData('date', $date);
}
/**
* Get the contact name for this plugin
*
* @return string
*/
public function getContactName()
{
return $this->getData('contactName');
}
/**
* Set the contact name for this plugin
*
* @param string $contactName
*/
public function setContactName($contactName)
{
$this->setData('contactName', $contactName);
}
/**
* Get the contact institution name for this plugin
*
* @return string
*/
public function getContactInstitutionName()
{
return $this->getData('contactInstitutionName');
}
/**
* Set the contact institution name for this plugin
*
* @param string $contactInstitutionName
*/
public function setContactInstitutionName($contactInstitutionName)
{
$this->setData('contactInstitutionName', $contactInstitutionName);
}
/**
* Get the contact email for this plugin
*
* @return string
*/
public function getContactEmail()
{
return $this->getData('contactEmail');
}
/**
* Set the contact email for this plugin
*
* @param string $contactEmail
*/
public function setContactEmail($contactEmail)
{
$this->setData('contactEmail', $contactEmail);
}
/**
* Get plugin summary.
*
* @param string $locale optional
*
* @return string
*/
public function getSummary($locale = null)
{
return $this->getData('summary', $locale);
}
/**
* Set plugin summary.
*
* @param string $summary
* @param string $locale optional
*/
public function setSummary($summary, $locale = null)
{
$this->setData('summary', $summary, $locale);
}
/**
* Get plugin description.
*
* @param string $locale optional
*
* @return string
*/
public function getDescription($locale = null)
{
return $this->getData('description', $locale);
}
/**
* Set plugin description.
*
* @param string $description
* @param string $locale optional
*/
public function setDescription($description, $locale = null)
{
$this->setData('description', $description, $locale);
}
/**
* Get plugin installation instructions.
*
* @param string $locale optional
*
* @return string
*/
public function getInstallationInstructions($locale = null)
{
return $this->getData('installation', $locale);
}
/**
* Set plugin installation instructions.
*
* @param string $installation
* @param string $locale optional
*/
public function setInstallationInstructions($installation, $locale = null)
{
$this->setData('installation', $installation, $locale);
}
/**
* Get release description.
*
* @param string $locale optional
*
* @return string
*/
public function getReleaseDescription($locale = null)
{
return $this->getData('releaseDescription', $locale);
}
/**
* Set plugin release description.
*
* @param string $releaseDescription
* @param string $locale optional
*/
public function setReleaseDescription($releaseDescription, $locale = null)
{
$this->setData('releaseDescription', $releaseDescription, $locale);
}
/**
* Get release MD5 checksum.
*
* @return string
*/
public function getReleaseMD5()
{
return $this->getData('releaseMD5');
}
/**
* Set plugin release MD5.
*
* @param string $releaseMD5
*/
public function setReleaseMD5($releaseMD5)
{
$this->setData('releaseMD5', $releaseMD5);
}
/**
* Get the certifications for this plugin release
*
* @return array
*/
public function getReleaseCertifications()
{
return $this->getData('releaseCertifications');
}
/**
* Set the certifications for this plugin release
*
* @param array $certifications
*/
public function setReleaseCertifications($certifications)
{
$this->setData('releaseCertifications', $certifications);
}
/**
* Get the package URL for this plugin release
*
* @return string
*/
public function getReleasePackage()
{
return $this->getData('releasePackage');
}
/**
* Set the package URL for this plugin release
*/
public function setReleasePackage($releasePackage)
{
$this->setData('releasePackage', $releasePackage);
}
/**
* Get the localized summary of the plugin.
*
* @return string
*/
public function getLocalizedSummary()
{
return $this->getLocalizedData('summary');
}
/**
* Get the localized installation instructions of the plugin.
*
* @return string
*/
public function getLocalizedInstallationInstructions()
{
return $this->getLocalizedData('installation');
}
/**
* Get the localized description of the plugin.
*
* @return string
*/
public function getLocalizedDescription()
{
return $this->getLocalizedData('description');
}
/**
* Get the localized release description of the plugin.
*
* @return string
*/
public function getLocalizedReleaseDescription()
{
return $this->getLocalizedData('releaseDescription');
}
/**
* Determine the version of this plugin that is currently installed,
* if any
*
* @return Version|null
*/
public function getInstalledVersion()
{
$versionDao = DAORegistry::getDAO('VersionDAO'); /** @var VersionDAO $versionDao */
return $versionDao->getCurrentVersion('plugins.' . $this->getCategory(), $this->getProduct());
}
/**
* Get the current state of the gallery plugin with respect to this
* installation.
*
* @return int PLUGIN_GALLERY_STATE_...
*/
public function getCurrentStatus()
{
$installedVersion = $this->getInstalledVersion();
if ($this->getVersion() === null) {
return PLUGIN_GALLERY_STATE_INCOMPATIBLE;
}
if (!$installedVersion) {
return PLUGIN_GALLERY_STATE_AVAILABLE;
}
if ($installedVersion->compare($this->getVersion(true)) > 0) {
return PLUGIN_GALLERY_STATE_NEWER;
}
if ($installedVersion->compare($this->getVersion(true)) < 0) {
return PLUGIN_GALLERY_STATE_UPGRADABLE;
}
$targetPath = Core::getBaseDir() . '/plugins/' . $this->getCategory() . '/' . $this->getProduct();
if (!is_dir($targetPath)) {
return PLUGIN_GALLERY_STATE_UPGRADABLE;
}
return PLUGIN_GALLERY_STATE_CURRENT;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\GalleryPlugin', '\GalleryPlugin');
}
+98
View File
@@ -0,0 +1,98 @@
<?php
/**
* @file classes/plugins/GatewayPlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class GatewayPlugin
*
* @ingroup plugins
*
* @brief Abstract class for gateway plugins
*/
namespace PKP\plugins;
use APP\core\Application;
use PKP\core\PKPRequest;
abstract class GatewayPlugin extends Plugin
{
/**
* Handle fetch requests for this plugin.
*
* @param array $args
* @param object $request
*/
abstract public function fetch($args, $request);
/**
* Determine whether the plugin can be enabled.
*
* @return bool
*/
public function getCanEnable()
{
return true;
}
/**
* Determine whether the plugin can be disabled.
*
* @return bool
*/
public function getCanDisable()
{
return true;
}
/**
* Determine whether or not this plugin is currently enabled.
*
* @return bool
*/
public function getEnabled()
{
return $this->getSetting($this->getCurrentContextId(), 'enabled');
}
/**
* Set whether or not this plugin is currently enabled.
*
* @param bool $enabled
*/
public function setEnabled($enabled)
{
$this->updateSetting($this->getCurrentContextId(), 'enabled', $enabled, 'bool');
}
/**
* Get the current context ID or the site-wide context ID (0) if no context
* can be found.
*/
public function getCurrentContextId()
{
$context = Application::get()->getRequest()->getContext();
return is_null($context) ? 0 : $context->getId();
}
/**
* Get policies to the authorization process
*
* @param PKPRequest $request
*
* @return array Set of authorization policies
*/
public function getPolicies($request)
{
return [];
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\GatewayPlugin', '\GatewayPlugin');
}
+25
View File
@@ -0,0 +1,25 @@
<?php
/**
* @file classes/plugins/GenericPlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class GenericPlugin
*
* @ingroup plugins
*
* @brief Abstract class for generic plugins
*/
namespace PKP\plugins;
abstract class GenericPlugin extends LazyLoadPlugin
{
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\GenericPlugin', '\GenericPlugin');
}
+204
View File
@@ -0,0 +1,204 @@
<?php
/**
* @file classes/plugins/Hook.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 Hook
*
* @ingroup plugins
*
* @brief Class implementing a registry of hooks for extending core functionality
*/
namespace PKP\plugins;
use PKP\core\Registry;
class Hook
{
public const SEQUENCE_CORE = 0;
public const SEQUENCE_NORMAL = 256;
public const SEQUENCE_LATE = 512;
public const SEQUENCE_LAST = 768;
public const CONTINUE = false;
public const ABORT = true;
/**
* Get the current set of hook registrations.
*
* @param string $hookName Name of hook to optionally return
*
* @return mixed Array of all hooks or just those attached to $hookName, or
* null if nothing has been attached to $hookName
*/
public static function &getHooks(?string $hookName = null): ?array
{
$hooks = & Registry::get('hooks', true, []);
if ($hookName === null) {
return $hooks;
}
if (isset($hooks[$hookName])) {
return $hooks[$hookName];
}
$returner = null;
return $returner;
}
/**
* Clear hooks registered against the given name.
*/
public static function clear(string $hookName): void
{
$hooks = & static::getHooks();
unset($hooks[$hookName]);
}
/**
* Register a hook against the given hook name.
*
* @param string $hookName Name of hook to register against
* @param object|array $callback Callback pseudo-type
* @param int $hookSequence Optional hook sequence specifier SEQUENCE_...
*/
public static function add(string $hookName, callable $callback, int $hookSequence = self::SEQUENCE_NORMAL): void
{
$hooks = & static::getHooks();
$hooks[$hookName][$hookSequence][] = $callback;
}
/**
* Alias of Hook::add
*
* @deprecated 3.4
*/
public static function register(string $hookName, callable $callback, int $hookSequence = self::SEQUENCE_NORMAL): void
{
self::add($hookName, $callback, $hookSequence);
}
/**
* Call each callback registered against $hookName in sequence.
* The first callback that returns ABORT will interrupt processing and
* this function will return ABORT; otherwise, all callbacks will be
* called in sequence and the return value of this call will be
* CONTINUE.
*
* The signature of the callback function should be:
* function callback($hookName, $args) : bool;
* ...and $args will receive the array provided here.
*
* This function should be considered deprecated in favour of
* Hook::call.
*/
public static function call(string $hookName, array $args = []): mixed
{
// Called only by Unit Test
// This behaviour is DEPRECATED and not replicated in the preferred
// Hook::call function.
if (self::rememberCalledHooks(true)) {
// Remember the called hooks for testing.
$calledHooks = & static::getCalledHooks();
$calledHooks[] = [
$hookName, $args
];
}
return self::run($hookName, [$args]);
}
/**
* Call each callback registered against $hookName in sequence.
* The first callback that returns ABORT will interrupt processing and
* this function will return ABORT; otherwise, all callbacks will be
* called in sequence and the return value of this call will be
* CONTINUE.
*
* The signature of a callback function should be:
* function callback($hookName, ...) : bool;
* where ... corresponds to the parameters named/listed in the $args
* parameter to Hook::call. These may be named if desired,
* and may include references.
*/
public static function run(string $hookName, $args): bool
{
$hooks = & static::getHooks();
if (!isset($hooks[$hookName])) {
return self::CONTINUE;
}
ksort($hooks[$hookName], SORT_NUMERIC);
foreach ($hooks[$hookName] as $priority => $hookList) {
foreach ($hookList as $callback) {
if (call_user_func_array($callback, array_merge([$hookName], $args)) === self::ABORT) {
return self::ABORT;
}
}
}
return self::CONTINUE;
}
//
// Methods required for testing only.
//
/**
* Set/query the flag that triggers storing of
* called hooks.
*
* @param bool $askOnly When set to true, the flag will not
* be changed but only returned.
* @param bool $updateTo When $askOnly is set to 'true' then
* this parameter defines the value of the flag.
*
* @return bool The current value of the flag.
*/
public static function rememberCalledHooks(bool $askOnly = false, bool $updateTo = true): bool
{
static $rememberCalledHooks = false;
if (!$askOnly) {
$rememberCalledHooks = $updateTo;
}
return $rememberCalledHooks;
}
/**
* Switch off the function to store hooks and delete all stored hooks.
* Always call this after using otherwise we get a severe memory.
*
* @param bool $leaveAlive Set this to true if you only want to
* delete hooks stored so far but if you want to record future
* hook calls, too.
*/
public static function resetCalledHooks(bool $leaveAlive = false): void
{
if (!$leaveAlive) {
static::rememberCalledHooks(false, false);
}
$calledHooks = & static::getCalledHooks();
$calledHooks = [];
}
/**
* Return a reference to the stored hooks.
*/
public static function &getCalledHooks(): array
{
static $calledHooks = [];
return $calledHooks;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\Hook', '\HookRegistry');
foreach (['SEQUENCE_CORE', 'SEQUENCE_NORMAL', 'SEQUENCE_LATE', 'SEQUENCE_LAST'] as $constantName) {
define('HOOK_' . $constantName, constant('\HookRegistry::' . $constantName));
}
}
@@ -0,0 +1,71 @@
<?php
/**
* @file classes/plugins/IPKPDoiRegistrationAgency.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class IPKPDoiRegistrationAgency
*
* @ingroup plugins
*
* @brief Interface that registration agency plugins must implement to support DOI registrations.
*/
namespace PKP\plugins;
use PKP\context\Context;
use PKP\doi\RegistrationAgencySettings;
interface IPKPDoiRegistrationAgency
{
/**
* Includes plugin in list of configurable registration agencies for DOI depositing functionality
*
* @param string $hookName DoiSettingsForm::setEnabledRegistrationAgencies
* @param array $args [
*
* @option $enabledRegistrationAgencies array
* ]
*/
public function addAsRegistrationAgencyOption(string $hookName, array $args);
/**
* Checks if plugin meets registration agency-specific requirements for being active and handling deposits
*
*/
public function isPluginConfigured(Context $context): bool;
/**
* Get configured registration agency display name for use in DOI management pages
*
*/
public function getRegistrationAgencyName(): string;
/**
* Get key for retrieving error message if one exists on DOI object
*
*/
public function getErrorMessageKey(): ?string;
/**
* Get key for retrieving registered message if one exists on DOI object
*
*/
public function getRegisteredMessageKey(): ?string;
/**
* Get settings management object. Handles saving and validating of form fields.
*
*/
public function getSettingsObject(): RegistrationAgencySettings;
/**
* Get an array of which types of pub objects are allowed to have DOIs assigned.
* Should be composed of Repo::doi()::TYPE_* constants.
* This will only dictate which pub object types are enabled and will not delete already assigned DOIs.
*/
public function getAllowedDoiTypes(): array;
}
@@ -0,0 +1,470 @@
<?php
/**
* @file classes/plugins/ImportExportPlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class ImportExportPlugin
*
* @ingroup plugins
*
* @brief Abstract class for import/export plugins
*/
namespace PKP\plugins;
use APP\facades\Repo;
use APP\template\TemplateManager;
use DateTime;
use Exception;
use PKP\config\Config;
use PKP\context\Context;
use PKP\core\JSONMessage;
use PKP\core\PKPApplication;
use PKP\core\PKPRequest;
use PKP\db\DAORegistry;
use PKP\file\FileManager;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\RedirectAction;
use PKP\plugins\importexport\native\PKPNativeImportExportDeployment;
use PKP\plugins\importexport\PKPImportExportDeployment;
use PKP\session\SessionManager;
abstract class ImportExportPlugin extends Plugin
{
/** @var PKPImportExportDeployment The deployment that processes import/export operations */
public $_childDeployment = null;
/** @var \APP\core\Request Request made available for plugin URL generation */
public $_request;
protected const EXPORT_FILE_DATE_PART_FORMAT = 'Ymd-His';
/**
* Execute import/export tasks using the command-line interface.
*
* @param string $scriptName The name of the command-line script (displayed as usage info)
* @param array $args Parameters to the plugin
*/
abstract public function executeCLI($scriptName, &$args);
/**
* Display the command-line usage information
*
* @param string $scriptName
*/
abstract public function usage($scriptName);
/**
* Whether this plugin provides CLI import/export
*/
public function supportsCLI(): bool
{
return true;
}
/**
* @copydoc Plugin::getActions()
*/
public function getActions($request, $actionArgs)
{
$dispatcher = $request->getDispatcher();
return array_merge(
[
new LinkAction(
'settings',
new RedirectAction($dispatcher->url(
$request,
PKPApplication::ROUTE_PAGE,
null,
'management',
'importexport',
['plugin', $this->getName()]
)),
__('manager.importExport'),
null
),
],
parent::getActions($request, $actionArgs)
);
}
/**
* Display the import/export plugin.
*
* @param array $args
* @param PKPRequest $request
*/
public function display($args, $request)
{
$templateMgr = TemplateManager::getManager($request);
$templateMgr->registerPlugin('function', 'plugin_url', [$this, 'pluginUrl']);
$this->_request = $request; // Store this for use by the pluginUrl function
$templateMgr->assign([
'breadcrumbs' => [
[
'id' => 'tools',
'name' => __('navigation.tools'),
'url' => $request->getRouter()->url($request, null, 'management', 'tools'),
],
[
'id' => $this->getPluginPath(),
'name' => $this->getDisplayName()
],
],
'pageTitle' => $this->getDisplayName(),
]);
}
/**
* Generate a URL into the plugin.
*
* @see calling conventions at http://www.smarty.net/docsv2/en/api.register.function.tpl
*
* @param array $params
* @param \Smarty $smarty
*
* @return string
*/
public function pluginUrl($params, $smarty)
{
$dispatcher = $this->_request->getDispatcher();
return $dispatcher->url($this->_request, PKPApplication::ROUTE_PAGE, null, 'management', 'importexport', array_merge(['plugin', $this->getName(), $params['path'] ?? []]));
}
/**
* Check if this is a relative path to the xml document
* that describes public identifiers to be imported.
*
* @param string $url path to the xml file
*/
public function isRelativePath($url)
{
// FIXME This is not very comprehensive, but will work for now.
if ($this->isAllowedMethod($url)) {
return false;
}
if ($url[0] == '/') {
return false;
}
return true;
}
/**
* Determine whether the specified URL describes an allowed protocol.
*
* @param string $url
*
* @return bool
*/
public function isAllowedMethod($url)
{
$allowedPrefixes = [
'http://',
'ftp://',
'https://',
'ftps://'
];
foreach ($allowedPrefixes as $prefix) {
if (substr($url, 0, strlen($prefix)) === $prefix) {
return true;
}
}
return false;
}
/**
* Get the plugin ID used as plugin settings prefix.
*
* @return string
*/
public function getPluginSettingsPrefix()
{
return '';
}
/**
* Return the plugin export directory.
*
* @return string The export directory path.
*/
public function getExportPath()
{
return Config::getVar('files', 'files_dir') . '/temp/';
}
/**
* Return the whole export file name.
*
* @param string $basePath Base path for temporary file storage
* @param string $objectsFileNamePart Part different for each object type.
* @param string $extension
* @param ?DateTime $dateFilenamePart
*
* @return string
*/
public function getExportFileName($basePath, $objectsFileNamePart, Context $context, $extension = '.xml', ?DateTime $dateFilenamePart = null)
{
$dateFilenamePartString = date(self::EXPORT_FILE_DATE_PART_FORMAT);
if (isset($dateFilenamePart)) {
$dateFilenamePartString = $dateFilenamePart->format(self::EXPORT_FILE_DATE_PART_FORMAT);
}
return $basePath . $this->getPluginSettingsPrefix() . '-' . $dateFilenamePartString . '-' . $objectsFileNamePart . '-' . $context->getId() . $extension;
}
/**
* Display XML validation errors.
*
* @param array $errors
* @param string $xml
*/
public function displayXMLValidationErrors($errors, $xml)
{
if (SessionManager::isDisabled()) {
echo __('plugins.importexport.common.validationErrors') . "\n";
foreach ($errors as $error) {
echo trim($error->message) . "\n";
}
libxml_clear_errors();
echo __('plugins.importexport.common.invalidXML') . "\n";
echo $xml . "\n";
} else {
header('Content-type: text/html; charset=utf-8');
echo '<html><body>';
echo '<h2>' . __('plugins.importexport.common.validationErrors') . '</h2>';
foreach ($errors as $error) {
echo '<p>' . trim($error->message) . '</p>';
}
libxml_clear_errors();
echo '<h3>' . __('plugins.importexport.common.invalidXML') . '</h3>';
echo '<p><pre>' . htmlspecialchars($xml) . '</pre></p>';
echo '</body></html>';
}
throw new Exception(__('plugins.importexport.common.error.validation'));
}
/**
* Set the deployment that processes import/export operations
*/
public function setDeployment($deployment)
{
$this->_childDeployment = $deployment;
}
/**
* Get the deployment that processes import/export operations
*
* @return PKPImportExportDeployment
*/
public function getDeployment()
{
return $this->_childDeployment;
}
/**
* Get the submissions and proceed to the export
*
* @param array $submissionIds Array of submissions to export
* @param PKPNativeImportExportDeployment $deployment
* @param array $opts
*/
public function getExportSubmissionsDeployment($submissionIds, $deployment, $opts = [])
{
$filter = $this->getExportFilter('exportSubmissions');
$submissions = [];
foreach ($submissionIds as $submissionId) {
$submission = Repo::submission()->get($submissionId);
if ($submission && $submission->getData('contextId') == $deployment->getContext()->getId()) {
$submissions[] = $submission;
}
}
$deployment->export($filter, $submissions, $opts);
}
/**
* Save the export result as an XML
*
* @param PKPNativeImportExportDeployment $deployment
*
* @return string
*/
public function exportResultXML($deployment)
{
$result = $deployment->processResult;
$foundErrors = $deployment->isProcessFailed();
$xml = null;
if (!$foundErrors && $result) {
$xml = $result->saveXml();
}
return $xml;
}
/**
* Gets template result for the export process
*
* @param PKPNativeImportExportDeployment $deployment
* @param \PKP\template\PKPTemplateManager $templateMgr
* @param string $exportFileName
*
* @return string
*/
public function getExportTemplateResult($deployment, $templateMgr, $exportFileName)
{
$result = $deployment->processResult;
$problems = $deployment->getWarningsAndErrors();
$foundErrors = $deployment->isProcessFailed();
if (!$foundErrors) {
$exportXml = $result->saveXml();
if ($exportXml) {
$dateFilenamePart = new DateTime();
$this->writeExportedFile($exportFileName, $exportXml, $deployment->getContext(), $dateFilenamePart);
$templateMgr->assign('exportedFileDatePart', $dateFilenamePart->format(self::EXPORT_FILE_DATE_PART_FORMAT));
$templateMgr->assign('exportedFileContentNamePart', $exportFileName);
}
}
$templateMgr->assign('validationErrors', $deployment->getXMLValidationErrors());
$templateMgr->assign('errorsAndWarnings', $problems);
$templateMgr->assign('errorsFound', $foundErrors);
// Display the results
$json = new JSONMessage(true, $templateMgr->fetch('plugins/importexport/resultsExport.tpl'));
header('Content-Type: application/json');
return $json->getString();
}
/**
* Gets template result for the import process
*
* @param string $filter
* @param string $xmlString
* @param PKPNativeImportExportDeployment $deployment
* @param \PKP\template\PKPTemplateManager $templateMgr
*
* @return string
*/
public function getImportTemplateResult($filter, $xmlString, $deployment, $templateMgr)
{
$deployment->import($filter, $xmlString);
$templateMgr->assign('content', $deployment->processResult);
$templateMgr->assign('validationErrors', $deployment->getXMLValidationErrors());
$problems = $deployment->getWarningsAndErrors();
$foundErrors = $deployment->isProcessFailed();
$templateMgr->assign('errorsAndWarnings', $problems);
$templateMgr->assign('errorsFound', $foundErrors);
$templateMgr->assign('importedRootObjects', $deployment->getImportedRootEntitiesWithNames());
// Display the results
$json = new JSONMessage(true, $templateMgr->fetch('plugins/importexport/resultsImport.tpl'));
header('Content-Type: application/json');
return $json->getString();
}
/**
* Gets the imported file path
*
* @param int $temporaryFileId
* @param \PKP\user\User $user
*
* @return string
*/
public function getImportedFilePath($temporaryFileId, $user)
{
$temporaryFileDao = DAORegistry::getDAO('TemporaryFileDAO'); /** @var \PKP\file\TemporaryFileDAO $temporaryFileDao */
$temporaryFile = $temporaryFileDao->getTemporaryFile($temporaryFileId, $user->getId());
if (!$temporaryFile) {
$json = new JSONMessage(true, __('plugins.inportexport.native.uploadFile'));
header('Content-Type: application/json');
return $json->getString();
}
$temporaryFilePath = $temporaryFile->getFilePath();
return $temporaryFilePath;
}
/**
* Gets a tab to display after the import/export operation is over
*
* @param PKPRequest $request
* @param string $title
* @param string $bounceUrl
* @param array $bounceParameterArray
*
* @return string
*/
public function getBounceTab($request, $title, $bounceUrl, $bounceParameterArray)
{
if (!$request->checkCSRF()) {
throw new Exception('CSRF mismatch!');
}
$json = new JSONMessage(true);
$json->setEvent('addTab', [
'title' => $title,
'url' => $request->url(
null,
null,
null,
['plugin', $this->getName(), $bounceUrl],
array_merge($bounceParameterArray, ['csrfToken' => $request->getSession()->getCSRFToken()])
),
]);
header('Content-Type: application/json');
return $json->getString();
}
/**
* Download file given it's name
*/
public function downloadExportedFile(string $exportedFileContentNamePart, string $exportedFileDatePart, PKPImportExportDeployment $deployment)
{
$date = DateTime::createFromFormat(self::EXPORT_FILE_DATE_PART_FORMAT, $exportedFileDatePart);
if (!$date) {
return false;
}
$exportFileName = $this->getExportFileName($this->getExportPath(), $exportedFileContentNamePart, $deployment->getContext(), '.xml', $date);
$fileManager = new FileManager();
$fileManager->downloadByPath($exportFileName);
$fileManager->deleteByPath($exportFileName);
return true;
}
/**
* Create file given it's name and content
*
* @param ?DateTime $dateFilenamePart
*
* @return string
*/
public function writeExportedFile(string $filename, string $fileContent, Context $context, ?DateTime $dateFilenamePart = null)
{
$fileManager = new FileManager();
$exportFileName = $this->getExportFileName($this->getExportPath(), $filename, $context, '.xml', $dateFilenamePart);
$fileManager->writeFile($exportFileName, $fileContent);
return $exportFileName;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\ImportExportPlugin', '\ImportExportPlugin');
}
+127
View File
@@ -0,0 +1,127 @@
<?php
/**
* @file classes/plugins/LazyLoadPlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class LazyLoadPlugin
*
* @ingroup plugins
*
* @brief Abstract class for plugins that optionally
* support lazy load.
*/
namespace PKP\plugins;
use APP\core\Application;
abstract class LazyLoadPlugin extends Plugin
{
//
// Override public methods from Plugin
//
/**
* @copydoc Plugin::register()
*
* @param null|mixed $mainContextId
*/
public function register($category, $path, $mainContextId = null)
{
if (!parent::register($category, $path, $mainContextId)) {
return false;
}
$this->addLocaleData();
return true;
}
//
// Override protected methods from Plugin
//
/**
* @see Plugin::getName()
*/
public function getName()
{
// Lazy load enabled plug-ins always use the plugin's class name
// as plug-in name. Legacy plug-ins will override this method so
// this implementation is backwards compatible.
// NB: strtolower was required for PHP4 compatibility.
$classNameParts = explode('\\', get_class($this)); // Separate namespace info from class name
return strtolower_codesafe(end($classNameParts));
}
//
// Public methods required to support lazy load.
//
/**
* Determine whether or not this plugin is currently enabled.
*
* @param int $contextId To identify if the plugin is enabled
* we need a context. This context is usually taken from the
* request but sometimes there is no context in the request
* (e.g. when executing CLI commands). Then the main context
* can be given as an explicit ID.
*
* @return bool
*/
public function getEnabled($contextId = null)
{
if ($contextId == null) {
$contextId = $this->getCurrentContextId();
if ($this->isSitePlugin()) {
$contextId = 0;
}
}
return $this->getSetting($contextId, 'enabled');
}
/**
* Set whether or not this plugin is currently enabled.
*
* @param bool $enabled
*/
public function setEnabled($enabled)
{
$contextId = $this->getCurrentContextId();
if ($this->isSitePlugin()) {
$contextId = 0;
}
$this->updateSetting($contextId, 'enabled', $enabled, 'bool');
}
/**
* @copydoc Plugin::getCanEnable()
*/
public function getCanEnable()
{
return true;
}
/**
* @copydoc Plugin::getCanDisable()
*/
public function getCanDisable()
{
return true;
}
/**
* Get the current context ID or the site-wide context ID (0) if no context
* can be found.
*/
public function getCurrentContextId()
{
$context = Application::get()->getRequest()->getContext();
return is_null($context) ? 0 : $context->getId();
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\LazyLoadPlugin', '\LazyLoadPlugin');
}
@@ -0,0 +1,58 @@
<?php
/**
* @file classes/plugins/MetadataPlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class MetadataPlugin
*
* @ingroup plugins
*
* @brief Abstract class for metadata plugins
*/
namespace PKP\plugins;
abstract class MetadataPlugin extends Plugin
{
//
// Override public methods from Plugin
//
/**
* @copydoc Plugin::register()
*
* @param null|mixed $mainContextId
*/
public function register($category, $path, $mainContextId = null)
{
if (!parent::register($category, $path, $mainContextId)) {
return false;
}
$this->addLocaleData();
return true;
}
/**
* Get a unique id for this metadata format
*
* @param string $format The format to check for support.
*
* @return string
*/
abstract public function supportsFormat($format);
/**
* Instantiate and return the schema object for this metadata format
*
* @param string $format The format to return the schema object for in case
* the plugin supports multiple formats.
*/
abstract public function getSchemaObject($format);
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\MetadataPlugin', '\MetadataPlugin');
}
@@ -0,0 +1,82 @@
<?php
/**
* @file lib/pkp/classes/plugins/OAIMetadataFormatPlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class OAIMetadataFormatPlugin
*
* @ingroup plugins
*
* @brief Abstract class for OAI Metadata format plugins
*/
namespace PKP\plugins;
abstract class OAIMetadataFormatPlugin extends Plugin
{
/**
* @copydoc Plugin::register()
*
* @param null|mixed $mainContextId
*/
public function register($category, $path, $mainContextId = null)
{
if (!parent::register($category, $path, $mainContextId)) {
return false;
}
$this->addLocaleData();
if ($this->getEnabled()) {
Hook::add('OAI::metadataFormats', [$this, 'callback_formatRequest']);
}
return true;
}
/**
* Get the metadata prefix for this plugin's format.
*/
public static function getMetadataPrefix()
{
assert(false); // Should always be overridden
}
public static function getSchema()
{
return '';
}
public static function getNamespace()
{
return '';
}
/**
* Get a hold of the class that does the formatting.
*/
abstract public function getFormatClass();
public function callback_formatRequest($hookName, $args)
{
$namesOnly = $args[0];
$identifier = $args[1];
$formats = & $args[2];
if ($namesOnly) {
$formats = array_merge($formats, [$this->getMetadataPrefix()]);
} else {
$formatClass = $this->getFormatClass();
$formats = array_merge(
$formats,
[$this->getMetadataPrefix() => new $formatClass($this->getMetadataPrefix(), $this->getSchema(), $this->getNamespace())]
);
}
return false;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\OAIMetadataFormatPlugin', '\OAIMetadataFormatPlugin');
}
+534
View File
@@ -0,0 +1,534 @@
<?php
/**
* @file classes/plugins/PKPPubIdPlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPPubIdPlugin
*
* @ingroup plugins
*
* @brief Abstract class for public identifiers plugins
*/
namespace PKP\plugins;
use APP\core\Application;
use APP\facades\Repo;
use APP\notification\Notification;
use APP\notification\NotificationManager;
use PKP\core\EntityDAO;
use PKP\core\JSONMessage;
use PKP\core\PKPRequest;
use PKP\db\DAO;
use PKP\db\SchemaDAO;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\AjaxModal;
use PKP\template\PKPTemplateManager;
abstract class PKPPubIdPlugin extends LazyLoadPlugin
{
//
// Implement template methods from Plugin
//
/**
* @copydoc Plugin::register()
*
* @param null|mixed $mainContextId
*/
public function register($category, $path, $mainContextId = null)
{
if (!parent::register($category, $path, $mainContextId)) {
return false;
}
if ($this->getEnabled($mainContextId)) {
// Enable storage of additional fields.
foreach ($this->getDAOs() as $dao) {
// Augment the object with the additional properties required by the pub ID plugin.
if ($dao instanceof SchemaDAO) {
// Schema-backed DAOs need the schema extended.
Hook::add('Schema::get::' . $dao->schemaName, [$this, 'addToSchema']);
} elseif ($dao instanceof EntityDAO) {
// Schema-backed DAOs need the schema extended.
Hook::add('Schema::get::' . $dao->schema, [$this, 'addToSchema']);
} else {
// For non-schema-backed DAOs, DAOName::getAdditionalFieldNames can be used.
$classNameParts = explode('\\', get_class($dao)); // Separate namespace info from class name
Hook::add(strtolower_codesafe(end($classNameParts)) . '::getAdditionalFieldNames', [$this, 'getAdditionalFieldNames']);
}
}
}
$this->addLocaleData();
return true;
}
/**
* @copydoc Plugin::getActions()
*/
public function getActions($request, $actionArgs)
{
$router = $request->getRouter();
return array_merge(
$this->getEnabled() ? [
new LinkAction(
'settings',
new AjaxModal(
$router->url($request, null, null, 'manage', null, $actionArgs),
$this->getDisplayName()
),
__('manager.plugins.settings'),
null
),
] : [],
parent::getActions($request, $actionArgs)
);
}
/**
* @copydoc Plugin::manage()
*/
public function manage($args, $request)
{
$user = $request->getUser();
$router = $request->getRouter();
$context = $router->getContext($request);
$form = $this->instantiateSettingsForm($context->getId());
$notificationManager = new NotificationManager();
switch ($request->getUserVar('verb')) {
case 'save':
$form->readInputData();
if ($form->validate()) {
$form->execute();
$notificationManager->createTrivialNotification($user->getId(), Notification::NOTIFICATION_TYPE_SUCCESS);
return new JSONMessage(true);
}
return new JSONMessage(true, $form->fetch($request));
case 'clearPubIds':
if (!$request->checkCSRF()) {
return new JSONMessage(false);
}
$contextDao = Application::getContextDAO();
$contextDao->deleteAllPubIds($context->getId(), $this->getPubIdType());
return new JSONMessage(true);
default:
$form->initData();
return new JSONMessage(true, $form->fetch($request));
}
}
//
// Protected template methods to be implemented by sub-classes.
//
/**
* Get the public identifier.
*
* @param object $pubObject
* Publication, Representation, SubmissionFile
*
* @return string
*/
abstract public function getPubId($pubObject);
/**
* Construct the public identifier from its prefix and suffix.
*
* @param string $pubIdPrefix
* @param string $pubIdSuffix
* @param int $contextId
*
* @return string
*/
abstract public function constructPubId($pubIdPrefix, $pubIdSuffix, $contextId);
/**
* Public identifier type, see
* http://dtd.nlm.nih.gov/publishing/tag-library/n-4zh0.html
*
* @return string
*/
abstract public function getPubIdType();
/**
* Public identifier type that will be displayed to the reader.
*
* @return string
*/
abstract public function getPubIdDisplayType();
/**
* Full name of the public identifier.
*
* @return string
*/
abstract public function getPubIdFullName();
/**
* Get the whole resolving URL.
*
* @param int $contextId
* @param string $pubId
*
* @return string resolving URL
*/
abstract public function getResolvingURL($contextId, $pubId);
/**
* Get the file (path + filename)
* to be included into the object's
* identifiers tab, e.g. for suffix editing.
*
* @return string
*/
abstract public function getPubIdMetadataFile();
/**
* Add JavaScript files to be loaded in the metadata file.
*
* @param PKPRequest $request
* @param PKPTemplateManager $templateMgr
*/
public function addJavaScript($request, $templateMgr)
{
}
/**
* Get the file (path + filename)
* for the pub id assignment
* to be included into other pages.
*
* @return string
*/
abstract public function getPubIdAssignFile();
/**
* Get the settings form.
*
* @param int $contextId
*
* @return object Settings form
*/
abstract public function instantiateSettingsForm($contextId);
/**
* Get the additional form field names,
* for metadata, e.g. suffix field name.
*
* @return array
*/
abstract public function getFormFieldNames();
/**
* Get the assign option form field name.
*
* @return string
*/
abstract public function getAssignFormFieldName();
/**
* Get the the prefix form field name.
*
* @return string
*/
abstract public function getPrefixFieldName();
/**
* Get the the suffix form field name.
*
* @return string
*/
abstract public function getSuffixFieldName();
/**
* Get the link actions used in the pub id forms,
* e.g. clear pub id.
*
* @return array
*/
abstract public function getLinkActions($pubObject);
/**
* Get the suffix patterns form field names for all objects.
*
* @return array (pub object type => suffix pattern field name)
*/
abstract public function getSuffixPatternsFieldNames();
/**
* Get additional field names to be considered for storage.
*
* @return array
*/
abstract public function getDAOFieldNames();
/**
* Get the possible publication object types.
*
* @return array
*/
public function getPubObjectTypes()
{
return [
'Publication' => '\APP\publication\Publication',
'Representation' => '\PKP\submission\Representation',
'SubmissionFile' => '\PKP\submissionFile\SubmissionFile',
];
}
/**
* Is this object type enabled in plugin settings
*
* @param string $pubObjectType
* @param int $contextId
*
* @return bool
*/
abstract public function isObjectTypeEnabled($pubObjectType, $contextId);
/**
* Get the error message for not unique pub id
*
* @return string
*/
abstract public function getNotUniqueErrorMsg();
/**
* Verify form data.
*
* @param string $fieldName The form field to be checked.
* @param string $fieldValue The value of the form field.
* @param object $pubObject
* @param int $contextId
* @param string $errorMsg Return validation error messages here.
*
* @return bool
*/
public function verifyData($fieldName, $fieldValue, $pubObject, $contextId, &$errorMsg)
{
// Verify pub id uniqueness.
if ($fieldName == $this->getSuffixFieldName()) {
if (empty($fieldValue)) {
return true;
}
// Construct the potential new pub id with the posted suffix.
$pubIdPrefix = $this->getSetting($contextId, $this->getPrefixFieldName());
if (empty($pubIdPrefix)) {
return true;
}
$newPubId = $this->constructPubId($pubIdPrefix, $fieldValue, $contextId);
if (!$this->checkDuplicate($newPubId, get_class($pubObject), $pubObject->getId(), $contextId)) {
$errorMsg = $this->getNotUniqueErrorMsg();
return false;
}
}
return true;
}
/**
* Check whether the given pubId is valid.
*
* @param string $pubId
*
* @return bool
*/
public function validatePubId($pubId)
{
return true; // Assume a valid ID by default;
}
/**
* Return an array of publication object types and
* the corresponding DAOs.
*
* @return array
*/
public function getDAOs()
{
return [
Repo::publication()->dao,
Application::getRepresentationDAO(),
Repo::submissionFile()->dao,
];
}
/**
* Can a pub id be assigned to the object.
*
* @param object $pubObject
* Publication, Representation, SubmissionFile, OJS Issue, OMP Chapter
*
* @return bool
* false, if the pub id contains an unresolved pattern i.e. '%' or
* if the custom suffix is empty i.e. the pub id null.
*/
public function canBeAssigned($pubObject)
{
// Has the pub id already been assigned.
$pubIdType = $this->getPubIdType();
$storedPubId = $pubObject->getStoredPubId($pubIdType);
if ($storedPubId) {
return false;
}
// Get the pub id.
$pubId = $this->getPubId($pubObject);
// Is the custom suffix empty i.e. the pub id null.
if (!$pubId) {
return false;
}
// Does the suffix contain unresolved pattern.
$containPatterns = strpos($pubId, '%') !== false;
return !$containPatterns;
}
/**
* Add properties for this type of public identifier to the entity's list for
* storage in the database.
* This is used for SchemaDAO-backed entities only.
*
* @see PKPPubIdPlugin::getAdditionalFieldNames()
*
* @param string $hookName `Schema::get::publication`
* @param array $params
*/
public function addToSchema($hookName, $params)
{
$schema = & $params[0];
foreach (array_merge($this->getFormFieldNames(), $this->getDAOFieldNames()) as $fieldName) {
$schema->properties->{$fieldName} = (object) [
'type' => 'string',
'apiSummary' => true,
'validation' => ['nullable'],
];
}
return false;
}
/**
* Add properties for this type of public identifier to the entity's list for
* storage in the database.
* This is used for non-SchemaDAO-backed entities only.
*
* @see PKPPubIdPlugin::addToSchema()
*
* @param string $hookName
* @param DAO $dao
* @param array $additionalFields
*
* @return bool
*/
public function getAdditionalFieldNames($hookName, $dao, &$additionalFields)
{
foreach (array_merge($this->getFormFieldNames(), $this->getDAOFieldNames()) as $fieldName) {
$additionalFields[] = $fieldName;
}
return false;
}
/**
* Return the object type.
*
* @param object $pubObject
*
* @return ?string
*/
public function getPubObjectType($pubObject)
{
$allowedTypes = $this->getPubObjectTypes();
$pubObjectType = null;
foreach ($allowedTypes as $type => $fqcn) {
if ($pubObject instanceof $fqcn) {
$pubObjectType = $type;
break;
}
}
if (is_null($pubObjectType)) {
// This must be a dev error, so bail with an assertion.
assert(false);
return null;
}
return $pubObjectType;
}
/**
* Set and store a public identifier.
*
* @param object $pubObject
* @param string $pubId
*/
public function setStoredPubId(&$pubObject, $pubId)
{
$dao = $pubObject->getDAO();
$dao->changePubId($pubObject->getId(), $this->getPubIdType(), $pubId);
$pubObject->setStoredPubId($this->getPubIdType(), $pubId);
}
//
// Public API
//
/**
* Check for duplicate public identifiers.
*
* Checks to see if a pubId has already been assigned to any object
* in the context.
*
* @param string $pubId
* @param string $pubObjectType Class name of the pub object being checked
* @param int $excludeId This object id will not be checked for duplicates
* @param int $contextId
*
* @return bool
*/
public function checkDuplicate($pubId, $pubObjectType, $excludeId, $contextId)
{
foreach ($this->getPubObjectTypes() as $type => $fqcn) {
if ($type === 'Publication') {
$typeDao = Repo::publication()->dao;
} elseif ($type === 'Representation') {
$typeDao = Application::getRepresentationDAO();
} elseif ($type === 'SubmissionFile') {
$typeDao = Repo::submissionFile()->dao;
}
$excludeTypeId = $type === $pubObjectType ? $excludeId : null;
if (isset($typeDao) && $typeDao->pubIdExists($this->getPubIdType(), $pubId, $excludeTypeId, $contextId)) {
return false;
}
}
return true;
}
/**
* Get the context object.
*
* @param int $contextId
*
* @return object Context
*/
public function getContext($contextId)
{
assert(is_numeric($contextId));
// Get the context object from the context (optimized).
$request = Application::get()->getRequest();
$router = $request->getRouter();
$context = $router->getContext($request);
if ($context && $context->getId() == $contextId) {
return $context;
}
// Fall back the database.
$contextDao = Application::getContextDAO();
return $contextDao->getById($contextId);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\PKPPubIdPlugin', '\PKPPubIdPlugin');
}
@@ -0,0 +1,70 @@
<?php
/**
* @file classes/plugins/PKPPubIdPluginDAO.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPPubIdPluginDAO
*
* @ingroup plugins
*
* @brief Interface that DAOs would need to implement in order for pub ID support to be added.
*/
namespace PKP\plugins;
interface PKPPubIdPluginDAO
{
/**
* Checks if public identifier exists (other than for the specified
* submission ID, which is treated as an exception).
*
* @param string $pubIdType One of the NLM pub-id-type values or
* 'other::something' if not part of the official NLM list
* (see <http://dtd.nlm.nih.gov/publishing/tag-library/n-4zh0.html>).
* @param string $pubId
* @param int $excludePubObjectId ID of the pub object to be excluded from the search.
* @param int $contextId
*
* @return bool
*/
public function pubIdExists($pubIdType, $pubId, $excludePubObjectId, $contextId);
/**
* Change the public ID of a submission.
*
* @param int $pubObjectId ID of the pub object
* @param string $pubIdType One of the NLM pub-id-type values or
* 'other::something' if not part of the official NLM list
* (see <http://dtd.nlm.nih.gov/publishing/tag-library/n-4zh0.html>).
* @param string $pubId
*/
public function changePubId($pubObjectId, $pubIdType, $pubId);
/**
* Delete the public ID of a submission.
*
* @param int $pubObjectId ID of the pub object
* @param string $pubIdType One of the NLM pub-id-type values or
* 'other::something' if not part of the official NLM list
* (see <http://dtd.nlm.nih.gov/publishing/tag-library/n-4zh0.html>).
*/
public function deletePubId($pubObjectId, $pubIdType);
/**
* Delete the public IDs of all submissions in this context.
*
* @param int $contextId
* @param string $pubIdType One of the NLM pub-id-type values or
* 'other::something' if not part of the official NLM list
* (see <http://dtd.nlm.nih.gov/publishing/tag-library/n-4zh0.html>).
*/
public function deleteAllPubIds($contextId, $pubIdType);
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\PKPPubIdPluginDAO', '\PKPPubIdPluginDAO');
}
@@ -0,0 +1,239 @@
<?php
/**
* @file classes/plugins/PKPPubIdPluginHelper.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPPubIdPluginHelper
*
* @ingroup plugins
*
* @brief Helper class for public identifiers plugins
*/
namespace PKP\plugins;
use APP\core\Application;
use PKP\core\PKPRequest;
use PKP\form\Form;
use PKP\submissionFile\SubmissionFile;
use PKP\template\PKPTemplateManager;
class PKPPubIdPluginHelper
{
/**
* Validate the additional form fields from public identifier plugins.
*
* @param int $contextId
* @param object $form PKPPublicIdentifiersForm
* @param object $pubObject
* Submission, Representation, SubmissionFile + OJS Issue
*/
public function validate($contextId, $form, $pubObject)
{
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $contextId);
if (!empty($pubIdPlugins)) {
foreach ($pubIdPlugins as $pubIdPlugin) {
$fieldNames = $pubIdPlugin->getFormFieldNames();
foreach ($fieldNames as $fieldName) {
$fieldValue = $form->getData($fieldName);
$errorMsg = '';
if (!$pubIdPlugin->verifyData($fieldName, $fieldValue, $pubObject, $contextId, $errorMsg)) {
$form->addError($fieldName, $errorMsg);
}
}
}
}
}
/**
* Set form link actions.
*
* @param int $contextId
* @param object $form PKPPublicIdentifiersForm
* @param object $pubObject
* Submission, Representation, SubmissionFile + OJS Issue
*/
public function setLinkActions($contextId, $form, $pubObject)
{
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $contextId);
if (!empty($pubIdPlugins)) {
foreach ($pubIdPlugins as $pubIdPlugin) {
$linkActions = $pubIdPlugin->getLinkActions($pubObject);
foreach ($linkActions as $linkActionName => $linkAction) {
$form->setData($linkActionName, $linkAction);
unset($linkAction);
}
}
}
}
/**
* Add pub id plugins JavaScripts.
*
* @param int $contextId
* @param PKPRequest $request
* @param PKPTemplateManager $templateMgr
*/
public function addJavaScripts($contextId, $request, $templateMgr)
{
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $contextId);
if (!empty($pubIdPlugins)) {
foreach ($pubIdPlugins as $pubIdPlugin) {
$pubIdPlugin->addJavaScript($request, $templateMgr);
}
}
}
/**
* Init the additional form fields from public identifier plugins.
*
* @param int $contextId
* @param Form $form PKPPublicIdentifiersForm|CatalogEntryFormatMetadataForm
* @param object $pubObject
* Submission, Representation, SubmissionFile + OJS Issue
*/
public function init($contextId, $form, $pubObject)
{
if (isset($pubObject)) {
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $contextId);
if (!empty($pubIdPlugins)) {
foreach ($pubIdPlugins as $pubIdPlugin) {
$fieldNames = $pubIdPlugin->getFormFieldNames();
foreach ($fieldNames as $fieldName) {
$form->setData($fieldName, $pubObject->getData($fieldName));
}
}
}
}
}
/**
* Read the additional input data from public identifier plugins.
*
* @param int $contextId
* @param Form $form PKPPublicIdentifiersForm
*/
public function readInputData($contextId, $form)
{
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $contextId);
if (!empty($pubIdPlugins)) {
foreach ($pubIdPlugins as $pubIdPlugin) {
$form->readUserVars($pubIdPlugin->getFormFieldNames());
$form->readUserVars([$pubIdPlugin->getAssignFormFieldName()]);
}
}
}
/**
* Read the the public identifiers' assign form field data.
*
* @param object $form Form containing the assign check box
* PKPAssignPublicIdentifiersForm
* OJS IssueEntryPublicationMetadataForm
*/
public function readAssignInputData($form)
{
$request = Application::get()->getRequest();
$context = $request->getContext();
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $context->getId());
if (!empty($pubIdPlugins)) {
foreach ($pubIdPlugins as $pubIdPlugin) {
$form->readUserVars([$pubIdPlugin->getAssignFormFieldName()]);
}
}
}
/**
* Set the additional data from public identifier plugins.
*
* @param int $contextId
* @param object $form PKPPublicIdentifiersForm
* @param object $pubObject
* Submission, Representation, SubmissionFile + OJS Issue
*/
public function execute($contextId, $form, $pubObject)
{
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $contextId);
if (!empty($pubIdPlugins)) {
foreach ($pubIdPlugins as $pubIdPlugin) {
// Public ID data can only be changed as long
// as no ID has been generated.
$storedId = $pubObject->getStoredPubId($pubIdPlugin->getPubIdType());
if (!$storedId) {
$fieldNames = $pubIdPlugin->getFormFieldNames();
foreach ($fieldNames as $fieldName) {
$data = $form->getData($fieldName);
$pubObject->setData($fieldName, $data);
}
if ($form->getData($pubIdPlugin->getAssignFormFieldName())) {
$pubId = $pubIdPlugin->getPubId($pubObject);
$pubObject->setStoredPubId($pubIdPlugin->getPubIdType(), $pubId);
}
}
}
}
}
/**
* Assign public identifier.
*
* @param int $contextId
* @param object $form
* @param object $pubObject
* @param bool $save Whether the pub id shall be saved here
* Submission, Representation, SubmissionFile + OJS Issue
*/
public function assignPubId($contextId, $form, $pubObject, $save = false)
{
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $contextId);
if (!empty($pubIdPlugins)) {
foreach ($pubIdPlugins as $pubIdPlugin) {
if ($form->getData($pubIdPlugin->getAssignFormFieldName())) {
$pubId = $pubIdPlugin->getPubId($pubObject);
if ($save) {
$pubIdPlugin->setStoredPubId($pubObject, $pubId);
} else {
$pubObject->setStoredPubId($pubIdPlugin->getPubIdType(), $pubId);
}
}
}
}
}
/**
* Clear a pubId from a pubObject.
*
* @param int $contextId
* @param string $pubIdPlugInClassName
* @param object $pubObject
* Submission, Representation, SubmissionFile + OJS Issue
*/
public function clearPubId($contextId, $pubIdPlugInClassName, $pubObject)
{
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $contextId);
if (!empty($pubIdPlugins)) {
foreach ($pubIdPlugins as $pubIdPlugin) {
$classNameParts = explode('\\', get_class($pubIdPlugin)); // Separate namespace info from class name
if (end($classNameParts) == $pubIdPlugInClassName) {
// clear the pubId:
// delete the pubId from the DB
$dao = $pubObject->getDAO();
$pubObjectId = $pubObject->getId();
$dao->deletePubId($pubObjectId, $pubIdPlugin->getPubIdType());
// set the object setting/data 'pub-id::...' to null, in order
// not to be considered in the DB object update later in the form
$settingName = 'pub-id::' . $pubIdPlugin->getPubIdType();
$pubObject->setData($settingName, null);
}
}
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\PKPPubIdPluginHelper', '\PKPPubIdPluginHelper');
}
@@ -0,0 +1,37 @@
<?php
/**
* @file classes/plugins/PKPViewableFilePlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPViewableFilePlugin
*
* @ingroup plugins
*
* @brief Abstract class for article galley plugins
*/
namespace PKP\plugins;
abstract class PKPViewableFilePlugin extends GenericPlugin
{
/**
* Get the filename of the template. (Default behavior may
* be overridden through some combination of this function and the
* displayArticleGalley function.)
* Returning null from this function results in an empty display.
*
* @return string
*/
public function getTemplateFilename()
{
return 'display.tpl';
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\PKPViewableFilePlugin', '\PKPViewableFilePlugin');
}
@@ -0,0 +1,72 @@
<?php
/**
* @file classes/plugins/GenericPlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PaymethodPlugin
*
* @ingroup plugins
*
* @brief Abstract class for generic plugins
*/
namespace PKP\plugins;
use PKP\context\Context;
abstract class PaymethodPlugin extends LazyLoadPlugin
{
public function register($category, $path, $mainContextId = null)
{
if (!parent::register($category, $path, $mainContextId)) {
return false;
}
Hook::add('API::payments::settings::edit', [$this, 'saveSettings']);
return true;
}
/**
* Get the payment form for this plugin.
*
* @param Context $context
* @param \PKP\payment\QueuedPayment $queuedPayment
*
* @return \PKP\form\Form
*/
abstract public function getPaymentForm($context, $queuedPayment);
/**
* Check whether this plugin is fully configured and ready for use.
*
* @param Context $context
*
* @return bool
*/
public function isConfigured($context)
{
return true;
}
/**
* Must be implemented in a child class to save payment settings and attach updated data to the response
*/
abstract public function saveSettings(string $hookName, array $args);
/**
* Handle incoming requests/notifications
*
* @param array $args
* @param \APP\core\Request $request
*/
abstract public function handle($args, $request);
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\PaymethodPlugin', '\PaymethodPlugin');
}
+834
View File
@@ -0,0 +1,834 @@
<?php
/**
* @defgroup plugins Plugins
* Implements a plugin structure that can be used to flexibly extend PKP
* software via the use of a set of plugin categories.
*/
/**
* @file classes/plugins/Plugin.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 Plugin
*
* @ingroup plugins
*
* @see PluginRegistry, PluginSettingsDAO
*
* @brief Abstract class for plugins
*
* For best performance, a plug-in should not be instantiated if it is
* disabled or the current page/operation does not require the plug-in's
* functionality.
*
* Newer plug-ins support enable/disable and request filter settings that
* enable the PKP library plug-in framework to lazy-load plug-ins only
* when their functionality is actually being required for a request.
*
* For backwards compatibility we need to assume that older plug-ins
* do not support lazy-load because their register() method and hooks
* may have side-effects required on all requests. We have no way of
* knowing on which pages these side effects are important so we need
* to load legacy plug-ins on all pages.
*
* In these cases the register() function will be called on every request
* when the category the plug-in belongs to is being loaded. This was the
* default behavior before plug-in lazy load was introduced.
*
* Plug-ins that want to enable lazy-load have to include a 'lazy-load'
* setting in their version.xml:
*
* <lazy-load>1</lazy-load>
*/
namespace PKP\plugins;
use APP\core\Application;
use APP\template\TemplateManager;
use Exception;
use Illuminate\Database\Migrations\Migration;
use PKP\config\Config;
use PKP\core\JSONMessage;
use PKP\core\PKPApplication;
use PKP\core\PKPRequest;
use PKP\core\Registry;
use PKP\db\DAORegistry;
use PKP\facades\Locale;
use PKP\facades\Repo;
use PKP\install\Installer;
use PKP\observers\events\PluginSettingChanged;
use PKP\site\Version;
use PKP\site\VersionDAO;
use PKP\template\PKPTemplateResource;
// Define the well-known file name for filter configuration data.
define('PLUGIN_FILTER_DATAFILE', 'filterConfig.xml');
define('PLUGIN_TEMPLATE_RESOURCE_PREFIX', 'plugins');
abstract class Plugin
{
/** @var string Path name to files for this plugin */
public $pluginPath;
/** @var string Category name this plugin is registered to*/
public $pluginCategory;
/** @var PKPRequest the current request object */
public $request;
/**
* Constructor
*/
public function __construct()
{
}
/*
* Public Plugin API (Registration and Initialization)
*/
/**
* Load and initialize the plug-in and register plugin hooks.
*
* For backwards compatibility this method will be called whenever
* the plug-in's category is being loaded. If, however, registerOn()
* returns an array then this method will only be called when
* the plug-in is enabled and an entry in the result set of
* registerOn() matches the current request operation. An empty array
* matches all request operations.
*
* @param string $category Name of category plugin was registered to
* @param string $path The path the plugin was found in
* @param int $mainContextId To identify if the plugin is enabled
* we need a context. This context is usually taken from the
* request but sometimes there is no context in the request
* (e.g. when executing CLI commands). Then the main context
* can be given as an explicit ID.
*
* @return bool True iff plugin registered successfully; if false,
* the plugin will not be executed.
*/
public function register($category, $path, $mainContextId = null)
{
$this->pluginPath = $path;
$this->pluginCategory = $category;
if ($this->getInstallMigration()) {
Hook::add('Installer::postInstall', [$this, 'updateSchema']);
}
if ($this->getInstallSitePluginSettingsFile()) {
Hook::add('Installer::postInstall', [$this, 'installSiteSettings']);
}
if ($this->getInstallEmailTemplatesFile()) {
Hook::add('Installer::postInstall', [$this, 'installEmailTemplates']);
Hook::add('Locale::installLocale', [$this, 'installLocale']);
}
if ($this->getInstallEmailTemplateDataFile()) {
Hook::add('Installer::postInstall', [$this, 'installEmailTemplateData']);
}
if ($this->getContextSpecificPluginSettingsFile()) {
Hook::add('Context::add', [$this, 'installContextSpecificSettings']);
}
Hook::add('Installer::postInstall', [$this, 'installFilters']);
$this->_registerTemplateResource();
return true;
}
/**
* Protected methods (may be overridden by custom plugins)
*/
//
// Plugin Display
//
/**
* Get the name of this plugin. The name must be unique within
* its category, and should be suitable for part of a filename
* (ie short, no spaces, and no dependencies on cases being unique).
*
* @return string name of plugin
*/
abstract public function getName();
/**
* Get the display name for this plugin.
*
* @return string
*/
abstract public function getDisplayName();
/**
* Get a description of this plugin.
*
* @return string
*/
abstract public function getDescription();
//
// Plugin Behavior and Management
//
/**
* Return a number indicating the sequence in which this plugin
* should be registered compared to others of its category.
* Higher = later.
*
* @return int
*/
public function getSeq()
{
return 0;
}
/**
* Site-wide plugins should override this function to return true.
*
* @return bool
*/
public function isSitePlugin()
{
return false;
}
/**
* Perform a management function.
*
* @param array $args
* @param PKPRequest $request
*
* @return JSONMessage A JSON-encoded response
*/
public function manage($args, $request)
{
throw new Exception('Unhandled management action!');
}
/**
* Determine whether or not this plugin should be hidden from the
* management interface. Useful in the case of derivative plugins,
* i.e. when a generic plugin registers a feed plugin.
*
* @return bool
*/
public function getHideManagement()
{
return false;
}
//
// Plugin Installation
//
/**
* @deprecated See https://github.com/pkp/pkp-lib/issues/2493
*/
final public function getInstallSchemaFile()
{
}
/**
* Get the installation migration for this plugin.
*
* @return ?Migration
*/
public function getInstallMigration()
{
return null;
}
/**
* Get the filename of the settings data for this plugin to install
* when the system is installed (i.e. site-level plugin settings).
* Subclasses using default settings should override this.
*
* @return string
*/
public function getInstallSitePluginSettingsFile()
{
return null;
}
/**
* Get the filename of the settings data for this plugin to install
* when a new application context (e.g. journal, conference or press)
* is installed.
*
* Subclasses using default settings should override this.
*
* @return string
*/
public function getContextSpecificPluginSettingsFile()
{
return null;
}
/**
* Get the filename of the email templates for this plugin.
* Subclasses using email templates should override this.
*
* @return string
*/
public function getInstallEmailTemplatesFile()
{
return null;
}
/**
* Get the filename of the email template data for this plugin.
* Subclasses using email templates should override this.
*
* @deprecated Starting with OJS/OMP 3.2, localized content should be specified via getInstallEmailTemplatesFile(). (pkp/pkp-lib#5461)
* @return string
*/
public function getInstallEmailTemplateDataFile()
{
return null;
}
/**
* Get the filename(s) of the filter configuration data for
* this plugin. Subclasses using filters can override this.
*
* The default implementation establishes "well known" locations
* for the filter configuration. If you keep your files in these
* locations then there's no need to override this method.
*
* @return string|array one or more file locations.
*/
public function getInstallFilterConfigFiles()
{
// Construct the well-known filter configuration file names.
$filterConfigFile = $this->getPluginPath() . '/filter/' . PLUGIN_FILTER_DATAFILE;
$filterConfigFiles = [
'./lib/pkp/' . $filterConfigFile,
'./' . $filterConfigFile
];
return $filterConfigFiles;
}
/*
* Protected helper methods (can be used by custom plugins but
* should not be overridden by custom plugins)
*/
/**
* Get the name of the category this plugin is registered to.
*
* @return string category
*/
public function getCategory()
{
return $this->pluginCategory;
}
/**
* Get the path this plugin's files are located in.
*
* @return string pathname
*/
public function getPluginPath()
{
return $this->pluginPath;
}
/**
* Get the directory name of the plugin
*
* @return string directory name
*/
public function getDirName()
{
return basename($this->pluginPath);
}
/**
* Return the Resource Name for templates in this plugin, or if specified, the full resource locator
* for a specific template.
*
* @param string $template path/filename, if desired
* @param bool $inCore True if a "core" template should be used.
*
* @return string
*/
public function getTemplateResource($template = null, $inCore = false)
{
$pluginPath = $this->getPluginPath();
if ($inCore) {
$pluginPath = PKP_LIB_PATH . "/{$pluginPath}";
}
$plugin = basename($pluginPath);
$category = basename(dirname($pluginPath));
$contextId = PKPApplication::CONTEXT_SITE;
if (Application::isInstalled()) {
$context = Application::get()->getRequest()->getContext();
if ($context instanceof \PKP\context\Context) {
$contextId = $context->getId();
}
}
// Slash characters (/) are not allowed in resource names, so use dashes (-) instead.
$resourceName = strtr(join('/', [PLUGIN_TEMPLATE_RESOURCE_PREFIX, $contextId, $pluginPath, $category, $plugin]), '/', '-');
return $resourceName . ($template !== null ? ":{$template}" : '');
}
/**
* Return the canonical template path of this plug-in
*
* @param bool $inCore Return the core template path if true.
*
* @return string|null
*/
public function getTemplatePath($inCore = false)
{
$templatePath = ($inCore ? PKP_LIB_PATH . '/' : '') . "{$this->getPluginPath()}/templates";
if (is_dir($templatePath)) {
return $templatePath;
}
return null;
}
/**
* Register this plugin's templates as a template resource
*
* @param bool $inCore True iff this is a core resource.
*/
protected function _registerTemplateResource($inCore = false)
{
if ($templatePath = $this->getTemplatePath($inCore)) {
$templateMgr = TemplateManager::getManager(Application::get()->getRequest());
$pluginTemplateResource = new PKPTemplateResource($templatePath);
$templateMgr->registerResource($this->getTemplateResource(null, $inCore), $pluginTemplateResource);
}
}
/**
* Call this method when an enabled plugin is registered in order to override
* template files. Any plugin which calls this method can
* override template files by adding their own templates to:
* <overridingPlugin>/templates/plugins/<category>/<originalPlugin>/templates/<path>.tpl
*
* @param string $hookName TemplateResource::getFilename
* @param array $args [
*
* @option string File path to preferred template. Leave as-is to not
* override template.
* @option string Template file requested
* ]
*
* @return bool
*/
public function _overridePluginTemplates($hookName, $args)
{
$filePath = & $args[0];
$template = $args[1];
$checkFilePath = $filePath;
// If there's a templates/ prefix on the template, clean up the test path.
if (strpos($filePath, 'plugins/') === 0) {
$checkFilePath = 'templates/' . $checkFilePath;
}
// If there's a lib/pkp/ prefix on the template, test without it.
$libPkpPrefix = 'lib/pkp/';
if (strpos($checkFilePath, $libPkpPrefix) === 0) {
$checkFilePath = substr($filePath, strlen($libPkpPrefix));
}
// Check if an overriding plugin exists in the plugin path.
if ($overriddenFilePath = $this->_findOverriddenTemplate($checkFilePath)) {
$filePath = $overriddenFilePath;
}
return false;
}
/**
* Recursive check for existing templates
*
* @param string $path
*
* @return string|null
*/
private function _findOverriddenTemplate($path)
{
$fullPath = sprintf('%s/%s', $this->getPluginPath(), $path);
if (file_exists($fullPath)) {
return $fullPath;
}
// Backward compatibility for OJS prior to 3.1.2; changed path to templates for plugins.
if (($fullPath = preg_replace("/templates\/(?!.*templates\/)/", '', $fullPath)) && file_exists($fullPath)) {
if (Config::getVar('debug', 'deprecation_warnings')) {
trigger_error('Deprecated: The template at ' . $fullPath . ' has moved and will not be found in the future.');
}
return $fullPath;
}
// Recursive check for templates in ancestors of a current theme plugin
if ($this instanceof ThemePlugin
&& $this->parent
&& $fullPath = $this->parent->_findOverriddenTemplate($path)) {
return $fullPath;
}
return null;
}
/**
* Load locale data for this plugin.
*/
public function addLocaleData(): void
{
$basePath = $this->getPluginPath() . '/locale';
foreach ([$basePath, "lib/pkp/{$basePath}"] as $path) {
if (is_dir($path)) {
Locale::registerPath($path);
}
}
}
/**
* Retrieve a plugin setting within the given context
*
* @param int $contextId Context ID
* @param string $name Setting name
*/
public function getSetting($contextId, $name)
{
if (!Application::isUpgrading() && !Application::isInstalled()) {
return null;
}
$pluginSettingsDao = DAORegistry::getDAO('PluginSettingsDAO'); /** @var PluginSettingsDAO $pluginSettingsDao */
return $pluginSettingsDao->getSetting($contextId, $this->getName(), $name);
}
/**
* Update a plugin setting within the given context.
*
* @param int $contextId Context ID
* @param string $name The name of the setting
* @param mixed $value Setting value
* @param string $type optional
*/
public function updateSetting($contextId, $name, $value, $type = null)
{
$pluginSettingsDao = DAORegistry::getDAO('PluginSettingsDAO'); /** @var PluginSettingsDAO $pluginSettingsDao */
$pluginSettingsDao->updateSetting($contextId, $this->getName(), $name, $value, $type);
event(new PluginSettingChanged($this, $name, $value, $contextId));
}
/**
* Load a PHP file from this plugin's installation directory.
*
* @deprecated 3.4.0 pkp/pkp-lib#8186
*
* @param string $class
*/
public function import($class)
{
require_once $this->getPluginPath() . '/' . str_replace('.', '/', $class) . '.inc.php';
}
/*
* Helper methods (for internal use only, should not
* be used by custom plug-ins)
*
* NB: These methods may change without notice in the future!
*/
/**
* Callback used to install settings on system install.
*
* @param string $hookName
* @param array $args
*
* @return bool
*/
public function installSiteSettings($hookName, $args)
{
$pluginSettingsDao = DAORegistry::getDAO('PluginSettingsDAO'); /** @var PluginSettingsDAO $pluginSettingsDao */
$pluginSettingsDao->installSettings(PKPApplication::CONTEXT_SITE, $this->getName(), $this->getInstallSitePluginSettingsFile());
return false;
}
/**
* Callback used to install settings on new context
* (e.g. journal, conference or press) creation.
*
* @param string $hookName
* @param array $args
*
* @return bool
*/
public function installContextSpecificSettings($hookName, $args)
{
$context = $args[0];
$pluginSettingsDao = DAORegistry::getDAO('PluginSettingsDAO'); /** @var PluginSettingsDAO $pluginSettingsDao */
$pluginSettingsDao->installSettings($context->getId(), $this->getName(), $this->getContextSpecificPluginSettingsFile());
return false;
}
/**
* Callback used to install email templates.
*
* @param string $hookName
* @param array $args
*
* @return bool
*/
public function installEmailTemplates($hookName, $args)
{
$installer = & $args[0]; /** @var Installer $installer */
$result = & $args[1];
// Load email template data as required from the locale files.
$locales = [];
foreach ($installer->installedLocales as $locale) {
if (file_exists($this->getPluginPath() . "/locale/{$locale}/emails.po")) {
$locales[] = $locale;
}
}
// Localized data is needed by the email installation
$this->addLocaleData();
$status = Repo::emailTemplate()->dao->installEmailTemplates($this->getInstallEmailTemplatesFile(), $locales, null, true);
if ($status === false) {
// The template file seems to be invalid.
$installer->setError(Installer::INSTALLER_ERROR_DB, str_replace('{$file}', $this->getInstallEmailTemplatesFile(), __('installer.installParseEmailTemplatesFileError')));
$result = false;
}
return false;
}
/**
* Callback used to install email template data.
*
* @deprecated Email template data should be installed via installEmailTemplates (pkp/pkp-lib#5461)
*
* @param string $hookName
* @param array $args
*
* @return bool
*/
public function installEmailTemplateData($hookName, $args)
{
$installer = & $args[0];
$result = & $args[1];
foreach ($installer->installedLocales as $locale) {
$filename = str_replace('{$installedLocale}', $locale, $this->getInstallEmailTemplateDataFile());
if (!file_exists($filename)) {
continue;
}
$sql = Repo::emailTemplate()->dao->installEmailTemplateData($filename, $locale, true);
if ($sql) {
$result = $installer->executeSQL($sql);
} else {
$installer->setError(Installer::INSTALLER_ERROR_DB, str_replace('{$file}', $filename, __('installer.installParseEmailTemplatesFileError')));
$result = false;
}
}
return false;
}
/**
* Callback used to install email template data on locale install.
*
* @param string $hookName
* @param array $args
*
* @return bool
*/
public function installLocale($hookName, $args)
{
$locale = & $args[0];
$filename = str_replace('{$installedLocale}', $locale, $this->getInstallEmailTemplateDataFile());
// Since pkp/pkp-lib#5461, there are two ways to specify localized email data in plugins.
// Install locale data specified in the old form. (Deprecated!)
if ($this->getInstallEmailTemplateDataFile()) {
Repo::emailTemplate()->dao->installEmailTemplateData($filename, $locale);
}
// Install locale data specified in the new form.
if (file_exists($this->getPluginPath() . "/locale/{$locale}/emails.po")) {
$this->addLocaleData();
Repo::emailTemplate()->dao->installEmailTemplateLocaleData($this->getInstallEmailTemplatesFile(), [$locale]);
}
return false;
}
/**
* Callback used to install filters.
*
* @param string $hookName
* @param array $args
*/
public function installFilters($hookName, $args)
{
$installer = & $args[0]; /** @var Installer $installer */
$result = & $args[1]; /** @var bool $result */
// Get the filter configuration file name(s).
$filterConfigFiles = $this->getInstallFilterConfigFiles();
if (is_scalar($filterConfigFiles)) {
$filterConfigFiles = [$filterConfigFiles];
}
// Run through the config file positions and see
// whether one of these exists and needs to be installed.
foreach ($filterConfigFiles as $filterConfigFile) {
// Is there a filter configuration?
if (!file_exists($filterConfigFile)) {
continue;
}
// Install the filter configuration.
$result = $installer->installFilterConfig($filterConfigFile);
if (!$result) {
// The filter configuration file seems to be invalid.
$installer->setError(Installer::INSTALLER_ERROR_DB, str_replace('{$file}', $filterConfigFile, __('installer.installParseFilterConfigFileError')));
}
}
// Do not stop installation.
return false;
}
/**
* Called during the install process to install the plugin schema,
* if applicable.
*
* @param string $hookName
* @param array $args
*
* @return bool
*/
public function updateSchema($hookName, $args)
{
$installer = & $args[0];
$result = & $args[1];
if ($migration = $this->getInstallMigration()) {
try {
$migration->up();
} catch (Exception $e) {
$installer->setError(Installer::INSTALLER_ERROR_DB, __('installer.installMigrationError', ['class' => get_class($migration), 'message' => $e->getMessage()]));
$result = false;
}
}
return false;
}
/**
* Extend the {url ...} smarty to support plugins.
*
* @param array $params
* @param \Smarty $smarty
*
* @return string
*/
public function smartyPluginUrl($params, $smarty)
{
$path = [$this->getCategory(), $this->getName()];
if (is_array($params['path'])) {
$params['path'] = array_merge($path, $params['path']);
} elseif (!empty($params['path'])) {
$params['path'] = array_merge($path, [$params['path']]);
} else {
$params['path'] = $path;
}
return $smarty->smartyUrl($params, $smarty);
}
/**
* Get the current version of this plugin
*
* @return ?Version
*/
public function getCurrentVersion()
{
$versionDao = DAORegistry::getDAO('VersionDAO'); /** @var VersionDAO $versionDao */
$pluginPath = $this->getPluginPath();
$product = basename($pluginPath);
$category = basename(dirname($pluginPath));
$installedPlugin = $versionDao->getCurrentVersion('plugins.' . $category, $product);
if ($installedPlugin) {
return $installedPlugin;
} else {
return false;
}
}
/**
* Get the current request object
*
* @return PKPRequest
*/
public function &getRequest()
{
if (!$this->request) {
$this->request = & Registry::get('request');
}
return $this->request;
}
/*
* Private helper methods
*/
/**
* Get a list of link actions for plugin management.
*
* @param PKPRequest $request
* @param array $actionArgs The list of action args to be included in request URLs.
*
* @return array List of LinkActions
*/
public function getActions($request, $actionArgs)
{
return [];
}
/**
* Determine whether the plugin can be enabled.
*
* @return bool
*/
public function getCanEnable()
{
return false;
}
/**
* Determine whether the plugin can be disabled.
*
* @return bool
*/
public function getCanDisable()
{
return false;
}
/**
* Determine whether the plugin is enabled.
*
* @return bool
*/
public function getEnabled()
{
return true;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\Plugin', '\Plugin');
}
@@ -0,0 +1,350 @@
<?php
/**
* @file classes/plugins/PluginGalleryDAO.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PluginGalleryDAO
*
* @ingroup plugins
*
* @see DAO
*
* @brief Operations for retrieving content from the PKP plugin gallery.
*/
namespace PKP\plugins;
use APP\core\Application;
use DOMDocument;
use DOMElement;
use PKP\cache\CacheManager;
use PKP\cache\FileCache;
use PKP\controllers\grid\plugins\PluginGalleryGridHandler;
use PKP\core\PKPApplication;
use PKP\core\PKPString;
use PKP\db\DAORegistry;
use PKP\site\VersionDAO;
use Throwable;
class PluginGalleryDAO extends \PKP\db\DAO
{
public const PLUGIN_GALLERY_XML_URL = 'https://pkp.sfu.ca/ojs/xml/plugins.xml';
/**
* The default timeout (in seconds) to wait plugins.xml request
*
* @see https://docs.guzzlephp.org/en/6.5/request-options.html#timeout
*/
public const DEFAULT_TIMEOUT = 10;
/**
* TTL's Cache in seconds
*/
public const TTL_CACHE_SECONDS = 86400;
/**
* Get a set of GalleryPlugin objects describing the available
* compatible plugins in their newest versions.
*
* @param PKPApplication $application
* @param string $category Optional category name to use as filter
* @param string $search Optional text to use as filter
*
* @return array GalleryPlugin objects
*/
public function getNewestCompatible($application, $category = null, $search = null)
{
$doc = $this->_getDocument();
$plugins = [];
foreach ($doc->getElementsByTagName('plugin') as $index => $element) {
$plugin = $this->_compatibleFromElement($element, $application);
// May be null if no compatible version exists; also
// apply search filters if any supplied.
if (
$plugin &&
($category == '' || $category == PluginGalleryGridHandler::PLUGIN_GALLERY_ALL_CATEGORY_SEARCH_VALUE || $plugin->getCategory() == $category) &&
($search == '' || PKPString::strpos(PKPString::strtolower(serialize($plugin)), PKPString::strtolower($search)) !== false)
) {
$plugins[$index] = $plugin;
}
}
return $plugins;
}
/**
* Get the external Plugin XML document
*
* @return ?string
*/
protected function getExternalDocument(): ?string
{
$application = Application::get();
$client = $application->getHttpClient();
/** @var VersionDAO */
$versionDao = DAORegistry::getDAO('VersionDAO');
$currentVersion = $versionDao->getCurrentVersion();
try {
$response = $client->request(
'GET',
static::PLUGIN_GALLERY_XML_URL,
[
'query' => [
'application' => $application->getName(),
'version' => $currentVersion->getVersionString()
],
'timeout' => self::DEFAULT_TIMEOUT,
]
);
return $response->getBody();
} catch (Throwable $e) {
error_log($e->getMessage());
return null;
}
}
/**
* Get the cached Plugin XML document
*
* @return ?string
*/
protected function getCachedDocument(): ?string
{
$cacheManager = CacheManager::getManager();
/** @var FileCache */
$cache = $cacheManager->getCache(
'loadPluginsXML',
Application::CONTEXT_SITE,
function (FileCache $cache) {
$cache->setEntireCache($this->getExternalDocument());
}
);
$cacheTime = $cache->getCacheTime();
// Checking if the cache is older than 1 day, or its null
if ($cacheTime === null || (time() - $cacheTime > self::TTL_CACHE_SECONDS)) {
// This cache is out of date; so, lets request a new version.
$response = $this->getExternalDocument();
// The plugins.xml request wasnt empty, so lets replace it
if ($response !== null) {
$cache->setEntireCache($response);
}
}
return $cache->getContents();
}
/**
* Get the DOM document for the plugin gallery.
*
* @return DOMDocument
*/
private function _getDocument()
{
$doc = new DOMDocument('1.0', 'utf-8');
$doc->loadXML($this->getCachedDocument());
return $doc;
}
/**
* Construct a new data object.
*
* @return GalleryPlugin
*/
public function newDataObject()
{
return new GalleryPlugin();
}
/**
* Build a GalleryPlugin from a DOM element, using the newest compatible
* release with the supplied Application.
*
* @param DOMElement $element
* @param Application $application
*
* @return GalleryPlugin|null, if no compatible plugin was available
*/
protected function _compatibleFromElement($element, $application)
{
$plugin = $this->newDataObject();
$plugin->setCategory($element->getAttribute('category'));
$plugin->setProduct($element->getAttribute('product'));
$doc = $element->ownerDocument;
$foundRelease = false;
for ($n = $element->firstChild; $n; $n = $n->nextSibling) {
if (!($n instanceof DOMElement)) {
continue;
}
switch ($n->tagName) {
case 'name':
$plugin->setName($n->nodeValue, $n->getAttribute('locale'));
break;
case 'homepage':
$plugin->setHomepage($n->nodeValue);
break;
case 'description':
$plugin->setDescription($n->nodeValue, $n->getAttribute('locale'));
break;
case 'installation':
$plugin->setInstallationInstructions($n->nodeValue, $n->getAttribute('locale'));
break;
case 'summary':
$plugin->setSummary($n->nodeValue, $n->getAttribute('locale'));
break;
case 'maintainer':
$this->_handleMaintainer($n, $plugin);
break;
case 'release':
// If a compatible release couldn't be
// found, return null.
if ($this->_handleRelease($n, $plugin, $application)) {
$foundRelease = true;
}
break;
default:
// Not erroring out here so that future
// additions won't break old releases.
}
}
if (!$foundRelease) {
// No compatible release was found.
return null;
}
return $plugin;
}
/**
* Handle a maintainer element
*
* @param GalleryPlugin $plugin
*/
public function _handleMaintainer($element, $plugin)
{
for ($n = $element->firstChild; $n; $n = $n->nextSibling) {
if (!($n instanceof DOMElement)) {
continue;
}
switch ($n->tagName) {
case 'name':
$plugin->setContactName($n->nodeValue);
break;
case 'institution':
$plugin->setContactInstitutionName($n->nodeValue);
break;
case 'email':
$plugin->setContactEmail($n->nodeValue);
break;
default:
// Not erroring out here so that future
// additions won't break old releases.
}
}
}
/**
* Handle a release element
*
* @param GalleryPlugin $plugin
* @param PKPApplication $application
*/
public function _handleRelease($element, $plugin, $application)
{
$release = [
'date' => strtotime($element->getAttribute('date')),
'version' => $element->getAttribute('version'),
'md5' => $element->getAttribute('md5'),
];
$compatible = false;
for ($n = $element->firstChild; $n; $n = $n->nextSibling) {
if (!($n instanceof DOMElement)) {
continue;
}
switch ($n->tagName) {
case 'description':
$release[$n->tagName][$n->getAttribute('locale')] = $n->nodeValue;
break;
case 'package':
$release['package'] = $n->nodeValue;
break;
case 'compatibility':
// If a compatible release couldn't be
// found, return null.
if ($this->_handleCompatibility($n, $plugin, $application)) {
$compatible = true;
}
break;
case 'certification':
$release[$n->tagName][] = $n->getAttribute('type');
break;
default:
// Not erroring out here so that future
// additions won't break old releases.
}
}
if ($compatible && (!$plugin->getData('version') || version_compare($plugin->getData('version'), $release['version'], '<'))) {
// This release is newer than the one found earlier, or
// this is the first compatible release we've found.
$plugin->setDate($release['date']);
$plugin->setVersion($release['version']);
$plugin->setReleaseMD5($release['md5']);
$plugin->setReleaseDescription($release['description']);
$plugin->setReleaseCertifications($release['certification'] ?? []);
$plugin->setReleasePackage($release['package']);
return true;
}
return false;
}
/**
* Handle a compatibility element, fishing out the most recent statement
* of compatibility.
*
* @param GalleryPlugin $plugin
* @param PKPApplication $application
*
* @return bool True iff a compatibility statement matched this app
*/
public function _handleCompatibility($element, $plugin, $application)
{
// Check that the compatibility statement refers to this app
if ($element->getAttribute('application') != $application->getName()) {
return false;
}
for ($n = $element->firstChild; $n; $n = $n->nextSibling) {
if (!($n instanceof DOMElement)) {
continue;
}
switch ($n->tagName) {
case 'version':
$installedVersion = $application->getCurrentVersion();
if ($installedVersion->isCompatible($n->nodeValue)) {
// Compatibility was determined.
return true;
}
break;
}
}
// No applicable compatibility statement found.
return false;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\PluginGalleryDAO', '\PluginGalleryDAO');
define('PLUGIN_GALLERY_XML_URL', PluginGalleryDAO::PLUGIN_GALLERY_XML_URL);
}
+263
View File
@@ -0,0 +1,263 @@
<?php
/**
* @file classes/plugins/PluginHelper.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PluginHelper
*
* @ingroup classes_plugins
*
* @brief Helper class implementing plugin administration functions.
*/
namespace PKP\plugins;
use APP\install\Install;
use APP\install\Upgrade;
use DirectoryIterator;
use Exception;
use Illuminate\Support\Arr;
use PharData;
use PKP\config\Config;
use PKP\core\Core;
use PKP\db\DAORegistry;
use PKP\file\FileManager;
use PKP\site\SiteDAO;
use PKP\site\Version;
use PKP\site\VersionCheck;
use PKP\site\VersionDAO;
use Throwable;
class PluginHelper
{
public const PLUGIN_ACTION_UPLOAD = 'upload';
public const PLUGIN_ACTION_UPGRADE = 'upgrade';
public const PLUGIN_VERSION_FILE = 'version.xml';
public const PLUGIN_INSTALL_FILE = 'install.xml';
public const PLUGIN_UPGRADE_FILE = 'upgrade.xml';
/**
* Extract the plugin, executes the callback, then cleanup the files
*
* @template T of mixed
*
* @param string $filePath Full path to plugin archive
* @param string $originalFileName Original filename of plugin archive
* @param callable(string $pluginDirectory):T $onExtracted The function will receive as parameter the directory of the plugin
*
* @throws Exception If any unexpected error happens (failure to unpack, absence of version.xml, etc)
*
* @return T Returns the result of the $onExtracted call
*/
private function extractPlugin(string $filePath, string $originalFileName, callable $onExtracted): mixed
{
$fileManager = new FileManager();
$extension = $this->sanitizeFilename($fileManager->parseFileExtension($originalFileName));
$baseName = $this->sanitizeFilename(basename($originalFileName, ".{$extension}")) ?: 'plugin';
// If the extension doesn't match the original one, copy (we don't know the original file) the file to another location to avoid issues with the PharData class
$filePathWithExtension = null;
if ($fileManager->parseFileExtension($filePath) !== $extension) {
$filePathWithExtension = ($fileManager->getTemporaryFile($baseName, ".{$extension}"))->getPathname();
$fileManager->copyFile($filePath, $filePathWithExtension) || throw new Exception('Failed to copy plugin file');
}
$extractPath = null;
try {
// Create a random directory to avoid symlink attacks.
$extractPath = rtrim(sys_get_temp_dir(), '\\/') . "/{$baseName}" . substr(md5(random_int(0, PHP_INT_MAX)), 0, 10) . '/';
$fileManager->mkdir($extractPath) || throw new Exception("Could not create directory {$extractPath}");
// Extract files
(new PharData($filePathWithExtension ?? $filePath))->extractTo($extractPath, null, true);
// Ensure there's a file named "version.xml" at the main directory or at the direct sub-directories
foreach (new DirectoryIterator($extractPath) as $current) {
if ($current->isDir() && $current->getBasename() !== '..' && is_file(($path = "{$current->getPathname()}/") . static::PLUGIN_VERSION_FILE)) {
return $onExtracted($path);
}
}
throw new Exception(__('manager.plugins.invalidPluginArchive'));
} finally {
// Cleanup the extracted folder on failure and rethrow
if ($extractPath) {
$fileManager->rmtree($extractPath);
}
// Cleanup the temporary archive file in case it was created
if ($filePathWithExtension) {
unlink($filePathWithExtension);
}
}
}
/**
* Installs an extracted plugin
*
* @param string $path path to plugin archive
* @param string $originalFileName Original filename of plugin archive
*
* @return Version Version of installed plugin on success
*/
public function installPlugin(string $path, string $originalFileName): Version
{
return $this->extractPlugin($path, $originalFileName, function (string $pluginFolder): Version {
$fileManager = new FileManager();
$versionFile = $pluginFolder . static::PLUGIN_VERSION_FILE;
$pluginVersion = VersionCheck::getValidPluginVersionInfo($versionFile);
/** @var VersionDAO */
$versionDao = DAORegistry::getDAO('VersionDAO');
$installedPlugin = $versionDao->getCurrentVersion($pluginVersion->getProductType(), $pluginVersion->getProduct());
$baseDir = Core::getBaseDir() . '/';
$destinyPath = $baseDir . strtr($pluginVersion->getProductType(), '.', '/') . "/{$pluginVersion->getProduct()}";
if ($installedPlugin && is_dir($destinyPath)) {
throw new Exception(
$installedPlugin->compare($pluginVersion) < 0
? __('manager.plugins.pleaseUpgrade')
: __('manager.plugins.installedVersionNewest')
);
}
// Copy the plug-in from the temporary folder to the target folder.
$fileManager->copyDir($pluginFolder, $destinyPath) || throw new Exception('Failed to copy plugin to destination folder');
try {
// Upgrade the database with the new plug-in.
$installFile = Arr::first(
["{$destinyPath}/" . static::PLUGIN_INSTALL_FILE, $baseDir . PKP_LIB_PATH . '/xml/defaultPluginInstall.xml'],
fn (string $path) => is_file($path)
)
?? throw new Exception('Missing installation file');
$siteDao = DAORegistry::getDAO('SiteDAO'); /** @var SiteDAO $siteDao */
$site = $siteDao->getSite();
$params = $this->_getConnectionParams();
$params['locale'] = $site->getPrimaryLocale();
$params['additionalLocales'] = $site->getSupportedLocales();
$installer = new Install($params, $installFile, true);
$installer->setCurrentVersion($pluginVersion);
$installer->execute() || throw new Exception(__('manager.plugins.installFailed', ['errorString' => $installer->getErrorString()]));
$versionDao->insertVersion($pluginVersion, true);
return $pluginVersion;
} catch (Throwable $e) {
// Delete the plugin files on failure
$fileManager->rmtree($destinyPath);
throw $e;
}
});
}
/**
* Load database connection parameters into an array (needed for upgrade).
*/
protected function _getConnectionParams(): array
{
return [
'connectionCharset' => Config::getVar('i18n', 'connection_charset'),
'databaseDriver' => Config::getVar('database', 'driver'),
'databaseHost' => Config::getVar('database', 'host'),
'databasePort' => Config::getVar('database', 'port'),
'unixSocket' => Config::getVar('database', 'unix_socket'),
'databaseUsername' => Config::getVar('database', 'username'),
'databasePassword' => Config::getVar('database', 'password'),
'databaseName' => Config::getVar('database', 'name')
];
}
/**
* Upgrade a plugin to a newer version from the user's filesystem
*
* @param string $path path to plugin archive
* @param string $originalFileName Original filename of plugin archive
*
*/
public function upgradePlugin(string $category, string $plugin, string $path, string $originalFileName): Version
{
return $this->extractPlugin($path, $originalFileName, function (string $pluginFolder) use ($category, $plugin): Version {
$fileManager = new FileManager();
$versionFile = $pluginFolder . static::PLUGIN_VERSION_FILE;
$pluginVersion = VersionCheck::getValidPluginVersionInfo($versionFile);
// Check whether the uploaded plug-in fits the original plug-in.
if ("plugins.{$category}" !== $pluginVersion->getProductType()) {
throw new Exception(__('manager.plugins.wrongCategory'));
}
if ($plugin !== $pluginVersion->getProduct()) {
throw new Exception(__('manager.plugins.wrongName'));
}
$versionDao = DAORegistry::getDAO('VersionDAO'); /** @var VersionDAO $versionDao */
$installedPlugin = $versionDao->getCurrentVersion($pluginVersion->getProductType(), $pluginVersion->getProduct());
if (!$installedPlugin) {
throw new Exception(__('manager.plugins.pleaseInstall'));
}
if ($installedPlugin->compare($pluginVersion) >= 0) {
throw new Exception(__('manager.plugins.installedVersionNewer'));
}
$destinyPath = Core::getBaseDir() . "/plugins/{$category}/{$plugin}";
// Delete existing files.
$fileManager->rmtree($destinyPath);
// Check whether deleting has worked.
if (is_dir($destinyPath)) {
throw new Exception(__('manager.plugins.deleteError', ['pluginName' => $pluginVersion->getProduct()]));
}
// Copy the plug-in from the temporary folder to the target folder.
$fileManager->copyDir($pluginFolder, $destinyPath) || throw new Exception('Could not copy plugin to destination!');
try {
$upgradeFile = "{$destinyPath}/" . static::PLUGIN_UPGRADE_FILE;
if ($fileManager->fileExists($upgradeFile)) {
/** @var SiteDAO */
$siteDao = DAORegistry::getDAO('SiteDAO');
$site = $siteDao->getSite();
$params = $this->_getConnectionParams();
$params['locale'] = $site->getPrimaryLocale();
$params['additionalLocales'] = $site->getSupportedLocales();
$installer = new Upgrade($params, $upgradeFile, true);
// Run the upgrade/migration
$installer->execute() || throw new Exception(__('manager.plugins.upgradeFailed', ['errorString' => $installer->getErrorString()]));
}
// Add the new version to the database
$pluginVersion->setCurrent(1);
$versionDao->insertVersion($pluginVersion, true);
return $pluginVersion;
} catch (Throwable $e) {
// Delete the plugin files on failure
$fileManager->rmtree($destinyPath);
throw $e;
}
});
}
/**
* Drops risky characters from a filename
*/
private function sanitizeFilename(string $filename): string
{
return preg_replace('/[^\w.-]/', '', $filename);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\PluginHelper', '\PluginHelper');
foreach ([
'PLUGIN_ACTION_UPLOAD',
'PLUGIN_ACTION_UPGRADE',
'PLUGIN_VERSION_FILE',
'PLUGIN_INSTALL_FILE',
'PLUGIN_UPGRADE_FILE',
] as $constantName) {
define($constantName, constant('\PluginHelper::' . $constantName));
}
}
+276
View File
@@ -0,0 +1,276 @@
<?php
/**
* @file classes/plugins/PluginRegistry.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 PluginRegistry
*
* @ingroup plugins
*
* @see Plugin
*
* @brief Registry class for managing plugins.
*/
namespace PKP\plugins;
use APP\core\Application;
use Exception;
use FilesystemIterator;
use Illuminate\Support\Arr;
use PKP\core\Registry;
use ReflectionObject;
class PluginRegistry
{
/** Base path of plugins */
public const PLUGINS_PREFIX = 'plugins/';
/**
* Return all plugins in the given category as an array, or, if the
* category is not specified, all plugins in an associative array of
* arrays by category.
*/
public static function &getPlugins(?string $category = null): array
{
$plugins = & Registry::get('plugins', true, []); // Reference necessary
if ($category !== null) {
$plugins[$category] ??= [];
return $plugins[$category];
}
return $plugins;
}
/**
* Get all plugins in a single array.
*/
public static function getAllPlugins(): array
{
return array_reduce(static::getPlugins(), fn (array $output, array $pluginsByCategory) => $output += $pluginsByCategory, []);
}
/**
* Register a plugin with the registry in the given category.
*
* @param string $category the name of the category to extend
* @param Plugin $plugin The instantiated plugin to add
* @param string $path The path the plugin was found in
* @param int $mainContextId To identify enabled plug-ins
* we need a context. This context is usually taken from the
* request but sometimes there is no context in the request
* (e.g. when executing CLI commands). Then the main context
* can be given as an explicit ID.
*
* @return bool True IFF the plugin was registered successfully
*/
public static function register(string $category, Plugin $plugin, string $path, ?int $mainContextId = null): bool
{
$pluginName = $plugin->getName();
$plugins = & static::getPlugins();
// If the plugin is already loaded or failed/refused to register
if (isset($plugins[$category][$pluginName]) || !$plugin->register($category, $path, $mainContextId)) {
return false;
}
$plugins[$category][$pluginName] = $plugin;
return true;
}
/**
* Get a plugin by category and name.
*/
public static function getPlugin(string $category, string $name): ?Plugin
{
return static::getPlugins()[$category][$name] ?? null;
}
/**
* Load all plugins for a given category.
*
* @param string $category The name of the category to load
* @param bool $enabledOnly if true load only enabled
* plug-ins (db-installation required), otherwise look on
* disk and load all available plug-ins (no db required).
* @param int $mainContextId To identify enabled plug-ins
* we need a context. This context is usually taken from the
* request but sometimes there is no context in the request
* (e.g. when executing CLI commands). Then the main context
* can be given as an explicit ID.
*
* @return array Set of plugins, sorted in sequence.
*/
public static function loadCategory(string $category, bool $enabledOnly = false, ?int $mainContextId = null): array
{
static $cache;
$key = implode("\0", func_get_args());
$plugins = $cache[$key] ??= $enabledOnly && Application::isInstalled()
? static::_loadFromDatabase($category, $mainContextId)
: static::_loadFromDisk($category);
// Fire a hook prior to registering plugins for a category
// n.b.: this should not be used from a PKPPlugin::register() call to "jump categories"
Hook::call('PluginRegistry::loadCategory', [&$category, &$plugins]);
// Register the plugins in sequence.
ksort($plugins);
array_walk_recursive($plugins, fn (Plugin $plugin, string $pluginPath) => static::register($category, $plugin, $pluginPath, $mainContextId));
// Return the list of successfully-registered plugins.
$plugins = & static::getPlugins($category);
// Fire a hook after all plugins of a category have been loaded, so they
// are able to interact if required
Hook::call("PluginRegistry::categoryLoaded::{$category}", [&$plugins]);
// Sort the plugins by priority before returning.
uasort($plugins, fn (Plugin $a, Plugin $b) => $a->getSeq() - $b->getSeq());
return $plugins;
}
/**
* Load a specific plugin from a category by path name.
* Similar to loadCategory, except that it only loads a single plugin
* within a category rather than loading all.
*
* @param int $mainContextId To identify enabled plug-ins
* we need a context. This context is usually taken from the
* request but sometimes there is no context in the request
* (e.g. when executing CLI commands). Then the main context
* can be given as an explicit ID.
*/
public static function loadPlugin(string $category, string $pluginName, ?int $mainContextId = null): ?Plugin
{
if ($plugin = static::_instantiatePlugin($category, $pluginName)) {
static::register($category, $plugin, self::PLUGINS_PREFIX . "{$category}/{$pluginName}", $mainContextId);
}
return $plugin;
}
/**
* Get a list of the various plugin categories available.
*
* NB: The categories are returned in the order in which they
* have to be registered and/or installed. Plug-ins in categories
* later in the list may depend on plug-ins in earlier
* categories.
*/
public static function getCategories(): array
{
$categories = Application::get()->getPluginCategories();
Hook::call('PluginRegistry::getCategories', [&$categories]);
return $categories;
}
/**
* Load all plugins in the system and return them in a single array.
*/
public static function loadAllPlugins(bool $enabledOnly = false): array
{
// Retrieve and register categories (order is significant).
$categories = static::getCategories();
return array_reduce($categories, fn (array $plugins, string $category) => $plugins + static::loadCategory($category, $enabledOnly), []);
}
/**
* Instantiate a plugin.
*/
private static function _instantiatePlugin(string $category, string $pluginName, ?string $classToCheck = null): ?Plugin
{
if (!preg_match('/^[a-z0-9]+$/i', $pluginName)) {
throw new Exception("Invalid product name \"{$pluginName}\"");
}
// First, try a namespaced class name matching the installation directory.
$pluginClassName = "\\APP\\plugins\\{$category}\\{$pluginName}\\" . ucfirst($pluginName) . 'Plugin';
$plugin = class_exists($pluginClassName)
? new $pluginClassName()
: static::_deprecatedInstantiatePlugin($category, $pluginName);
$classToCheck = $classToCheck ?: Plugin::class;
$isObject = is_object($plugin);
// Complements $classToCheck with a namespace when needed
if (!str_contains($classToCheck, '\\') && $isObject && ($reflection = new ReflectionObject($plugin))->inNamespace()) {
$classToCheck = "{$reflection->getNamespaceName()}\\{$classToCheck}";
}
if ($plugin !== null && !($plugin instanceof $classToCheck)) {
$type = $isObject ? $plugin::class : gettype($plugin);
error_log(new Exception("Plugin {$pluginName} expected to inherit from {$classToCheck}, actual type {$type}"));
return null;
}
return $plugin;
}
/**
* Attempts to retrieve plugins from the database.
*/
private static function _loadFromDatabase(string $category, ?int $mainContextId = null): array
{
$plugins = [];
$categoryDir = static::PLUGINS_PREFIX . $category;
$products = Application::get()->getEnabledProducts("plugins.{$category}", $mainContextId);
foreach ($products as $product) {
$name = $product->getProduct();
if ($plugin = static::_instantiatePlugin($category, $name, $product->getProductClassname())) {
$plugins[$plugin->getSeq()]["{$categoryDir}/{$name}"] = $plugin;
}
}
return $plugins;
}
/**
* Get all plug-ins from disk without querying the database, used during installation.
*/
private static function _loadFromDisk(string $category): array
{
$categoryDir = static::PLUGINS_PREFIX . $category;
if (!is_dir($categoryDir)) {
return [];
}
$plugins = [];
foreach (new FilesystemIterator($categoryDir) as $path) {
if (!$path->isDir()) {
continue;
}
$pluginName = $path->getFilename();
if ($plugin = static::_instantiatePlugin($category, $pluginName)) {
$plugins[$plugin->getSeq()]["{$categoryDir}/{$pluginName}"] = $plugin;
}
}
return $plugins;
}
/**
* Instantiate a plugin.
*
* @deprecated 3.4.0 Old way to instantiate a plugin
*/
private static function _deprecatedInstantiatePlugin(string $category, string $pluginName): ?Plugin
{
$pluginPath = static::PLUGINS_PREFIX . "{$category}/{$pluginName}";
// Try the plug-in wrapper for backwards compatibility.
$pluginWrapper = "{$pluginPath}/index.php";
if (file_exists($pluginWrapper)) {
return include $pluginWrapper;
}
// Try the well-known plug-in class name next (with and without ".inc.php")
$pluginClassName = ucfirst($pluginName) . ucfirst($category) . 'Plugin';
if (Arr::first(['.inc.php', '.php'], fn (string $suffix) => file_exists("{$pluginPath}/{$pluginClassName}{$suffix}"))) {
$pluginPackage = "plugins.{$category}.{$pluginName}";
return instantiate("{$pluginPackage}.{$pluginClassName}", $pluginClassName, $pluginPackage, 'register');
}
return null;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\PluginRegistry', '\PluginRegistry');
define('PLUGINS_PREFIX', PluginRegistry::PLUGINS_PREFIX);
}
@@ -0,0 +1,316 @@
<?php
/**
* @file classes/plugins/PluginSettingsDAO.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PluginSettingsDAO
*
* @ingroup plugins
*
* @see Plugin
*
* @brief Operations for retrieving and modifying plugin settings.
*/
namespace PKP\plugins;
use Illuminate\Support\Facades\DB;
use PKP\cache\CacheManager;
use PKP\cache\GenericCache;
use PKP\xml\PKPXMLParser;
class PluginSettingsDAO extends \PKP\db\DAO
{
/**
* Get the cache for plugin settings.
*
* @param int $contextId Context ID
* @param string $pluginName Plugin symbolic name
*
* @return GenericCache
*/
public function _getCache($contextId, $pluginName)
{
static $settingCache = [];
return $settingCache[(int) $contextId][$pluginName] ??= CacheManager::getManager()->getCache(
'pluginSettings-' . $contextId,
$pluginName,
[$this, '_cacheMiss']
);
}
/**
* Retrieve a plugin setting value.
*
* @param int $contextId Context ID
* @param string $pluginName Plugin symbolic name
* @param string $name Setting name
*/
public function getSetting($contextId, $pluginName, $name)
{
// Normalize the plug-in name to lower case.
$pluginName = strtolower_codesafe($pluginName);
// Retrieve the setting.
$cache = $this->_getCache($contextId, $pluginName);
return $cache->get($name);
}
/**
* Does the plugin setting exist.
*
* @param int $contextId Context ID
* @param string $pluginName Plugin symbolic name
* @param string $name Setting name
*
* @return bool
*/
public function settingExists($contextId, $pluginName, $name)
{
$pluginName = strtolower_codesafe($pluginName);
$result = $this->retrieve(
'SELECT COUNT(*) AS row_count FROM plugin_settings WHERE plugin_name = ? AND context_id = ? AND setting_name = ?',
[$pluginName, (int) $contextId, $name]
);
$row = $result->current();
return $row ? (bool) $row->row_count : false;
}
/**
* Callback for a cache miss.
*
* @param object $cache Cache object
* @param string $id Identifier to look up in cache
*/
public function _cacheMiss($cache, $id)
{
$contextParts = explode('-', $cache->getContext());
$contextId = array_pop($contextParts);
$settings = $this->getPluginSettings($contextId, $cache->getCacheId());
if (!isset($settings[$id])) {
// Make sure that even null values are cached
$cache->setCache($id, null);
return null;
}
return $settings[$id];
}
/**
* Retrieve and cache all settings for a plugin.
*
* @param int $contextId Context ID
* @param string $pluginName Plugin symbolic name
*
* @return array
*/
public function getPluginSettings($contextId, $pluginName)
{
// Normalize plug-in name to lower case.
$pluginName = strtolower_codesafe($pluginName);
$result = $this->retrieve(
'SELECT setting_name, setting_value, setting_type FROM plugin_settings WHERE plugin_name = ? AND context_id = ?',
[$pluginName, (int) $contextId]
);
$pluginSettings = [];
foreach ($result as $row) {
$pluginSettings[$row->setting_name] = $this->convertFromDB($row->setting_value, $row->setting_type);
}
$cache = $this->_getCache($contextId, $pluginName);
$cache->setEntireCache($pluginSettings);
return $pluginSettings;
}
/**
* Add/update a plugin setting.
*
* @param int $contextId Context ID
* @param string $pluginName Symbolic plugin name
* @param string $name Setting name
* @param mixed $value Setting value
* @param string $type data type of the setting. If omitted, type will be guessed
*/
public function updateSetting($contextId, $pluginName, $name, $value, $type = null)
{
// Normalize the plug-in name to lower case.
$pluginName = strtolower_codesafe($pluginName);
$cache = $this->_getCache($contextId, $pluginName);
$cache->setCache($name, $value);
$value = $this->convertToDB($value, $type);
DB::table('plugin_settings')->updateOrInsert(
['context_id' => (int) $contextId, 'plugin_name' => $pluginName, 'setting_name' => $name],
['setting_value' => $value, 'setting_type' => $type]
);
}
/**
* Delete a plugin setting.
*
* @param int $contextId
* @param int $pluginName
* @param string $name
*/
public function deleteSetting($contextId, $pluginName, $name)
{
// Normalize the plug-in name to lower case.
$pluginName = strtolower_codesafe($pluginName);
$cache = $this->_getCache($contextId, $pluginName);
$cache->setCache($name, null);
return $this->update(
'DELETE FROM plugin_settings WHERE plugin_name = ? AND setting_name = ? AND context_id = ?',
[$pluginName, $name, (int) $contextId]
);
}
/**
* Delete all settings for a plugin.
*
* @param int $contextId
* @param string $pluginName
*/
public function deleteSettingsByPlugin($contextId, $pluginName)
{
// Normalize the plug-in name to lower case.
$pluginName = strtolower_codesafe($pluginName);
$cache = $this->_getCache($contextId, $pluginName);
$cache->flush();
return $this->update(
'DELETE FROM plugin_settings WHERE context_id = ? AND plugin_name = ?',
[(int) $contextId, $pluginName]
);
}
/**
* Delete all settings for a context.
*
* @param int $contextId
*/
public function deleteByContextId($contextId)
{
return $this->update(
'DELETE FROM plugin_settings WHERE context_id = ?',
[(int) $contextId]
);
}
/**
* Used internally by installSettings to perform variable and translation replacements.
*
* @param string $rawInput contains text including variable and/or translate replacements.
* @param array $paramArray contains variables for replacement
*
* @return string
*/
public function _performReplacement($rawInput, $paramArray = [])
{
$value = preg_replace_callback('{{translate key="([^"]+)"}}', fn ($matches) => __($matches[1]), (string) $rawInput);
foreach ($paramArray as $pKey => $pValue) {
$value = str_replace('{$' . $pKey . '}', $pValue, $value);
}
return $value;
}
/**
* Used internally by installSettings to recursively build nested arrays.
* Deals with translation and variable replacement calls.
*
* @param object $node XMLNode <array> tag
* @param array $paramArray Parameters to be replaced in key/value contents
*/
public function _buildObject($node, $paramArray = [])
{
$value = [];
foreach ($node->getChildren() as $element) {
$key = $element->getAttribute('key');
$childArray = $element->getChildByName('array');
if (isset($childArray)) {
$content = $this->_buildObject($childArray, $paramArray);
} else {
$content = $this->_performReplacement($element->getValue(), $paramArray);
}
if (!empty($key)) {
$key = $this->_performReplacement($key, $paramArray);
$value[$key] = $content;
} else {
$value[] = $content;
}
}
return $value;
}
/**
* Install plugin settings from an XML file.
*
* @param string $pluginName name of plugin for settings to apply to
* @param string $filename Name of XML file to parse and install
* @param array $paramArray Optional parameters for variable replacement in settings
*/
public function installSettings($contextId, $pluginName, $filename, $paramArray = [])
{
$xmlParser = new PKPXMLParser();
$tree = $xmlParser->parse($filename);
if (!$tree) {
return false;
}
// Check for existing settings and leave them if they are already in place.
$currentSettings = $this->getPluginSettings($contextId, $pluginName);
foreach ($tree->getChildren() as $setting) {
$nameNode = $setting->getChildByName('name');
$valueNode = $setting->getChildByName('value');
if (isset($nameNode) && isset($valueNode)) {
$type = $setting->getAttribute('type');
$name = $nameNode->getValue();
// If the setting already exists, respect it.
if (isset($currentSettings[$name])) {
continue;
}
if ($type == 'object') {
$arrayNode = $valueNode->getChildByName('array');
$value = $this->_buildObject($arrayNode, $paramArray);
} else {
$value = $this->_performReplacement($valueNode->getValue(), $paramArray);
}
// Replace translate calls with translated content
$this->updateSetting($contextId, $pluginName, $name, $value, $type);
}
}
}
}
/**
* Used internally by plugin setting installation code to perform translation
* function.
*
* @param array $matches
*
* @return string
*/
function _installer_plugin_regexp_callback($matches)
{
return __($matches[1]);
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\PluginSettingsDAO', '\PluginSettingsDAO');
}
+67
View File
@@ -0,0 +1,67 @@
<?php
/**
* @file classes/plugins/ReportPlugin.php
*
* Copyright (c) 2013-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class ReportPlugin
*
* @ingroup plugins
*
* @brief Abstract class for report plugins
*/
namespace PKP\plugins;
use PKP\core\PKPApplication;
use PKP\core\PKPRequest;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\RedirectAction;
abstract class ReportPlugin extends Plugin
{
//
// Public methods.
//
/**
* @copydoc Plugin::getActions()
*/
public function getActions($request, $actionArgs)
{
$dispatcher = $request->getDispatcher();
return array_merge(
$this->getEnabled() ? [
new LinkAction(
'settings',
new RedirectAction($dispatcher->url(
$request,
PKPApplication::ROUTE_PAGE,
null,
'stats',
'reports',
'report',
['pluginName' => $this->getName()]
)),
__('manager.statistics.reports'),
null
)
] : [],
parent::getActions($request, $actionArgs)
);
}
/**
* Displays the report
*
* @param array $args
* @param PKPRequest $request
*/
abstract public function display($args, $request);
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\ReportPlugin', '\ReportPlugin');
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,732 @@
<?php
/**
* @defgroup classes_plugins_importexport import/export deployment
*/
/**
* @file classes/plugins/importexport/PKPImportExportDeployment.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 PKPImportExportDeployment
*
* @ingroup plugins_importexport
*
* @brief Base class configuring the import/export process to an
* application's specifics.
*/
namespace PKP\plugins\importexport;
use APP\facades\Repo;
use APP\publication\Publication;
use APP\submission\Submission;
use Error;
use Exception;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;
use LibXMLError;
use PKP\context\Context;
use PKP\core\PKPApplication;
use PKP\user\User;
class PKPImportExportDeployment
{
/** @var array Array of possible validation errors */
private $xmlValidationErrors = [];
/** @var bool Indicator that the import/export process has failed */
private $processFailed = false;
/** @var mixed The import/export process result */
public $processResult = null;
/** @var Context The current import/export context */
private Context $_context;
/** @var ?User The current import/export user */
private $_user;
/** @var ?Submission The current import/export submission */
private $_submission;
/** @var Publication The current import/export publication */
private ?Publication $_publication;
/** @var array The processed import objects IDs */
private $_processedObjectsIds = [];
/** @var array Warnings keyed by object IDs */
private $_processedObjectsErrors = [];
/** @var array Errors keyed by object IDs */
private $_processedObjectsWarnings = [];
/** @var array Connection between the file from the XML import file and the new IDs after they are imported */
private $_fileDBIds;
/** @var array Connection between the submission file IDs from the XML import file and the new IDs after they are imported */
private $_submissionFileDBIds;
/** @var array Connection between the author id from the XML import file and the DB file IDs */
private $_authorDBIds;
/** @var string Base path for the import source */
private $_baseImportPath = '';
/** @var array A list of imported root elements to display to the user after the import is complete */
private $_importedRootEntities;
/** @var array A list of exported root elements to display to the user after the export is complete */
private $_exportRootEntities;
/**
* Constructor
*
* @param Context $context
* @param ?User $user optional
*/
public function __construct($context, $user = null)
{
$this->setContext($context);
$this->setUser($user);
$this->setSubmission(null);
$this->setPublication(null);
$this->setFileDBIds([]);
$this->setSubmissionFileDBIds([]);
$this->_processedObjectsIds = [];
$this->_importedRootEntities = [];
}
//
// Deployment items for subclasses to override
//
/**
* Get the submission node name
*
* @return string
*/
public function getSubmissionNodeName()
{
assert(false);
}
/**
* Get the submissions node name
*
* @return string
*/
public function getSubmissionsNodeName()
{
assert(false);
}
/**
* Get the representation node name
*
* @return string
*/
public function getRepresentationNodeName()
{
assert(false);
}
/**
* Get the namespace URN
*
* @return string
*/
public function getNamespace()
{
assert(false);
}
/**
* Get the schema filename.
*
* @return string
*/
public function getSchemaFilename()
{
assert(false);
}
//
// Getter/setters
//
/**
* Set the import/export context.
*
* @param Context $context
*/
public function setContext($context)
{
$this->_context = $context;
}
/**
* Get the import/export context.
*
*/
public function getContext(): Context
{
return $this->_context;
}
/**
* Set the import/export submission.
*/
public function setSubmission(?Submission $submission)
{
$this->_submission = $submission;
if ($submission) {
$this->addProcessedObjectId(PKPApplication::ASSOC_TYPE_SUBMISSION, $submission->getId());
}
}
/**
* Get the import/export submission.
*/
public function getSubmission(): ?Submission
{
return $this->_submission;
}
/**
* Set the import/export publication.
*/
public function setPublication(?Publication $publication)
{
$this->_publication = $publication;
if ($publication) {
$this->addProcessedObjectId(PKPApplication::ASSOC_TYPE_PUBLICATION, $publication->getId());
}
}
/**
* Get the import/export publication.
*
* @return Publication
*/
public function getPublication(): ?Publication
{
return $this->_publication;
}
/**
* Add the processed object ID.
*
* @param int $assocType Application::ASSOC_TYPE_...
* @param int $assocId
*/
public function addProcessedObjectId($assocType, $assocId)
{
$this->_processedObjectsIds[$assocType][] = $assocId;
}
/**
* Add the error message to the processed object ID.
*
* @param int $assocType Application::ASSOC_TYPE_...
* @param ?int $assocId
* @param string $errorMsg
*/
public function addError($assocType, $assocId, $errorMsg)
{
if (($this->getObjectTypes()[$assocType] ?? null) === null) {
error_log(new InvalidArgumentException("Invalid assocType \"{$assocType}\""));
}
$this->_processedObjectsErrors[$assocType][$assocId][] = $errorMsg;
}
/**
* Add the warning message to the processed object ID.
*
* @param int $assocType Application::ASSOC_TYPE_...
* @param int $assocId
* @param string $warningMsg
*/
public function addWarning($assocType, $assocId, $warningMsg)
{
if (($this->getObjectTypes()[$assocType] ?? null) === null) {
error_log(new InvalidArgumentException("Invalid assocType \"{$assocType}\""));
}
$this->_processedObjectsWarnings[$assocType][$assocId][] = $warningMsg;
}
/**
* Get the processed objects IDs.
*
* @param int $assocType Application::ASSOC_TYPE_...
*
* @return ?array
*/
public function getProcessedObjectsIds($assocType)
{
if (array_key_exists($assocType, $this->_processedObjectsIds)) {
return $this->_processedObjectsIds[$assocType];
}
return null;
}
/**
* Get the processed objects errors.
*
* @param int $assocType Application::ASSOC_TYPE_...
*
* @return ?array
*/
public function getProcessedObjectsErrors($assocType)
{
if (array_key_exists($assocType, $this->_processedObjectsErrors)) {
return $this->_processedObjectsErrors[$assocType];
}
return null;
}
/**
* Get the processed objects errors.
*
* @param int $assocType Application::ASSOC_TYPE_...
*
* @return ?array
*/
public function getProcessedObjectsWarnings($assocType)
{
if (array_key_exists($assocType, $this->_processedObjectsWarnings)) {
return $this->_processedObjectsWarnings[$assocType];
}
return null;
}
/**
* Remove the processed objects.
*
* @param int $assocType Application::ASSOC_TYPE_...
*/
public function removeImportedObjects($assocType)
{
switch ($assocType) {
case PKPApplication::ASSOC_TYPE_SUBMISSION:
$processedSubmissionsIds = $this->getProcessedObjectsIds(PKPApplication::ASSOC_TYPE_SUBMISSION);
if (!empty($processedSubmissionsIds)) {
foreach ($processedSubmissionsIds as $submissionId) {
if ($submissionId) {
Repo::submission()->dao->deleteById($submissionId);
}
}
}
break;
}
}
/**
* Set the import/export user.
*
* @param ?User $user
*/
public function setUser($user)
{
$this->_user = $user;
}
/**
* Get the import/export user.
*
* @return ?User
*/
public function getUser()
{
return $this->_user;
}
/**
* Get the array of the inserted file DB Ids.
*
* @return array
*/
public function getFileDBIds()
{
return $this->_fileDBIds;
}
/**
* Set the array of the inserted file DB Ids.
*
* @param array $fileDBIds
*/
public function setFileDBIds($fileDBIds)
{
return $this->_fileDBIds = $fileDBIds;
}
/**
* Get the file DB Id.
*
* @param int $fileId The old file id
*
* @return ?int The new file id
*/
public function getFileDBId($fileId)
{
if (array_key_exists($fileId, $this->_fileDBIds)) {
return $this->_fileDBIds[$fileId];
}
return null;
}
/**
* Set the file DB Id.
*
* @param int $fileId The old file id
* @param int $DBId The new file id
*/
public function setFileDBId($fileId, $DBId)
{
return $this->_fileDBIds[$fileId] = $DBId;
}
/**
* Get the array of the inserted submission file DB Ids.
*
* @return array
*/
public function getSubmissionFileDBIds()
{
return $this->_submissionFileDBIds;
}
/**
* Set the array of the inserted submission file DB Ids.
*
* @param array $submissionFileDBIds
*/
public function setSubmissionFileDBIds($submissionFileDBIds)
{
return $this->_submissionFileDBIds = $submissionFileDBIds;
}
/**
* Get the submission file DB Id.
*
* @return ?int The new submission file id
*/
public function getSubmissionFileDBId($submissionFileDBId)
{
if (array_key_exists($submissionFileDBId, $this->_submissionFileDBIds)) {
return $this->_submissionFileDBIds[$submissionFileDBId];
}
return null;
}
/**
* Set the submission file DB Id.
*
* @param int $submissionFileDBId The old submission file id
* @param int $DBId The new submission file id
*/
public function setSubmissionFileDBId($submissionFileDBId, $DBId)
{
return $this->_submissionFileDBIds[$submissionFileDBId] = $DBId;
}
/**
* Set the array of the inserted author DB Ids.
*
* @param array $authorDBIds
*/
public function setAuthorDBIds($authorDBIds)
{
return $this->_authorDBIds = $authorDBIds;
}
/**
* Get the array of the inserted author DB Ids.
*
* @return array
*/
public function getAuthorDBIds()
{
return $this->_authorDBIds;
}
/**
* Get the author DB Id.
*
* @param int $authorId
*
* @return ?int
*/
public function getAuthorDBId($authorId)
{
if (array_key_exists($authorId, $this->_authorDBIds)) {
return $this->_authorDBIds[$authorId];
}
return null;
}
/**
* Set the author DB Id.
*
* @param int $authorId
* @param int $DBId
*/
public function setAuthorDBId($authorId, $DBId)
{
return $this->_authorDBIds[$authorId] = $DBId;
}
/**
* Set the directory location for the import source
*
* @param string $path
*/
public function setImportPath($path)
{
$this->_baseImportPath = $path;
}
/**
* Get the directory location for the import source
*
* @return string
*/
public function getImportPath()
{
return $this->_baseImportPath;
}
/**
* Add the imported root entities.
*
* @param int $assocType Application::ASSOC_TYPE_...
*/
public function addImportedRootEntity($assocType, $entity)
{
$this->_importedRootEntities[$assocType][] = $entity;
}
/**
* Get the imported root entities.
*
* @param int $assocType Application::ASSOC_TYPE_...
*/
public function getImportedRootEntities($assocType)
{
if (array_key_exists($assocType, $this->_importedRootEntities)) {
return $this->_importedRootEntities[$assocType];
}
return null;
}
/**
* Set export root entities
*
* @param array $exportRootEntities
*/
public function setExportRootEntities($exportRootEntities)
{
$this->_exportRootEntities = $exportRootEntities;
}
/**
* Get export root entities
*
* @return array
*/
public function getExportRootEntities()
{
return $this->_exportRootEntities;
}
/**
* Wraps the import process
*
* @param string $rootFilter
* @param string $importXml
*/
public function import($rootFilter, $importXml)
{
$dbConnection = DB::connection();
try {
$currentFilter = PKPImportExportFilter::getFilter($rootFilter, $this);
$dbConnection->beginTransaction();
libxml_use_internal_errors(true);
$result = $currentFilter->execute($importXml);
$dbConnection->commit();
$this->processResult = $result;
} catch (Error | Exception $e) {
$this->addError(PKPApplication::ASSOC_TYPE_NONE, 0, $e->getMessage());
$dbConnection->rollBack();
$this->processFailed = true;
} finally {
$this->xmlValidationErrors = array_filter(libxml_get_errors(), fn (LibXMLError $error) => in_array($error->level, [LIBXML_ERR_ERROR, LIBXML_ERR_FATAL]));
libxml_clear_errors();
}
}
/**
* Wraps the export process
*
* @param string $rootFilter
* @param array $exportObjects
* @param array $opts
*/
public function export($rootFilter, $exportObjects, $opts = [])
{
try {
$this->setExportRootEntities($exportObjects);
$currentFilter = PKPImportExportFilter::getFilter($rootFilter, $this, $opts);
libxml_use_internal_errors(true);
$result = $currentFilter->execute($exportObjects, true);
$this->xmlValidationErrors = array_filter(libxml_get_errors(), function ($a) {
return $a->level == LIBXML_ERR_ERROR || $a->level == LIBXML_ERR_FATAL;
});
libxml_clear_errors();
if (!$result) {
$this->addError(PKPApplication::ASSOC_TYPE_NONE, 0, 'Export result is empty.');
$this->processFailed = true;
}
$this->processResult = $result;
} catch (Error | Exception $e) {
$this->addError(PKPApplication::ASSOC_TYPE_NONE, 0, $e->getMessage());
$this->processFailed = true;
}
}
/**
* Getter method for XMLValidation Errors
*
* @return array
*/
public function getXMLValidationErrors()
{
return $this->xmlValidationErrors;
}
/**
* Get all public objects, with their
* respective names as array values.
*
* @return array
*/
protected function getObjectTypes()
{
$objectTypes = [
PKPApplication::ASSOC_TYPE_NONE => __('plugins.importexport.native.common.any'),
PKPApplication::ASSOC_TYPE_SUBMISSION => __('submission.submission'),
PKPApplication::ASSOC_TYPE_AUTHOR => __('user.role.author'),
PKPApplication::ASSOC_TYPE_PUBLICATION => __('submission.publication'),
PKPApplication::ASSOC_TYPE_SECTION => __('section.section'),
PKPApplication::ASSOC_TYPE_SUBMISSION_FILE => __('submission.submissionFile'),
];
return $objectTypes;
}
/**
* Get object type string.
*
* @param mixed $assocType int or null (optional)
*
* @return mixed string or array
*/
public function getObjectTypeString($assocType = null)
{
$objectTypes = $this->getObjectTypes();
if (is_null($assocType)) {
return $objectTypes;
} else {
if (isset($objectTypes[$assocType])) {
return $objectTypes[$assocType];
} else {
assert(false);
}
}
}
/**
* Get possible Warnings and Errors from the import/export process
*
* @return array
*/
public function getWarningsAndErrors()
{
$problems = [];
$objectTypes = $this->getObjectTypes();
foreach ($objectTypes as $assocType => $name) {
$foundWarnings = $this->getProcessedObjectsWarnings($assocType);
if (!empty($foundWarnings)) {
$problems['warnings'][$name][] = $foundWarnings;
}
$foundErrors = $this->getProcessedObjectsErrors($assocType);
if (!empty($foundErrors)) {
$problems['errors'][$name][] = $foundErrors;
}
}
if (count($validationErrors = $this->getXMLValidationErrors())) {
$validationErrors = array_map(fn (LibXMLError $e) => "Line {$e->line} Column {$e->column}: {$e->message}", $validationErrors);
$genericError = $objectTypes[PKPApplication::ASSOC_TYPE_NONE];
$problems['errors'][$genericError][] = [$validationErrors];
}
return $problems;
}
/**
* Get import entities with their names
*
* @return array
*/
public function getImportedRootEntitiesWithNames()
{
$rootEntities = [];
$objectTypes = $this->getObjectTypes();
foreach ($objectTypes as $assocType => $name) {
$entities = $this->getImportedRootEntities($assocType);
if (!empty($entities)) {
$rootEntities[$name][] = $entities;
}
}
return $rootEntities;
}
/**
* Returns an indication that the import/export process has failed
*
* @return bool
*/
public function isProcessFailed()
{
if ($this->processFailed || count($this->xmlValidationErrors) > 0) {
return true;
}
return false;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\importexport\PKPImportExportDeployment', '\PKPImportExportDeployment');
}
@@ -0,0 +1,97 @@
<?php
/**
* @file classes/plugins/importexport/PKPImportExportFilter.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 PKPImportExportFilter
*
* @ingroup plugins_importexport_native
*
* @brief Base helper class for import/export filters
*/
namespace PKP\plugins\importexport;
// FIXME: Add namespacing
use APP\plugins\importexport\native\NativeImportExportDeployment;
use Exception;
use PKP\db\DAORegistry;
use PKP\filter\Filter;
use PKP\filter\FilterDAO;
use PKP\filter\PersistableFilter;
use PKP\plugins\importexport\native\filter\NativeExportFilter;
use PKP\plugins\importexport\native\PKPNativeImportExportDeployment;
class PKPImportExportFilter extends PersistableFilter
{
/** @var PKPNativeImportExportDeployment */
private $_deployment;
//
// Deployment management
//
/**
* Set the import/export deployment
*
* @param NativeImportExportDeployment $deployment
*/
public function setDeployment($deployment)
{
$this->_deployment = $deployment;
}
/**
* Get the import/export deployment
*
* @return PKPNativeImportExportDeployment
*/
public function getDeployment()
{
return $this->_deployment;
}
/**
* Static method that gets the filter object given its name
*
* @param string $filter
* @param PKPImportExportDeployment $deployment
* @param array $opts
*
* @return Filter
*/
public static function getFilter($filter, $deployment, $opts = [])
{
$filterDao = DAORegistry::getDAO('FilterDAO'); /** @var FilterDAO $filterDao */
$filters = $filterDao->getObjectsByGroup($filter);
if (count($filters) != 1) {
throw new Exception(
__(
'plugins.importexport.native.common.error.filter.configuration.count',
[
'filterName' => $filter,
'filterCount' => count($filters)
]
)
);
}
$currentFilter = array_shift($filters);
$currentFilter->setDeployment($deployment);
if ($currentFilter instanceof NativeExportFilter) {
$currentFilter->setOpts($opts);
}
return $currentFilter;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\importexport\PKPImportExportFilter', '\PKPImportExportFilter');
}