weekly release 2.4dev
[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) {
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}