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