Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
74051f0
create no-embbed option for exporting issues
Godoy0722 Apr 1, 2025
8ff8475
Changed by Jonas and commited by Guilherme
Godoy0722 Apr 3, 2025
f704bb7
Changed by Jonas and commited by Guilherme
Godoy0722 Apr 3, 2025
641f11a
Changed by Jonas and commited by Guilherme
Godoy0722 Apr 3, 2025
95c5b87
Improve behavior so the user can chose tif the file is embed or an url
Godoy0722 Apr 3, 2025
60b8ccc
Update plugins/importexport/native/NativeImportExportPlugin.inc.php
Godoy0722 Apr 22, 2025
662e491
Update plugins/importexport/native/filter/IssueGalleyNativeXmlFilter.…
Godoy0722 Apr 22, 2025
c45081d
Update plugins/importexport/native/filter/IssueGalleyNativeXmlFilter.…
Godoy0722 Apr 22, 2025
b70b4e7
Update plugins/importexport/native/NativeImportExportPlugin.inc.php
Godoy0722 Apr 22, 2025
8afb136
Update plugins/importexport/native/NativeImportExportPlugin.inc.php
Godoy0722 Apr 22, 2025
c7f3bcc
Update plugins/importexport/native/NativeImportExportPlugin.inc.php
Godoy0722 Apr 22, 2025
cd25f3b
Update plugins/importexport/native/filter/IssueGalleyNativeXmlFilter.…
Godoy0722 Apr 22, 2025
27e866b
Update plugins/importexport/native/native.xsd
Godoy0722 Apr 22, 2025
c0b3605
Update plugins/importexport/native/native.xsd
Godoy0722 Apr 22, 2025
b62d6d3
Update plugins/importexport/native/filter/NativeFilterHelper.inc.php
Godoy0722 Apr 22, 2025
35dcf45
Update plugins/importexport/native/locale/en_US/locale.po
Godoy0722 Apr 22, 2025
129fe93
Update behavior for Native XML export issues for new serializationMod…
Godoy0722 Apr 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions plugins/importexport/native/NativeImportExportPlugin.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ function display($args, $request) {
$exportXml = $this->exportSubmissions(
(array) $request->getUserVar('selectedSubmissions'),
$request->getContext(),
$request->getUser()
$request->getUser(),
$this->getExportOptionsFromRequest($request)
);
import('lib.pkp.classes.file.FileManager');
$fileManager = new FileManager();
Expand All @@ -209,7 +210,8 @@ function display($args, $request) {
$exportXml = $this->exportIssues(
(array) $request->getUserVar('selectedIssues'),
$request->getContext(),
$request->getUser()
$request->getUser(),
$this->getExportOptionsFromRequest($request)
);
import('lib.pkp.classes.file.FileManager');
$fileManager = new FileManager();
Expand All @@ -224,6 +226,23 @@ function display($args, $request) {
}
}

/**
* Get export options from the request object
* @param $request PKPRequest
* @return array
*/
protected function getExportOptionsFromRequest(PKPRequest $request): array {
$opts = [];

$serializationMode = $request->getUserVar('serializationMode');

if (isset($serializationMode)) {
$opts['serializationMode'] = $serializationMode;
}

return $opts;
}

/**
* Get the XML for a set of submissions.
* @param $submissionIds array Array of submission IDs
Expand Down Expand Up @@ -327,7 +346,7 @@ function usage($scriptName) {
* @see PKPImportExportPlugin::executeCLI()
*/
function executeCLI($scriptName, &$args) {
$opts = $this->parseOpts($args, ['no-embed', 'use-file-urls']);
$opts = $this->parseOpts($args, ['no-embed', 'use-file-urls', 'serializationMode:']);
$command = array_shift($args);
$xmlFile = array_shift($args);
$journalPath = array_shift($args);
Expand Down Expand Up @@ -515,6 +534,14 @@ function parseOpts(&$args, $optCodes) {
continue;
}
$opt = substr($arg, 2);
// Check for --serializationMode:value format
if (strpos($opt, 'serializationMode:') === 0) {
$value = substr($opt, strlen('serializationMode:'));
if (in_array($value, ['embed', 'url', 'relative'])) {
$opts['serializationMode'] = $value;
}
continue;
}
if (in_array($opt, $optCodes)) {
$opts[$opt] = true;
continue;
Expand All @@ -525,6 +552,17 @@ function parseOpts(&$args, $optCodes) {
}
}
$args = $newArgs;

if (!isset($opts['serializationMode'])) {
if (isset($opts['use-file-urls'])) {
$opts['serializationMode'] = 'url';
} elseif (isset($opts['no-embed'])) {
$opts['serializationMode'] = 'relative';
} else {
$opts['serializationMode'] = 'embed';
}
}

return $opts;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ function createIssueGalleyNode($doc, $issueGalley) {
* @param $issueGalley IssueGalley
*/
function addFile($doc, $issueGalleyNode, $issueGalley) {
$issueFileDao = DAORegistry::getDAO('IssueFileDAO'); /* @var $issueFileDao IssueFileDAO */
$issueFileDao = DAORegistry::getDAO('IssueFileDAO'); /** @var IssueFileDAO $issueFileDao */
$issueFile = $issueFileDao->getById($issueGalley->getFileId());

if ($issueFile) {
Expand All @@ -120,13 +120,27 @@ function addFile($doc, $issueGalleyNode, $issueGalley) {
$issueFileNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'file_size', $issueFile->getFileSize()));
$issueFileNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'content_type', htmlspecialchars($issueFile->getContentType(), ENT_COMPAT, 'UTF-8')));
$issueFileNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'original_file_name', htmlspecialchars($issueFile->getOriginalFileName(), ENT_COMPAT, 'UTF-8')));


$issueFileNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'date_uploaded', strftime('%Y-%m-%d', strtotime($issueFile->getDateUploaded()))));
$issueFileNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'date_modified', strftime('%Y-%m-%d', strtotime($issueFile->getDateModified()))));


$embedNode = $doc->createElementNS($deployment->getNamespace(), 'embed', base64_encode(file_get_contents($filePath)));
$embedNode->setAttribute('encoding', 'base64');
$issueFileNode->appendChild($embedNode);
if ($this->opts['serializationMode'] !== self::SERIALIZATION_MODE_EMBED) {
$request = Application::get()->getRequest();

$fileUrl = ($this->opts['serializationMode'] === self::SERIALIZATION_MODE_URL)
? $request->url(null, 'issue', 'view', array($issueGalley->getIssueId(), $issueGalley->getId()))
: "{$request->getBasePath()}/{$filePath}";

$hrefNode = $doc->createElementNS($deployment->getNamespace(), 'href');
$hrefNode->setAttribute('src', htmlspecialchars($fileUrl, ENT_COMPAT, 'UTF-8'));
$hrefNode->setAttribute('mime_type', $issueFile->getFileType());
$issueFileNode->appendChild($hrefNode);
} else {
$embedNode = $doc->createElementNS($deployment->getNamespace(), 'embed', base64_encode(file_get_contents($filePath)));
$embedNode->setAttribute('encoding', 'base64');
$issueFileNode->appendChild($embedNode);
}

$issueGalleyNode->appendChild($issueFileNode);
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ function addIssueGalleys($doc, $issueNode, $issue) {
$nativeExportFilters = $filterDao->getObjectsByGroup('issuegalley=>native-xml');
assert(count($nativeExportFilters)==1); // Assert only a single serialization filter
$exportFilter = array_shift($nativeExportFilters);
$exportFilter->setOpts($this->opts);
$exportFilter->setDeployment($this->getDeployment());

$issueGalleyDao = DAORegistry::getDAO('IssueGalleyDAO'); /* @var $issueGalleyDao IssueGalleyDAO */
Expand Down
127 changes: 118 additions & 9 deletions plugins/importexport/native/filter/NativeFilterHelper.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,26 @@ function createPublicationCoversNode($filter, $doc, $object) {
$coverNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'cover_image', htmlspecialchars($coverImageName, ENT_COMPAT, 'UTF-8')));
$coverNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'cover_image_alt_text', htmlspecialchars($coverImage['altText'], ENT_COMPAT, 'UTF-8')));

$embedNode = $doc->createElementNS($deployment->getNamespace(), 'embed', base64_encode(file_get_contents($filePath)));
$embedNode->setAttribute('encoding', 'base64');
$coverNode->appendChild($embedNode);
if (!empty($filter->opts['use-file-urls'])) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess all the notes I've left above can be applied here and on the next modified block (around the lines 136-155).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it! I'll take a look and update these as well.

import('classes.file.PublicFileManager');
$publicFileManager = new PublicFileManager();
$request = Application::get()->getRequest();

if (!empty($filter->opts['use-absolute-urls'])) {
$fileUrl = $request->getBaseUrl() . '/' . $publicFileManager->getContextFilesPath($object->getJournalId()) . '/' . $coverImageName;
} else {
$fileUrl = $request->getBasePath() . '/' . $publicFileManager->getContextFilesPath($object->getJournalId()) . '/' . $coverImageName;
}

$hrefNode = $doc->createElementNS($deployment->getNamespace(), 'href');
$hrefNode->setAttribute('src', htmlspecialchars($fileUrl, ENT_COMPAT, 'UTF-8'));
$hrefNode->setAttribute('mime_type', PKPString::mime_content_type($coverImageName));
$coverNode->appendChild($hrefNode);
} else if (empty($filter->opts['no-embed'])) {
$embedNode = $doc->createElementNS($deployment->getNamespace(), 'embed', base64_encode(file_get_contents($filePath)));
$embedNode->setAttribute('encoding', 'base64');
$coverNode->appendChild($embedNode);
}
$coversNode->appendChild($coverNode);
}
}
Expand Down Expand Up @@ -116,9 +133,32 @@ function createIssueCoversNode($filter, $doc, $object) {
$coverNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'cover_image', htmlspecialchars($coverImage, ENT_COMPAT, 'UTF-8')));
$coverNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'cover_image_alt_text', htmlspecialchars($object->getCoverImageAltText($locale), ENT_COMPAT, 'UTF-8')));

$embedNode = $doc->createElementNS($deployment->getNamespace(), 'embed', base64_encode(file_get_contents($filePath)));
$embedNode->setAttribute('encoding', 'base64');
$coverNode->appendChild($embedNode);
import('lib.pkp.plugins.importexport.native.filter.NativeExportFilter');
if ($filter->opts['serializationMode'] !== NativeExportFilter::SERIALIZATION_MODE_EMBED) {
import('classes.file.PublicFileManager');
$publicFileManager = new PublicFileManager();
$request = Application::get()->getRequest();

$fileUrl = ($filter->opts['serializationMode'] === NativeExportFilter::SERIALIZATION_MODE_URL)
? $filePath
: "{$request->getBasePath()}/{$coverImage}";

$fileUrl = ($filter->opts['serializationMode'] === NativeExportFilter::SERIALIZATION_MODE_URL)
? $request->url(null, 'issue', 'view', array($object->getId(), $coverImage))
: $request->getBasePath() . '/' . $publicFileManager->getContextFilesPath($object->getJournalId()) . '/' . $coverImage;


$hrefNode = $doc->createElementNS($deployment->getNamespace(), 'href');
$hrefNode->setAttribute('src', htmlspecialchars($fileUrl, ENT_COMPAT, 'UTF-8'));
import('lib.pkp.classes.core.PKPString');
$mimeType = PKPString::mime_content_type($filePath);
$hrefNode->setAttribute('mime_type', $mimeType);
$coverNode->appendChild($hrefNode);
} else {
$embedNode = $doc->createElementNS($deployment->getNamespace(), 'embed', base64_encode(file_get_contents($filePath)));
$embedNode->setAttribute('encoding', 'base64');
$coverNode->appendChild($embedNode);
}
$coversNode->appendChild($coverNode);
}
}
Expand Down Expand Up @@ -207,22 +247,43 @@ function parsePublicationCover($filter, $node, $object) {
case 'cover_image_alt_text':
$coverImage['altText'] = $n->textContent;
break;
case 'href':
case 'embed':
if (!isset($coverImage['uploadName'])) {
$deployment->addWarning(ASSOC_TYPE_PUBLICATION, $object->getId(), __('plugins.importexport.common.error.coverImageNameUnspecified'));
break;
}

import('classes.file.PublicFileManager');
$publicFileManager = new PublicFileManager();
$filePath = $publicFileManager->getContextFilesPath($context->getId()) . '/' . $coverImage['uploadName'];
$allowedFileTypes = ['gif', 'jpg', 'png', 'webp'];
$extension = pathinfo(strtolower($filePath), PATHINFO_EXTENSION);

if (!in_array($extension, $allowedFileTypes)) {
$deployment->addWarning(ASSOC_TYPE_PUBLICATION, $object->getId(), __('plugins.importexport.common.error.invalidFileExtension'));
break;
}

file_put_contents($filePath, base64_decode($n->textContent));
if ($n->tagName === 'embed') {
file_put_contents($filePath, base64_decode($n->textContent));
break;
}

// If it falls through to here, it's a href mode.
$imageUrl = $n->getAttribute('src');
if (empty($imageUrl)) {
$deployment->addWarning(ASSOC_TYPE_PUBLICATION, $object->getId(), __('plugins.importexport.common.error.missingHref'));
break;
}

$fileContents = file_get_contents($imageUrl);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loading HTTP content through this function isn't reliable, it depends on a configuration which some hosts disable, take a look at how the <href> is handled on the existing code, and use the same pattern.

Notice the existing code already covers URLs, relative and absolute paths, it will just be needed to keep the allowedFileTypes stuff.

if ($fileContents === false) {
$deployment->addWarning(ASSOC_TYPE_PUBLICATION, $object->getId(), __('plugins.importexport.common.error.failedDownloadingFile', array('url' => $imageUrl)));
break;
}

file_put_contents($filePath, $fileContents);
break;
default:
$deployment->addWarning(ASSOC_TYPE_PUBLICATION, $object->getId(), __('plugins.importexport.common.error.unknownElement', array('param' => $n->tagName)));
Expand Down Expand Up @@ -267,6 +328,7 @@ function parseIssueCover($filter, $node, $object) {
break;
case 'cover_image_alt_text': $object->setCoverImageAltText($n->textContent, $locale); break;
case 'embed':
case 'href':
if (!$object->getCoverImage($locale)) {
$deployment->addWarning(ASSOC_TYPE_ISSUE, $object->getId(), __('plugins.importexport.common.error.coverImageNameUnspecified'));
break;
Expand All @@ -282,13 +344,60 @@ function parseIssueCover($filter, $node, $object) {
break;
}

file_put_contents($filePath, base64_decode($n->textContent));
if ($n->tagName === 'embed') {
file_put_contents($filePath, base64_decode($n->textContent));
break;
}

// If it falls through to here, it's a href mode.
$imageUrl = $n->getAttribute('src');
if (empty($imageUrl)) {
$deployment->addWarning(ASSOC_TYPE_PUBLICATION, $object->getId(), __('plugins.importexport.common.error.missingHref'));
break;
}

$fileContents = file_get_contents($imageUrl);
if ($fileContents === false) {
$deployment->addWarning(ASSOC_TYPE_PUBLICATION, $object->getId(), __('plugins.importexport.common.error.failedDownloadingFile', array('url' => $imageUrl)));
break;
}

file_put_contents($filePath, $fileContents);
break;
case 'file_url':
if (!$object->getCoverImage($locale)) {
$deployment->addWarning(ASSOC_TYPE_ISSUE, $object->getId(), __('plugins.importexport.common.error.coverImageNameUnspecified'));
break;
}

import('classes.file.PublicFileManager');
$publicFileManager = new PublicFileManager();
$filePath = $publicFileManager->getContextFilesPath($context->getId()) . '/' . $object->getCoverImage($locale);
$allowedFileTypes = ['gif', 'jpg', 'png', 'webp'];
$extension = pathinfo(strtolower($filePath), PATHINFO_EXTENSION);
if (!in_array($extension, $allowedFileTypes)) {
$deployment->addWarning(ASSOC_TYPE_ISSUE, $object->getId(), __('plugins.importexport.common.error.invalidFileExtension'));
break;
}

$imageUrl = $n->textContent;
if (empty($imageUrl)) {
$deployment->addWarning(ASSOC_TYPE_ISSUE, $object->getId(), __('plugins.importexport.common.error.missingFileUrl'));
break;
}

$fileContents = file_get_contents($imageUrl);
if ($fileContents === false) {
$deployment->addWarning(ASSOC_TYPE_ISSUE, $object->getId(), __('plugins.importexport.common.error.failedDownloadingFile', array('url' => $imageUrl)));
break;
}

file_put_contents($filePath, $fileContents);
break;
default:
$deployment->addWarning(ASSOC_TYPE_ISSUE, $object->getId(), __('plugins.importexport.common.error.unknownElement', array('param' => $n->tagName)));
}
}
}
}

}
15 changes: 15 additions & 0 deletions plugins/importexport/native/locale/en_US/locale.po
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ msgstr "A cover image with an invalid file extension was specified."
msgid "plugins.importexport.common.error.coverImageNameUnspecified"
msgstr "A cover image was embedded without specifying a name."

msgid "plugins.importexport.common.error.missingHref"
msgstr "The href element is missing the required href attribute."

msgid "plugins.importexport.native.error.unknownUser"
msgstr "The specified user, \"{$userName}\", does not exist."

Expand Down Expand Up @@ -125,3 +128,15 @@ msgid "plugins.importexport.native.import.error.publishedDateMissing"
msgstr ""
"The article \"{$articleTitle}\" is contained within an issue, but has no "
"published date."

msgid "plugins.importexport.native.exportOptions.coverImagesAndSubmissionFiles"
msgstr "Submission Files and Cover Images Handling"

msgid "plugins.importexport.native.exportOptions.files.embed"
msgstr "Embed images and submission files in XML (base64) - uses more memory"

msgid "plugins.importexport.native.exportOptions.files.relative"
msgstr "Use relative URLs for images and submission files - reduces memory usage"

msgid "plugins.importexport.native.exportOptions.files.url"
msgstr "Use absolute URLs for images and submission files - reduces memory usage"
28 changes: 26 additions & 2 deletions plugins/importexport/native/native.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,19 @@
<element name="original_file_name" type="string" minOccurs="1" maxOccurs="1" />
<element name="date_uploaded" type="date" minOccurs="1" maxOccurs="1" />
<element name="date_modified" type="date" minOccurs="1" maxOccurs="1" />
<element name="embed" type="pkp:embed" />
<choice minOccurs="1" maxOccurs="1">
<element name="embed" type="pkp:embed" />
<element name="href">
<complexType>
<simpleContent>
<extension base="string">
<attribute name="src" type="string" use="required"/>
<attribute name="mime_type" type="string" use="required"/>
</extension>
</simpleContent>
</complexType>
</element>
</choice>
</sequence>
</complexType>

Expand All @@ -152,7 +164,19 @@
<sequence>
<element name="cover_image" type="string" minOccurs="1" maxOccurs="1" />
<element name="cover_image_alt_text" type="string" minOccurs="1" maxOccurs="1" />
<element name="embed" type="pkp:embed" />
<choice minOccurs="1" maxOccurs="1">
<element name="embed" type="pkp:embed" />
<element name="href">
<complexType>
<simpleContent>
<extension base="string">
<attribute name="src" type="string" use="required"/>
<attribute name="mime_type" type="string" use="required"/>
</extension>
</simpleContent>
</complexType>
</element>
</choice>
</sequence>
<attribute name="locale" type="string" />
</complexType>
Expand Down
Loading
Loading