Skip to content

Conversation

Copy link

Copilot AI commented Jan 15, 2026

Implements the complete export workflow for the OJS Flat Metadata Exporter plugin, enabling bulk export of journal issues with metadata and galley files packaged for institutional repositories.

Implementation

  • Export workflow: Creates temporary directories per issue, generates Dublin Core metadata CSV files, copies galley files, archives as ZIP, streams download, and cleans up
  • Metadata extraction: Added getArticleCSVRow() to populate 12 DC fields (title, creator, DOI, subject, abstract, date, language, genre, rights, type, relation)
  • File handling: Added _cleanFileName() sanitization using regex pattern /[^\w\-\.]/ to prevent path traversal
  • Issue display: Updated display() to fetch and render published issues in default case using Repo::issue()->getMany()

Key changes

Directory structure created:

ojs-export-{timestamp}/
  └── {journal-acronym}_{issue-identification}/
      ├── metadata.csv
      └── galleys/
          └── {submission-id}-{galley-id}-{filename}

Security considerations:

  • Issue validation against context to prevent cross-journal access
  • File name sanitization prevents directory traversal
  • Uses Symfony Process with array arguments (not shell strings)
  • Fixed issueIds parameter to array format for proper repository queries

Cross-platform compatibility:

  • Added DIRECTORY_SEPARATOR for path construction (line 99)
Original prompt

Please update the OJSFlatMetadataExporterPlugin.php file in the UIUCLibrary/OJSFlatMetadataExporter repository to implement the core export functionality.

The updated file should contain the logic to:

  1. Receive selected issue IDs from the OJS interface.
  2. Create a main temporary directory for the export process.
  3. For each selected issue, create a subdirectory named using the journal acronym and issue identification (e.g., journal-name_volume-1-issue-2).
  4. Inside each issue subdirectory, create a metadata.csv file.
  5. Populate the CSV with a header row and one row per article, containing the metadata we've discussed (Title, Creator, DOI, etc.).
  6. Create a galleys subdirectory inside each issue directory.
  7. Copy all galley files for each article into this galleys directory.
  8. Once all issues are processed, create a single zip archive of the main temporary directory.
  9. Stream this zip file to the user's browser for download.
  10. Clean up the temporary directory and zip file from the server.

Here is the complete, updated content for OJSFlatMetadataExporter/OJSFlatMetadataExporterPlugin.php:

<?php

/**
 * @file plugins/importexport/ojsFlatMetadataExporter/OJSFlatMetadataExporterPlugin.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 OJSFlatMetadataExporterPlugin
 * @ingroup plugins_importexport_ojsFlatMetadataExporter
 *
 * @brief OJS Flat Metadata Export plugin
 */

namespace APP\plugins\importexport\OJSFlatMetadataExporter;

use PKP\plugins\ImportExportPlugin;
use APP\template\TemplateManager;
use Symfony\Component\Process\Process;
use APP\facades\Repo;
use PKP\file\FileManager;
use PKP\services\PKPSchemaService;
use APP\core\Application;

class OJSFlatMetadataExporterPlugin extends ImportExportPlugin {
    /**
     * @see Plugin::register()
     */
    public function register($category, $path, $mainContextId = null) {
        $success = parent::register($category, $path, $mainContextId);
        $this->addLocaleData();
        return $success;
    }

    /**
     * @see Plugin::getName()
     */
    public function getName() {
        return 'OJSFlatMetadataExporterPlugin';
    }

    /**
     * @see Plugin::getDisplayName()
     */
    public function getDisplayName() {
        return __('plugins.importexport.OJSFlatMetadataExporter.displayName');
    }

    /**
     * @see Plugin::getDescription()
     */
    public function getDescription() {
        return __('plugins.importexport.OJSFlatMetadataExporter.description');
    }

    /**
     * @see ImportExportPlugin::display()
     */
    public function display($args, $request) {
        parent::display($args, $request);
        $context = $request->getContext();

        switch (array_shift($args)) {
            case 'export':
                $issueIds = $request->getUserVar('selectedIssues');
                if (empty($issueIds)) {
                    // This part is for user feedback, which we can improve later.
                    // For now, we assume valid selection.
                }
                $this->exportIssues($issueIds, $context, $request);
                break;
            default:
                // Display the export form
                $issueRepo = Repo::issue();
                $issues = $issueRepo->getMany([
                    'contextId' => $context->getId(),
                    'isPublished' => true,
                    'orderBy' => 'datePublished',
                    'orderDirection' => 'DESC',
                ]);

                $templateMgr = TemplateManager::getManager($request);
                $templateMgr->assign('issues', $issues);
                $templateMgr->display($this->getTemplateResource('index.tpl'));
                break;
        }
    }

    /**
     * Perform the export.
     *
     * @param array $issueIds
     * @param \APP\journal\Journal $context
     * @param \PKP\core\PKPRequest $request
     */
    public function exportIssues($issueIds, $context, $request) {
        $fileManager = new FileManager();
        $exportDir = $fileManager->getBasePath() . 'ojs-export-' . time();
        $fileManager->mkdir($exportDir);

        $issues = array_map(function($issueId) {
            return Repo::issue()->get($issueId);
        }, $issueIds);

        foreach ($issues as $issue) {
            if (!$issue || $issue->getJournalId() != $context->getId()) {
                continue;
            }

            $issueDirName = $this->_cleanFileName($context->getAcronym($context->getPrimaryLocale()) . '_' . $issue->getIssueIdentification());
            $issuePath = $exportDir . DIRECTORY_SEPARATOR . $issueDirName;
            $fileManager->mkdir($issuePath);

            $galleysPath = $issuePath . DIRECTORY_SEPARATOR . 'galleys';
            $fileManager->mkdir($galleysPath);

            $csvFilePath = $issuePath . DIRECTORY_S...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

Copilot AI and others added 2 commits January 15, 2026 18:06
Co-authored-by: alexdryden <47127862+alexdryden@users.noreply.github.com>
…tation

Co-authored-by: alexdryden <47127862+alexdryden@users.noreply.github.com>
Copilot AI changed the title [WIP] Update OJSFlatMetadataExporterPlugin with core export functionality Implement core export functionality for OJS Flat Metadata Exporter Jan 15, 2026
Copilot AI requested a review from alexdryden January 15, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants