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