Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
bbdbfe7
Allowed to store custom modules and themes in custom/.
Jan 12, 2026
4d047c0
Used addons/modules and addons/themes for composer.
Feb 2, 2026
429ed31
Replaced ini by composer, with fallback module.ini.
Feb 2, 2026
6a6e0d8
Managed extra key standalone for composer.
Feb 2, 2026
bc1b6b0
Added tests for composer addons.
Feb 2, 2026
4bb7e62
Added addons/readme.md.
Feb 2, 2026
ffdbec0
Added specific theme keys in composer.
Feb 2, 2026
79986fe
Loaded add-ons metadata via composer installed.json.
Feb 2, 2026
423b853
Removed check of omeka constraint version for composer add-ons.
Feb 2, 2026
3fbe8b8
Added more tests and fixtures for addons.
Feb 2, 2026
14ab866
Cleaned description by removing "Module/Theme for Omeka S:".
Feb 2, 2026
6ca5662
Added theme test-override for testing theme overriding.
Feb 2, 2026
8e29fb5
Added script and key omeka-assets to simplify assets management.
Feb 2, 2026
3ea5d4a
Added script to install add-on dependencies manually.
Feb 2, 2026
3fe1ea3
Updated tests to tests assets.
Feb 2, 2026
7b6b37b
Fixed override of Common and modules installed with composer.
Feb 2, 2026
c739d30
Added tests for override.
Feb 2, 2026
299b008
Fixed notice when a theme as no form.
Feb 2, 2026
cf2fb68
Renamed addons directory as composer-addons.
Feb 4, 2026
9288683
Removed the feature to manage an add-on as standalone.
Feb 4, 2026
59fbca8
Moved internal composer plugin omeka-assets into generic external-ass…
Feb 4, 2026
64ca11e
Removed management of specific keys for theme.
Feb 4, 2026
43a54b5
Moved key "configurable" from ini to module.config.php.
Feb 4, 2026
f89616f
Removed specific code written for a smooth evolution with module Common.
Feb 4, 2026
51848c1
Replace key "provide" by official "branch-alias" in composer.json.
Feb 4, 2026
08437af
Refactored InfoReader.
Feb 9, 2026
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
/files/
/modules/
/themes/
/composer-addons/modules/*
!/composer-addons/modules/.gitkeep
/composer-addons/themes/*
!/composer-addons/themes/.gitkeep
/application/test/config/database.ini
/application/test/.phpunit.result.cache
/application/asset/css/font-awesome/
Expand Down
1 change: 1 addition & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
->exclude('application/data/overrides')
->exclude('config')
->exclude('files')
->exclude('composer-addons')
->exclude('modules')
->exclude('node_modules')
->exclude('themes')
Expand Down
1 change: 1 addition & 0 deletions application/config/application.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
'module_paths' => [
'Omeka' => OMEKA_PATH . '/application',
OMEKA_PATH . '/modules',
OMEKA_PATH . '/composer-addons/modules',
],
'config_glob_paths' => [
OMEKA_PATH . '/config/local.config.php',
Expand Down
8 changes: 6 additions & 2 deletions application/data/composer-addon-installer/composer.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
{
"name": "omeka/composer-addon-installer",
"version": "2.0",
"version": "3.0",
"description": "Composer plugin to install Omeka S modules and themes into composer-addons/modules/ and composer-addons/themes/.",
"type": "composer-plugin",
"license": "GPL-3.0",
"require": {
"php": ">=8.1",
"composer-plugin-api": "^2.0"
},
"autoload": {
"psr-4": {"Omeka\\Composer\\": "src/"}
"psr-4": {
"Omeka\\Composer\\": "src/"
}
},
"extra": {
"class": "Omeka\\Composer\\AddonInstallerPlugin"
Expand Down
104 changes: 91 additions & 13 deletions application/data/composer-addon-installer/src/AddonInstaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,124 @@
use Composer\Package\PackageInterface;
use Composer\Installer\LibraryInstaller;

/**
* Composer installer for Omeka S modules and themes.
*
* Installs packages to composer-addons/modules/ or composer-addons/themes/ based on type.
* Name transformations align with composer/installers OmekaSInstaller.
*
* Supports extra options:
* - installer-name: Explicit install name (overrides auto-detection)
*/
class AddonInstaller extends LibraryInstaller
{
/**
* Gets the name this package is to be installed with, either from the
* <pre>extra.install-name</pre> property or the package name.
* Gets the name this package is to be installed with.
*
* Priority:
* 1. extra.installer-name (explicit)
* 2. Auto-transformation based on package type
*
* For modules: removes prefixes/suffixes and converts to CamelCase
* For themes: removes prefixes/suffixes, keeps lowercase with hyphens
*
* @return string
*/
public static function getInstallName(PackageInterface $package)
public static function getInstallName(PackageInterface $package): string
{
$extra = $package->getExtra();

// Support both installer-name (composer/installers) and install-name
// (legacy).
if (isset($extra['installer-name'])) {
return $extra['installer-name'];
}
if (isset($extra['install-name'])) {
return $extra['install-name'];
}

$packageName = $package->getPrettyName();
$slashPos = strpos($packageName, '/');
if ($slashPos === false) {
throw new \InvalidArgumentException('Addon package names must contain a slash');
throw new \InvalidArgumentException('Add-on package names must contain a slash'); // @translate
}

$addonName = substr($packageName, $slashPos + 1);
return $addonName;
$name = substr($packageName, $slashPos + 1);
$type = $package->getType();

if ($type === 'omeka-s-module') {
return static::inflectModuleName($name);
}

if ($type === 'omeka-s-theme') {
return static::inflectThemeName($name);
}

return $name;
}

/**
* Transform module name: remove prefixes/suffixes, convert to CamelCase.
*
* Examples:
* - omeka-s-module-common → Common
* - value-suggest → ValueSuggest
* - bulk-import-module → BulkImport
* - neatline-omeka-s → Neatline
* - module-lessonplans → Lessonplans
*
* @param string $name
* @return string
*/
protected static function inflectModuleName($name): string
{
// Remove Omeka prefixes/suffixes.
$name = preg_replace('/^(omeka-?s?-?)?(module-)?/', '', $name);
$name = preg_replace('/(-module)?(-omeka-?s?)?$/', '', $name);

// Convert kebab-case to CamelCase.
$name = strtr($name, ['-' => ' ']);
$name = strtr(ucwords($name), [' ' => '']);

return $name;
}

public function getInstallPath(PackageInterface $package)
/**
* Transform theme name: remove prefixes/suffixes, keep lowercase.
*
* Examples:
* - omeka-s-theme-repository → repository
* - my-custom-theme → my-custom
* - flavor-theme-omeka → flavor
*
* @param string $name
* @return string
*/
protected static function inflectThemeName($name): string
{
// Remove Omeka prefixes/suffixes.
$name = preg_replace('/^(omeka-?s?-?)?(theme-)?/', '', $name);
$name = preg_replace('/(-theme)?(-omeka-?s?)?$/', '', $name);

return $name;
}

public function getInstallPath(PackageInterface $package): string
{
$addonName = static::getInstallName($package);
switch ($package->getType()) {
case 'omeka-s-theme':
return 'themes/' . $addonName;
case 'omeka-s-module':
return 'modules/' . $addonName;
return 'composer-addons/modules/' . $addonName;
case 'omeka-s-theme':
return 'composer-addons/themes/' . $addonName;
default:
throw new \InvalidArgumentException('Invalid Omeka S addon package type');
throw new \InvalidArgumentException('Invalid Omeka S add-on package type'); // @translate
}
}

public function supports($packageType)
public function supports($packageType): bool
{
return in_array($packageType, ['omeka-s-theme', 'omeka-s-module']);
return $packageType === 'omeka-s-module'
|| $packageType === 'omeka-s-theme';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;

/**
* Composer plugin for Omeka S add-on installation.
*
* Registers the AddonInstaller for modules and themes.
*/
class AddonInstallerPlugin implements PluginInterface
{
public function activate(Composer $composer, IOInterface $io)
Expand Down
142 changes: 142 additions & 0 deletions application/data/scripts/install-addon-deps.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/usr/bin/env php
<?php
/**
* Install composer dependencies for a git-cloned module or theme.
*
* For add-ons installed via git clone in modules/ or themes/, dependencies
* are not installed automatically. This script reads the add-on composer.json
* and installs its dependencies via the root Omeka composer.
*
* Note: Add-ons installed via `composer require` (in composer-addons/modules/ or
* composer-addons/themes/) have their dependencies installed automatically.
*
* Usage:
* php application/data/scripts/install-addon-deps.php ModuleName
* php application/data/scripts/install-addon-deps.php --theme theme-name
* php application/data/scripts/install-addon-deps.php --dry-run ModuleName
*
* Options:
* --theme Specify a theme instead of a module
* --dry-run Show what would be installed without actually installing
*/
$args = array_slice($argv, 1);
$dryRun = false;
$isTheme = false;
$addonName = null;

foreach ($args as $arg) {
if ($arg === '--dry-run') {
$dryRun = true;
} elseif ($arg === '--theme') {
$isTheme = true;
} elseif (strpos($arg, '--') !== 0) {
$addonName = $arg;
}
}

if (!$addonName) {
echo "Usage: php application/data/scripts/install-addon-deps.php [--dry-run] [--theme] Name\n";
echo "\nOptions:\n";
echo " --theme Specify a theme instead of a module\n";
echo " --dry-run Show what would be installed without actually installing\n";
exit(1);
}

// Find addon path
if ($isTheme) {
$possiblePaths = [
dirname(__DIR__, 3) . '/themes/' . $addonName,
dirname(__DIR__, 3) . '/composer-addons/themes/' . $addonName,
];
$addonType = 'Theme';
} else {
$possiblePaths = [
dirname(__DIR__, 3) . '/modules/' . $addonName,
dirname(__DIR__, 3) . '/composer-addons/modules/' . $addonName,
];
$addonType = 'Module';
}

$addonPath = null;
foreach ($possiblePaths as $path) {
if (is_dir($path)) {
$addonPath = $path;
break;
}
}

if (!$addonPath) {
echo "Error: $addonType '$addonName' not found.\n";
exit(1);
}

$composerJson = $addonPath . '/composer.json';
if (!file_exists($composerJson)) {
echo "Error: No composer.json found in $addonPath\n";
exit(1);
}

$json = json_decode(file_get_contents($composerJson), true);
if (!$json) {
echo "Error: Invalid composer.json\n";
exit(1);
}

$require = $json['require'] ?? [];

if (empty($require)) {
echo "No dependencies to install for $addonName.\n";
exit(0);
}

// Filter out packages provided by Omeka or PHP
$toInstall = [];
foreach ($require as $package => $version) {
// Skip Omeka core packages (provided by Omeka)
if (in_array($package, ['omeka/omeka-s', 'omeka/omeka-s-core'])) {
continue;
}
// Skip PHP extensions
if (strpos($package, 'ext-') === 0) {
continue;
}
// Skip PHP version constraint
if ($package === 'php') {
continue;
}
$toInstall[$package] = $version;
}

if (empty($toInstall)) {
echo "No external dependencies to install for $addonName.\n";
exit(0);
}

echo "Dependencies for $addonName:\n";
foreach ($toInstall as $package => $version) {
echo " - $package: $version\n";
}

if ($dryRun) {
echo "\n[Dry run] Would run:\n";
echo " composer require " . implode(' ', array_keys($toInstall)) . "\n";
exit(0);
}

echo "\nInstalling...\n";

$packages = implode(' ', array_map(function ($pkg, $ver) {
// Use version constraint if specific, otherwise let composer decide
if (preg_match('/^\^|~|>=|<=|>|<|\*/', $ver)) {
return escapeshellarg("$pkg:$ver");
}
return escapeshellarg($pkg);
}, array_keys($toInstall), $toInstall));

$command = "cd " . escapeshellarg(dirname(__DIR__, 3)) . " && composer require $packages --no-interaction 2>&1";

echo "Running: composer require " . implode(' ', array_keys($toInstall)) . "\n\n";

passthru($command, $exitCode);

exit($exitCode);
2 changes: 1 addition & 1 deletion application/src/Module/AbstractModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public function getAutoloaderConfig()
return;
}

$autoloadPath = sprintf('%1$s/modules/%2$s/src', OMEKA_PATH, $namespace);
$autoloadPath = dirname($classInfo->getFileName()) . '/src';
return [
'Laminas\Loader\StandardAutoloader' => [
'namespaces' => [
Expand Down
Loading