source: extensions/FCKEditor/ckeditor/ckeditor_php5.php @ 9293

Last change on this file since 9293 was 9293, checked in by patdenice, 13 years ago

Update to CK Editor 3.5.2

File size: 15.7 KB
Line 
1<?php
2/*
3* Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
4* For licensing, see LICENSE.html or http://ckeditor.com/license
5*/
6
7/**
8 * \brief CKEditor class that can be used to create editor
9 * instances in PHP pages on server side.
10 * @see http://ckeditor.com
11 *
12 * Sample usage:
13 * @code
14 * $CKEditor = new CKEditor();
15 * $CKEditor->editor("editor1", "<p>Initial value.</p>");
16 * @endcode
17 */
18class CKEditor
19{
20        /**
21         * The version of %CKEditor.
22         */
23        const version = '3.5.2';
24        /**
25         * A constant string unique for each release of %CKEditor.
26         */
27        const timestamp = 'B1GG4Z6';
28
29        /**
30         * URL to the %CKEditor installation directory (absolute or relative to document root).
31         * If not set, CKEditor will try to guess it's path.
32         *
33         * Example usage:
34         * @code
35         * $CKEditor->basePath = '/ckeditor/';
36         * @endcode
37         */
38        public $basePath;
39        /**
40         * An array that holds the global %CKEditor configuration.
41         * For the list of available options, see http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.config.html
42         *
43         * Example usage:
44         * @code
45         * $CKEditor->config['height'] = 400;
46         * // Use @@ at the beggining of a string to ouput it without surrounding quotes.
47         * $CKEditor->config['width'] = '@@screen.width * 0.8';
48         * @endcode
49         */
50        public $config = array();
51        /**
52         * A boolean variable indicating whether CKEditor has been initialized.
53         * Set it to true only if you have already included
54         * &lt;script&gt; tag loading ckeditor.js in your website.
55         */
56        public $initialized = false;
57        /**
58         * Boolean variable indicating whether created code should be printed out or returned by a function.
59         *
60         * Example 1: get the code creating %CKEditor instance and print it on a page with the "echo" function.
61         * @code
62         * $CKEditor = new CKEditor();
63         * $CKEditor->returnOutput = true;
64         * $code = $CKEditor->editor("editor1", "<p>Initial value.</p>");
65         * echo "<p>Editor 1:</p>";
66         * echo $code;
67         * @endcode
68         */
69        public $returnOutput = false;
70        /**
71         * An array with textarea attributes.
72         *
73         * When %CKEditor is created with the editor() method, a HTML &lt;textarea&gt; element is created,
74         * it will be displayed to anyone with JavaScript disabled or with incompatible browser.
75         */
76        public $textareaAttributes = array( "rows" => 8, "cols" => 60 );
77        /**
78         * A string indicating the creation date of %CKEditor.
79         * Do not change it unless you want to force browsers to not use previously cached version of %CKEditor.
80         */
81        public $timestamp = "B1GG4Z6";
82        /**
83         * An array that holds event listeners.
84         */
85        private $events = array();
86        /**
87         * An array that holds global event listeners.
88         */
89        private $globalEvents = array();
90
91        /**
92         * Main Constructor.
93         *
94         *  @param $basePath (string) URL to the %CKEditor installation directory (optional).
95         */
96        function __construct($basePath = null) {
97                if (!empty($basePath)) {
98                        $this->basePath = $basePath;
99                }
100        }
101
102        /**
103         * Creates a %CKEditor instance.
104         * In incompatible browsers %CKEditor will downgrade to plain HTML &lt;textarea&gt; element.
105         *
106         * @param $name (string) Name of the %CKEditor instance (this will be also the "name" attribute of textarea element).
107         * @param $value (string) Initial value (optional).
108         * @param $config (array) The specific configurations to apply to this editor instance (optional).
109         * @param $events (array) Event listeners for this editor instance (optional).
110         *
111         * Example usage:
112         * @code
113         * $CKEditor = new CKEditor();
114         * $CKEditor->editor("field1", "<p>Initial value.</p>");
115         * @endcode
116         *
117         * Advanced example:
118         * @code
119         * $CKEditor = new CKEditor();
120         * $config = array();
121         * $config['toolbar'] = array(
122         *     array( 'Source', '-', 'Bold', 'Italic', 'Underline', 'Strike' ),
123         *     array( 'Image', 'Link', 'Unlink', 'Anchor' )
124         * );
125         * $events['instanceReady'] = 'function (ev) {
126         *     alert("Loaded: " + ev.editor.name);
127         * }';
128         * $CKEditor->editor("field1", "<p>Initial value.</p>", $config, $events);
129         * @endcode
130         */
131        public function editor($name, $value = "", $config = array(), $events = array())
132        {
133                $attr = "";
134                foreach ($this->textareaAttributes as $key => $val) {
135                        $attr.= " " . $key . '="' . str_replace('"', '&quot;', $val) . '"';
136                }
137                $out = "<textarea name=\"" . $name . "\"" . $attr . ">" . htmlspecialchars($value) . "</textarea>\n";
138                if (!$this->initialized) {
139                        $out .= $this->init();
140                }
141
142                $_config = $this->configSettings($config, $events);
143
144                $js = $this->returnGlobalEvents();
145                if (!empty($_config))
146                        $js .= "CKEDITOR.replace('".$name."', ".$this->jsEncode($_config).");";
147                else
148                        $js .= "CKEDITOR.replace('".$name."');";
149
150                $out .= $this->script($js);
151
152                if (!$this->returnOutput) {
153                        print $out;
154                        $out = "";
155                }
156
157                return $out;
158        }
159
160        /**
161         * Replaces a &lt;textarea&gt; with a %CKEditor instance.
162         *
163         * @param $id (string) The id or name of textarea element.
164         * @param $config (array) The specific configurations to apply to this editor instance (optional).
165         * @param $events (array) Event listeners for this editor instance (optional).
166         *
167         * Example 1: adding %CKEditor to &lt;textarea name="article"&gt;&lt;/textarea&gt; element:
168         * @code
169         * $CKEditor = new CKEditor();
170         * $CKEditor->replace("article");
171         * @endcode
172         */
173        public function replace($id, $config = array(), $events = array())
174        {
175                $out = "";
176                if (!$this->initialized) {
177                        $out .= $this->init();
178                }
179
180                $_config = $this->configSettings($config, $events);
181
182                $js = $this->returnGlobalEvents();
183                if (!empty($_config)) {
184                        $js .= "CKEDITOR.replace('".$id."', ".$this->jsEncode($_config).");";
185                }
186                else {
187                        $js .= "CKEDITOR.replace('".$id."');";
188                }
189                $out .= $this->script($js);
190
191                if (!$this->returnOutput) {
192                        print $out;
193                        $out = "";
194                }
195
196                return $out;
197        }
198
199        /**
200         * Replace all &lt;textarea&gt; elements available in the document with editor instances.
201         *
202         * @param $className (string) If set, replace all textareas with class className in the page.
203         *
204         * Example 1: replace all &lt;textarea&gt; elements in the page.
205         * @code
206         * $CKEditor = new CKEditor();
207         * $CKEditor->replaceAll();
208         * @endcode
209         *
210         * Example 2: replace all &lt;textarea class="myClassName"&gt; elements in the page.
211         * @code
212         * $CKEditor = new CKEditor();
213         * $CKEditor->replaceAll( 'myClassName' );
214         * @endcode
215         */
216        public function replaceAll($className = null)
217        {
218                $out = "";
219                if (!$this->initialized) {
220                        $out .= $this->init();
221                }
222
223                $_config = $this->configSettings();
224
225                $js = $this->returnGlobalEvents();
226                if (empty($_config)) {
227                        if (empty($className)) {
228                                $js .= "CKEDITOR.replaceAll();";
229                        }
230                        else {
231                                $js .= "CKEDITOR.replaceAll('".$className."');";
232                        }
233                }
234                else {
235                        $classDetection = "";
236                        $js .= "CKEDITOR.replaceAll( function(textarea, config) {\n";
237                        if (!empty($className)) {
238                                $js .= "        var classRegex = new RegExp('(?:^| )' + '". $className ."' + '(?:$| )');\n";
239                                $js .= "        if (!classRegex.test(textarea.className))\n";
240                                $js .= "                return false;\n";
241                        }
242                        $js .= "        CKEDITOR.tools.extend(config, ". $this->jsEncode($_config) .", true);";
243                        $js .= "} );";
244
245                }
246
247                $out .= $this->script($js);
248
249                if (!$this->returnOutput) {
250                        print $out;
251                        $out = "";
252                }
253
254                return $out;
255        }
256
257        /**
258         * Adds event listener.
259         * Events are fired by %CKEditor in various situations.
260         *
261         * @param $event (string) Event name.
262         * @param $javascriptCode (string) Javascript anonymous function or function name.
263         *
264         * Example usage:
265         * @code
266         * $CKEditor->addEventHandler('instanceReady', 'function (ev) {
267         *     alert("Loaded: " + ev.editor.name);
268         * }');
269         * @endcode
270         */
271        public function addEventHandler($event, $javascriptCode)
272        {
273                if (!isset($this->events[$event])) {
274                        $this->events[$event] = array();
275                }
276                // Avoid duplicates.
277                if (!in_array($javascriptCode, $this->events[$event])) {
278                        $this->events[$event][] = $javascriptCode;
279                }
280        }
281
282        /**
283         * Clear registered event handlers.
284         * Note: this function will have no effect on already created editor instances.
285         *
286         * @param $event (string) Event name, if not set all event handlers will be removed (optional).
287         */
288        public function clearEventHandlers($event = null)
289        {
290                if (!empty($event)) {
291                        $this->events[$event] = array();
292                }
293                else {
294                        $this->events = array();
295                }
296        }
297
298        /**
299         * Adds global event listener.
300         *
301         * @param $event (string) Event name.
302         * @param $javascriptCode (string) Javascript anonymous function or function name.
303         *
304         * Example usage:
305         * @code
306         * $CKEditor->addGlobalEventHandler('dialogDefinition', 'function (ev) {
307         *     alert("Loading dialog: " + ev.data.name);
308         * }');
309         * @endcode
310         */
311        public function addGlobalEventHandler($event, $javascriptCode)
312        {
313                if (!isset($this->globalEvents[$event])) {
314                        $this->globalEvents[$event] = array();
315                }
316                // Avoid duplicates.
317                if (!in_array($javascriptCode, $this->globalEvents[$event])) {
318                        $this->globalEvents[$event][] = $javascriptCode;
319                }
320        }
321
322        /**
323         * Clear registered global event handlers.
324         * Note: this function will have no effect if the event handler has been already printed/returned.
325         *
326         * @param $event (string) Event name, if not set all event handlers will be removed (optional).
327         */
328        public function clearGlobalEventHandlers($event = null)
329        {
330                if (!empty($event)) {
331                        $this->globalEvents[$event] = array();
332                }
333                else {
334                        $this->globalEvents = array();
335                }
336        }
337
338        /**
339         * Prints javascript code.
340         *
341         * @param string $js
342         */
343        private function script($js)
344        {
345                $out = "<script type=\"text/javascript\">";
346                $out .= "//<![CDATA[\n";
347                $out .= $js;
348                $out .= "\n//]]>";
349                $out .= "</script>\n";
350
351                return $out;
352        }
353
354        /**
355         * Returns the configuration array (global and instance specific settings are merged into one array).
356         *
357         * @param $config (array) The specific configurations to apply to editor instance.
358         * @param $events (array) Event listeners for editor instance.
359         */
360        private function configSettings($config = array(), $events = array())
361        {
362                $_config = $this->config;
363                $_events = $this->events;
364
365                if (is_array($config) && !empty($config)) {
366                        $_config = array_merge($_config, $config);
367                }
368
369                if (is_array($events) && !empty($events)) {
370                        foreach ($events as $eventName => $code) {
371                                if (!isset($_events[$eventName])) {
372                                        $_events[$eventName] = array();
373                                }
374                                if (!in_array($code, $_events[$eventName])) {
375                                        $_events[$eventName][] = $code;
376                                }
377                        }
378                }
379
380                if (!empty($_events)) {
381                        foreach($_events as $eventName => $handlers) {
382                                if (empty($handlers)) {
383                                        continue;
384                                }
385                                else if (count($handlers) == 1) {
386                                        $_config['on'][$eventName] = '@@'.$handlers[0];
387                                }
388                                else {
389                                        $_config['on'][$eventName] = '@@function (ev){';
390                                        foreach ($handlers as $handler => $code) {
391                                                $_config['on'][$eventName] .= '('.$code.')(ev);';
392                                        }
393                                        $_config['on'][$eventName] .= '}';
394                                }
395                        }
396                }
397
398                return $_config;
399        }
400
401        /**
402         * Return global event handlers.
403         */
404        private function returnGlobalEvents()
405        {
406                static $returnedEvents;
407                $out = "";
408
409                if (!isset($returnedEvents)) {
410                        $returnedEvents = array();
411                }
412
413                if (!empty($this->globalEvents)) {
414                        foreach ($this->globalEvents as $eventName => $handlers) {
415                                foreach ($handlers as $handler => $code) {
416                                        if (!isset($returnedEvents[$eventName])) {
417                                                $returnedEvents[$eventName] = array();
418                                        }
419                                        // Return only new events
420                                        if (!in_array($code, $returnedEvents[$eventName])) {
421                                                $out .= ($code ? "\n" : "") . "CKEDITOR.on('". $eventName ."', $code);";
422                                                $returnedEvents[$eventName][] = $code;
423                                        }
424                                }
425                        }
426                }
427
428                return $out;
429        }
430
431        /**
432         * Initializes CKEditor (executed only once).
433         */
434        private function init()
435        {
436                static $initComplete;
437                $out = "";
438
439                if (!empty($initComplete)) {
440                        return "";
441                }
442
443                if ($this->initialized) {
444                        $initComplete = true;
445                        return "";
446                }
447
448                $args = "";
449                $ckeditorPath = $this->ckeditorPath();
450
451                if (!empty($this->timestamp) && $this->timestamp != "%"."TIMESTAMP%") {
452                        $args = '?t=' . $this->timestamp;
453                }
454
455                // Skip relative paths...
456                if (strpos($ckeditorPath, '..') !== 0) {
457                        $out .= $this->script("window.CKEDITOR_BASEPATH='". $ckeditorPath ."';");
458                }
459
460                $out .= "<script type=\"text/javascript\" src=\"" . $ckeditorPath . 'ckeditor.js' . $args . "\"></script>\n";
461
462                $extraCode = "";
463                if ($this->timestamp != self::timestamp) {
464                        $extraCode .= ($extraCode ? "\n" : "") . "CKEDITOR.timestamp = '". $this->timestamp ."';";
465                }
466                if ($extraCode) {
467                        $out .= $this->script($extraCode);
468                }
469
470                $initComplete = $this->initialized = true;
471
472                return $out;
473        }
474
475        /**
476         * Return path to ckeditor.js.
477         */
478        private function ckeditorPath()
479        {
480                if (!empty($this->basePath)) {
481                        return $this->basePath;
482                }
483
484                /**
485                 * The absolute pathname of the currently executing script.
486                 * Note: If a script is executed with the CLI, as a relative path, such as file.php or ../file.php,
487                 * $_SERVER['SCRIPT_FILENAME'] will contain the relative path specified by the user.
488                 */
489                if (isset($_SERVER['SCRIPT_FILENAME'])) {
490                        $realPath = dirname($_SERVER['SCRIPT_FILENAME']);
491                }
492                else {
493                        /**
494                         * realpath - Returns canonicalized absolute pathname
495                         */
496                        $realPath = realpath( './' ) ;
497                }
498
499                /**
500                 * The filename of the currently executing script, relative to the document root.
501                 * For instance, $_SERVER['PHP_SELF'] in a script at the address http://example.com/test.php/foo.bar
502                 * would be /test.php/foo.bar.
503                 */
504                $selfPath = dirname($_SERVER['PHP_SELF']);
505                $file = str_replace("\\", "/", __FILE__);
506
507                if (!$selfPath || !$realPath || !$file) {
508                        return "/ckeditor/";
509                }
510
511                $documentRoot = substr($realPath, 0, strlen($realPath) - strlen($selfPath));
512                $fileUrl = substr($file, strlen($documentRoot));
513                $ckeditorUrl = str_replace("ckeditor_php5.php", "", $fileUrl);
514
515                return $ckeditorUrl;
516        }
517
518        /**
519         * This little function provides a basic JSON support.
520         * http://php.net/manual/en/function.json-encode.php
521         *
522         * @param mixed $val
523         * @return string
524         */
525        private function jsEncode($val)
526        {
527                if (is_null($val)) {
528                        return 'null';
529                }
530                if ($val === false) {
531                        return 'false';
532                }
533                if ($val === true) {
534                        return 'true';
535                }
536                if (is_scalar($val))
537                {
538                        if (is_float($val))
539                        {
540                                // Always use "." for floats.
541                                $val = str_replace(",", ".", strval($val));
542                        }
543
544                        // Use @@ to not use quotes when outputting string value
545                        if (strpos($val, '@@') === 0) {
546                                return substr($val, 2);
547                        }
548                        else {
549                                // All scalars are converted to strings to avoid indeterminism.
550                                // PHP's "1" and 1 are equal for all PHP operators, but
551                                // JS's "1" and 1 are not. So if we pass "1" or 1 from the PHP backend,
552                                // we should get the same result in the JS frontend (string).
553                                // Character replacements for JSON.
554                                static $jsonReplaces = array(array("\\", "/", "\n", "\t", "\r", "\b", "\f", '"'),
555                                array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"'));
556
557                                $val = str_replace($jsonReplaces[0], $jsonReplaces[1], $val);
558                                if (strtoupper(substr($val, 0, 9)) == 'CKEDITOR.') {
559                                        return $val;
560                                }
561
562                                return '"' . $val . '"';
563                        }
564                }
565                $isList = true;
566                for ($i = 0, reset($val); $i < count($val); $i++, next($val))
567                {
568                        if (key($val) !== $i)
569                        {
570                                $isList = false;
571                                break;
572                        }
573                }
574                $result = array();
575                if ($isList)
576                {
577                        foreach ($val as $v) $result[] = $this->jsEncode($v);
578                        return '[ ' . join(', ', $result) . ' ]';
579                }
580                else
581                {
582                        foreach ($val as $k => $v) $result[] = $this->jsEncode($k).': '.$this->jsEncode($v);
583                        return '{ ' . join(', ', $result) . ' }';
584                }
585        }
586}
Note: See TracBrowser for help on using the repository browser.