mirror of
https://github.com/flarum/framework.git
synced 2025-03-04 14:41:10 +08:00
Rough extension management implementation
This commit is contained in:
parent
5c1b62107c
commit
299bfc0e0a
@ -12,4 +12,6 @@ app.initializers.add('routes', routes);
|
|||||||
app.initializers.add('preload', preload, -100);
|
app.initializers.add('preload', preload, -100);
|
||||||
app.initializers.add('boot', boot, -100);
|
app.initializers.add('boot', boot, -100);
|
||||||
|
|
||||||
|
app.extensionSettings = {};
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
21
framework/core/js/admin/src/components/AddExtensionModal.js
Normal file
21
framework/core/js/admin/src/components/AddExtensionModal.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import Modal from 'flarum/components/Modal';
|
||||||
|
|
||||||
|
export default class AddExtensionModal extends Modal {
|
||||||
|
className() {
|
||||||
|
return 'AddExtensionModal Modal--small';
|
||||||
|
}
|
||||||
|
|
||||||
|
title() {
|
||||||
|
return 'Add Extension';
|
||||||
|
}
|
||||||
|
|
||||||
|
content() {
|
||||||
|
return (
|
||||||
|
<div className="Modal-body">
|
||||||
|
<p>One day, this dialog will allow you to add an extension to your forum with ease. We're building an ecosystem as we speak!</p>
|
||||||
|
<p>In the meantime, if you manage to get your hands on a new extension, simply drop it in your forum's <code>extensions</code> directory.</p>
|
||||||
|
<p>If you're a developer, you can <a href="">read the docs</a> and have a go at building your own.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,101 @@
|
|||||||
import Component from 'flarum/Component';
|
import Component from 'flarum/Component';
|
||||||
|
import LinkButton from 'flarum/components/LinkButton';
|
||||||
|
import Button from 'flarum/components/Button';
|
||||||
|
import Dropdown from 'flarum/components/Dropdown';
|
||||||
|
import Separator from 'flarum/components/Separator';
|
||||||
|
import AddExtensionModal from 'flarum/components/AddExtensionModal';
|
||||||
|
import ItemList from 'flarum/utils/ItemList';
|
||||||
|
|
||||||
export default class ExtensionsPage extends Component {
|
export default class ExtensionsPage extends Component {
|
||||||
view() {
|
view() {
|
||||||
return (
|
return (
|
||||||
<div className="ExtensionsPage"/>
|
<div className="ExtensionsPage">
|
||||||
|
<div className="ExtensionsPage-header">
|
||||||
|
<div className="container">
|
||||||
|
{Button.component({
|
||||||
|
children: 'Add Extension',
|
||||||
|
icon: 'plus',
|
||||||
|
className: 'Button Button--primary',
|
||||||
|
onclick: () => app.modal.show(new AddExtensionModal())
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ExtensionsPage-list">
|
||||||
|
<div className="container">
|
||||||
|
<ul className="ExtensionList">
|
||||||
|
{app.extensions
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
.map(extension => (
|
||||||
|
<li className={'ExtensionListItem ' + (!this.isEnabled(extension.name) ? 'disabled' : '')}>
|
||||||
|
{Dropdown.component({
|
||||||
|
icon: 'ellipsis-v',
|
||||||
|
children: this.controlItems(extension).toArray(),
|
||||||
|
className: 'ExtensionListItem-controls',
|
||||||
|
buttonClassName: 'Button Button--icon Button--flat',
|
||||||
|
menuClassName: 'Dropdown-menu--right'
|
||||||
|
})}
|
||||||
|
<div className="ExtensionListItem-content">
|
||||||
|
<span className="ExtensionListItem-icon ExtensionIcon"/>
|
||||||
|
<h4 className="ExtensionListItem-title">
|
||||||
|
{extension.title}{' '}
|
||||||
|
<small className="ExtensionListItem-version">{extension.version}</small>
|
||||||
|
</h4>
|
||||||
|
<div className="ExtensionListItem-description">{extension.description}</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controlItems(extension) {
|
||||||
|
const items = new ItemList();
|
||||||
|
const enabled = this.isEnabled(extension.name);
|
||||||
|
|
||||||
|
if (app.extensionSettings[extension.name]) {
|
||||||
|
items.add('settings', Button.component({
|
||||||
|
icon: 'cog',
|
||||||
|
children: 'Settings',
|
||||||
|
onclick: app.extensionSettings[extension.name]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
items.add('toggle', Button.component({
|
||||||
|
icon: enabled ? 'times' : 'check',
|
||||||
|
children: enabled ? 'Disable' : 'Enable',
|
||||||
|
onclick: () => {
|
||||||
|
app.request({
|
||||||
|
url: app.forum.attribute('apiUrl') + '/extensions/' + extension.name,
|
||||||
|
method: 'PATCH',
|
||||||
|
data: {enabled: !enabled}
|
||||||
|
}).then(() => window.location.reload());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
items.add('uninstall', Button.component({
|
||||||
|
icon: 'trash-o',
|
||||||
|
children: 'Uninstall'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
items.add('separator2', Separator.component());
|
||||||
|
|
||||||
|
items.add('support', LinkButton.component({
|
||||||
|
icon: 'support',
|
||||||
|
children: 'Support'
|
||||||
|
}));
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
isEnabled(name) {
|
||||||
|
const enabled = JSON.parse(app.config.extensions_enabled);
|
||||||
|
|
||||||
|
return enabled.indexOf(name) !== -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
68
framework/core/less/admin/ExtensionsPage.less
Normal file
68
framework/core/less/admin/ExtensionsPage.less
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
.ExtensionsPage-header {
|
||||||
|
padding: 20px 0;
|
||||||
|
background: @control-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExtensionsPage-list {
|
||||||
|
padding: 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExtensionList {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
.clearfix();
|
||||||
|
|
||||||
|
> li {
|
||||||
|
float: left;
|
||||||
|
width: 350px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ExtensionListItem.disabled .ExtensionListItem-content {
|
||||||
|
opacity: 0.5;
|
||||||
|
color: @muted-color;
|
||||||
|
}
|
||||||
|
.ExtensionListItem-controls {
|
||||||
|
float: right;
|
||||||
|
margin-top: -5px;
|
||||||
|
visibility: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.ExtensionListItem:hover &, &.open {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ExtensionListItem {
|
||||||
|
padding-left: 42px;
|
||||||
|
padding-right: 20px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
.ExtensionListItem-icon {
|
||||||
|
float: left;
|
||||||
|
margin-left: -42px;
|
||||||
|
}
|
||||||
|
.ExtensionListItem-title {
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 3px 0 5px;
|
||||||
|
|
||||||
|
small {
|
||||||
|
color: @muted-more-color;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: normal;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ExtensionListItem-description {
|
||||||
|
font-size: 12px;
|
||||||
|
color: @muted-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExtensionIcon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
background: @control-bg;
|
||||||
|
color: @control-color;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
@ -5,3 +5,4 @@
|
|||||||
@import "BasicsPage.less";
|
@import "BasicsPage.less";
|
||||||
@import "PermissionsPage.less";
|
@import "PermissionsPage.less";
|
||||||
@import "EditGroupModal.less";
|
@import "EditGroupModal.less";
|
||||||
|
@import "ExtensionsPage.less";
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
> li {
|
> li {
|
||||||
> a, > button {
|
> a, > button, > span {
|
||||||
padding: 8px 15px;
|
padding: 8px 15px;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -42,12 +42,6 @@
|
|||||||
&.hasIcon {
|
&.hasIcon {
|
||||||
padding-left: 40px;
|
padding-left: 40px;
|
||||||
}
|
}
|
||||||
&:hover {
|
|
||||||
background: @control-bg;
|
|
||||||
}
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Button-icon {
|
.Button-icon {
|
||||||
float: left;
|
float: left;
|
||||||
@ -60,6 +54,14 @@
|
|||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
> a, > button {
|
||||||
|
&:hover {
|
||||||
|
background: @control-bg;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
&.active {
|
&.active {
|
||||||
> a, > button {
|
> a, > button {
|
||||||
background: @control-bg;
|
background: @control-bg;
|
||||||
|
@ -33,6 +33,7 @@ class ClientAction extends BaseClientAction
|
|||||||
$view->setVariable('config', $this->settings->all());
|
$view->setVariable('config', $this->settings->all());
|
||||||
$view->setVariable('locales', app('flarum.localeManager')->getLocales());
|
$view->setVariable('locales', app('flarum.localeManager')->getLocales());
|
||||||
$view->setVariable('permissions', Permission::map());
|
$view->setVariable('permissions', Permission::map());
|
||||||
|
$view->setVariable('extensions', app('flarum.extensions')->getInfo());
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
}
|
}
|
||||||
|
43
framework/core/src/Api/Actions/Extensions/UpdateAction.php
Normal file
43
framework/core/src/Api/Actions/Extensions/UpdateAction.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php namespace Flarum\Api\Actions\Extensions;
|
||||||
|
|
||||||
|
use Flarum\Api\Actions\JsonApiAction;
|
||||||
|
use Flarum\Api\Request;
|
||||||
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
|
use Flarum\Core\Exceptions\PermissionDeniedException;
|
||||||
|
use Flarum\Support\ExtensionManager;
|
||||||
|
|
||||||
|
class UpdateAction extends JsonApiAction
|
||||||
|
{
|
||||||
|
protected $extensions;
|
||||||
|
|
||||||
|
public function __construct(ExtensionManager $extensions)
|
||||||
|
{
|
||||||
|
$this->extensions = $extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function respond(Request $request)
|
||||||
|
{
|
||||||
|
if (! $request->actor->isAdmin()) {
|
||||||
|
throw new PermissionDeniedException;
|
||||||
|
}
|
||||||
|
|
||||||
|
$enabled = $request->get('enabled');
|
||||||
|
$name = $request->get('name');
|
||||||
|
|
||||||
|
if ($enabled === true) {
|
||||||
|
$this->extensions->enable($name);
|
||||||
|
} elseif ($enabled === false) {
|
||||||
|
$this->extensions->disable($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
app('flarum.formatter')->flush();
|
||||||
|
|
||||||
|
$assetPath = public_path('assets');
|
||||||
|
$manifest = file_get_contents($assetPath . '/rev-manifest.json');
|
||||||
|
$revisions = json_decode($manifest, true);
|
||||||
|
|
||||||
|
foreach ($revisions as $file => $revision) {
|
||||||
|
@unlink($assetPath . '/' . substr_replace($file, '-' . $revision, strrpos($file, '.'), 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -313,6 +313,19 @@ class ApiServiceProvider extends ServiceProvider
|
|||||||
$this->action('Flarum\Api\Actions\Groups\DeleteAction')
|
$this->action('Flarum\Api\Actions\Groups\DeleteAction')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Extensions
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Toggle an extension
|
||||||
|
$routes->patch(
|
||||||
|
'/extensions/{name}',
|
||||||
|
'flarum.api.extensions.update',
|
||||||
|
$this->action('Flarum\Api\Actions\Extensions\UpdateAction')
|
||||||
|
);
|
||||||
|
|
||||||
event(new RegisterApiRoutes($routes));
|
event(new RegisterApiRoutes($routes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,12 @@ class Formatter
|
|||||||
return $configurator;
|
return $configurator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function flush()
|
||||||
|
{
|
||||||
|
$this->cache->forget('flarum.formatter.parser');
|
||||||
|
$this->cache->forget('flarum.formatter.renderer');
|
||||||
|
}
|
||||||
|
|
||||||
protected function getComponent($key)
|
protected function getComponent($key)
|
||||||
{
|
{
|
||||||
$cacheKey = 'flarum.formatter.' . $key;
|
$cacheKey = 'flarum.formatter.' . $key;
|
||||||
|
@ -4,4 +4,11 @@ use Flarum\Support\ServiceProvider;
|
|||||||
|
|
||||||
class Extension extends ServiceProvider
|
class Extension extends ServiceProvider
|
||||||
{
|
{
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
104
framework/core/src/Support/ExtensionManager.php
Normal file
104
framework/core/src/Support/ExtensionManager.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php namespace Flarum\Support;
|
||||||
|
|
||||||
|
use Flarum\Support\ServiceProvider;
|
||||||
|
use Flarum\Core\Settings\SettingsRepository;
|
||||||
|
use Illuminate\Contracts\Container\Container;
|
||||||
|
|
||||||
|
class ExtensionManager
|
||||||
|
{
|
||||||
|
protected $config;
|
||||||
|
|
||||||
|
protected $app;
|
||||||
|
|
||||||
|
public function __construct(SettingsRepository $config, Container $app)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
$this->app = $app;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInfo()
|
||||||
|
{
|
||||||
|
$extensionsDir = $this->getExtensionsDir();
|
||||||
|
|
||||||
|
$dirs = array_diff(scandir($extensionsDir), ['.', '..']);
|
||||||
|
$extensions = [];
|
||||||
|
|
||||||
|
foreach ($dirs as $dir) {
|
||||||
|
if (file_exists($manifest = $extensionsDir . '/' . $dir . '/flarum.json')) {
|
||||||
|
$extensions[] = json_decode(file_get_contents($manifest));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function enable($extension)
|
||||||
|
{
|
||||||
|
$enabled = $this->getEnabled();
|
||||||
|
|
||||||
|
if (! in_array($extension, $enabled)) {
|
||||||
|
$enabled[] = $extension;
|
||||||
|
|
||||||
|
$class = $this->load($extension);
|
||||||
|
|
||||||
|
$class->install();
|
||||||
|
|
||||||
|
// run migrations
|
||||||
|
// vendor publish
|
||||||
|
|
||||||
|
$this->setEnabled($enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function disable($extension)
|
||||||
|
{
|
||||||
|
$enabled = $this->getEnabled();
|
||||||
|
|
||||||
|
if (($k = array_search($extension, $enabled)) !== false) {
|
||||||
|
unset($enabled[$k]);
|
||||||
|
|
||||||
|
$this->setEnabled($enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uninstall($extension)
|
||||||
|
{
|
||||||
|
$this->disable($extension);
|
||||||
|
|
||||||
|
$class = $this->load($extension);
|
||||||
|
|
||||||
|
$class->uninstall();
|
||||||
|
|
||||||
|
// run migrations
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getEnabled()
|
||||||
|
{
|
||||||
|
$config = $this->config->get('extensions_enabled');
|
||||||
|
|
||||||
|
return json_decode($config, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setEnabled(array $enabled)
|
||||||
|
{
|
||||||
|
$enabled = array_values(array_unique($enabled));
|
||||||
|
|
||||||
|
$this->config->set('extensions_enabled', json_encode($enabled));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function load($extension)
|
||||||
|
{
|
||||||
|
if (file_exists($file = $this->getExtensionsDir() . '/' . $extension . '/bootstrap.php')) {
|
||||||
|
$className = require $file;
|
||||||
|
|
||||||
|
$class = new $className($this->app);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getExtensionsDir()
|
||||||
|
{
|
||||||
|
return public_path('extensions');
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,8 @@ class ExtensionsServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
|
$this->app->bind('flarum.extensions', 'Flarum\Support\ExtensionManager');
|
||||||
|
|
||||||
$config = $this->app->make('Flarum\Core\Settings\SettingsRepository')->get('extensions_enabled');
|
$config = $this->app->make('Flarum\Core\Settings\SettingsRepository')->get('extensions_enabled');
|
||||||
$extensions = json_decode($config, true);
|
$extensions = json_decode($config, true);
|
||||||
$providers = [];
|
$providers = [];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user