]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Fixes #378 - Plugin administration UI. 388/head
authorArthurHoaro <arthur@hoa.ro>
Wed, 18 Nov 2015 16:40:42 +0000 (17:40 +0100)
committerArthurHoaro <arthur@hoa.ro>
Sun, 31 Jan 2016 17:54:48 +0000 (18:54 +0100)
20 files changed:
application/Config.php
application/PluginManager.php
application/Router.php
inc/plugin_admin.js [new file with mode: 0644]
inc/shaarli.css
index.php
plugins/addlink_toolbar/addlink_toolbar.meta [new file with mode: 0644]
plugins/archiveorg/archiveorg.meta [new file with mode: 0644]
plugins/demo_plugin/demo_plugin.meta [new file with mode: 0644]
plugins/playvideos/playvideos.meta [new file with mode: 0644]
plugins/qrcode/qrcode.meta [new file with mode: 0644]
plugins/readityourself/readityourself.meta [new file with mode: 0644]
plugins/readityourself/readityourself.php
plugins/wallabag/wallabag.meta [new file with mode: 0644]
plugins/wallabag/wallabag.php
tests/ConfigTest.php
tests/PluginManagerTest.php
tests/plugins/test/test.meta [new file with mode: 0644]
tpl/pluginsadmin.html [new file with mode: 0644]
tpl/tools.html

index c71ef68cd9ade1d07eeb6ebaf5a60338f8336339..9af5a535a5b9181e0c02457fa206a49c7b26e60e 100644 (file)
@@ -73,6 +73,106 @@ function writeConfig($config, $isLoggedIn)
     }
 }
 
+/**
+ * Process plugin administration form data and save it in an array.
+ *
+ * @param array $formData Data sent by the plugin admin form.
+ *
+ * @return array New list of enabled plugin, ordered.
+ *
+ * @throws PluginConfigOrderException Plugins can't be sorted because their order is invalid.
+ */
+function save_plugin_config($formData)
+{
+    // Make sure there are no duplicates in orders.
+    if (!validate_plugin_order($formData)) {
+        throw new PluginConfigOrderException();
+    }
+
+    $plugins = array();
+    $newEnabledPlugins = array();
+    foreach ($formData as $key => $data) {
+        if (startsWith($key, 'order')) {
+            continue;
+        }
+
+        // If there is no order, it means a disabled plugin has been enabled.
+        if (isset($formData['order_' . $key])) {
+            $plugins[(int) $formData['order_' . $key]] = $key;
+        }
+        else {
+            $newEnabledPlugins[] = $key;
+        }
+    }
+
+    // New enabled plugins will be added at the end of order.
+    $plugins = array_merge($plugins, $newEnabledPlugins);
+
+    // Sort plugins by order.
+    if (!ksort($plugins)) {
+        throw new PluginConfigOrderException();
+    }
+
+    $finalPlugins = array();
+    // Make plugins order continuous.
+    foreach ($plugins as $plugin) {
+        $finalPlugins[] = $plugin;
+    }
+
+    return $finalPlugins;
+}
+
+/**
+ * Validate plugin array submitted.
+ * Will fail if there is duplicate orders value.
+ *
+ * @param array $formData Data from submitted form.
+ *
+ * @return bool true if ok, false otherwise.
+ */
+function validate_plugin_order($formData)
+{
+    $orders = array();
+    foreach ($formData as $key => $value) {
+        // No duplicate order allowed.
+        if (in_array($value, $orders)) {
+            return false;
+        }
+
+        if (startsWith($key, 'order')) {
+            $orders[] = $value;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Affect plugin parameters values into plugins array.
+ *
+ * @param mixed $plugins Plugins array ($plugins[<plugin_name>]['parameters']['param_name'] = <value>.
+ * @param mixed $config  Plugins configuration.
+ *
+ * @return mixed Updated $plugins array.
+ */
+function load_plugin_parameter_values($plugins, $config)
+{
+    $out = $plugins;
+    foreach ($plugins as $name => $plugin) {
+        if (empty($plugin['parameters'])) {
+            continue;
+        }
+
+        foreach ($plugin['parameters'] as $key => $param) {
+            if (!empty($config[$key])) {
+                $out[$name]['parameters'][$key] = $config[$key];
+            }
+        }
+    }
+
+    return $out;
+}
+
 /**
  * Milestone 0.9 - shaarli/Shaarli#41: options.php is not supported anymore.
  * ==> if user is loggedIn, merge its content with config.php, then delete options.php.
@@ -132,3 +232,17 @@ class UnauthorizedConfigException extends Exception
         $this->message = 'You are not authorized to alter config.';
     }
 }
+
+/**
+ * Exception used if an error occur while saving plugin configuration.
+ */
+class PluginConfigOrderException extends Exception
+{
+    /**
+     * Construct exception.
+     */
+    public function __construct()
+    {
+        $this->message = 'An error occurred while trying to save plugins loading order.';
+    }
+}
index 803f11b4b707000a6bdab1235ee6ab49655ac8db..787ac6a930f63f4c1245a3178b6de444be6569a9 100644 (file)
@@ -33,6 +33,12 @@ class PluginManager
      */
     public static $PLUGINS_PATH = 'plugins';
 
+    /**
+     * Plugins meta files extension.
+     * @var string $META_EXT
+     */
+    public static $META_EXT = 'meta';
+
     /**
      * Private constructor: new instances not allowed.
      */
@@ -162,6 +168,51 @@ class PluginManager
     {
         return 'hook_' . $pluginName . '_' . $hook;
     }
+
+    /**
+     * Retrieve plugins metadata from *.meta (INI) files into an array.
+     * Metadata contains:
+     *   - plugin description [description]
+     *   - parameters split with ';' [parameters]
+     *
+     * Respects plugins order from settings.
+     *
+     * @return array plugins metadata.
+     */
+    public function getPluginsMeta()
+    {
+        $metaData = array();
+        $dirs = glob(self::$PLUGINS_PATH . '/*', GLOB_ONLYDIR | GLOB_MARK);
+
+        // Browse all plugin directories.
+        foreach ($dirs as $pluginDir) {
+            $plugin = basename($pluginDir);
+            $metaFile = $pluginDir . $plugin . '.' . self::$META_EXT;
+            if (!is_file($metaFile) || !is_readable($metaFile)) {
+                continue;
+            }
+
+            $metaData[$plugin] = parse_ini_file($metaFile);
+            $metaData[$plugin]['order'] = array_search($plugin, $this->authorizedPlugins);
+
+            // Read parameters and format them into an array.
+            if (isset($metaData[$plugin]['parameters'])) {
+                $params = explode(';', $metaData[$plugin]['parameters']);
+            } else {
+                $params = array();
+            }
+            $metaData[$plugin]['parameters'] = array();
+            foreach ($params as $param) {
+                if (empty($param)) {
+                    continue;
+                }
+
+                $metaData[$plugin]['parameters'][$param] = '';
+            }
+        }
+
+        return $metaData;
+    }
 }
 
 /**
index 0c813847b282162c40e61ae6a2e16e83a34d3508..6185f08e932c742806135767792d6f3c2cf8e7bc 100644 (file)
@@ -35,6 +35,10 @@ class Router
 
     public static $PAGE_LINKLIST = 'linklist';
 
+    public static $PAGE_PLUGINSADMIN = 'pluginadmin';
+
+    public static $PAGE_SAVE_PLUGINSADMIN = 'save_pluginadmin';
+
     /**
      * Reproducing renderPage() if hell, to avoid regression.
      *
@@ -112,6 +116,14 @@ class Router
             return self::$PAGE_IMPORT;
         }
 
+        if (startswith($query, 'do='. self::$PAGE_PLUGINSADMIN)) {
+            return self::$PAGE_PLUGINSADMIN;
+        }
+
+        if (startswith($query, 'do='. self::$PAGE_SAVE_PLUGINSADMIN)) {
+            return self::$PAGE_SAVE_PLUGINSADMIN;
+        }
+
         return self::$PAGE_LINKLIST;
     }
 }
\ No newline at end of file
diff --git a/inc/plugin_admin.js b/inc/plugin_admin.js
new file mode 100644 (file)
index 0000000..134ffb3
--- /dev/null
@@ -0,0 +1,67 @@
+/**
+ * Change the position counter of a row.
+ *
+ * @param elem  Element Node to change.
+ * @param toPos int     New position.
+ */
+function changePos(elem, toPos)
+{
+    var elemName = elem.getAttribute('data-line')
+
+    elem.setAttribute('data-order', toPos);
+    var hiddenInput = document.querySelector('[name="order_'+ elemName +'"]');
+    hiddenInput.setAttribute('value', toPos);
+}
+
+/**
+ * Move a row up or down.
+ *
+ * @param pos  Element Node to move.
+ * @param move int     Move: +1 (down) or -1 (up)
+ */
+function changeOrder(pos, move)
+{
+    var newpos = parseInt(pos) + move;
+    var line = document.querySelector('[data-order="'+ pos +'"]');
+    var changeline = document.querySelector('[data-order="'+ newpos +'"]');
+    var parent = changeline.parentNode;
+
+    changePos(line, newpos);
+    changePos(changeline, parseInt(pos));
+    var changeItem = move < 0 ? changeline : changeline.nextSibling;
+    parent.insertBefore(line, changeItem);
+}
+
+/**
+ * Move a row up in the table.
+ *
+ * @param pos int row counter.
+ *
+ * @returns false
+ */
+function orderUp(pos)
+{
+    if (pos == 0) {
+        return false;
+    }
+    changeOrder(pos, -1);
+    return false;
+}
+
+/**
+ * Move a row down in the table.
+ *
+ * @param pos int row counter.
+ *
+ * @returns false
+ */
+function orderDown(pos)
+{
+    var lastpos = document.querySelector('[data-order]:last-child').getAttribute('data-order');
+    if (pos == lastpos) {
+        return false;
+    }
+
+    changeOrder(pos, +1);
+    return false;
+}
index 96e2cae1ce5c2d22dbf7fb74e75d47129c7ad4c7..f137555e856904c598bb3c38527efb2aac17de1b 100644 (file)
@@ -1103,6 +1103,66 @@ ul.errors {
     float: left;
 }
 
+#pluginsadmin {
+    width: 80%;
+    padding: 20px 0 0 20px;
+}
+
+#pluginsadmin section {
+    padding: 20px 0;
+}
+
+#pluginsadmin .plugin_parameters {
+    margin: 10px 0;
+}
+
+#pluginsadmin h1 {
+    font-style: normal;
+}
+
+#pluginsadmin h2 {
+    font-size: 1.4em;
+    font-weight: bold;
+}
+
+#pluginsadmin table {
+    width: 100%;
+}
+
+#pluginsadmin table, #pluginsadmin th, #pluginsadmin td {
+    border-width: 1px 0;
+    border-style: solid;
+    border-color: #c0c0c0;
+}
+
+#pluginsadmin table th {
+    font-weight: bold;
+    padding: 10px 0;
+}
+
+#pluginsadmin table td {
+    padding: 5px 0;
+}
+
+#pluginsadmin input[type=submit] {
+    margin: 10px 0;
+}
+
+#pluginsadmin .plugin_parameter {
+    padding: 5px 0;
+    border-width: 1px 0;
+    border-style: solid;
+    border-color: #c0c0c0;
+}
+
+#pluginsadmin .float_label {
+    float: left;
+    width: 20%;
+}
+
+#pluginsadmin a {
+    color: black;
+}
 /* 404 page */
 .error-container {
 
index beba9c32a2541abeb15b74587a7cf1a7ac86783a..51b7b39179287e30b07c1b5dc95c178c321d5eb8 100644 (file)
--- a/index.php
+++ b/index.php
@@ -1770,6 +1770,54 @@ HTML;
         exit;
     }
 
+    // Plugin administration page
+    if ($targetPage == Router::$PAGE_PLUGINSADMIN) {
+        $pluginMeta = $pluginManager->getPluginsMeta();
+
+        // 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, $GLOBALS['plugins']);
+        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->renderPage('pluginsadmin');
+        exit;
+    }
+
+    // Plugin administration form action
+    if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) {
+        try {
+            if (isset($_POST['parameters_form'])) {
+                unset($_POST['parameters_form']);
+                foreach ($_POST as $param => $value) {
+                    $GLOBALS['plugins'][$param] = escape($value);
+                }
+            }
+            else {
+                $GLOBALS['config']['ENABLED_PLUGINS'] = save_plugin_config($_POST);
+            }
+            writeConfig($GLOBALS, isLoggedIn());
+        }
+        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=pluginsadmin\';</script>';
+            exit;
+        }
+        header('Location: ?do='. Router::$PAGE_PLUGINSADMIN);
+        exit;
+    }
+
     // -------- Otherwise, simply display search form and links:
     showLinkList($PAGE, $LINKSDB);
     exit;
diff --git a/plugins/addlink_toolbar/addlink_toolbar.meta b/plugins/addlink_toolbar/addlink_toolbar.meta
new file mode 100644 (file)
index 0000000..2f0b586
--- /dev/null
@@ -0,0 +1 @@
+description="Adds the addlink input on the linklist page."
diff --git a/plugins/archiveorg/archiveorg.meta b/plugins/archiveorg/archiveorg.meta
new file mode 100644 (file)
index 0000000..8b5703e
--- /dev/null
@@ -0,0 +1 @@
+description="For each link, add an Archive.org icon."
diff --git a/plugins/demo_plugin/demo_plugin.meta b/plugins/demo_plugin/demo_plugin.meta
new file mode 100644 (file)
index 0000000..b063ecb
--- /dev/null
@@ -0,0 +1 @@
+description="A demo plugin covering all use cases for template designers and plugin developers."
diff --git a/plugins/playvideos/playvideos.meta b/plugins/playvideos/playvideos.meta
new file mode 100644 (file)
index 0000000..c2b0908
--- /dev/null
@@ -0,0 +1 @@
+description="Add a button in the toolbar allowing to watch all videos."
diff --git a/plugins/qrcode/qrcode.meta b/plugins/qrcode/qrcode.meta
new file mode 100644 (file)
index 0000000..cbf371e
--- /dev/null
@@ -0,0 +1 @@
+description="For each link, add a QRCode icon ."
diff --git a/plugins/readityourself/readityourself.meta b/plugins/readityourself/readityourself.meta
new file mode 100644 (file)
index 0000000..bd611dd
--- /dev/null
@@ -0,0 +1,2 @@
+description="For each link, add a ReadItYourself icon to save the shaared URL."
+parameters=READITYOUSELF_URL;
\ No newline at end of file
index 1b030bc84a2aa7039d416ed583f9f2e00a1a379f..c8df4c4fc8595078a17dc3be6e955382d61d9746 100644 (file)
@@ -13,7 +13,7 @@ if (is_file(PluginManager::$PLUGINS_PATH . '/readityourself/config.php')) {
     include PluginManager::$PLUGINS_PATH . '/readityourself/config.php';
 }
 
-if (!isset($GLOBALS['plugins']['READITYOUSELF_URL'])) {
+if (empty($GLOBALS['plugins']['READITYOUSELF_URL'])) {
     $GLOBALS['plugin_errors'][] = 'Readityourself plugin error: '.
         'Please define "$GLOBALS[\'plugins\'][\'READITYOUSELF_URL\']" '.
         'in "plugins/readityourself/config.php" or in your Shaarli config.php file.';
diff --git a/plugins/wallabag/wallabag.meta b/plugins/wallabag/wallabag.meta
new file mode 100644 (file)
index 0000000..8763c4a
--- /dev/null
@@ -0,0 +1,2 @@
+description="For each link, add a Wallabag icon to save it in your instance."
+parameters="WALLABAG_URL"
\ No newline at end of file
index e3c399a9ab5d85a124db5f99a1f0a135f8fbbbc8..0d6fc66de5fdf5df269455a0365c18f3fabcd557 100644 (file)
@@ -11,7 +11,7 @@ if (is_file(PluginManager::$PLUGINS_PATH . '/wallabag/config.php')) {
     include PluginManager::$PLUGINS_PATH . '/wallabag/config.php';
 }
 
-if (!isset($GLOBALS['plugins']['WALLABAG_URL'])) {
+if (empty($GLOBALS['plugins']['WALLABAG_URL'])) {
     $GLOBALS['plugin_errors'][] = 'Wallabag plugin error: '.
         'Please define "$GLOBALS[\'plugins\'][\'WALLABAG_URL\']" '.
         'in "plugins/wallabag/config.php" or in your Shaarli config.php file.';
index adebfcc31270fbe90a1388c2d074ebc17b9bb937..492ddd3b1a40fffa0af45466687d6ade8330eaaa 100644 (file)
@@ -174,4 +174,113 @@ class ConfigTest extends PHPUnit_Framework_TestCase
         include self::$configFields['config']['CONFIG_FILE'];
         $this->assertEquals(self::$configFields['login'], $GLOBALS['login']);
     }
+
+    /**
+     * Test save_plugin_config with valid data.
+     *
+     * @throws PluginConfigOrderException
+     */
+    public function testSavePluginConfigValid()
+    {
+        $data = array(
+            'order_plugin1' => 2,   // no plugin related
+            'plugin2' => 0,         // new - at the end
+            'plugin3' => 0,         // 2nd
+            'order_plugin3' => 8,
+            'plugin4' => 0,         // 1st
+            'order_plugin4' => 5,
+        );
+
+        $expected = array(
+            'plugin3',
+            'plugin4',
+            'plugin2',
+        );
+
+        $out = save_plugin_config($data);
+        $this->assertEquals($expected, $out);
+    }
+
+    /**
+     * Test save_plugin_config with invalid data.
+     *
+     * @expectedException              PluginConfigOrderException
+     */
+    public function testSavePluginConfigInvalid()
+    {
+        $data = array(
+            'plugin2' => 0,
+            'plugin3' => 0,
+            'order_plugin3' => 0,
+            'plugin4' => 0,
+            'order_plugin4' => 0,
+        );
+
+        save_plugin_config($data);
+    }
+
+    /**
+     * Test save_plugin_config without data.
+     */
+    public function testSavePluginConfigEmpty()
+    {
+        $this->assertEquals(array(), save_plugin_config(array()));
+    }
+
+    /**
+     * Test validate_plugin_order with valid data.
+     */
+    public function testValidatePluginOrderValid()
+    {
+        $data = array(
+            'order_plugin1' => 2,
+            'plugin2' => 0,
+            'plugin3' => 0,
+            'order_plugin3' => 1,
+            'plugin4' => 0,
+            'order_plugin4' => 5,
+        );
+
+        $this->assertTrue(validate_plugin_order($data));
+    }
+
+    /**
+     * Test validate_plugin_order with invalid data.
+     */
+    public function testValidatePluginOrderInvalid()
+    {
+        $data = array(
+            'order_plugin1' => 2,
+            'order_plugin3' => 1,
+            'order_plugin4' => 1,
+        );
+
+        $this->assertFalse(validate_plugin_order($data));
+    }
+
+    /**
+     * Test load_plugin_parameter_values.
+     */
+    public function testLoadPluginParameterValues()
+    {
+        $plugins = array(
+            'plugin_name' => array(
+                'parameters' => array(
+                    'param1' => true,
+                    'param2' => false,
+                    'param3' => '',
+                )
+            )
+        );
+
+        $parameters = array(
+            'param1' => 'value1',
+            'param2' => 'value2',
+        );
+
+        $result = load_plugin_parameter_values($plugins, $parameters);
+        $this->assertEquals('value1', $result['plugin_name']['parameters']['param1']);
+        $this->assertEquals('value2', $result['plugin_name']['parameters']['param2']);
+        $this->assertEquals('', $result['plugin_name']['parameters']['param3']);
+    }
 }
index df2614b557d8cf94a03679531e3a4e125706a440..348082c763c2dde8a25c474ae0979248a6a9ad0e 100644 (file)
@@ -63,4 +63,23 @@ class PluginManagerTest extends PHPUnit_Framework_TestCase
 
         $pluginManager->load(array('nope', 'renope'));
     }
+
+    /**
+     * Test plugin metadata loading.
+     */
+    public function testGetPluginsMeta()
+    {
+        $pluginManager = PluginManager::getInstance();
+
+        PluginManager::$PLUGINS_PATH = self::$pluginPath;
+        $pluginManager->load(array(self::$pluginName));
+
+        $expectedParameters = array(
+            'pop' => '',
+            'hip' => '',
+        );
+        $meta = $pluginManager->getPluginsMeta();
+        $this->assertEquals('test plugin', $meta[self::$pluginName]['description']);
+        $this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']);
+    }
 }
\ No newline at end of file
diff --git a/tests/plugins/test/test.meta b/tests/plugins/test/test.meta
new file mode 100644 (file)
index 0000000..ab999ed
--- /dev/null
@@ -0,0 +1,2 @@
+description="test plugin"
+parameters="pop;hip"
\ No newline at end of file
diff --git a/tpl/pluginsadmin.html b/tpl/pluginsadmin.html
new file mode 100644 (file)
index 0000000..4f7d091
--- /dev/null
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<html>
+<head>{include="includes"}</head>
+<body>
+<div id="pageheader">
+  {include="page.header"}
+</div>
+
+<noscript>
+  <div>
+    <ul class="errors">
+      <li>You need to enable Javascript to change plugin loading order.</li>
+    </ul>
+  </div>
+  <div class="clear"></div>
+</noscript>
+
+<div id="pluginsadmin">
+  <form action="?do=save_pluginadmin" method="POST">
+    <section id="enabled_plugins">
+      <h1>Enabled Plugins</h1>
+
+      <div>
+        {if="count($enabledPlugins)==0"}
+          <p>No plugin enabled.</p>
+        {else}
+          <table id="plugin_table">
+            <thead>
+            <tr>
+              <th class="center">Disable</th>
+              <th class="center">Order</th>
+              <th>Name</th>
+              <th>Description</th>
+            </tr>
+            </thead>
+            <tbody>
+            {loop="$enabledPlugins"}
+              <tr data-line="{$key}" data-order="{$counter}">
+                <td class="center"><input type="checkbox" name="{$key}" checked="checked"></td>
+                <td class="center">
+                  <a href="#"
+                     onclick="return orderUp(this.parentNode.parentNode.getAttribute('data-order'));">
+                    ▲
+                  </a>
+                  <a href="#"
+                     onclick="return orderDown(this.parentNode.parentNode.getAttribute('data-order'));">
+                    ▼
+                  </a>
+                  <input type="hidden" name="order_{$key}" value="{$counter}">
+                </td>
+                <td>{$key}</td>
+                <td>{$value.description}</td>
+              </tr>
+            {/loop}
+            </tbody>
+          </table>
+        {/if}
+      </div>
+    </section>
+
+    <section id="disabled_plugins">
+      <h1>Disabled Plugins</h1>
+
+      <div>
+        {if="count($disabledPlugins)==0"}
+          <p>No plugin disabled.</p>
+        {else}
+          <table>
+            <tr>
+              <th class="center">Enable</th>
+              <th>Name</th>
+              <th>Description</th>
+            </tr>
+            {loop="$disabledPlugins"}
+              <tr>
+                <td class="center"><input type="checkbox" name="{$key}"></td>
+                <td>{$key}</td>
+                <td>{$value.description}</td>
+              </tr>
+            {/loop}
+          </table>
+        {/if}
+      </div>
+
+      <div class="center">
+        <input type="submit" value="Save"/>
+      </div>
+    </section>
+  </form>
+
+  <form action="?do=save_pluginadmin" method="POST">
+    <section id="plugin_parameters">
+      <h1>Enabled Plugin Parameters</h1>
+
+      <div>
+        {if="count($enabledPlugins)==0"}
+          <p>No plugin enabled.</p>
+        {else}
+          {loop="$enabledPlugins"}
+            {if="count($value.parameters) > 0"}
+              <div class="plugin_parameters">
+                <h2>{$key}</h2>
+                {loop="$value.parameters"}
+                  <div class="plugin_parameter">
+                    <div class="float_label">
+                      <label for="{$key}">
+                        <code>{$key}</code>
+                      </label>
+                    </div>
+                    <div class="float_input">
+                      <input name="{$key}" value="{$value}" id="{$key}"/>
+                    </div>
+                  </div>
+                {/loop}
+              </div>
+            {/if}
+          {/loop}
+        {/if}
+        <div class="center">
+          <input type="submit" name="parameters_form" value="Save"/>
+        </div>
+      </div>
+    </section>
+  </form>
+
+</div>
+{include="page.footer"}
+
+<script src="inc/plugin_admin.js#"></script>
+</body>
+</html>
\ No newline at end of file
index c13f4f16e3626ca23b241e8eb7ecfbec78425b34..78b816633ac5ca3b1723c3a6232b9236d7463b7b 100644 (file)
@@ -5,11 +5,18 @@
 <div id="pageheader">
        {include="page.header"}
        <div id="toolsdiv">
-               {if="!$GLOBALS['config']['OPEN_SHAARLI']"}<a href="?do=changepasswd"><b>Change password</b>  <span>: Change your password.</span></a><br><br>{/if}
-               <a href="?do=configure"><b>Configure your Shaarli</b> <span>:  Change Title, timezone...</span></a><br><br>
-               <a href="?do=changetag"><b>Rename/delete tags</b> <span>:  Rename or delete a tag in all links</span></a><br><br>
-               <a href="?do=import"><b>Import</b> <span>:  Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)</span></a> <br><br>
-               <a href="?do=export"><b>Export</b> <span>:  Export Netscape html bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)</span></a><br><br>
+               <a href="?do=configure"><b>Configure your Shaarli</b><span>: Change Title, timezone...</span></a>
+               <br><br>
+               <a href="?do=pluginadmin"><b>Plugin administration</b><span>: Enable, disable and configure plugins.</span></a>
+    <br><br>
+               {if="!$GLOBALS['config']['OPEN_SHAARLI']"}<a href="?do=changepasswd"><b>Change password</b><span>: Change your password.</span></a>
+    <br><br>{/if}
+               <a href="?do=changetag"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a>
+    <br><br>
+               <a href="?do=import"><b>Import</b><span>: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)</span></a>
+    <br><br>
+               <a href="?do=export"><b>Export</b><span>: Export Netscape html bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)</span></a>
+    <br><br>
                <a class="smallbutton"
                   onclick="return alertBookmarklet();"
                   href="javascript:(