mirror of
https://github.com/flarum/framework.git
synced 2025-02-07 18:33:01 +08:00
Basic Extension Dependency Support (#2188)
- Don't enable an extension if its dependencies are not enabled - Don't disable an extension if its dependencies are not disabled
This commit is contained in:
parent
9251aa925f
commit
eb717bb034
|
@ -123,6 +123,7 @@ export default class ExtensionsPage extends Page {
|
|||
url: app.forum.attribute('apiUrl') + '/extensions/' + id,
|
||||
method: 'PATCH',
|
||||
body: { enabled: !enabled },
|
||||
errorHandler: this.onerror.bind(this),
|
||||
})
|
||||
.then(() => {
|
||||
if (!enabled) localStorage.setItem('enabledExtension', id);
|
||||
|
@ -131,4 +132,23 @@ export default class ExtensionsPage extends Page {
|
|||
|
||||
app.modal.show(LoadingModal);
|
||||
}
|
||||
|
||||
onerror(e) {
|
||||
// We need to give the modal animation time to start; if we close the modal too early,
|
||||
// it breaks the bootstrap modal library.
|
||||
// TODO: This workaround should be removed when we move away from bootstrap JS for modals.
|
||||
setTimeout(() => {
|
||||
app.modal.close();
|
||||
|
||||
const error = JSON.parse(e.responseText).errors[0];
|
||||
|
||||
app.alerts.show(
|
||||
{ type: 'error' },
|
||||
app.translator.trans(`core.lib.error.${error.code}_message`, {
|
||||
extension: error.extension,
|
||||
extensions: error.extensions.join(', '),
|
||||
})
|
||||
);
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extension\Exception;
|
||||
|
||||
use Exception;
|
||||
use Flarum\Extension\Extension;
|
||||
|
||||
/**
|
||||
* This exception is thrown when someone attempts to disable an extension
|
||||
* that other enabled extensions depend on.
|
||||
*/
|
||||
class DependentExtensionsException extends Exception
|
||||
{
|
||||
public $extension;
|
||||
public $dependent_extensions;
|
||||
|
||||
/**
|
||||
* @param $extension: The extension we are attempting to disable.
|
||||
* @param $dependent_extensions: Enabled Flarum extensions that depend on this extension.
|
||||
*/
|
||||
public function __construct(Extension $extension, array $dependent_extensions)
|
||||
{
|
||||
$this->extension = $extension;
|
||||
$this->dependent_extensions = $dependent_extensions;
|
||||
|
||||
parent::__construct($extension->getId().' could not be disabled, because it is a dependency of: '.implode(', ', $this->getDependentExtensionIds()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of IDs for extensions that depend on this extension.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDependentExtensionIds()
|
||||
{
|
||||
return array_map(function (Extension $extension) {
|
||||
return $extension->getId();
|
||||
}, $this->dependent_extensions);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extension\Exception;
|
||||
|
||||
use Flarum\Foundation\ErrorHandling\HandledError;
|
||||
|
||||
class DependentExtensionsExceptionHandler
|
||||
{
|
||||
public function handle(DependentExtensionsException $e): HandledError
|
||||
{
|
||||
return (new HandledError(
|
||||
$e,
|
||||
'dependent_extensions',
|
||||
409
|
||||
))->withDetails($this->errorDetails($e));
|
||||
}
|
||||
|
||||
protected function errorDetails(DependentExtensionsException $e): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'extension' => $e->extension->getId(),
|
||||
'extensions' => $e->getDependentExtensionIds(),
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extension\Exception;
|
||||
|
||||
use Exception;
|
||||
use Flarum\Extension\Extension;
|
||||
|
||||
/**
|
||||
* This exception is thrown when someone attempts to enable an extension
|
||||
* whose Flarum extension dependencies are not all enabled.
|
||||
*/
|
||||
class MissingDependenciesException extends Exception
|
||||
{
|
||||
public $extension;
|
||||
public $missing_dependencies;
|
||||
|
||||
/**
|
||||
* @param $extension: The extension we are attempting to enable.
|
||||
* @param $missing_dependencies: Extensions that this extension depends on, and are not enabled.
|
||||
*/
|
||||
public function __construct(Extension $extension, array $missing_dependencies = null)
|
||||
{
|
||||
$this->extension = $extension;
|
||||
$this->missing_dependencies = $missing_dependencies;
|
||||
|
||||
parent::__construct($extension->getId().' could not be enabled, because it depends on: '.implode(', ', $this->getMissingDependencyIds()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of IDs for missing (disabled) extensions that this extension depends on.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMissingDependencyIds()
|
||||
{
|
||||
return array_map(function (Extension $extension) {
|
||||
return $extension->getId();
|
||||
}, $this->missing_dependencies);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extension\Exception;
|
||||
|
||||
use Flarum\Foundation\ErrorHandling\HandledError;
|
||||
|
||||
class MissingDependenciesExceptionHandler
|
||||
{
|
||||
public function handle(MissingDependenciesException $e): HandledError
|
||||
{
|
||||
return (new HandledError(
|
||||
$e,
|
||||
'missing_dependencies',
|
||||
409
|
||||
))->withDetails($this->errorDetails($e));
|
||||
}
|
||||
|
||||
protected function errorDetails(MissingDependenciesException $e): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'extension' => $e->extension->getId(),
|
||||
'extensions' => $e->getMissingDependencyIds(),
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ use Flarum\Extend\LifecycleInterface;
|
|||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use League\Flysystem\Adapter\Local;
|
||||
use League\Flysystem\Filesystem;
|
||||
|
@ -51,6 +52,14 @@ class Extension implements Arrayable
|
|||
'jpg' => 'image/jpeg',
|
||||
];
|
||||
|
||||
protected static function nameToId($name)
|
||||
{
|
||||
list($vendor, $package) = explode('/', $name);
|
||||
$package = str_replace(['flarum-ext-', 'flarum-'], '', $package);
|
||||
|
||||
return "$vendor-$package";
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique Id of the extension.
|
||||
*
|
||||
|
@ -60,6 +69,7 @@ class Extension implements Arrayable
|
|||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The directory of this extension.
|
||||
*
|
||||
|
@ -74,6 +84,13 @@ class Extension implements Arrayable
|
|||
*/
|
||||
protected $composerJson;
|
||||
|
||||
/**
|
||||
* The IDs of all Flarum extensions that this extension depends on.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $extensionDependencyIds;
|
||||
|
||||
/**
|
||||
* Whether the extension is installed.
|
||||
*
|
||||
|
@ -104,9 +121,7 @@ class Extension implements Arrayable
|
|||
*/
|
||||
protected function assignId()
|
||||
{
|
||||
list($vendor, $package) = explode('/', $this->name);
|
||||
$package = str_replace(['flarum-ext-', 'flarum-'], '', $package);
|
||||
$this->id = "$vendor-$package";
|
||||
$this->id = static::nameToId($this->name);
|
||||
}
|
||||
|
||||
public function extend(Container $app)
|
||||
|
@ -182,6 +197,24 @@ class Extension implements Arrayable
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of flarum extensions that this extension depends on.
|
||||
*
|
||||
* @param array $extensionSet: An associative array where keys are the composer package names
|
||||
* of installed extensions. Used to figure out which dependencies
|
||||
* are flarum extensions.
|
||||
*/
|
||||
public function calculateDependencies($extensionSet)
|
||||
{
|
||||
$this->extensionDependencyIds = (new Collection(Arr::get($this->composerJson, 'require', [])))
|
||||
->keys()
|
||||
->filter(function ($key) use ($extensionSet) {
|
||||
return array_key_exists($key, $extensionSet);
|
||||
})->map(function ($key) {
|
||||
return static::nameToId($key);
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
@ -253,6 +286,16 @@ class Extension implements Arrayable
|
|||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* The IDs of all Flarum extensions that this extension depends on.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getExtensionDependencyIds()
|
||||
{
|
||||
return $this->extensionDependencyIds;
|
||||
}
|
||||
|
||||
private function getExtenders(): array
|
||||
{
|
||||
$extenderFile = $this->getExtenderFile();
|
||||
|
@ -363,12 +406,13 @@ class Extension implements Arrayable
|
|||
public function toArray()
|
||||
{
|
||||
return (array) array_merge([
|
||||
'id' => $this->getId(),
|
||||
'version' => $this->getVersion(),
|
||||
'path' => $this->path,
|
||||
'icon' => $this->getIcon(),
|
||||
'hasAssets' => $this->hasAssets(),
|
||||
'hasMigrations' => $this->hasMigrations(),
|
||||
'id' => $this->getId(),
|
||||
'version' => $this->getVersion(),
|
||||
'path' => $this->getPath(),
|
||||
'icon' => $this->getIcon(),
|
||||
'hasAssets' => $this->hasAssets(),
|
||||
'hasMigrations' => $this->hasMigrations(),
|
||||
'extensionDependencyIds' => $this->getExtensionDependencyIds(),
|
||||
], $this->composerJson);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,11 +83,18 @@ class ExtensionManager
|
|||
// Composer 2.0 changes the structure of the installed.json manifest
|
||||
$installed = $installed['packages'] ?? $installed;
|
||||
|
||||
// We calculate and store a set of composer package names for all installed Flarum extensions,
|
||||
// so we know what is and isn't a flarum extension in `calculateDependencies`.
|
||||
// Using keys of an associative array allows us to do these checks in constant time.
|
||||
$installedSet = [];
|
||||
|
||||
foreach ($installed as $package) {
|
||||
if (Arr::get($package, 'type') != 'flarum-extension' || empty(Arr::get($package, 'name'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$installedSet[Arr::get($package, 'name')] = true;
|
||||
|
||||
$path = isset($package['install-path'])
|
||||
? $this->paths->vendor.'/composer/'.$package['install-path']
|
||||
: $this->paths->vendor.'/'.Arr::get($package, 'name');
|
||||
|
@ -101,6 +108,11 @@ class ExtensionManager
|
|||
|
||||
$extensions->put($extension->getId(), $extension);
|
||||
}
|
||||
|
||||
foreach ($extensions as $extension) {
|
||||
$extension->calculateDependencies($installedSet);
|
||||
}
|
||||
|
||||
$this->extensions = $extensions->sortBy(function ($extension, $name) {
|
||||
return $extension->composerJsonAttribute('extra.flarum-extension.title');
|
||||
});
|
||||
|
@ -133,17 +145,27 @@ class ExtensionManager
|
|||
|
||||
$extension = $this->getExtension($name);
|
||||
|
||||
$missingDependencies = [];
|
||||
$enabledIds = $this->getEnabled();
|
||||
foreach ($extension->getExtensionDependencyIds() as $dependencyId) {
|
||||
if (! in_array($dependencyId, $enabledIds)) {
|
||||
$missingDependencies[] = $this->getExtension($dependencyId);
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($missingDependencies)) {
|
||||
throw new Exception\MissingDependenciesException($extension, $missingDependencies);
|
||||
}
|
||||
|
||||
$this->dispatcher->dispatch(new Enabling($extension));
|
||||
|
||||
$enabled = $this->getEnabled();
|
||||
|
||||
$enabled[] = $name;
|
||||
$enabledIds[] = $name;
|
||||
|
||||
$this->migrate($extension);
|
||||
|
||||
$this->publishAssets($extension);
|
||||
|
||||
$this->setEnabled($enabled);
|
||||
$this->setEnabled($enabledIds);
|
||||
|
||||
$extension->enable($this->container);
|
||||
|
||||
|
@ -165,6 +187,18 @@ class ExtensionManager
|
|||
|
||||
$extension = $this->getExtension($name);
|
||||
|
||||
$dependentExtensions = [];
|
||||
|
||||
foreach ($this->getEnabledExtensions() as $possibleDependent) {
|
||||
if (in_array($extension->getId(), $possibleDependent->getExtensionDependencyIds())) {
|
||||
$dependentExtensions[] = $possibleDependent;
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($dependentExtensions)) {
|
||||
throw new Exception\DependentExtensionsException($extension, $dependentExtensions);
|
||||
}
|
||||
|
||||
$this->dispatcher->dispatch(new Disabling($extension));
|
||||
|
||||
unset($enabled[$k]);
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
|
||||
namespace Flarum\Foundation;
|
||||
|
||||
use Flarum\Extension\Exception\DependentExtensionsException;
|
||||
use Flarum\Extension\Exception\DependentExtensionsExceptionHandler;
|
||||
use Flarum\Extension\Exception\MissingDependenciesException;
|
||||
use Flarum\Extension\Exception\MissingDependenciesExceptionHandler;
|
||||
use Flarum\Foundation\ErrorHandling\ExceptionHandler;
|
||||
use Flarum\Foundation\ErrorHandling\LogReporter;
|
||||
use Flarum\Foundation\ErrorHandling\Registry;
|
||||
|
@ -57,6 +61,8 @@ class ErrorServiceProvider extends AbstractServiceProvider
|
|||
return [
|
||||
IlluminateValidationException::class => ExceptionHandler\IlluminateValidationExceptionHandler::class,
|
||||
ValidationException::class => ExceptionHandler\ValidationExceptionHandler::class,
|
||||
DependentExtensionsException::class => DependentExtensionsExceptionHandler::class,
|
||||
MissingDependenciesException::class => MissingDependenciesExceptionHandler::class,
|
||||
];
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user