471 lines
14 KiB
PHP
471 lines
14 KiB
PHP
<?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');
|
|
}
|