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