<?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');
}
|