Help: version.xml in plugin directory contains invalid data (OJS 3.4.0-8)

Hi everyone,

I’m currently developing a custom plugin for OJS 3.4.0-8 named disciplineDisplay. I’ve followed the standard structure and placed the plugin in the plugins/generic/disciplineDisplay/ directory. However, when I try to install or enable the plugin, I get the following error:

“version.xml in plugin directory contains invalid data.”

here is the content of my version.xml file:

xml:

<?xml version="1.0" encoding="UTF-8"?>
<plugin name="disciplineDisplay"
        category="generic"
        version="1.0.0.0"
        application="ojs2"
        lazyLoad="true">
    <release>
        <version>1.0.0.0</version>
        <date>2025-06-19</date>
        <notes>Initial release of the Discipline Display Plugin.</notes>
    </release>
</plugin>

I’ve confirmed that:

  • The plugin is placed under the correct path.
  • All plugin files exist (DisciplineDisplayPlugin.inc.php, version.xml, locale.xml, and article_details.tpl).
  • The XML is well-formed with UTF-8 encoding (without BOM).
  • Permissions are properly set.

Despite this, the plugin does not appear in the Installed Plugins list or Generic Plugins tab, and I can’t install it via the plugin gallery either.

Any guidance or suggestions would be greatly appreciated. Thank you in advance!


System Info:

  • OJS version: 3.4.0-8
  • PHP: 8.x
  • Hosting: Shared (cPanel)
  • Plugin: Manually uploaded via File Manager

Best Regards,
Darryl

Hi @darrylnuydaph,

Typically OJS 3.4.0 plugin code is named .php, not .inc.php. It is possible to use .inc.php-based code, but you’d need to add an index.php wrapper to get it to load.

Regards,
Alec Smecher
Public Knowledge Project Team

Thank you for the clarification regarding the file naming convention in OJS 3.4.0+. I now understand that the plugin entry point should typically be a .php file rather than .inc.php, which explains why the system initially failed to recognize or load my plugin properly.

I had originally used DisciplineDisplayPlugin.inc.php and wasn’t aware that an index.php wrapper was required to load .inc.php files correctly. This led to a lot of confusion, including the plugin not appearing in the installed plugins list, the version.xml validation errors, and the locale strings failing to resolve.

Right now, I am fixing the hook for the metadata discipline in the article page to show this correctly in the frontend. After going through several iterations — including restructuring the plugin, correcting the XML file, manually uploading via cPanel, and clearing caches — I now see how critical proper naming and structure are for plugin registration and activation in OJS 3.4+.

I appreciate the insight, and I will follow the .php convention moving forward to ensure compatibility and easier debugging. Thanks again for the helpful recommendation — it definitely made the issue clearer.

Best Regards,
Darryl

Hi @darrylnuydaph,

Glad to hear you got un-stuck – but in retrospect I could’ve provided more helpful information!

These changes are part of a move starting with OJS 3.4.0 to standardize our PHP coding with modern standards. All of our code moved from .inc.php to .php, and we introduced namespacing and autoloading. There is some measure of backwards compatibility but not complete. You can see some documentation about this in the 3.4 Release Notebook, which in turn links to a more specific resource on plugin adaptation.

Thanks,
Alec Smecher
Public Knowledge Project Team

Hello Again,

The plugin was successfully installed and enabled, but the discipline metadata wasn’t showing on the article page. This was likely due to OJS 3.4’s changes in routing and hook handling. I’ve adjusted the plugin to use ArticleHandler::view, assign the discipline data using TemplateManager::assign(), and ensure it’s rendered in the article template.

My purpose is to view the Discipline in the article page.
Below is the code that needs to be checked — until now, it’s still not viewing on the article page:


:file_folder: Plugin Structure

ShowDisciplinePlugin/
├── ShowDisciplinePluginPlugin.php
├── plugin.json
├── version.xml
├── locale/
│   └── en_US/
│       └── locale.xml
└── templates/
    └── frontend/
        └── objects/
            └── article_details.tpl

:wrench: ShowDisciplinePluginPlugin.php

<?php

namespace APP\plugins\generic\ShowDisciplinePlugin;

use PKP\plugins\GenericPlugin;
use PKP\plugins\Hook;
use APP\submission\Submission;

class ShowDisciplinePluginPlugin extends GenericPlugin
{
    public function register($category, $path, $mainContextId = null): bool
    {
        if (!parent::register($category, $path, $mainContextId)) return false;

        $this->addLocaleData();
        Hook::add('ArticleHandler::view', [$this, 'addDisciplineToTemplate']);
        return true;
    }

    public function addDisciplineToTemplate($hookName, $args)
    {
        $request = $args[0];
        $article = $args[1];

        if (!$article instanceof Submission) return false;

        $templateMgr = \TemplateManager::getManager($request);
        $disciplines = $this->getDisciplines($article);
        if (!empty($disciplines)) {
            $templateMgr->assign('disciplineText', implode(', ', $disciplines));
        }

        return false;
    }

    private function getDisciplines(Submission $article): array
    {
        $publication = $article->getCurrentPublication();
        $publicationId = $publication->getId();
        $contextId = $article->getData('contextId');
        $locale = \App::getLocale();

        $vocabDao = \DAORegistry::getDAO('ControlledVocabDAO');
        $vocab = $vocabDao->getBySymbolic('submissionDiscipline', ASSOC_TYPE_PUBLICATION, $publicationId, $contextId);
        if (!$vocab) return [];

        $entryDao = \DAORegistry::getDAO('ControlledVocabEntryDAO');
        $entries = $entryDao->getByControlledVocabId($vocab->getId())->toArray();

        $result = [];
        foreach ($entries as $entry) {
            $label = $entry->getLocalizedSetting('submissionDiscipline', $locale);
            if ($label) {
                $result[] = $label;
            }
        }

        return $result;
    }

    public function getDisplayName(): string
    {
        return __('plugins.generic.ShowDisciplinePlugin.name');
    }

    public function getDescription(): string
    {
        return __('plugins.generic.ShowDisciplinePlugin.description');
    }
}

:page_facing_up: plugin.json

{
  "name": "ShowDisciplinePlugin",
  "category": "generic",
  "version": "1.0.0",
  "mainClass": "APP\\\\plugins\\\\generic\\\\ShowDisciplinePlugin\\\\ShowDisciplinePluginPlugin",
  "enabled": true
}

:card_index_dividers: version.xml

<?xml version="1.0" encoding="UTF-8"?>
<version>
  <application>ShowDisciplinePlugin</application>
  <type>plugins.generic</type>
  <release>1.0.0</release>
  <date>2025-06-20</date>
  <lazyLoad>true</lazyLoad>
</version>

:globe_showing_europe_africa: locale/en_US/locale.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE locale SYSTEM "../../../../lib/pkp/dtd/locale.dtd">
<locale name="en_US" full_name="English (US)">
    <message key="plugins.generic.ShowDisciplinePlugin.name">Show Discipline Plugin</message>
    <message key="plugins.generic.ShowDisciplinePlugin.description">Displays the discipline metadata before the abstract on the article page.</message>
    <message key="submission.discipline">Discipline</message>
</locale>

:page_facing_up: templates/frontend/objects/article_details.tpl

{** Custom Article Template for ShowDisciplinePlugin **}

{if $disciplineText}
<section class="item discipline">
    <h2 class="label">{translate key="submission.discipline"}</h2>
    <span class="value">{$disciplineText|escape}</span>
</section>
{/if}

<section class="item abstract">
    <h2 class="label">{translate key="article.abstract"}</h2>
    <div class="value">
        {$article->getLocalizedData('abstract')|strip_unsafe_html|nl2br}
    </div>
</section>

:repeat_button: Recap

:white_check_mark: Plugin installs and enables.
:prohibited: Discipline metadata still not showing.


:white_check_mark: Code correctly assigns and prepares the output.
:pushpin: Needs to confirm if ArticleHandler::view hook is still triggered as expected in OJS 3.4.

Best Regards
Darryl