MDL-40621 fixed small bug in function editor_tinymce_plugin::fix_row()
[moodle.git] / lib / editor / tinymce / classes / plugin.php
CommitLineData
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
17defined('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 */
36abstract 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) {
c64f1317
PS
155
156 if ($this->is_button_present($params, $button)) {
157 return true;
158 }
159
160 $row = $this->fix_row($params, $row);
fae91170 161
162 $field = 'theme_advanced_buttons' . $row;
163 $old = $params[$field];
164
165 // Empty = add at end.
166 if ($after === '') {
167 $params[$field] = $old . ',' . $button;
168 return true;
169 }
170
171 // Try to add after given plugin.
172 $params[$field] = preg_replace('~(,|^)(' . preg_quote($after) . ')(,|$)~',
173 '$1$2,' . $button . '$3', $old);
174 if ($params[$field] !== $old) {
175 return true;
176 }
177
178 // If always adding, recurse to add it empty.
179 if ($alwaysadd) {
180 return $this->add_button_after($params, $row, $button);
181 }
182
183 // Otherwise return false (failed to add).
184 return false;
185 }
186
187 /**
188 * Adds a button to the editor.
189 *
190 * Specify the location of this button using the $before variable. If you
191 * leave this blank, the button will be added at the start.
192 *
193 * If you want to try different possible locations depending on existing
194 * plugins you can set $alwaysadd to false and check the return value
195 * to see if it succeeded.
196 *
197 * @param array $params TinyMCE init parameters array
c64f1317 198 * @param int $row Row to add button to (1 to 10)
fae91170 199 * @param string $button Identifier of button/plugin
200 * @param string $before Adds button directly before the named plugin
0bff314d 201 * @param bool $alwaysadd If specified $before string not found, add at start
fae91170 202 * @return bool True if added
203 */
204 protected function add_button_before(array &$params, $row, $button,
205 $before = '', $alwaysadd = true) {
c64f1317
PS
206
207 if ($this->is_button_present($params, $button)) {
208 return true;
209 }
210 $row = $this->fix_row($params, $row);
fae91170 211
212 $field = 'theme_advanced_buttons' . $row;
213 $old = $params[$field];
214
215 // Empty = add at start.
216 if ($before === '') {
217 $params[$field] = $button . ',' . $old;
218 return true;
219 }
220
0bff314d 221 // Try to add before given plugin.
fae91170 222 $params[$field] = preg_replace('~(,|^)(' . preg_quote($before) . ')(,|$)~',
223 '$1' . $button . ',$2$3', $old);
224 if ($params[$field] !== $old) {
225 return true;
226 }
227
228 // If always adding, recurse to add it empty.
229 if ($alwaysadd) {
230 return $this->add_button_before($params, $row, $button);
231 }
232
233 // Otherwise return false (failed to add).
234 return false;
235 }
236
237 /**
c64f1317
PS
238 * Tests if button already present.
239 * @param array $params
240 * @param string $button
241 * @return bool
242 */
243 private function is_button_present(array $params, $button) {
244 for($i=1; $i<=10; $i++) {
245 $field = 'theme_advanced_buttons' . $i;
246 if (!isset($params[$field])) {
247 continue;
248 }
249 $buttons = explode(',', $params[$field]);
250 if (in_array($button, $buttons)) {
251 return true;
252 }
253 }
254 return false;
255 }
256
257 /**
258 * Checks the row value is valid, fix if necessary.
fae91170 259 *
c64f1317
PS
260 * @param array $params TinyMCE init parameters array
261 * @param int $row Row to add button if exists
262 * @return int requested row if exists, lower number if does not exist.
fae91170 263 */
c64f1317
PS
264 private function fix_row(array &$params, $row) {
265 $row = ($row < 1) ? 1 : (int)$row;
266 $row = ($row > 10) ? 10 : $row;
267
268 $field = 'theme_advanced_buttons' . $row;
269 if (isset($params[$field])) {
270 return $row;
271 }
272 for($i=$row; $i>=1; $i--) {
0bff314d 273 $field = 'theme_advanced_buttons' . $i;
c64f1317 274 if (isset($params[$field])) {
0bff314d 275 return $i;
c64f1317 276 }
fae91170 277 }
c64f1317
PS
278 // This should not happen.
279 return 1;
fae91170 280 }
281
282 /**
283 * Adds a JavaScript plugin into TinyMCE. Note that adding a plugin does
284 * not by itself add a button; you must do both.
285 *
286 * If you leave $pluginname blank (default) it uses the folder name.
287 *
288 * @param array $params TinyMCE init parameters array
289 * @param string $pluginname Identifier for plugin within TinyMCE
290 * @param string $jsfile Name of JS file (within plugin 'tinymce' directory)
291 */
292 protected function add_js_plugin(&$params, $pluginname='', $jsfile='editor_plugin.js') {
293 global $CFG;
294
295 // Set default plugin name.
296 if ($pluginname === '') {
297 $pluginname = $this->plugin;
298 }
299
300 // Add plugin to list in params, so it doesn't try to load it again.
301 $params['plugins'] .= ',-' . $pluginname;
302
303 // Add special param that causes Moodle TinyMCE init to load the plugin.
304 if (!isset($params['moodle_init_plugins'])) {
305 $params['moodle_init_plugins'] = '';
306 } else {
307 $params['moodle_init_plugins'] .= ',';
308 }
309
310 // Get URL of main JS file and store in params.
311 $jsurl = $this->get_tinymce_file_url($jsfile, false);
312 $params['moodle_init_plugins'] .= $pluginname . ':' . $jsurl;
313 }
314
315 /**
316 * Returns URL to files in the TinyMCE folder within this plugin, suitable
317 * for client-side use such as loading JavaScript files. (This URL normally
318 * goes through loader.php and contains the plugin version to ensure
319 * correct and long-term cacheing.)
320 *
321 * @param string $file Filename or path within the folder
322 * @param bool $absolute Set false to get relative URL from plugins folder
323 */
324 public function get_tinymce_file_url($file='', $absolute=true) {
325 global $CFG;
326
327 // Version number comes from plugin version.php, except in developer
328 // mode where the special string 'dev' is used (prevents cacheing and
329 // serves unminified JS).
330 if (debugging('', DEBUG_DEVELOPER)) {
331 $version = '-1';
332 } else {
333 $version = $this->get_version();
334 }
335
336 // Calculate the JS url (relative to the TinyMCE plugins folder - using
337 // relative URL saves a few bytes in each HTML page).
338 if ($CFG->slasharguments) {
339 // URL is usually from loader.php...
340 $jsurl = 'loader.php/' . $this->plugin . '/' . $version . '/' . $file;
341 } else {
342 // ...except when slash arguments are turned off it serves direct.
343 // In this situation there is no version details and it is up to
344 // the browser and server to negotiate cacheing, which will mean
345 // requesting the JS files frequently (reduced performance).
346 $jsurl = $this->plugin . '/tinymce/' . $file;
347 }
348
349 if ($absolute) {
350 $jsurl = $CFG->wwwroot . '/lib/editor/tinymce/plugins/' . $jsurl;
351 }
352
353 return $jsurl;
354 }
355
356 /**
357 * Obtains version number from version.php for this plugin.
358 *
359 * @return string Version number
360 */
361 protected function get_version() {
362 global $CFG;
363
364 $plugin = new stdClass;
365 require($CFG->dirroot . '/lib/editor/tinymce/plugins/' . $this->plugin . '/version.php');
366 return $plugin->version;
367 }
368
369 /**
370 * Calls all available plugins to adjust the TinyMCE init parameters.
371 *
372 * @param array $params TinyMCE init parameters array
373 * @param context $context Context where editor is being shown
374 * @param array $options Options for this editor
375 */
376 public static function all_update_init_params(array &$params,
377 context $context, array $options = null) {
378 global $CFG;
379
380 // Get list of plugin directories.
381 $plugins = get_plugin_list('tinymce');
382
0b785822
PS
383 // Get list of disabled subplugins.
384 $disabled = array();
385 if ($params['moodle_config']->disabledsubplugins) {
386 foreach (explode(',', $params['moodle_config']->disabledsubplugins) as $sp) {
387 $sp = trim($sp);
388 if ($sp !== '') {
389 $disabled[$sp] = $sp;
390 }
391 }
392 }
393
fae91170 394 // Construct all the plugins.
395 $pluginobjects = array();
396 foreach ($plugins as $plugin => $dir) {
0b785822
PS
397 if (isset($disabled[$plugin])) {
398 continue;
399 }
fae91170 400 require_once($dir . '/lib.php');
401 $classname = 'tinymce_' . $plugin;
402 $pluginobjects[] = new $classname($plugin);
403 }
404
405 // Sort plugins by sort order and name.
406 usort($pluginobjects, array('editor_tinymce_plugin', 'compare_plugins'));
407
408 // Run the function for each plugin.
409 foreach ($pluginobjects as $obj) {
410 $obj->update_init_params($params, $context, $options);
411 }
412 }
413
414 /**
415 * Gets a named plugin object. Will cause fatal error if plugin doesn't exist.
416 *
417 * @param string $plugin Name of plugin e.g. 'moodleemoticon'
418 * @return editor_tinymce_plugin Plugin object
419 */
420 public static function get($plugin) {
421 $dir = get_component_directory('tinymce_' . $plugin);
422 require_once($dir . '/lib.php');
423 $classname = 'tinymce_' . $plugin;
424 return new $classname($plugin);
425 }
426
427 /**
428 * Compares two plugins.
429 * @param editor_tinymce_plugin $a
430 * @param editor_tinymce_plugin $b
431 * @return Negative number if $a is before $b
432 */
433 public static function compare_plugins(editor_tinymce_plugin $a, editor_tinymce_plugin $b) {
434 // Use sort order first.
435 $order = $a->get_sort_order() - $b->get_sort_order();
436 if ($order != 0) {
437 return $order;
438 }
439
440 // Then sort alphabetically.
441 return strcmp($a->plugin, $b->plugin);
442 }
443}