Process plugins administration page through Slim controllers

This commit is contained in:
ArthurHoaro 2020-06-20 15:14:24 +02:00
parent 78657347c5
commit 1b8620b1ad
13 changed files with 312 additions and 62 deletions

View file

@ -88,7 +88,12 @@ public function build(): ShaarliContainer
}; };
$container['pluginManager'] = function (ShaarliContainer $container): PluginManager { $container['pluginManager'] = function (ShaarliContainer $container): PluginManager {
return new PluginManager($container->conf); $pluginManager = new PluginManager($container->conf);
// FIXME! Configuration is already injected
$pluginManager->load($container->conf->get('general.enabled_plugins'));
return $pluginManager;
}; };
$container['formatterFactory'] = function (ShaarliContainer $container): FormatterFactory { $container['formatterFactory'] = function (ShaarliContainer $container): FormatterFactory {

View file

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace Shaarli\Front\Controller\Admin;
use Exception;
use Slim\Http\Request;
use Slim\Http\Response;
/**
* Class PluginsController
*
* Slim controller used to handle Shaarli plugins configuration page (display + save new config).
*/
class PluginsController extends ShaarliAdminController
{
/**
* GET /admin/plugins - Displays the configuration page
*/
public function index(Request $request, Response $response): Response
{
$pluginMeta = $this->container->pluginManager->getPluginsMeta();
// Split plugins into 2 arrays: ordered enabled plugins and disabled.
$enabledPlugins = array_filter($pluginMeta, function ($v) {
return ($v['order'] ?? false) !== false;
});
$enabledPlugins = load_plugin_parameter_values($enabledPlugins, $this->container->conf->get('plugins', []));
uasort(
$enabledPlugins,
function ($a, $b) {
return $a['order'] - $b['order'];
}
);
$disabledPlugins = array_filter($pluginMeta, function ($v) {
return ($v['order'] ?? false) === false;
});
$this->assignView('enabledPlugins', $enabledPlugins);
$this->assignView('disabledPlugins', $disabledPlugins);
$this->assignView(
'pagetitle',
t('Plugin Administration') .' - '. $this->container->conf->get('general.title', 'Shaarli')
);
return $response->write($this->render('pluginsadmin'));
}
/**
* POST /admin/plugins - Update Shaarli's configuration
*/
public function save(Request $request, Response $response): Response
{
$this->checkToken($request);
try {
$parameters = $request->getParams() ?? [];
$this->executeHooks($parameters);
if (isset($parameters['parameters_form'])) {
unset($parameters['parameters_form']);
foreach ($parameters as $param => $value) {
$this->container->conf->set('plugins.'. $param, escape($value));
}
} else {
$this->container->conf->set('general.enabled_plugins', save_plugin_config($parameters));
}
$this->container->conf->write($this->container->loginManager->isLoggedIn());
$this->container->history->updateSettings();
$this->saveSuccessMessage(t('Setting successfully saved.'));
} catch (Exception $e) {
$this->saveErrorMessage(
t('ERROR while saving plugin configuration: ') . PHP_EOL . $e->getMessage()
);
}
return $this->redirect($response, '/admin/plugins');
}
/**
* @param mixed[] $data Variables passed to the template engine
*
* @return mixed[] Template data after active plugins render_picwall hook execution.
*/
protected function executeHooks(array $data): array
{
$this->container->pluginManager->executeHooks(
'save_plugin_parameters',
$data
);
return $data;
}
}

View file

@ -16,7 +16,7 @@ class PluginManager
* *
* @var array $authorizedPlugins * @var array $authorizedPlugins
*/ */
private $authorizedPlugins; private $authorizedPlugins = [];
/** /**
* List of loaded plugins. * List of loaded plugins.

View file

@ -43,7 +43,7 @@ http://<replace_domain>/admin/export
http://<replace_domain>/admin/import http://<replace_domain>/admin/import
http://<replace_domain>/login http://<replace_domain>/login
http://<replace_domain>/picture-wall http://<replace_domain>/picture-wall
http://<replace_domain>/?do=pluginadmin http://<replace_domain>/admin/plugins
http://<replace_domain>/tags/cloud http://<replace_domain>/tags/cloud
http://<replace_domain>/tags/list http://<replace_domain>/tags/list
``` ```

View file

@ -584,60 +584,14 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM
// Plugin administration page // Plugin administration page
if ($targetPage == Router::$PAGE_PLUGINSADMIN) { if ($targetPage == Router::$PAGE_PLUGINSADMIN) {
$pluginMeta = $pluginManager->getPluginsMeta(); header('Location: ./admin/plugins');
// Split plugins into 2 arrays: ordered enabled plugins and disabled.
$enabledPlugins = array_filter($pluginMeta, function ($v) {
return $v['order'] !== false;
});
// Load parameters.
$enabledPlugins = load_plugin_parameter_values($enabledPlugins, $conf->get('plugins', array()));
uasort(
$enabledPlugins,
function ($a, $b) {
return $a['order'] - $b['order'];
}
);
$disabledPlugins = array_filter($pluginMeta, function ($v) {
return $v['order'] === false;
});
$PAGE->assign('enabledPlugins', $enabledPlugins);
$PAGE->assign('disabledPlugins', $disabledPlugins);
$PAGE->assign('pagetitle', t('Plugin administration') .' - '. $conf->get('general.title', 'Shaarli'));
$PAGE->renderPage('pluginsadmin');
exit; exit;
} }
// Plugin administration form action // Plugin administration form action
if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) { if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) {
try { // This route is no longer supported in legacy mode
if (isset($_POST['parameters_form'])) { header('Location: ./admin/plugins');
$pluginManager->executeHooks('save_plugin_parameters', $_POST);
unset($_POST['parameters_form']);
foreach ($_POST as $param => $value) {
$conf->set('plugins.'. $param, escape($value));
}
} else {
$conf->set('general.enabled_plugins', save_plugin_config($_POST));
}
$conf->write($loginManager->isLoggedIn());
$history->updateSettings();
} catch (Exception $e) {
error_log(
'ERROR while saving plugin configuration:.' . PHP_EOL .
$e->getMessage()
);
// TODO: do not handle exceptions/errors in JS.
echo '<script>alert("'
. $e->getMessage()
.'");document.location=\'./?do='
. Router::$PAGE_PLUGINSADMIN
.'\';</script>';
exit;
}
header('Location: ./?do='. Router::$PAGE_PLUGINSADMIN);
exit; exit;
} }
@ -1022,6 +976,8 @@ function install($conf, $sessionManager, $loginManager)
$this->post('/admin/export', '\Shaarli\Front\Controller\Admin\ExportController:export'); $this->post('/admin/export', '\Shaarli\Front\Controller\Admin\ExportController:export');
$this->get('/admin/import', '\Shaarli\Front\Controller\Admin\ImportController:index'); $this->get('/admin/import', '\Shaarli\Front\Controller\Admin\ImportController:index');
$this->post('/admin/import', '\Shaarli\Front\Controller\Admin\ImportController:import'); $this->post('/admin/import', '\Shaarli\Front\Controller\Admin\ImportController:import');
$this->get('/admin/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:index');
$this->post('/admin/plugins', '\Shaarli\Front\Controller\Admin\PluginsController:save');
$this->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage'); $this->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage');
$this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility'); $this->get('/visibility/{visibility}', '\Shaarli\Front\Controller\Admin\SessionFilterController:visibility');

View file

@ -21,7 +21,7 @@ The directory structure should look like:
To enable the plugin, you can either: To enable the plugin, you can either:
* enable it in the plugins administration page (`?do=pluginadmin`). * enable it in the plugins administration page (`/admin/plugins`).
* add `wallabag` to your list of enabled plugins in `data/config.json.php` (`general.enabled_plugins` section). * add `wallabag` to your list of enabled plugins in `data/config.json.php` (`general.enabled_plugins` section).
### Configuration ### Configuration

View file

@ -0,0 +1,190 @@
<?php
declare(strict_types=1);
namespace Shaarli\Front\Controller\Admin;
use PHPUnit\Framework\TestCase;
use Shaarli\Config\ConfigManager;
use Shaarli\Front\Exception\WrongTokenException;
use Shaarli\Security\SessionManager;
use Slim\Http\Request;
use Slim\Http\Response;
class PluginsControllerTest extends TestCase
{
use FrontAdminControllerMockHelper;
/** @var PluginsController */
protected $controller;
public function setUp(): void
{
$this->createContainer();
$this->controller = new PluginsController($this->container);
}
/**
* Test displaying plugins admin page
*/
public function testIndex(): void
{
$assignedVariables = [];
$this->assignTemplateVars($assignedVariables);
$request = $this->createMock(Request::class);
$response = new Response();
$data = [
'plugin1' => ['order' => 2, 'other' => 'field'],
'plugin2' => ['order' => 1],
'plugin3' => ['order' => false, 'abc' => 'def'],
'plugin4' => [],
];
$this->container->pluginManager
->expects(static::once())
->method('getPluginsMeta')
->willReturn($data);
$result = $this->controller->index($request, $response);
static::assertSame(200, $result->getStatusCode());
static::assertSame('pluginsadmin', (string) $result->getBody());
static::assertSame('Plugin Administration - Shaarli', $assignedVariables['pagetitle']);
static::assertSame(
['plugin2' => $data['plugin2'], 'plugin1' => $data['plugin1']],
$assignedVariables['enabledPlugins']
);
static::assertSame(
['plugin3' => $data['plugin3'], 'plugin4' => $data['plugin4']],
$assignedVariables['disabledPlugins']
);
}
/**
* Test save plugins admin page
*/
public function testSaveEnabledPlugins(): void
{
$parameters = [
'plugin1' => 'on',
'order_plugin1' => '2',
'plugin2' => 'on',
];
$request = $this->createMock(Request::class);
$request
->expects(static::atLeastOnce())
->method('getParams')
->willReturnCallback(function () use ($parameters): array {
return $parameters;
})
;
$response = new Response();
$this->container->pluginManager
->expects(static::once())
->method('executeHooks')
->with('save_plugin_parameters', $parameters)
;
$this->container->conf
->expects(static::atLeastOnce())
->method('set')
->with('general.enabled_plugins', ['plugin1', 'plugin2'])
;
$result = $this->controller->save($request, $response);
static::assertSame(302, $result->getStatusCode());
static::assertSame(['/subfolder/admin/plugins'], $result->getHeader('location'));
}
/**
* Test save plugin parameters
*/
public function testSavePluginParameters(): void
{
$parameters = [
'parameters_form' => true,
'parameter1' => 'blip',
'parameter2' => 'blop',
];
$request = $this->createMock(Request::class);
$request
->expects(static::atLeastOnce())
->method('getParams')
->willReturnCallback(function () use ($parameters): array {
return $parameters;
})
;
$response = new Response();
$this->container->pluginManager
->expects(static::once())
->method('executeHooks')
->with('save_plugin_parameters', $parameters)
;
$this->container->conf
->expects(static::atLeastOnce())
->method('set')
->withConsecutive(['plugins.parameter1', 'blip'], ['plugins.parameter2', 'blop'])
;
$result = $this->controller->save($request, $response);
static::assertSame(302, $result->getStatusCode());
static::assertSame(['/subfolder/admin/plugins'], $result->getHeader('location'));
}
/**
* Test save plugin parameters - error encountered
*/
public function testSaveWithError(): void
{
$request = $this->createMock(Request::class);
$response = new Response();
$this->container->conf = $this->createMock(ConfigManager::class);
$this->container->conf
->expects(static::atLeastOnce())
->method('write')
->willThrowException(new \Exception($message = 'error message'))
;
$this->container->sessionManager = $this->createMock(SessionManager::class);
$this->container->sessionManager->method('checkToken')->willReturn(true);
$this->container->sessionManager
->expects(static::once())
->method('setSessionParameter')
->with(
SessionManager::KEY_ERROR_MESSAGES,
['ERROR while saving plugin configuration: ' . PHP_EOL . $message]
)
;
$result = $this->controller->save($request, $response);
static::assertSame(302, $result->getStatusCode());
static::assertSame(['/subfolder/admin/plugins'], $result->getHeader('location'));
}
/**
* Test save plugin parameters - wrong token
*/
public function testSaveWrongToken(): void
{
$this->container->sessionManager = $this->createMock(SessionManager::class);
$this->container->sessionManager->method('checkToken')->willReturn(false);
$request = $this->createMock(Request::class);
$response = new Response();
$this->expectException(WrongTokenException::class);
$this->controller->save($request, $response);
}
}

View file

@ -12,10 +12,10 @@
<link type="text/css" rel="stylesheet" href="{$asset_path}/css/markdown.min.css?v={$version_hash}#" /> <link type="text/css" rel="stylesheet" href="{$asset_path}/css/markdown.min.css?v={$version_hash}#" />
{/if} {/if}
{loop="$plugins_includes.css_files"} {loop="$plugins_includes.css_files"}
<link type="text/css" rel="stylesheet" href="{$value}?v={$version_hash}#"/> <link type="text/css" rel="stylesheet" href="{$base_path}/{$value}?v={$version_hash}#"/>
{/loop} {/loop}
{if="is_file('data/user.css')"} {if="is_file('data/user.css')"}
<link type="text/css" rel="stylesheet" href="data/user.css#" /> <link type="text/css" rel="stylesheet" href="{$base_path}/data/user.css#" />
{/if} {/if}
<link rel="search" type="application/opensearchdescription+xml" href="{$base_path}/open-search#" <link rel="search" type="application/opensearchdescription+xml" href="{$base_path}/open-search#"
title="Shaarli search - {$shaarlititle}" /> title="Shaarli search - {$shaarlititle}" />

View file

@ -25,7 +25,7 @@
{/loop} {/loop}
{loop="$plugins_footer.js_files"} {loop="$plugins_footer.js_files"}
<script src="{$value}#"></script> <script src="{$base_path}/{$value}#"></script>
{/loop} {/loop}
<div id="js-translations" class="hidden"> <div id="js-translations" class="hidden">

View file

@ -16,7 +16,7 @@
<div class="clear"></div> <div class="clear"></div>
</noscript> </noscript>
<form method="POST" action="{$base_path}/?do=save_pluginadmin" name="pluginform" id="pluginform" class="pluginform-container"> <form method="POST" action="{$base_path}/admin/plugins" name="pluginform" id="pluginform" class="pluginform-container">
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-lg-1-8 pure-u-1-24"></div> <div class="pure-u-lg-1-8 pure-u-1-24"></div>
<div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-complete"> <div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-complete">
@ -127,7 +127,7 @@ <h3 class="window-subtitle">{'Disabled Plugins'|t}</h3>
<input type="hidden" name="token" value="{$token}"> <input type="hidden" name="token" value="{$token}">
</form> </form>
<form action="{$base_path}/?do=save_pluginadmin" method="POST"> <form action="{$base_path}/admin/plugins" method="POST">
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-lg-1-8 pure-u-1-24"></div> <div class="pure-u-lg-1-8 pure-u-1-24"></div>
<div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-light"> <div class="pure-u-lg-3-4 pure-u-22-24 page-form page-form-light">
@ -173,6 +173,7 @@ <h3 class="window-subtitle">{function="str_replace('_', ' ', $key)"}</h3>
</section> </section>
</div> </div>
</div> </div>
<input type="hidden" name="token" value="{$token}">
</form> </form>
{include="page.footer"} {include="page.footer"}

View file

@ -16,7 +16,7 @@ <h2 class="window-title">{'Settings'|t}</h2>
</a> </a>
</div> </div>
<div class="tools-item"> <div class="tools-item">
<a href="{$base_path}/?do=pluginadmin" title="{'Enable, disable and configure plugins'|t}"> <a href="{$base_path}/admin/plugins" title="{'Enable, disable and configure plugins'|t}">
<span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Plugin administration'|t}</span> <span class="pure-button pure-u-lg-2-3 pure-u-3-4">{'Plugin administration'|t}</span>
</a> </a>
</div> </div>

View file

@ -16,7 +16,7 @@
</noscript> </noscript>
<div id="pluginsadmin"> <div id="pluginsadmin">
<form action="{$base_path}/?do=save_pluginadmin" method="POST"> <form action="{$base_path}/admin/plugins" method="POST">
<section id="enabled_plugins"> <section id="enabled_plugins">
<h1>Enabled Plugins</h1> <h1>Enabled Plugins</h1>
@ -88,7 +88,7 @@ <h1>Disabled Plugins</h1>
</section> </section>
</form> </form>
<form action="{$base_path}/?do=save_pluginadmin" method="POST"> <form action="{$base_path}/admin/plugins" method="POST">
<section id="plugin_parameters"> <section id="plugin_parameters">
<h1>Enabled Plugin Parameters</h1> <h1>Enabled Plugin Parameters</h1>

View file

@ -7,7 +7,7 @@
<div id="toolsdiv"> <div id="toolsdiv">
<a href="{$base_path}/admin/configure"><b>Configure your Shaarli</b><span>: Change Title, timezone...</span></a> <a href="{$base_path}/admin/configure"><b>Configure your Shaarli</b><span>: Change Title, timezone...</span></a>
<br><br> <br><br>
<a href="{$base_path}/?do=pluginadmin"><b>Plugin administration</b><span>: Enable, disable and configure plugins.</span></a> <a href="{$base_path}/admin/plugins"><b>Plugin administration</b><span>: Enable, disable and configure plugins.</span></a>
<br><br> <br><br>
{if="!$openshaarli"}<a href="{$base_path}/admin/password"><b>Change password</b><span>: Change your password.</span></a> {if="!$openshaarli"}<a href="{$base_path}/admin/password"><b>Change password</b><span>: Change your password.</span></a>
<br><br>{/if} <br><br>{/if}