diff options
-rw-r--r-- | application/Config.php | 114 | ||||
-rw-r--r-- | application/PluginManager.php | 51 | ||||
-rw-r--r-- | application/Router.php | 12 | ||||
-rw-r--r-- | inc/plugin_admin.js | 67 | ||||
-rw-r--r-- | inc/shaarli.css | 60 | ||||
-rw-r--r-- | index.php | 48 | ||||
-rw-r--r-- | plugins/addlink_toolbar/addlink_toolbar.meta | 1 | ||||
-rw-r--r-- | plugins/archiveorg/archiveorg.meta | 1 | ||||
-rw-r--r-- | plugins/demo_plugin/demo_plugin.meta | 1 | ||||
-rw-r--r-- | plugins/playvideos/playvideos.meta | 1 | ||||
-rw-r--r-- | plugins/qrcode/qrcode.meta | 1 | ||||
-rw-r--r-- | plugins/readityourself/readityourself.meta | 2 | ||||
-rw-r--r-- | plugins/readityourself/readityourself.php | 2 | ||||
-rw-r--r-- | plugins/wallabag/wallabag.meta | 2 | ||||
-rw-r--r-- | plugins/wallabag/wallabag.php | 2 | ||||
-rw-r--r-- | tests/ConfigTest.php | 109 | ||||
-rw-r--r-- | tests/PluginManagerTest.php | 19 | ||||
-rw-r--r-- | tests/plugins/test/test.meta | 2 | ||||
-rw-r--r-- | tpl/pluginsadmin.html | 131 | ||||
-rw-r--r-- | tpl/tools.html | 17 |
20 files changed, 636 insertions, 7 deletions
diff --git a/application/Config.php b/application/Config.php index c71ef68c..9af5a535 100644 --- a/application/Config.php +++ b/application/Config.php | |||
@@ -74,6 +74,106 @@ function writeConfig($config, $isLoggedIn) | |||
74 | } | 74 | } |
75 | 75 | ||
76 | /** | 76 | /** |
77 | * Process plugin administration form data and save it in an array. | ||
78 | * | ||
79 | * @param array $formData Data sent by the plugin admin form. | ||
80 | * | ||
81 | * @return array New list of enabled plugin, ordered. | ||
82 | * | ||
83 | * @throws PluginConfigOrderException Plugins can't be sorted because their order is invalid. | ||
84 | */ | ||
85 | function save_plugin_config($formData) | ||
86 | { | ||
87 | // Make sure there are no duplicates in orders. | ||
88 | if (!validate_plugin_order($formData)) { | ||
89 | throw new PluginConfigOrderException(); | ||
90 | } | ||
91 | |||
92 | $plugins = array(); | ||
93 | $newEnabledPlugins = array(); | ||
94 | foreach ($formData as $key => $data) { | ||
95 | if (startsWith($key, 'order')) { | ||
96 | continue; | ||
97 | } | ||
98 | |||
99 | // If there is no order, it means a disabled plugin has been enabled. | ||
100 | if (isset($formData['order_' . $key])) { | ||
101 | $plugins[(int) $formData['order_' . $key]] = $key; | ||
102 | } | ||
103 | else { | ||
104 | $newEnabledPlugins[] = $key; | ||
105 | } | ||
106 | } | ||
107 | |||
108 | // New enabled plugins will be added at the end of order. | ||
109 | $plugins = array_merge($plugins, $newEnabledPlugins); | ||
110 | |||
111 | // Sort plugins by order. | ||
112 | if (!ksort($plugins)) { | ||
113 | throw new PluginConfigOrderException(); | ||
114 | } | ||
115 | |||
116 | $finalPlugins = array(); | ||
117 | // Make plugins order continuous. | ||
118 | foreach ($plugins as $plugin) { | ||
119 | $finalPlugins[] = $plugin; | ||
120 | } | ||
121 | |||
122 | return $finalPlugins; | ||
123 | } | ||
124 | |||
125 | /** | ||
126 | * Validate plugin array submitted. | ||
127 | * Will fail if there is duplicate orders value. | ||
128 | * | ||
129 | * @param array $formData Data from submitted form. | ||
130 | * | ||
131 | * @return bool true if ok, false otherwise. | ||
132 | */ | ||
133 | function validate_plugin_order($formData) | ||
134 | { | ||
135 | $orders = array(); | ||
136 | foreach ($formData as $key => $value) { | ||
137 | // No duplicate order allowed. | ||
138 | if (in_array($value, $orders)) { | ||
139 | return false; | ||
140 | } | ||
141 | |||
142 | if (startsWith($key, 'order')) { | ||
143 | $orders[] = $value; | ||
144 | } | ||
145 | } | ||
146 | |||
147 | return true; | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * Affect plugin parameters values into plugins array. | ||
152 | * | ||
153 | * @param mixed $plugins Plugins array ($plugins[<plugin_name>]['parameters']['param_name'] = <value>. | ||
154 | * @param mixed $config Plugins configuration. | ||
155 | * | ||
156 | * @return mixed Updated $plugins array. | ||
157 | */ | ||
158 | function load_plugin_parameter_values($plugins, $config) | ||
159 | { | ||
160 | $out = $plugins; | ||
161 | foreach ($plugins as $name => $plugin) { | ||
162 | if (empty($plugin['parameters'])) { | ||
163 | continue; | ||
164 | } | ||
165 | |||
166 | foreach ($plugin['parameters'] as $key => $param) { | ||
167 | if (!empty($config[$key])) { | ||
168 | $out[$name]['parameters'][$key] = $config[$key]; | ||
169 | } | ||
170 | } | ||
171 | } | ||
172 | |||
173 | return $out; | ||
174 | } | ||
175 | |||
176 | /** | ||
77 | * Milestone 0.9 - shaarli/Shaarli#41: options.php is not supported anymore. | 177 | * Milestone 0.9 - shaarli/Shaarli#41: options.php is not supported anymore. |
78 | * ==> if user is loggedIn, merge its content with config.php, then delete options.php. | 178 | * ==> if user is loggedIn, merge its content with config.php, then delete options.php. |
79 | * | 179 | * |
@@ -132,3 +232,17 @@ class UnauthorizedConfigException extends Exception | |||
132 | $this->message = 'You are not authorized to alter config.'; | 232 | $this->message = 'You are not authorized to alter config.'; |
133 | } | 233 | } |
134 | } | 234 | } |
235 | |||
236 | /** | ||
237 | * Exception used if an error occur while saving plugin configuration. | ||
238 | */ | ||
239 | class PluginConfigOrderException extends Exception | ||
240 | { | ||
241 | /** | ||
242 | * Construct exception. | ||
243 | */ | ||
244 | public function __construct() | ||
245 | { | ||
246 | $this->message = 'An error occurred while trying to save plugins loading order.'; | ||
247 | } | ||
248 | } | ||
diff --git a/application/PluginManager.php b/application/PluginManager.php index 803f11b4..787ac6a9 100644 --- a/application/PluginManager.php +++ b/application/PluginManager.php | |||
@@ -34,6 +34,12 @@ class PluginManager | |||
34 | public static $PLUGINS_PATH = 'plugins'; | 34 | public static $PLUGINS_PATH = 'plugins'; |
35 | 35 | ||
36 | /** | 36 | /** |
37 | * Plugins meta files extension. | ||
38 | * @var string $META_EXT | ||
39 | */ | ||
40 | public static $META_EXT = 'meta'; | ||
41 | |||
42 | /** | ||
37 | * Private constructor: new instances not allowed. | 43 | * Private constructor: new instances not allowed. |
38 | */ | 44 | */ |
39 | private function __construct() | 45 | private function __construct() |
@@ -162,6 +168,51 @@ class PluginManager | |||
162 | { | 168 | { |
163 | return 'hook_' . $pluginName . '_' . $hook; | 169 | return 'hook_' . $pluginName . '_' . $hook; |
164 | } | 170 | } |
171 | |||
172 | /** | ||
173 | * Retrieve plugins metadata from *.meta (INI) files into an array. | ||
174 | * Metadata contains: | ||
175 | * - plugin description [description] | ||
176 | * - parameters split with ';' [parameters] | ||
177 | * | ||
178 | * Respects plugins order from settings. | ||
179 | * | ||
180 | * @return array plugins metadata. | ||
181 | */ | ||
182 | public function getPluginsMeta() | ||
183 | { | ||
184 | $metaData = array(); | ||
185 | $dirs = glob(self::$PLUGINS_PATH . '/*', GLOB_ONLYDIR | GLOB_MARK); | ||
186 | |||
187 | // Browse all plugin directories. | ||
188 | foreach ($dirs as $pluginDir) { | ||
189 | $plugin = basename($pluginDir); | ||
190 | $metaFile = $pluginDir . $plugin . '.' . self::$META_EXT; | ||
191 | if (!is_file($metaFile) || !is_readable($metaFile)) { | ||
192 | continue; | ||
193 | } | ||
194 | |||
195 | $metaData[$plugin] = parse_ini_file($metaFile); | ||
196 | $metaData[$plugin]['order'] = array_search($plugin, $this->authorizedPlugins); | ||
197 | |||
198 | // Read parameters and format them into an array. | ||
199 | if (isset($metaData[$plugin]['parameters'])) { | ||
200 | $params = explode(';', $metaData[$plugin]['parameters']); | ||
201 | } else { | ||
202 | $params = array(); | ||
203 | } | ||
204 | $metaData[$plugin]['parameters'] = array(); | ||
205 | foreach ($params as $param) { | ||
206 | if (empty($param)) { | ||
207 | continue; | ||
208 | } | ||
209 | |||
210 | $metaData[$plugin]['parameters'][$param] = ''; | ||
211 | } | ||
212 | } | ||
213 | |||
214 | return $metaData; | ||
215 | } | ||
165 | } | 216 | } |
166 | 217 | ||
167 | /** | 218 | /** |
diff --git a/application/Router.php b/application/Router.php index 0c813847..6185f08e 100644 --- a/application/Router.php +++ b/application/Router.php | |||
@@ -35,6 +35,10 @@ class Router | |||
35 | 35 | ||
36 | public static $PAGE_LINKLIST = 'linklist'; | 36 | public static $PAGE_LINKLIST = 'linklist'; |
37 | 37 | ||
38 | public static $PAGE_PLUGINSADMIN = 'pluginadmin'; | ||
39 | |||
40 | public static $PAGE_SAVE_PLUGINSADMIN = 'save_pluginadmin'; | ||
41 | |||
38 | /** | 42 | /** |
39 | * Reproducing renderPage() if hell, to avoid regression. | 43 | * Reproducing renderPage() if hell, to avoid regression. |
40 | * | 44 | * |
@@ -112,6 +116,14 @@ class Router | |||
112 | return self::$PAGE_IMPORT; | 116 | return self::$PAGE_IMPORT; |
113 | } | 117 | } |
114 | 118 | ||
119 | if (startswith($query, 'do='. self::$PAGE_PLUGINSADMIN)) { | ||
120 | return self::$PAGE_PLUGINSADMIN; | ||
121 | } | ||
122 | |||
123 | if (startswith($query, 'do='. self::$PAGE_SAVE_PLUGINSADMIN)) { | ||
124 | return self::$PAGE_SAVE_PLUGINSADMIN; | ||
125 | } | ||
126 | |||
115 | return self::$PAGE_LINKLIST; | 127 | return self::$PAGE_LINKLIST; |
116 | } | 128 | } |
117 | } \ No newline at end of file | 129 | } \ No newline at end of file |
diff --git a/inc/plugin_admin.js b/inc/plugin_admin.js new file mode 100644 index 00000000..134ffb33 --- /dev/null +++ b/inc/plugin_admin.js | |||
@@ -0,0 +1,67 @@ | |||
1 | /** | ||
2 | * Change the position counter of a row. | ||
3 | * | ||
4 | * @param elem Element Node to change. | ||
5 | * @param toPos int New position. | ||
6 | */ | ||
7 | function changePos(elem, toPos) | ||
8 | { | ||
9 | var elemName = elem.getAttribute('data-line') | ||
10 | |||
11 | elem.setAttribute('data-order', toPos); | ||
12 | var hiddenInput = document.querySelector('[name="order_'+ elemName +'"]'); | ||
13 | hiddenInput.setAttribute('value', toPos); | ||
14 | } | ||
15 | |||
16 | /** | ||
17 | * Move a row up or down. | ||
18 | * | ||
19 | * @param pos Element Node to move. | ||
20 | * @param move int Move: +1 (down) or -1 (up) | ||
21 | */ | ||
22 | function changeOrder(pos, move) | ||
23 | { | ||
24 | var newpos = parseInt(pos) + move; | ||
25 | var line = document.querySelector('[data-order="'+ pos +'"]'); | ||
26 | var changeline = document.querySelector('[data-order="'+ newpos +'"]'); | ||
27 | var parent = changeline.parentNode; | ||
28 | |||
29 | changePos(line, newpos); | ||
30 | changePos(changeline, parseInt(pos)); | ||
31 | var changeItem = move < 0 ? changeline : changeline.nextSibling; | ||
32 | parent.insertBefore(line, changeItem); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Move a row up in the table. | ||
37 | * | ||
38 | * @param pos int row counter. | ||
39 | * | ||
40 | * @returns false | ||
41 | */ | ||
42 | function orderUp(pos) | ||
43 | { | ||
44 | if (pos == 0) { | ||
45 | return false; | ||
46 | } | ||
47 | changeOrder(pos, -1); | ||
48 | return false; | ||
49 | } | ||
50 | |||
51 | /** | ||
52 | * Move a row down in the table. | ||
53 | * | ||
54 | * @param pos int row counter. | ||
55 | * | ||
56 | * @returns false | ||
57 | */ | ||
58 | function orderDown(pos) | ||
59 | { | ||
60 | var lastpos = document.querySelector('[data-order]:last-child').getAttribute('data-order'); | ||
61 | if (pos == lastpos) { | ||
62 | return false; | ||
63 | } | ||
64 | |||
65 | changeOrder(pos, +1); | ||
66 | return false; | ||
67 | } | ||
diff --git a/inc/shaarli.css b/inc/shaarli.css index 96e2cae1..f137555e 100644 --- a/inc/shaarli.css +++ b/inc/shaarli.css | |||
@@ -1103,6 +1103,66 @@ ul.errors { | |||
1103 | float: left; | 1103 | float: left; |
1104 | } | 1104 | } |
1105 | 1105 | ||
1106 | #pluginsadmin { | ||
1107 | width: 80%; | ||
1108 | padding: 20px 0 0 20px; | ||
1109 | } | ||
1110 | |||
1111 | #pluginsadmin section { | ||
1112 | padding: 20px 0; | ||
1113 | } | ||
1114 | |||
1115 | #pluginsadmin .plugin_parameters { | ||
1116 | margin: 10px 0; | ||
1117 | } | ||
1118 | |||
1119 | #pluginsadmin h1 { | ||
1120 | font-style: normal; | ||
1121 | } | ||
1122 | |||
1123 | #pluginsadmin h2 { | ||
1124 | font-size: 1.4em; | ||
1125 | font-weight: bold; | ||
1126 | } | ||
1127 | |||
1128 | #pluginsadmin table { | ||
1129 | width: 100%; | ||
1130 | } | ||
1131 | |||
1132 | #pluginsadmin table, #pluginsadmin th, #pluginsadmin td { | ||
1133 | border-width: 1px 0; | ||
1134 | border-style: solid; | ||
1135 | border-color: #c0c0c0; | ||
1136 | } | ||
1137 | |||
1138 | #pluginsadmin table th { | ||
1139 | font-weight: bold; | ||
1140 | padding: 10px 0; | ||
1141 | } | ||
1142 | |||
1143 | #pluginsadmin table td { | ||
1144 | padding: 5px 0; | ||
1145 | } | ||
1146 | |||
1147 | #pluginsadmin input[type=submit] { | ||
1148 | margin: 10px 0; | ||
1149 | } | ||
1150 | |||
1151 | #pluginsadmin .plugin_parameter { | ||
1152 | padding: 5px 0; | ||
1153 | border-width: 1px 0; | ||
1154 | border-style: solid; | ||
1155 | border-color: #c0c0c0; | ||
1156 | } | ||
1157 | |||
1158 | #pluginsadmin .float_label { | ||
1159 | float: left; | ||
1160 | width: 20%; | ||
1161 | } | ||
1162 | |||
1163 | #pluginsadmin a { | ||
1164 | color: black; | ||
1165 | } | ||
1106 | /* 404 page */ | 1166 | /* 404 page */ |
1107 | .error-container { | 1167 | .error-container { |
1108 | 1168 | ||
@@ -1770,6 +1770,54 @@ HTML; | |||
1770 | exit; | 1770 | exit; |
1771 | } | 1771 | } |
1772 | 1772 | ||
1773 | // Plugin administration page | ||
1774 | if ($targetPage == Router::$PAGE_PLUGINSADMIN) { | ||
1775 | $pluginMeta = $pluginManager->getPluginsMeta(); | ||
1776 | |||
1777 | // Split plugins into 2 arrays: ordered enabled plugins and disabled. | ||
1778 | $enabledPlugins = array_filter($pluginMeta, function($v) { return $v['order'] !== false; }); | ||
1779 | // Load parameters. | ||
1780 | $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $GLOBALS['plugins']); | ||
1781 | uasort( | ||
1782 | $enabledPlugins, | ||
1783 | function($a, $b) { return $a['order'] - $b['order']; } | ||
1784 | ); | ||
1785 | $disabledPlugins = array_filter($pluginMeta, function($v) { return $v['order'] === false; }); | ||
1786 | |||
1787 | $PAGE->assign('enabledPlugins', $enabledPlugins); | ||
1788 | $PAGE->assign('disabledPlugins', $disabledPlugins); | ||
1789 | $PAGE->renderPage('pluginsadmin'); | ||
1790 | exit; | ||
1791 | } | ||
1792 | |||
1793 | // Plugin administration form action | ||
1794 | if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) { | ||
1795 | try { | ||
1796 | if (isset($_POST['parameters_form'])) { | ||
1797 | unset($_POST['parameters_form']); | ||
1798 | foreach ($_POST as $param => $value) { | ||
1799 | $GLOBALS['plugins'][$param] = escape($value); | ||
1800 | } | ||
1801 | } | ||
1802 | else { | ||
1803 | $GLOBALS['config']['ENABLED_PLUGINS'] = save_plugin_config($_POST); | ||
1804 | } | ||
1805 | writeConfig($GLOBALS, isLoggedIn()); | ||
1806 | } | ||
1807 | catch (Exception $e) { | ||
1808 | error_log( | ||
1809 | 'ERROR while saving plugin configuration:.' . PHP_EOL . | ||
1810 | $e->getMessage() | ||
1811 | ); | ||
1812 | |||
1813 | // TODO: do not handle exceptions/errors in JS. | ||
1814 | echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=pluginsadmin\';</script>'; | ||
1815 | exit; | ||
1816 | } | ||
1817 | header('Location: ?do='. Router::$PAGE_PLUGINSADMIN); | ||
1818 | exit; | ||
1819 | } | ||
1820 | |||
1773 | // -------- Otherwise, simply display search form and links: | 1821 | // -------- Otherwise, simply display search form and links: |
1774 | showLinkList($PAGE, $LINKSDB); | 1822 | showLinkList($PAGE, $LINKSDB); |
1775 | exit; | 1823 | exit; |
diff --git a/plugins/addlink_toolbar/addlink_toolbar.meta b/plugins/addlink_toolbar/addlink_toolbar.meta new file mode 100644 index 00000000..2f0b5866 --- /dev/null +++ b/plugins/addlink_toolbar/addlink_toolbar.meta | |||
@@ -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 index 00000000..8b5703e1 --- /dev/null +++ b/plugins/archiveorg/archiveorg.meta | |||
@@ -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 index 00000000..b063ecb7 --- /dev/null +++ b/plugins/demo_plugin/demo_plugin.meta | |||
@@ -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 index 00000000..c2b0908e --- /dev/null +++ b/plugins/playvideos/playvideos.meta | |||
@@ -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 index 00000000..cbf371ea --- /dev/null +++ b/plugins/qrcode/qrcode.meta | |||
@@ -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 index 00000000..bd611dd0 --- /dev/null +++ b/plugins/readityourself/readityourself.meta | |||
@@ -0,0 +1,2 @@ | |||
1 | description="For each link, add a ReadItYourself icon to save the shaared URL." | ||
2 | parameters=READITYOUSELF_URL; \ No newline at end of file | ||
diff --git a/plugins/readityourself/readityourself.php b/plugins/readityourself/readityourself.php index 1b030bc8..c8df4c4f 100644 --- a/plugins/readityourself/readityourself.php +++ b/plugins/readityourself/readityourself.php | |||
@@ -13,7 +13,7 @@ if (is_file(PluginManager::$PLUGINS_PATH . '/readityourself/config.php')) { | |||
13 | include PluginManager::$PLUGINS_PATH . '/readityourself/config.php'; | 13 | include PluginManager::$PLUGINS_PATH . '/readityourself/config.php'; |
14 | } | 14 | } |
15 | 15 | ||
16 | if (!isset($GLOBALS['plugins']['READITYOUSELF_URL'])) { | 16 | if (empty($GLOBALS['plugins']['READITYOUSELF_URL'])) { |
17 | $GLOBALS['plugin_errors'][] = 'Readityourself plugin error: '. | 17 | $GLOBALS['plugin_errors'][] = 'Readityourself plugin error: '. |
18 | 'Please define "$GLOBALS[\'plugins\'][\'READITYOUSELF_URL\']" '. | 18 | 'Please define "$GLOBALS[\'plugins\'][\'READITYOUSELF_URL\']" '. |
19 | 'in "plugins/readityourself/config.php" or in your Shaarli config.php file.'; | 19 | '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 index 00000000..8763c4a2 --- /dev/null +++ b/plugins/wallabag/wallabag.meta | |||
@@ -0,0 +1,2 @@ | |||
1 | description="For each link, add a Wallabag icon to save it in your instance." | ||
2 | parameters="WALLABAG_URL" \ No newline at end of file | ||
diff --git a/plugins/wallabag/wallabag.php b/plugins/wallabag/wallabag.php index e3c399a9..0d6fc66d 100644 --- a/plugins/wallabag/wallabag.php +++ b/plugins/wallabag/wallabag.php | |||
@@ -11,7 +11,7 @@ if (is_file(PluginManager::$PLUGINS_PATH . '/wallabag/config.php')) { | |||
11 | include PluginManager::$PLUGINS_PATH . '/wallabag/config.php'; | 11 | include PluginManager::$PLUGINS_PATH . '/wallabag/config.php'; |
12 | } | 12 | } |
13 | 13 | ||
14 | if (!isset($GLOBALS['plugins']['WALLABAG_URL'])) { | 14 | if (empty($GLOBALS['plugins']['WALLABAG_URL'])) { |
15 | $GLOBALS['plugin_errors'][] = 'Wallabag plugin error: '. | 15 | $GLOBALS['plugin_errors'][] = 'Wallabag plugin error: '. |
16 | 'Please define "$GLOBALS[\'plugins\'][\'WALLABAG_URL\']" '. | 16 | 'Please define "$GLOBALS[\'plugins\'][\'WALLABAG_URL\']" '. |
17 | 'in "plugins/wallabag/config.php" or in your Shaarli config.php file.'; | 17 | 'in "plugins/wallabag/config.php" or in your Shaarli config.php file.'; |
diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index adebfcc3..492ddd3b 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php | |||
@@ -174,4 +174,113 @@ class ConfigTest extends PHPUnit_Framework_TestCase | |||
174 | include self::$configFields['config']['CONFIG_FILE']; | 174 | include self::$configFields['config']['CONFIG_FILE']; |
175 | $this->assertEquals(self::$configFields['login'], $GLOBALS['login']); | 175 | $this->assertEquals(self::$configFields['login'], $GLOBALS['login']); |
176 | } | 176 | } |
177 | |||
178 | /** | ||
179 | * Test save_plugin_config with valid data. | ||
180 | * | ||
181 | * @throws PluginConfigOrderException | ||
182 | */ | ||
183 | public function testSavePluginConfigValid() | ||
184 | { | ||
185 | $data = array( | ||
186 | 'order_plugin1' => 2, // no plugin related | ||
187 | 'plugin2' => 0, // new - at the end | ||
188 | 'plugin3' => 0, // 2nd | ||
189 | 'order_plugin3' => 8, | ||
190 | 'plugin4' => 0, // 1st | ||
191 | 'order_plugin4' => 5, | ||
192 | ); | ||
193 | |||
194 | $expected = array( | ||
195 | 'plugin3', | ||
196 | 'plugin4', | ||
197 | 'plugin2', | ||
198 | ); | ||
199 | |||
200 | $out = save_plugin_config($data); | ||
201 | $this->assertEquals($expected, $out); | ||
202 | } | ||
203 | |||
204 | /** | ||
205 | * Test save_plugin_config with invalid data. | ||
206 | * | ||
207 | * @expectedException PluginConfigOrderException | ||
208 | */ | ||
209 | public function testSavePluginConfigInvalid() | ||
210 | { | ||
211 | $data = array( | ||
212 | 'plugin2' => 0, | ||
213 | 'plugin3' => 0, | ||
214 | 'order_plugin3' => 0, | ||
215 | 'plugin4' => 0, | ||
216 | 'order_plugin4' => 0, | ||
217 | ); | ||
218 | |||
219 | save_plugin_config($data); | ||
220 | } | ||
221 | |||
222 | /** | ||
223 | * Test save_plugin_config without data. | ||
224 | */ | ||
225 | public function testSavePluginConfigEmpty() | ||
226 | { | ||
227 | $this->assertEquals(array(), save_plugin_config(array())); | ||
228 | } | ||
229 | |||
230 | /** | ||
231 | * Test validate_plugin_order with valid data. | ||
232 | */ | ||
233 | public function testValidatePluginOrderValid() | ||
234 | { | ||
235 | $data = array( | ||
236 | 'order_plugin1' => 2, | ||
237 | 'plugin2' => 0, | ||
238 | 'plugin3' => 0, | ||
239 | 'order_plugin3' => 1, | ||
240 | 'plugin4' => 0, | ||
241 | 'order_plugin4' => 5, | ||
242 | ); | ||
243 | |||
244 | $this->assertTrue(validate_plugin_order($data)); | ||
245 | } | ||
246 | |||
247 | /** | ||
248 | * Test validate_plugin_order with invalid data. | ||
249 | */ | ||
250 | public function testValidatePluginOrderInvalid() | ||
251 | { | ||
252 | $data = array( | ||
253 | 'order_plugin1' => 2, | ||
254 | 'order_plugin3' => 1, | ||
255 | 'order_plugin4' => 1, | ||
256 | ); | ||
257 | |||
258 | $this->assertFalse(validate_plugin_order($data)); | ||
259 | } | ||
260 | |||
261 | /** | ||
262 | * Test load_plugin_parameter_values. | ||
263 | */ | ||
264 | public function testLoadPluginParameterValues() | ||
265 | { | ||
266 | $plugins = array( | ||
267 | 'plugin_name' => array( | ||
268 | 'parameters' => array( | ||
269 | 'param1' => true, | ||
270 | 'param2' => false, | ||
271 | 'param3' => '', | ||
272 | ) | ||
273 | ) | ||
274 | ); | ||
275 | |||
276 | $parameters = array( | ||
277 | 'param1' => 'value1', | ||
278 | 'param2' => 'value2', | ||
279 | ); | ||
280 | |||
281 | $result = load_plugin_parameter_values($plugins, $parameters); | ||
282 | $this->assertEquals('value1', $result['plugin_name']['parameters']['param1']); | ||
283 | $this->assertEquals('value2', $result['plugin_name']['parameters']['param2']); | ||
284 | $this->assertEquals('', $result['plugin_name']['parameters']['param3']); | ||
285 | } | ||
177 | } | 286 | } |
diff --git a/tests/PluginManagerTest.php b/tests/PluginManagerTest.php index df2614b5..348082c7 100644 --- a/tests/PluginManagerTest.php +++ b/tests/PluginManagerTest.php | |||
@@ -63,4 +63,23 @@ class PluginManagerTest extends PHPUnit_Framework_TestCase | |||
63 | 63 | ||
64 | $pluginManager->load(array('nope', 'renope')); | 64 | $pluginManager->load(array('nope', 'renope')); |
65 | } | 65 | } |
66 | |||
67 | /** | ||
68 | * Test plugin metadata loading. | ||
69 | */ | ||
70 | public function testGetPluginsMeta() | ||
71 | { | ||
72 | $pluginManager = PluginManager::getInstance(); | ||
73 | |||
74 | PluginManager::$PLUGINS_PATH = self::$pluginPath; | ||
75 | $pluginManager->load(array(self::$pluginName)); | ||
76 | |||
77 | $expectedParameters = array( | ||
78 | 'pop' => '', | ||
79 | 'hip' => '', | ||
80 | ); | ||
81 | $meta = $pluginManager->getPluginsMeta(); | ||
82 | $this->assertEquals('test plugin', $meta[self::$pluginName]['description']); | ||
83 | $this->assertEquals($expectedParameters, $meta[self::$pluginName]['parameters']); | ||
84 | } | ||
66 | } \ No newline at end of file | 85 | } \ No newline at end of file |
diff --git a/tests/plugins/test/test.meta b/tests/plugins/test/test.meta new file mode 100644 index 00000000..ab999ed4 --- /dev/null +++ b/tests/plugins/test/test.meta | |||
@@ -0,0 +1,2 @@ | |||
1 | description="test plugin" | ||
2 | parameters="pop;hip" \ No newline at end of file | ||
diff --git a/tpl/pluginsadmin.html b/tpl/pluginsadmin.html new file mode 100644 index 00000000..4f7d091e --- /dev/null +++ b/tpl/pluginsadmin.html | |||
@@ -0,0 +1,131 @@ | |||
1 | <!DOCTYPE html> | ||
2 | <html> | ||
3 | <head>{include="includes"}</head> | ||
4 | <body> | ||
5 | <div id="pageheader"> | ||
6 | {include="page.header"} | ||
7 | </div> | ||
8 | |||
9 | <noscript> | ||
10 | <div> | ||
11 | <ul class="errors"> | ||
12 | <li>You need to enable Javascript to change plugin loading order.</li> | ||
13 | </ul> | ||
14 | </div> | ||
15 | <div class="clear"></div> | ||
16 | </noscript> | ||
17 | |||
18 | <div id="pluginsadmin"> | ||
19 | <form action="?do=save_pluginadmin" method="POST"> | ||
20 | <section id="enabled_plugins"> | ||
21 | <h1>Enabled Plugins</h1> | ||
22 | |||
23 | <div> | ||
24 | {if="count($enabledPlugins)==0"} | ||
25 | <p>No plugin enabled.</p> | ||
26 | {else} | ||
27 | <table id="plugin_table"> | ||
28 | <thead> | ||
29 | <tr> | ||
30 | <th class="center">Disable</th> | ||
31 | <th class="center">Order</th> | ||
32 | <th>Name</th> | ||
33 | <th>Description</th> | ||
34 | </tr> | ||
35 | </thead> | ||
36 | <tbody> | ||
37 | {loop="$enabledPlugins"} | ||
38 | <tr data-line="{$key}" data-order="{$counter}"> | ||
39 | <td class="center"><input type="checkbox" name="{$key}" checked="checked"></td> | ||
40 | <td class="center"> | ||
41 | <a href="#" | ||
42 | onclick="return orderUp(this.parentNode.parentNode.getAttribute('data-order'));"> | ||
43 | ▲ | ||
44 | </a> | ||
45 | <a href="#" | ||
46 | onclick="return orderDown(this.parentNode.parentNode.getAttribute('data-order'));"> | ||
47 | ▼ | ||
48 | </a> | ||
49 | <input type="hidden" name="order_{$key}" value="{$counter}"> | ||
50 | </td> | ||
51 | <td>{$key}</td> | ||
52 | <td>{$value.description}</td> | ||
53 | </tr> | ||
54 | {/loop} | ||
55 | </tbody> | ||
56 | </table> | ||
57 | {/if} | ||
58 | </div> | ||
59 | </section> | ||
60 | |||
61 | <section id="disabled_plugins"> | ||
62 | <h1>Disabled Plugins</h1> | ||
63 | |||
64 | <div> | ||
65 | {if="count($disabledPlugins)==0"} | ||
66 | <p>No plugin disabled.</p> | ||
67 | {else} | ||
68 | <table> | ||
69 | <tr> | ||
70 | <th class="center">Enable</th> | ||
71 | <th>Name</th> | ||
72 | <th>Description</th> | ||
73 | </tr> | ||
74 | {loop="$disabledPlugins"} | ||
75 | <tr> | ||
76 | <td class="center"><input type="checkbox" name="{$key}"></td> | ||
77 | <td>{$key}</td> | ||
78 | <td>{$value.description}</td> | ||
79 | </tr> | ||
80 | {/loop} | ||
81 | </table> | ||
82 | {/if} | ||
83 | </div> | ||
84 | |||
85 | <div class="center"> | ||
86 | <input type="submit" value="Save"/> | ||
87 | </div> | ||
88 | </section> | ||
89 | </form> | ||
90 | |||
91 | <form action="?do=save_pluginadmin" method="POST"> | ||
92 | <section id="plugin_parameters"> | ||
93 | <h1>Enabled Plugin Parameters</h1> | ||
94 | |||
95 | <div> | ||
96 | {if="count($enabledPlugins)==0"} | ||
97 | <p>No plugin enabled.</p> | ||
98 | {else} | ||
99 | {loop="$enabledPlugins"} | ||
100 | {if="count($value.parameters) > 0"} | ||
101 | <div class="plugin_parameters"> | ||
102 | <h2>{$key}</h2> | ||
103 | {loop="$value.parameters"} | ||
104 | <div class="plugin_parameter"> | ||
105 | <div class="float_label"> | ||
106 | <label for="{$key}"> | ||
107 | <code>{$key}</code> | ||
108 | </label> | ||
109 | </div> | ||
110 | <div class="float_input"> | ||
111 | <input name="{$key}" value="{$value}" id="{$key}"/> | ||
112 | </div> | ||
113 | </div> | ||
114 | {/loop} | ||
115 | </div> | ||
116 | {/if} | ||
117 | {/loop} | ||
118 | {/if} | ||
119 | <div class="center"> | ||
120 | <input type="submit" name="parameters_form" value="Save"/> | ||
121 | </div> | ||
122 | </div> | ||
123 | </section> | ||
124 | </form> | ||
125 | |||
126 | </div> | ||
127 | {include="page.footer"} | ||
128 | |||
129 | <script src="inc/plugin_admin.js#"></script> | ||
130 | </body> | ||
131 | </html> \ No newline at end of file | ||
diff --git a/tpl/tools.html b/tpl/tools.html index c13f4f16..78b81663 100644 --- a/tpl/tools.html +++ b/tpl/tools.html | |||
@@ -5,11 +5,18 @@ | |||
5 | <div id="pageheader"> | 5 | <div id="pageheader"> |
6 | {include="page.header"} | 6 | {include="page.header"} |
7 | <div id="toolsdiv"> | 7 | <div id="toolsdiv"> |
8 | {if="!$GLOBALS['config']['OPEN_SHAARLI']"}<a href="?do=changepasswd"><b>Change password</b> <span>: Change your password.</span></a><br><br>{/if} | 8 | <a href="?do=configure"><b>Configure your Shaarli</b><span>: Change Title, timezone...</span></a> |
9 | <a href="?do=configure"><b>Configure your Shaarli</b> <span>: Change Title, timezone...</span></a><br><br> | 9 | <br><br> |
10 | <a href="?do=changetag"><b>Rename/delete tags</b> <span>: Rename or delete a tag in all links</span></a><br><br> | 10 | <a href="?do=pluginadmin"><b>Plugin administration</b><span>: Enable, disable and configure plugins.</span></a> |
11 | <a href="?do=import"><b>Import</b> <span>: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)</span></a> <br><br> | 11 | <br><br> |
12 | <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> | 12 | {if="!$GLOBALS['config']['OPEN_SHAARLI']"}<a href="?do=changepasswd"><b>Change password</b><span>: Change your password.</span></a> |
13 | <br><br>{/if} | ||
14 | <a href="?do=changetag"><b>Rename/delete tags</b><span>: Rename or delete a tag in all links</span></a> | ||
15 | <br><br> | ||
16 | <a href="?do=import"><b>Import</b><span>: Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)</span></a> | ||
17 | <br><br> | ||
18 | <a href="?do=export"><b>Export</b><span>: Export Netscape html bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)</span></a> | ||
19 | <br><br> | ||
13 | <a class="smallbutton" | 20 | <a class="smallbutton" |
14 | onclick="return alertBookmarklet();" | 21 | onclick="return alertBookmarklet();" |
15 | href="javascript:( | 22 | href="javascript:( |