2 // This file is part of Moodle - http://moodle.org/
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.
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.
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();
20 * TinyMCE text editor plugin base class.
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.
26 * As well as overridable functions, other utility functions in this class
27 * can be used when writing the plugins.
29 * Finally, a static function in this class is used to call into all the
30 * plugins when required.
32 * @package editor_tinymce
33 * @copyright 2012 The Open University
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 abstract class editor_tinymce_plugin {
37 /** @var string Plugin folder */
40 /** @var array Plugin settings */
41 protected $config = null;
43 /** @var array list of buttons defined by this plugin */
44 protected $buttons = array();
47 * @param string $plugin Name of folder
49 public function __construct($plugin) {
50 $this->plugin = $plugin;
54 * Returns list of buttons defined by this plugin.
55 * useful mostly as information when setting custom toolbar.
59 public function get_buttons() {
60 return $this->buttons;
63 * Makes sure config is loaded and cached.
66 protected function load_config() {
67 if (!isset($this->config)) {
68 $name = $this->get_name();
69 $this->config = get_config("tinymce_$name");
74 * Returns plugin config value.
76 * @param string $default value if config does not exist yet
77 * @return string value or default
79 public function get_config($name, $default = null) {
81 return isset($this->config->$name) ? $this->config->$name : $default;
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
90 public function set_config($name, $value) {
91 $pluginname = $this->get_name();
93 if ($value === null) {
94 unset($this->config->$name);
96 $this->config->$name = $value;
98 set_config($name, $value, "tinymce_$pluginname");
102 * Returns name of this tinymce plugin.
105 public function get_name() {
106 // All class names start with "tinymce_".
107 $words = explode('_', get_class($this), 2);
112 * Adjusts TinyMCE init parameters for this plugin.
114 * Subclasses must implement this function in order to carry out changes
115 * to the TinyMCE settings.
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
121 protected abstract function update_init_params(array &$params, context $context,
122 array $options = null);
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.
132 protected function get_sort_order() {
137 * Adds a button to the editor, after another button (or at the end).
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.
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.
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
153 protected function add_button_after(array &$params, $row, $button,
154 $after = '', $alwaysadd = true) {
156 if ($this->is_button_present($params, $button)) {
160 $row = $this->fix_row($params, $row);
162 $field = 'theme_advanced_buttons' . $row;
163 $old = $params[$field];
165 // Empty = add at end.
167 $params[$field] = $old . ',' . $button;
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) {
178 // If always adding, recurse to add it empty.
180 return $this->add_button_after($params, $row, $button);
183 // Otherwise return false (failed to add).
188 * Adds a button to the editor.
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.
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.
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 $before string not found, add at start
202 * @return bool True if added
204 protected function add_button_before(array &$params, $row, $button,
205 $before = '', $alwaysadd = true) {
207 if ($this->is_button_present($params, $button)) {
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;
221 // Try to add before given plugin.
222 $params[$field] = preg_replace('~(,|^)(' . preg_quote($before) . ')(,|$)~',
223 '$1' . $button . ',$2$3', $old);
224 if ($params[$field] !== $old) {
228 // If always adding, recurse to add it empty.
230 return $this->add_button_before($params, $row, $button);
233 // Otherwise return false (failed to add).
238 * Tests if button already present.
239 * @param array $params
240 * @param string $button
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])) {
249 $buttons = explode(',', $params[$field]);
250 if (in_array($button, $buttons)) {
258 * Checks the row value is valid, fix if necessary.
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.
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])) {
272 for($i=$row; $i>=1; $i--) {
273 $field = 'theme_advanced_buttons' . $i;
274 if (isset($params[$field])) {
278 // This should not happen.
283 * Adds a JavaScript plugin into TinyMCE. Note that adding a plugin does
284 * not by itself add a button; you must do both.
286 * If you leave $pluginname blank (default) it uses the folder name.
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)
292 protected function add_js_plugin(&$params, $pluginname='', $jsfile='editor_plugin.js') {
295 // Set default plugin name.
296 if ($pluginname === '') {
297 $pluginname = $this->plugin;
300 // Add plugin to list in params, so it doesn't try to load it again.
301 $params['plugins'] .= ',-' . $pluginname;
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'] = '';
307 $params['moodle_init_plugins'] .= ',';
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;
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.)
321 * @param string $file Filename or path within the folder
322 * @param bool $absolute Set false to get relative URL from plugins folder
324 public function get_tinymce_file_url($file='', $absolute=true) {
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)) {
333 $version = $this->get_version();
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;
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;
350 $jsurl = $CFG->wwwroot . '/lib/editor/tinymce/plugins/' . $jsurl;
357 * Obtains version number from version.php for this plugin.
359 * @return string Version number
361 protected function get_version() {
364 $plugin = new stdClass;
365 require($CFG->dirroot . '/lib/editor/tinymce/plugins/' . $this->plugin . '/version.php');
366 return $plugin->version;
370 * Calls all available plugins to adjust the TinyMCE init parameters.
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
376 public static function all_update_init_params(array &$params,
377 context $context, array $options = null) {
380 // Get list of plugin directories.
381 $plugins = get_plugin_list('tinymce');
383 // Get list of disabled subplugins.
385 if ($params['moodle_config']->disabledsubplugins) {
386 foreach (explode(',', $params['moodle_config']->disabledsubplugins) as $sp) {
389 $disabled[$sp] = $sp;
394 // Construct all the plugins.
395 $pluginobjects = array();
396 foreach ($plugins as $plugin => $dir) {
397 if (isset($disabled[$plugin])) {
400 require_once($dir . '/lib.php');
401 $classname = 'tinymce_' . $plugin;
402 $pluginobjects[] = new $classname($plugin);
405 // Sort plugins by sort order and name.
406 usort($pluginobjects, array('editor_tinymce_plugin', 'compare_plugins'));
408 // Run the function for each plugin.
409 foreach ($pluginobjects as $obj) {
410 $obj->update_init_params($params, $context, $options);
415 * Gets a named plugin object. Will cause fatal error if plugin doesn't exist.
417 * @param string $plugin Name of plugin e.g. 'moodleemoticon'
418 * @return editor_tinymce_plugin Plugin object
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);
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
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();
440 // Then sort alphabetically.
441 return strcmp($a->plugin, $b->plugin);