35fe4f2a3ed54f0d249d844e0f8a59212d4697c3
[moodle.git] / lib / editor / tinymce / classes / plugin.php
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/>.
17 defined('MOODLE_INTERNAL') || die();
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;
40     /** @var array Plugin settings */
41     protected $config = null;
43     /** @var array list of buttons defined by this plugin */
44     protected $buttons = array();
46     /**
47      * @param string $plugin Name of folder
48      */
49     public function __construct($plugin) {
50         $this->plugin = $plugin;
51     }
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     }
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     }
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     }
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     }
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     }
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);
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     }
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) {
156         if ($this->is_button_present($params, $button)) {
157             return true;
158         }
160         $row = $this->fix_row($params, $row);
162         $field = 'theme_advanced_buttons' . $row;
163         $old = $params[$field];
165         // Empty = add at end.
166         if ($after === '') {
167             $params[$field] = $old . ',' . $button;
168             return true;
169         }
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         }
178         // If always adding, recurse to add it empty.
179         if ($alwaysadd) {
180             return $this->add_button_after($params, $row, $button);
181         }
183         // Otherwise return false (failed to add).
184         return false;
185     }
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
198      * @param int $row Row to add button to (1 to 10)
199      * @param string $button Identifier of button/plugin
200      * @param string $before Adds button directly before the named plugin
201      * @param bool $alwaysadd If specified $after string not found, add at start
202      * @return bool True if added
203      */
204     protected function add_button_before(array &$params, $row, $button,
205             $before = '', $alwaysadd = true) {
207         if ($this->is_button_present($params, $button)) {
208             return true;
209         }
210         $row = $this->fix_row($params, $row);
212         $field = 'theme_advanced_buttons' . $row;
213         $old = $params[$field];
215         // Empty = add at start.
216         if ($before === '') {
217             $params[$field] = $button . ',' . $old;
218             return true;
219         }
221         // Try to add after given plugin.
222         $params[$field] = preg_replace('~(,|^)(' . preg_quote($before) . ')(,|$)~',
223                 '$1' . $button . ',$2$3', $old);
224         if ($params[$field] !== $old) {
225             return true;
226         }
228         // If always adding, recurse to add it empty.
229         if ($alwaysadd) {
230             return $this->add_button_before($params, $row, $button);
231         }
233         // Otherwise return false (failed to add).
234         return false;
235     }
237     /**
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     }
257     /**
258      * Checks the row value is valid, fix if necessary.
259      *
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.
263      */
264     private function fix_row(array &$params, $row) {
265         $row = ($row < 1) ? 1 : (int)$row;
266         $row = ($row > 10) ? 10 : $row;
268         $field = 'theme_advanced_buttons' . $row;
269         if (isset($params[$field])) {
270             return $row;
271         }
272         for($i=$row; $i>=1; $i--) {
273             if (isset($params[$field])) {
274                 return $row;
275             }
276         }
277         // This should not happen.
278         return 1;
279     }
281     /**
282      * Adds a JavaScript plugin into TinyMCE. Note that adding a plugin does
283      * not by itself add a button; you must do both.
284      *
285      * If you leave $pluginname blank (default) it uses the folder name.
286      *
287      * @param array $params TinyMCE init parameters array
288      * @param string $pluginname Identifier for plugin within TinyMCE
289      * @param string $jsfile Name of JS file (within plugin 'tinymce' directory)
290      */
291     protected function add_js_plugin(&$params, $pluginname='', $jsfile='editor_plugin.js') {
292         global $CFG;
294         // Set default plugin name.
295         if ($pluginname === '') {
296             $pluginname = $this->plugin;
297         }
299         // Add plugin to list in params, so it doesn't try to load it again.
300         $params['plugins'] .= ',-' . $pluginname;
302         // Add special param that causes Moodle TinyMCE init to load the plugin.
303         if (!isset($params['moodle_init_plugins'])) {
304             $params['moodle_init_plugins'] = '';
305         } else {
306             $params['moodle_init_plugins'] .= ',';
307         }
309         // Get URL of main JS file and store in params.
310         $jsurl = $this->get_tinymce_file_url($jsfile, false);
311         $params['moodle_init_plugins'] .= $pluginname . ':' . $jsurl;
312     }
314     /**
315      * Returns URL to files in the TinyMCE folder within this plugin, suitable
316      * for client-side use such as loading JavaScript files. (This URL normally
317      * goes through loader.php and contains the plugin version to ensure
318      * correct and long-term cacheing.)
319      *
320      * @param string $file Filename or path within the folder
321      * @param bool $absolute Set false to get relative URL from plugins folder
322      */
323     public function get_tinymce_file_url($file='', $absolute=true) {
324         global $CFG;
326         // Version number comes from plugin version.php, except in developer
327         // mode where the special string 'dev' is used (prevents cacheing and
328         // serves unminified JS).
329         if (debugging('', DEBUG_DEVELOPER)) {
330             $version = '-1';
331         } else {
332             $version = $this->get_version();
333         }
335         // Calculate the JS url (relative to the TinyMCE plugins folder - using
336         // relative URL saves a few bytes in each HTML page).
337         if ($CFG->slasharguments) {
338             // URL is usually from loader.php...
339             $jsurl = 'loader.php/' . $this->plugin . '/' . $version . '/' . $file;
340         } else {
341             // ...except when slash arguments are turned off it serves direct.
342             // In this situation there is no version details and it is up to
343             // the browser and server to negotiate cacheing, which will mean
344             // requesting the JS files frequently (reduced performance).
345             $jsurl = $this->plugin . '/tinymce/' . $file;
346         }
348         if ($absolute) {
349             $jsurl = $CFG->wwwroot . '/lib/editor/tinymce/plugins/' . $jsurl;
350         }
352         return $jsurl;
353     }
355     /**
356      * Obtains version number from version.php for this plugin.
357      *
358      * @return string Version number
359      */
360     protected function get_version() {
361         global $CFG;
363         $plugin = new stdClass;
364         require($CFG->dirroot . '/lib/editor/tinymce/plugins/' . $this->plugin . '/version.php');
365         return $plugin->version;
366     }
368     /**
369      * Calls all available plugins to adjust the TinyMCE init parameters.
370      *
371      * @param array $params TinyMCE init parameters array
372      * @param context $context Context where editor is being shown
373      * @param array $options Options for this editor
374      */
375     public static function all_update_init_params(array &$params,
376             context $context, array $options = null) {
377         global $CFG;
379         // Get list of plugin directories.
380         $plugins = get_plugin_list('tinymce');
382         // Get list of disabled subplugins.
383         $disabled = array();
384         if ($params['moodle_config']->disabledsubplugins) {
385             foreach (explode(',', $params['moodle_config']->disabledsubplugins) as $sp) {
386                 $sp = trim($sp);
387                 if ($sp !== '') {
388                     $disabled[$sp] = $sp;
389                 }
390             }
391         }
393         // Construct all the plugins.
394         $pluginobjects = array();
395         foreach ($plugins as $plugin => $dir) {
396             if (isset($disabled[$plugin])) {
397                 continue;
398             }
399             require_once($dir . '/lib.php');
400             $classname = 'tinymce_' . $plugin;
401             $pluginobjects[] = new $classname($plugin);
402         }
404         // Sort plugins by sort order and name.
405         usort($pluginobjects, array('editor_tinymce_plugin', 'compare_plugins'));
407         // Run the function for each plugin.
408         foreach ($pluginobjects as $obj) {
409             $obj->update_init_params($params, $context, $options);
410         }
411     }
413     /**
414      * Gets a named plugin object. Will cause fatal error if plugin doesn't exist.
415      *
416      * @param string $plugin Name of plugin e.g. 'moodleemoticon'
417      * @return editor_tinymce_plugin Plugin object
418      */
419     public static function get($plugin) {
420         $dir = get_component_directory('tinymce_' . $plugin);
421         require_once($dir . '/lib.php');
422         $classname = 'tinymce_' . $plugin;
423         return new $classname($plugin);
424     }
426     /**
427      * Compares two plugins.
428      * @param editor_tinymce_plugin $a
429      * @param editor_tinymce_plugin $b
430      * @return Negative number if $a is before $b
431      */
432     public static function compare_plugins(editor_tinymce_plugin $a, editor_tinymce_plugin $b) {
433         // Use sort order first.
434         $order = $a->get_sort_order() - $b->get_sort_order();
435         if ($order != 0) {
436             return $order;
437         }
439         // Then sort alphabetically.
440         return strcmp($a->plugin, $b->plugin);
441     }