1 <?php
2 /**
3 * @package Joomla.Libraries
4 * @subpackage Editor
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE.txt
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 use Joomla\Registry\Registry;
13
14 /**
15 * JEditor class to handle WYSIWYG editors
16 *
17 * @since 1.5
18 */
19 class JEditor extends JObject
20 {
21 /**
22 * An array of Observer objects to notify
23 *
24 * @var array
25 * @since 1.5
26 */
27 protected $_observers = array();
28
29 /**
30 * The state of the observable object
31 *
32 * @var mixed
33 * @since 1.5
34 */
35 protected $_state = null;
36
37 /**
38 * A multi dimensional array of [function][] = key for observers
39 *
40 * @var array
41 * @since 1.5
42 */
43 protected $_methods = array();
44
45 /**
46 * Editor Plugin object
47 *
48 * @var object
49 * @since 1.5
50 */
51 protected $_editor = null;
52
53 /**
54 * Editor Plugin name
55 *
56 * @var string
57 * @since 1.5
58 */
59 protected $_name = null;
60
61 /**
62 * Object asset
63 *
64 * @var string
65 * @since 1.6
66 */
67 protected $asset = null;
68
69 /**
70 * Object author
71 *
72 * @var string
73 * @since 1.6
74 */
75 protected $author = null;
76
77 /**
78 * JEditor instances container.
79 *
80 * @var JEditor[]
81 * @since 2.5
82 */
83 protected static $instances = array();
84
85 /**
86 * Constructor
87 *
88 * @param string $editor The editor name
89 */
90 public function __construct($editor = 'none')
91 {
92 $this->_name = $editor;
93 }
94
95 /**
96 * Returns the global Editor object, only creating it
97 * if it doesn't already exist.
98 *
99 * @param string $editor The editor to use.
100 *
101 * @return JEditor The Editor object.
102 *
103 * @since 1.5
104 */
105 public static function getInstance($editor = 'none')
106 {
107 $signature = serialize($editor);
108
109 if (empty(self::$instances[$signature]))
110 {
111 self::$instances[$signature] = new JEditor($editor);
112 }
113
114 return self::$instances[$signature];
115 }
116
117 /**
118 * Get the state of the JEditor object
119 *
120 * @return mixed The state of the object.
121 *
122 * @since 1.5
123 */
124 public function getState()
125 {
126 return $this->_state;
127 }
128
129 /**
130 * Attach an observer object
131 *
132 * @param array|object $observer An observer object to attach or an array with handler and event keys
133 *
134 * @return void
135 *
136 * @since 1.5
137 */
138 public function attach($observer)
139 {
140 if (is_array($observer))
141 {
142 if (!isset($observer['handler']) || !isset($observer['event']) || !is_callable($observer['handler']))
143 {
144 return;
145 }
146
147 // Make sure we haven't already attached this array as an observer
148 foreach ($this->_observers as $check)
149 {
150 if (is_array($check) && $check['event'] == $observer['event'] && $check['handler'] == $observer['handler'])
151 {
152 return;
153 }
154 }
155
156 $this->_observers[] = $observer;
157 end($this->_observers);
158 $methods = array($observer['event']);
159 }
160 else
161 {
162 if (!($observer instanceof JEditor))
163 {
164 return;
165 }
166
167 // Make sure we haven't already attached this object as an observer
168 $class = get_class($observer);
169
170 foreach ($this->_observers as $check)
171 {
172 if ($check instanceof $class)
173 {
174 return;
175 }
176 }
177
178 $this->_observers[] = $observer;
179
180 // @todo We require a JEditor object above but get the methods from JPlugin - something isn't right here!
181 $methods = array_diff(get_class_methods($observer), get_class_methods('JPlugin'));
182 }
183
184 $key = key($this->_observers);
185
186 foreach ($methods as $method)
187 {
188 $method = strtolower($method);
189
190 if (!isset($this->_methods[$method]))
191 {
192 $this->_methods[$method] = array();
193 }
194
195 $this->_methods[$method][] = $key;
196 }
197 }
198
199 /**
200 * Detach an observer object
201 *
202 * @param object $observer An observer object to detach.
203 *
204 * @return boolean True if the observer object was detached.
205 *
206 * @since 1.5
207 */
208 public function detach($observer)
209 {
210 $retval = false;
211
212 $key = array_search($observer, $this->_observers);
213
214 if ($key !== false)
215 {
216 unset($this->_observers[$key]);
217 $retval = true;
218
219 foreach ($this->_methods as &$method)
220 {
221 $k = array_search($key, $method);
222
223 if ($k !== false)
224 {
225 unset($method[$k]);
226 }
227 }
228 }
229
230 return $retval;
231 }
232
233 /**
234 * Initialise the editor
235 *
236 * @return void
237 *
238 * @since 1.5
239 */
240 public function initialise()
241 {
242 // Check if editor is already loaded
243 if ($this->_editor === null)
244 {
245 return;
246 }
247
248 $args['event'] = 'onInit';
249
250 $return = '';
251 $results[] = $this->_editor->update($args);
252
253 foreach ($results as $result)
254 {
255 if (trim($result))
256 {
257 // @todo remove code: $return .= $result;
258 $return = $result;
259 }
260 }
261
262 $document = JFactory::getDocument();
263
264 if (!empty($return) && method_exists($document, 'addCustomTag'))
265 {
266 $document->addCustomTag($return);
267 }
268 }
269
270 /**
271 * Display the editor area.
272 *
273 * @param string $name The control name.
274 * @param string $html The contents of the text area.
275 * @param string $width The width of the text area (px or %).
276 * @param string $height The height of the text area (px or %).
277 * @param integer $col The number of columns for the textarea.
278 * @param integer $row The number of rows for the textarea.
279 * @param boolean $buttons True and the editor buttons will be displayed.
280 * @param string $id An optional ID for the textarea (note: since 1.6). If not supplied the name is used.
281 * @param string $asset The object asset
282 * @param object $author The author.
283 * @param array $params Associative array of editor parameters.
284 *
285 * @return string
286 *
287 * @since 1.5
288 */
289 public function display($name, $html, $width, $height, $col, $row, $buttons = true, $id = null, $asset = null, $author = null, $params = array())
290 {
291 $this->asset = $asset;
292 $this->author = $author;
293 $this->_loadEditor($params);
294
295 // Check whether editor is already loaded
296 if ($this->_editor === null)
297 {
298 JFactory::getApplication()->enqueueMessage(JText::_('JLIB_NO_EDITOR_PLUGIN_PUBLISHED'), 'error');
299
300 return;
301 }
302
303 // Backwards compatibility. Width and height should be passed without a semicolon from now on.
304 // If editor plugins need a unit like "px" for CSS styling, they need to take care of that
305 $width = str_replace(';', '', $width);
306 $height = str_replace(';', '', $height);
307
308 $return = null;
309
310 $args['name'] = $name;
311 $args['content'] = $html;
312 $args['width'] = $width;
313 $args['height'] = $height;
314 $args['col'] = $col;
315 $args['row'] = $row;
316 $args['buttons'] = $buttons;
317 $args['id'] = $id ?: $name;
318 $args['asset'] = $asset;
319 $args['author'] = $author;
320 $args['params'] = $params;
321 $args['event'] = 'onDisplay';
322
323 $results[] = $this->_editor->update($args);
324
325 foreach ($results as $result)
326 {
327 if (trim($result))
328 {
329 $return .= $result;
330 }
331 }
332
333 return $return;
334 }
335
336 /**
337 * Save the editor content
338 *
339 * @param string $editor The name of the editor control
340 *
341 * @return string
342 *
343 * @since 1.5
344 *
345 * @deprecated 4.0 Bind functionality to form submit through javascript
346 */
347 public function save($editor)
348 {
349 $this->_loadEditor();
350
351 // Check whether editor is already loaded
352 if ($this->_editor === null)
353 {
354 return;
355 }
356
357 $args[] = $editor;
358 $args['event'] = 'onSave';
359
360 $return = '';
361 $results[] = $this->_editor->update($args);
362
363 foreach ($results as $result)
364 {
365 if (trim($result))
366 {
367 $return .= $result;
368 }
369 }
370
371 return $return;
372 }
373
374 /**
375 * Get the editor contents
376 *
377 * @param string $editor The name of the editor control
378 *
379 * @return string
380 *
381 * @since 1.5
382 *
383 * @deprecated 4.0 Use Joomla.editors API, see core.js
384 */
385 public function getContent($editor)
386 {
387 $this->_loadEditor();
388
389 $args['name'] = $editor;
390 $args['event'] = 'onGetContent';
391
392 $return = '';
393 $results[] = $this->_editor->update($args);
394
395 foreach ($results as $result)
396 {
397 if (trim($result))
398 {
399 $return .= $result;
400 }
401 }
402
403 return $return;
404 }
405
406 /**
407 * Set the editor contents
408 *
409 * @param string $editor The name of the editor control
410 * @param string $html The contents of the text area
411 *
412 * @return string
413 *
414 * @since 1.5
415 *
416 * @deprecated 4.0 Use Joomla.editors API, see core.js
417 */
418 public function setContent($editor, $html)
419 {
420 $this->_loadEditor();
421
422 $args['name'] = $editor;
423 $args['html'] = $html;
424 $args['event'] = 'onSetContent';
425
426 $return = '';
427 $results[] = $this->_editor->update($args);
428
429 foreach ($results as $result)
430 {
431 if (trim($result))
432 {
433 $return .= $result;
434 }
435 }
436
437 return $return;
438 }
439
440 /**
441 * Get the editor extended buttons (usually from plugins)
442 *
443 * @param string $editor The name of the editor.
444 * @param mixed $buttons Can be boolean or array, if boolean defines if the buttons are
445 * displayed, if array defines a list of buttons not to show.
446 *
447 * @return array
448 *
449 * @since 1.5
450 */
451 public function getButtons($editor, $buttons = true)
452 {
453 $result = array();
454
455 if (is_bool($buttons) && !$buttons)
456 {
457 return $result;
458 }
459
460 // Get plugins
461 $plugins = JPluginHelper::getPlugin('editors-xtd');
462
463 foreach ($plugins as $plugin)
464 {
465 if (is_array($buttons) && in_array($plugin->name, $buttons))
466 {
467 continue;
468 }
469
470 JPluginHelper::importPlugin('editors-xtd', $plugin->name, false);
471 $className = 'PlgEditorsXtd' . $plugin->name;
472
473 if (!class_exists($className))
474 {
475 $className = 'PlgButton' . $plugin->name;
476 }
477
478 if (class_exists($className))
479 {
480 $plugin = new $className($this, (array) $plugin);
481 }
482
483 // Try to authenticate
484 if (!method_exists($plugin, 'onDisplay'))
485 {
486 continue;
487 }
488
489 $button = $plugin->onDisplay($editor, $this->asset, $this->author);
490
491 if (empty($button))
492 {
493 continue;
494 }
495
496 if (is_array($button))
497 {
498 $result = array_merge($result, $button);
499 continue;
500 }
501
502 $result[] = $button;
503 }
504
505 return $result;
506 }
507
508 /**
509 * Load the editor
510 *
511 * @param array $config Associative array of editor config parameters
512 *
513 * @return mixed
514 *
515 * @since 1.5
516 */
517 protected function _loadEditor($config = array())
518 {
519 // Check whether editor is already loaded
520 if ($this->_editor !== null)
521 {
522 return;
523 }
524
525 // Build the path to the needed editor plugin
526 $name = JFilterInput::getInstance()->clean($this->_name, 'cmd');
527 $path = JPATH_PLUGINS . '/editors/' . $name . '/' . $name . '.php';
528
529 if (!is_file($path))
530 {
531 JLog::add(JText::_('JLIB_HTML_EDITOR_CANNOT_LOAD'), JLog::WARNING, 'jerror');
532
533 return false;
534 }
535
536 // Require plugin file
537 require_once $path;
538
539 // Get the plugin
540 $plugin = JPluginHelper::getPlugin('editors', $this->_name);
541
542 // If no plugin is published we get an empty array and there not so much to do with it
543 if (empty($plugin))
544 {
545 return false;
546 }
547
548 $params = new Registry($plugin->params);
549 $params->loadArray($config);
550 $plugin->params = $params;
551
552 // Build editor plugin classname
553 $name = 'PlgEditor' . $this->_name;
554
555 if ($this->_editor = new $name($this, (array) $plugin))
556 {
557 // Load plugin parameters
558 $this->initialise();
559 JPluginHelper::importPlugin('editors-xtd');
560 }
561 }
562 }
563