Commit | Line | Data |
---|---|---|
fae91170 | 1 | <?php |
2 | // This file is part of Moodle - http://moodle.org/ | |
3 | // | |
4 | // Moodle is free software: you can redistribute it and/or modify | |
5 | // it under the terms of the GNU General Public License as published by | |
6 | // the Free Software Foundation, either version 3 of the License, or | |
7 | // (at your option) any later version. | |
8 | // | |
9 | // Moodle is distributed in the hope that it will be useful, | |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | // GNU General Public License for more details. | |
13 | // | |
14 | // You should have received a copy of the GNU General Public License | |
15 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
17 | defined('MOODLE_INTERNAL') || die(); | |
18 | ||
19 | /** | |
20 | * TinyMCE text editor plugin base class. | |
21 | * | |
22 | * This is a base class for TinyMCE plugins implemented within Moodle. These | |
23 | * plugins can optionally provide new buttons/plugins within TinyMCE itself, | |
24 | * or configure the TinyMCE options. | |
25 | * | |
26 | * As well as overridable functions, other utility functions in this class | |
27 | * can be used when writing the plugins. | |
28 | * | |
29 | * Finally, a static function in this class is used to call into all the | |
30 | * plugins when required. | |
31 | * | |
32 | * @package editor_tinymce | |
33 | * @copyright 2012 The Open University | |
34 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
35 | */ | |
36 | abstract class editor_tinymce_plugin { | |
37 | /** @var string Plugin folder */ | |
38 | protected $plugin; | |
39 | ||
116ad39b PS |
40 | /** @var array Plugin settings */ |
41 | protected $config = null; | |
42 | ||
0b785822 PS |
43 | /** @var array list of buttons defined by this plugin */ |
44 | protected $buttons = array(); | |
45 | ||
fae91170 | 46 | /** |
47 | * @param string $plugin Name of folder | |
48 | */ | |
49 | public function __construct($plugin) { | |
50 | $this->plugin = $plugin; | |
51 | } | |
52 | ||
0b785822 PS |
53 | /** |
54 | * Returns list of buttons defined by this plugin. | |
55 | * useful mostly as information when setting custom toolbar. | |
56 | * | |
57 | * @return array | |
58 | */ | |
59 | public function get_buttons() { | |
60 | return $this->buttons; | |
61 | } | |
116ad39b PS |
62 | /** |
63 | * Makes sure config is loaded and cached. | |
64 | * @return void | |
65 | */ | |
66 | protected function load_config() { | |
67 | if (!isset($this->config)) { | |
68 | $name = $this->get_name(); | |
69 | $this->config = get_config("tinymce_$name"); | |
70 | } | |
71 | } | |
72 | ||
73 | /** | |
74 | * Returns plugin config value. | |
75 | * @param string $name | |
76 | * @param string $default value if config does not exist yet | |
77 | * @return string value or default | |
78 | */ | |
79 | public function get_config($name, $default = null) { | |
80 | $this->load_config(); | |
81 | return isset($this->config->$name) ? $this->config->$name : $default; | |
82 | } | |
83 | ||
84 | /** | |
85 | * Sets plugin config value. | |
86 | * @param string $name name of config | |
87 | * @param string $value string config value, null means delete | |
88 | * @return string value | |
89 | */ | |
90 | public function set_config($name, $value) { | |
91 | $pluginname = $this->get_name(); | |
92 | $this->load_config(); | |
93 | if ($value === null) { | |
94 | unset($this->config->$name); | |
95 | } else { | |
96 | $this->config->$name = $value; | |
97 | } | |
98 | set_config($name, $value, "tinymce_$pluginname"); | |
99 | } | |
100 | ||
101 | /** | |
102 | * Returns name of this tinymce plugin. | |
103 | * @return string | |
104 | */ | |
105 | public function get_name() { | |
106 | // All class names start with "tinymce_". | |
107 | $words = explode('_', get_class($this), 2); | |
108 | return $words[1]; | |
109 | } | |
110 | ||
fae91170 | 111 | /** |
112 | * Adjusts TinyMCE init parameters for this plugin. | |
113 | * | |
114 | * Subclasses must implement this function in order to carry out changes | |
115 | * to the TinyMCE settings. | |
116 | * | |
117 | * @param array $params TinyMCE init parameters array | |
118 | * @param context $context Context where editor is being shown | |
119 | * @param array $options Options for this editor | |
120 | */ | |
121 | protected abstract function update_init_params(array &$params, context $context, | |
122 | array $options = null); | |
123 | ||
124 | /** | |
125 | * Gets the order in which to run this plugin. Order usually only matters if | |
126 | * (a) the place you add your button might depend on another plugin, or | |
127 | * (b) you want to make some changes to layout etc. that should happen last. | |
128 | * The default order is 100; within that, plugins are sorted alphabetically. | |
129 | * Return a lower number if you want this plugin to run earlier, or a higher | |
130 | * number if you want it to run later. | |
131 | */ | |
132 | protected function get_sort_order() { | |
133 | return 100; | |
134 | } | |
135 | ||
136 | /** | |
137 | * Adds a button to the editor, after another button (or at the end). | |
138 | * | |
139 | * Specify the location of this button using the $after variable. If you | |
140 | * leave this blank, the button will be added at the end. | |
141 | * | |
142 | * If you want to try different possible locations depending on existing | |
143 | * plugins you can set $alwaysadd to false and check the return value | |
144 | * to see if it succeeded. | |
145 | * | |
146 | * @param array $params TinyMCE init parameters array | |
147 | * @param int $row Row to add button to (1 to 3) | |
148 | * @param string $button Identifier of button/plugin | |
149 | * @param string $after Adds button directly after the named plugin | |
150 | * @param bool $alwaysadd If specified $after string not found, add at end | |
151 | * @return bool True if added | |
152 | */ | |
153 | protected function add_button_after(array &$params, $row, $button, | |
154 | $after = '', $alwaysadd = true) { | |
155 | $this->check_row($row); | |
156 | ||
157 | $field = 'theme_advanced_buttons' . $row; | |
158 | $old = $params[$field]; | |
159 | ||
160 | // Empty = add at end. | |
161 | if ($after === '') { | |
162 | $params[$field] = $old . ',' . $button; | |
163 | return true; | |
164 | } | |
165 | ||
166 | // Try to add after given plugin. | |
167 | $params[$field] = preg_replace('~(,|^)(' . preg_quote($after) . ')(,|$)~', | |
168 | '$1$2,' . $button . '$3', $old); | |
169 | if ($params[$field] !== $old) { | |
170 | return true; | |
171 | } | |
172 | ||
173 | // If always adding, recurse to add it empty. | |
174 | if ($alwaysadd) { | |
175 | return $this->add_button_after($params, $row, $button); | |
176 | } | |
177 | ||
178 | // Otherwise return false (failed to add). | |
179 | return false; | |
180 | } | |
181 | ||
182 | /** | |
183 | * Adds a button to the editor. | |
184 | * | |
185 | * Specify the location of this button using the $before variable. If you | |
186 | * leave this blank, the button will be added at the start. | |
187 | * | |
188 | * If you want to try different possible locations depending on existing | |
189 | * plugins you can set $alwaysadd to false and check the return value | |
190 | * to see if it succeeded. | |
191 | * | |
192 | * @param array $params TinyMCE init parameters array | |
193 | * @param int $row Row to add button to (1 to 3) | |
194 | * @param string $button Identifier of button/plugin | |
195 | * @param string $before Adds button directly before the named plugin | |
196 | * @param bool $alwaysadd If specified $after string not found, add at start | |
197 | * @return bool True if added | |
198 | */ | |
199 | protected function add_button_before(array &$params, $row, $button, | |
200 | $before = '', $alwaysadd = true) { | |
201 | $this->check_row($row); | |
202 | ||
203 | $field = 'theme_advanced_buttons' . $row; | |
204 | $old = $params[$field]; | |
205 | ||
206 | // Empty = add at start. | |
207 | if ($before === '') { | |
208 | $params[$field] = $button . ',' . $old; | |
209 | return true; | |
210 | } | |
211 | ||
212 | // Try to add after given plugin. | |
213 | $params[$field] = preg_replace('~(,|^)(' . preg_quote($before) . ')(,|$)~', | |
214 | '$1' . $button . ',$2$3', $old); | |
215 | if ($params[$field] !== $old) { | |
216 | return true; | |
217 | } | |
218 | ||
219 | // If always adding, recurse to add it empty. | |
220 | if ($alwaysadd) { | |
221 | return $this->add_button_before($params, $row, $button); | |
222 | } | |
223 | ||
224 | // Otherwise return false (failed to add). | |
225 | return false; | |
226 | } | |
227 | ||
228 | /** | |
229 | * Checks the row value is valid. | |
230 | * | |
231 | * @param int $row Row to add button to (1 to 3) | |
232 | * @throws coding_exception If row value is outside the range 1-3 | |
233 | */ | |
234 | private function check_row($row) { | |
235 | if ($row < 1 || $row > 3) { | |
236 | throw new coding_exception("Invalid row option: $row"); | |
237 | } | |
238 | } | |
239 | ||
240 | /** | |
241 | * Adds a JavaScript plugin into TinyMCE. Note that adding a plugin does | |
242 | * not by itself add a button; you must do both. | |
243 | * | |
244 | * If you leave $pluginname blank (default) it uses the folder name. | |
245 | * | |
246 | * @param array $params TinyMCE init parameters array | |
247 | * @param string $pluginname Identifier for plugin within TinyMCE | |
248 | * @param string $jsfile Name of JS file (within plugin 'tinymce' directory) | |
249 | */ | |
250 | protected function add_js_plugin(&$params, $pluginname='', $jsfile='editor_plugin.js') { | |
251 | global $CFG; | |
252 | ||
253 | // Set default plugin name. | |
254 | if ($pluginname === '') { | |
255 | $pluginname = $this->plugin; | |
256 | } | |
257 | ||
258 | // Add plugin to list in params, so it doesn't try to load it again. | |
259 | $params['plugins'] .= ',-' . $pluginname; | |
260 | ||
261 | // Add special param that causes Moodle TinyMCE init to load the plugin. | |
262 | if (!isset($params['moodle_init_plugins'])) { | |
263 | $params['moodle_init_plugins'] = ''; | |
264 | } else { | |
265 | $params['moodle_init_plugins'] .= ','; | |
266 | } | |
267 | ||
268 | // Get URL of main JS file and store in params. | |
269 | $jsurl = $this->get_tinymce_file_url($jsfile, false); | |
270 | $params['moodle_init_plugins'] .= $pluginname . ':' . $jsurl; | |
271 | } | |
272 | ||
273 | /** | |
274 | * Returns URL to files in the TinyMCE folder within this plugin, suitable | |
275 | * for client-side use such as loading JavaScript files. (This URL normally | |
276 | * goes through loader.php and contains the plugin version to ensure | |
277 | * correct and long-term cacheing.) | |
278 | * | |
279 | * @param string $file Filename or path within the folder | |
280 | * @param bool $absolute Set false to get relative URL from plugins folder | |
281 | */ | |
282 | public function get_tinymce_file_url($file='', $absolute=true) { | |
283 | global $CFG; | |
284 | ||
285 | // Version number comes from plugin version.php, except in developer | |
286 | // mode where the special string 'dev' is used (prevents cacheing and | |
287 | // serves unminified JS). | |
288 | if (debugging('', DEBUG_DEVELOPER)) { | |
289 | $version = '-1'; | |
290 | } else { | |
291 | $version = $this->get_version(); | |
292 | } | |
293 | ||
294 | // Calculate the JS url (relative to the TinyMCE plugins folder - using | |
295 | // relative URL saves a few bytes in each HTML page). | |
296 | if ($CFG->slasharguments) { | |
297 | // URL is usually from loader.php... | |
298 | $jsurl = 'loader.php/' . $this->plugin . '/' . $version . '/' . $file; | |
299 | } else { | |
300 | // ...except when slash arguments are turned off it serves direct. | |
301 | // In this situation there is no version details and it is up to | |
302 | // the browser and server to negotiate cacheing, which will mean | |
303 | // requesting the JS files frequently (reduced performance). | |
304 | $jsurl = $this->plugin . '/tinymce/' . $file; | |
305 | } | |
306 | ||
307 | if ($absolute) { | |
308 | $jsurl = $CFG->wwwroot . '/lib/editor/tinymce/plugins/' . $jsurl; | |
309 | } | |
310 | ||
311 | return $jsurl; | |
312 | } | |
313 | ||
314 | /** | |
315 | * Obtains version number from version.php for this plugin. | |
316 | * | |
317 | * @return string Version number | |
318 | */ | |
319 | protected function get_version() { | |
320 | global $CFG; | |
321 | ||
322 | $plugin = new stdClass; | |
323 | require($CFG->dirroot . '/lib/editor/tinymce/plugins/' . $this->plugin . '/version.php'); | |
324 | return $plugin->version; | |
325 | } | |
326 | ||
327 | /** | |
328 | * Calls all available plugins to adjust the TinyMCE init parameters. | |
329 | * | |
330 | * @param array $params TinyMCE init parameters array | |
331 | * @param context $context Context where editor is being shown | |
332 | * @param array $options Options for this editor | |
333 | */ | |
334 | public static function all_update_init_params(array &$params, | |
335 | context $context, array $options = null) { | |
336 | global $CFG; | |
337 | ||
338 | // Get list of plugin directories. | |
339 | $plugins = get_plugin_list('tinymce'); | |
340 | ||
0b785822 PS |
341 | // Get list of disabled subplugins. |
342 | $disabled = array(); | |
343 | if ($params['moodle_config']->disabledsubplugins) { | |
344 | foreach (explode(',', $params['moodle_config']->disabledsubplugins) as $sp) { | |
345 | $sp = trim($sp); | |
346 | if ($sp !== '') { | |
347 | $disabled[$sp] = $sp; | |
348 | } | |
349 | } | |
350 | } | |
351 | ||
fae91170 | 352 | // Construct all the plugins. |
353 | $pluginobjects = array(); | |
354 | foreach ($plugins as $plugin => $dir) { | |
0b785822 PS |
355 | if (isset($disabled[$plugin])) { |
356 | continue; | |
357 | } | |
fae91170 | 358 | require_once($dir . '/lib.php'); |
359 | $classname = 'tinymce_' . $plugin; | |
360 | $pluginobjects[] = new $classname($plugin); | |
361 | } | |
362 | ||
363 | // Sort plugins by sort order and name. | |
364 | usort($pluginobjects, array('editor_tinymce_plugin', 'compare_plugins')); | |
365 | ||
366 | // Run the function for each plugin. | |
367 | foreach ($pluginobjects as $obj) { | |
368 | $obj->update_init_params($params, $context, $options); | |
369 | } | |
370 | } | |
371 | ||
372 | /** | |
373 | * Gets a named plugin object. Will cause fatal error if plugin doesn't exist. | |
374 | * | |
375 | * @param string $plugin Name of plugin e.g. 'moodleemoticon' | |
376 | * @return editor_tinymce_plugin Plugin object | |
377 | */ | |
378 | public static function get($plugin) { | |
379 | $dir = get_component_directory('tinymce_' . $plugin); | |
380 | require_once($dir . '/lib.php'); | |
381 | $classname = 'tinymce_' . $plugin; | |
382 | return new $classname($plugin); | |
383 | } | |
384 | ||
385 | /** | |
386 | * Compares two plugins. | |
387 | * @param editor_tinymce_plugin $a | |
388 | * @param editor_tinymce_plugin $b | |
389 | * @return Negative number if $a is before $b | |
390 | */ | |
391 | public static function compare_plugins(editor_tinymce_plugin $a, editor_tinymce_plugin $b) { | |
392 | // Use sort order first. | |
393 | $order = $a->get_sort_order() - $b->get_sort_order(); | |
394 | if ($order != 0) { | |
395 | return $order; | |
396 | } | |
397 | ||
398 | // Then sort alphabetically. | |
399 | return strcmp($a->plugin, $b->plugin); | |
400 | } | |
401 | } |