diff --git a/.idea/jsonSchemas.xml b/.idea/jsonSchemas.xml
index 5534e9f5c0..8fcbb5a86e 100644
--- a/.idea/jsonSchemas.xml
+++ b/.idea/jsonSchemas.xml
@@ -22,24 +22,6 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/.tools/psalm/baseline.xml b/.tools/psalm/baseline.xml
index a505cf08d8..1fc2bbf9a0 100644
--- a/.tools/psalm/baseline.xml
+++ b/.tools/psalm/baseline.xml
@@ -1451,31 +1451,17 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/AGENTS.md b/AGENTS.md
index 19b86ff7cb..60c201f01a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -55,7 +55,7 @@ This repository contains the **REDAXO core** under `src/` (`Redaxo\Core\`), back
### Key concepts
- **`Core` static class** (`src/Core.php`) — central application registry for paths, config, request, current user.
-- **Addon system** — `Addon` / `AddonManager` (`src/Addon/`). Each addon has a `package.yml` (metadata + page config) plus optional `boot.php` (runtime init) and `install.php` (schema/data setup — must be idempotent, runs on every `console migrate`).
+- **Addon system** — `Addon` / `AddonManager` (`src/Addon/`). Each addon is a subclass of `Addon`, registered via composer.json `extra.redaxo.addon-class`. Metadata comes from composer.json; integration happens through overridable hooks — `boot()` (runtime init), `install()`/`uninstall()` (schema/data setup — must be idempotent, runs on every `console migrate`), `getPages()` (backend pages) — plus the `$load` and `$defaultConfig` properties.
- **Extension points** — REDAXO's hook/event system: register listeners with `Extension::register('NAME', ...)`, fire points with `Extension::registerPoint(new ExtensionPoint(...))`. Classes live under `Redaxo\Core\ExtensionPoint`. This is the primary integration mechanism for addons.
- **Fragments** (`fragments/`) — template snippets rendered via `Fragment` (`src/View/Fragment.php`).
- **Boot flow** — `AbstractProject` (Symfony `RuntimeInterface`) drives boot via `boot/core.php` → `boot/addons.php` → environment entry (`boot/backend.php`, `boot/frontend.php`, `boot/console.php`).
diff --git a/lang/de_de.lang b/lang/de_de.lang
index de82267b9f..01a675cfa6 100644
--- a/lang/de_de.lang
+++ b/lang/de_de.lang
@@ -327,7 +327,6 @@ package_activate = aktivieren
package_yes = ja
package_no = nein
package_caption = Liste der verfügbaren Packages
-package_invalid_yml_file = Die package.yml ist invalid:
package_install_cant_copy_files = Fehler beim Kopieren des /assets Ordners!
package_install_cant_delete_files = Fehler beim Löschen des /assets Ordners!
package_jump_to = Zu "{0}" springen
diff --git a/lang/en_gb.lang b/lang/en_gb.lang
index 012cea274c..a63dc2bf81 100644
--- a/lang/en_gb.lang
+++ b/lang/en_gb.lang
@@ -326,7 +326,6 @@ package_activate = activate
package_yes = yes
package_no = no
package_caption = List of available packages
-package_invalid_yml_file = The file package.yml is invalid:
package_install_cant_copy_files = An error occurred when copying the /assets folder!
package_install_cant_delete_files = An error occurred when deleting the /assets folder!
package_jump_to = Jump to "{0}"
diff --git a/pages/system/report.html.php b/pages/system/report.html.php
index 362f490951..56de286c99 100644
--- a/pages/system/report.html.php
+++ b/pages/system/report.html.php
@@ -23,7 +23,7 @@
foreach ($group as $label => $value) {
if (SystemReport::TITLE_PACKAGES === $title || SystemReport::TITLE_REDAXO === $title) {
if (null === $value) {
- throw new RuntimeException('Package ' . $label . ' does not define a proper version in its package.yml.');
+ throw new RuntimeException('Package ' . $label . ' does not define a proper version in its composer.json.');
}
if (Version::isUnstable($value)) {
$value = ' ' . escape($value);
diff --git a/schemas/package.json b/schemas/package.json
deleted file mode 100644
index 8f883e909d..0000000000
--- a/schemas/package.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-07/schema#",
- "$id": "https://redaxo.org/schemas/package.json",
- "title": "JSON schema for REDAXO package.yml",
- "type": "object",
- "properties": {
- "default_config": {
- "description": "Default values for Redaxo\\Core\\Config",
- "type": "object"
- }
- },
- "additionalProperties": true
-}
diff --git a/src/Addon/Addon.php b/src/Addon/Addon.php
index eb9e2cda31..9a03b1cf90 100644
--- a/src/Addon/Addon.php
+++ b/src/Addon/Addon.php
@@ -16,7 +16,6 @@
use Redaxo\Core\Filesystem\Path;
use Redaxo\Core\Filesystem\Url;
use Redaxo\Core\Translation\I18n;
-use Redaxo\Core\Util\Exception\YamlParseException;
use Redaxo\Core\Util\Formatter;
use Redaxo\Core\Util\Type;
use Redaxo\Core\View\Fragment;
@@ -33,10 +32,6 @@
abstract class Addon
{
- final public const string FILE_PACKAGE = 'package.yml';
-
- private const string PROPERTIES_CACHE_FILE = 'packages.cache';
-
/**
* Array of all addons.
*
@@ -62,6 +57,13 @@ abstract class Addon
/** Loading position relative to other addons during boot. Override to load this addon early or late. */
public protected(set) LoadOrder $load = LoadOrder::Normal;
+ /**
+ * Default config values applied on install (only for keys that are not already set). Override to provide defaults.
+ *
+ * @var array
+ */
+ public protected(set) array $defaultConfig = [];
+
/** Lifecycle state of the addon. */
public private(set) AddonState $state = AddonState::Uninstalled;
@@ -72,9 +74,6 @@ abstract class Addon
*/
private array $properties = [];
- /** Flag whether the properties of package.yml are loaded. */
- private bool $propertiesLoaded = false;
-
/** @var array|null */
private ?array $composerJson = null;
@@ -205,9 +204,6 @@ final public function getProperty(string $key, mixed $default = null): mixed
/** @param non-empty-string $key */
final public function hasProperty(string $key): bool
{
- if (!isset($this->properties[$key]) && !$this->propertiesLoaded) {
- $this->loadProperties();
- }
return isset($this->properties[$key]);
}
@@ -321,75 +317,6 @@ final public function i18n(string $key, string|int ...$replacements): string
return I18n::msg($key, ...$replacements);
}
- /** Loads the properties of package.yml. */
- final public function loadProperties(bool $force = false): void
- {
- $file = $this->getPath(self::FILE_PACKAGE);
- if (!is_file($file)) {
- $this->propertiesLoaded = true;
- return;
- }
-
- /** @var array}>|null $cache */
- static $cache = null;
- if (null === $cache) {
- /** @var array}> $cache */
- $cache = File::getCache(Path::coreCache(self::PROPERTIES_CACHE_FILE));
- }
- $id = $this->name;
-
- if ($force) {
- unset($cache[$id]);
- }
-
- $isCached = isset($cache[$id]);
- $isBackendAdmin = Core::isBackend() && Core::getUser()?->admin;
- if (!$isCached || (Core::getConsole() || $isBackendAdmin) && $cache[$id]['timestamp'] < filemtime($file)) {
- try {
- $properties = File::getConfig($file);
-
- $cache[$id]['timestamp'] = filemtime($file);
- $cache[$id]['data'] = $properties;
-
- /** @var bool $registeredShutdown */
- static $registeredShutdown = false;
- if (!$registeredShutdown) {
- $registeredShutdown = true;
- register_shutdown_function(static function () use (&$cache) {
- foreach ($cache as $addon => $_) {
- if (!self::exists($addon)) {
- unset($cache[$addon]);
- }
- }
- File::putCache(Path::coreCache(self::PROPERTIES_CACHE_FILE), $cache);
- });
- }
- } catch (YamlParseException $exception) {
- if ($this->isInstalled()) {
- throw $exception;
- }
-
- $properties = [];
- }
- } else {
- $properties = $cache[$id]['data'];
- }
-
- $this->properties = [];
- if ($properties) {
- foreach ($properties as $key => $value) {
- $key = Type::string($key);
- if ('supportpage' !== $key) {
- $value = I18n::translateArray($value, false, $this->i18n(...));
- } elseif (null !== $value && !preg_match('@^https?://@i', $value)) {
- $value = 'https://' . $value;
- }
- $this->properties[$key] = $value;
- }
- }
- $this->propertiesLoaded = true;
- }
-
final public function getLicense(): ?string
{
/** @var string|list|null $license */
@@ -423,12 +350,6 @@ final public function clearCache(): void
throw new RuntimeException('Addon cache directory "' . $cacheDir . '" is not writable.');
}
- $cache = File::getCache($path = Path::coreCache(self::PROPERTIES_CACHE_FILE));
- if ($cache) {
- unset($cache[$this->name]);
- File::putCache($path, $cache);
- }
-
Extension::registerPoint(new AddonCacheDeleted($this));
}
diff --git a/src/Addon/AddonManager.php b/src/Addon/AddonManager.php
index 9b8f8c69be..bf3d1aa0be 100644
--- a/src/Addon/AddonManager.php
+++ b/src/Addon/AddonManager.php
@@ -14,7 +14,6 @@
use Redaxo\Core\Filesystem\Path;
use Redaxo\Core\Filesystem\Url;
use Redaxo\Core\Translation\I18n;
-use Redaxo\Core\Util\Exception\YamlParseException;
use Redaxo\Core\Util\Str;
use Redaxo\Core\Util\Type;
@@ -66,14 +65,6 @@ public function getMessage(): string
public function install(): bool
{
try {
- // check package.yml
- $addonFile = $this->addon->getPath(Addon::FILE_PACKAGE);
- try {
- File::getConfig($addonFile);
- } catch (YamlParseException $e) {
- throw new UserMessageException($this->i18n('invalid_yml_file') . ' ' . $e->getMessage());
- }
-
// check requirements and conflicts
$message = '';
if (!$this->checkRequirements()) {
@@ -91,7 +82,7 @@ public function install(): bool
$this->addon->install();
$successMessage = (string) $this->addon->getProperty('successmsg', '');
- foreach ($this->addon->getProperty('default_config', []) as $key => $value) {
+ foreach ($this->addon->defaultConfig as $key => $value) {
if (!$this->addon->hasConfig($key)) {
$this->addon->setConfig($key, $value);
}
diff --git a/src/Console/CommandLoader.php b/src/Console/CommandLoader.php
index 236d50f3a6..88527f6173 100644
--- a/src/Console/CommandLoader.php
+++ b/src/Console/CommandLoader.php
@@ -22,7 +22,7 @@
/**
* Discovers all commands that are marked with the {@see AsCommand} attribute and extend {@see AbstractCommand},
* both in the core and in the active addons. Addons therefore register their commands simply by adding the
- * attribute to a command class — no `package.yml` configuration is required.
+ * attribute to a command class.
*
* Commands are returned as {@see LazyCommand}, so that listing the commands (e.g. `console list`) does not
* instantiate every command class — only the command that is actually executed is instantiated.