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 | |
17 | defined('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 | */ |
36 | abstract 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 | } |