ckeditor_php5.php (16038B)
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 */ 18 class 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 * <script> 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 <textarea> 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 <textarea> 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('"', '"', $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 <textarea> 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 <textarea name="article"></textarea> 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 <textarea> 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 <textarea> elements in the page. 205 * @code 206 * $CKEditor = new CKEditor(); 207 * $CKEditor->replaceAll(); 208 * @endcode 209 * 210 * Example 2: replace all <textarea class="myClassName"> 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 }