Project

General

Profile

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 = '4.6.2';
24
    /**
25
     * A constant string unique for each release of %CKEditor.
26
     */
27
    const timestamp = 'G2VG';
28
    /**
29
     * A string indicating the creation date of %CKEditor.
30
     * Do not change it unless you want to force browsers to not use previously cached version of %CKEditor.
31
     */
32
    public $timestamp = "G2VG";
33

    
34
    /**
35
     * URL to the %CKEditor installation directory (absolute or relative to document root).
36
     * If not set, CKEditor will try to guess it's path.
37
     *
38
     * Example usage:
39
     * @code
40
     * $CKEditor->basePath = '/ckeditor/';
41
     * @endcode
42
     */
43
    public $basePath;
44
    /**
45
     * An array that holds the global %CKEditor configuration.
46
     * For the list of available options, see http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.config.html
47
     *
48
     * Example usage:
49
     * @code
50
     * $CKEditor->config['height'] = 400;
51
     * // Use @@ at the beggining of a string to ouput it without surrounding quotes.
52
     * $CKEditor->config['width'] = '@@screen.width * 0.8';
53
     * @endcode
54
     */
55
    public $config = array();
56
    /**
57
     * A boolean variable indicating whether CKEditor has been initialized.
58
     * Set it to true only if you have already included
59
     * &lt;script&gt; tag loading ckeditor.js in your website.
60
     */
61
    public $initialized = false;
62
    /**
63
     * Boolean variable indicating whether created code should be printed out or returned by a function.
64
     *
65
     * Example 1: get the code creating %CKEditor instance and print it on a page with the "echo" function.
66
     * @code
67
     * $CKEditor = new CKEditor();
68
     * $CKEditor->bOutputAsBuffer = true;
69
     * $code = $CKEditor->editor("editor1", "<p>Initial value.</p>");
70
     * echo "<p>Editor 1:</p>";
71
     * echo $code;
72
     * @endcode
73
     */
74
    public $bOutputAsBuffer = false;
75
    /**
76
     * An array with textarea attributes.
77
     *
78
     * When %CKEditor is created with the editor() method, a HTML &lt;textarea&gt; element is created,
79
     * it will be displayed to anyone with JavaScript disabled or with incompatible browser.
80
     */
81
    public $textareaAttributes = array( "rows" => 8, "cols" => 60 );
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
   * json_last_error — JSON error codes
92
   */
93
    private $aMessage = array(
94
      'JSON_ERROR_NONE',
95
      'JSON_ERROR_DEPTH',
96
      'JSON_ERROR_STATE_MISMATCH',
97
      'JSON_ERROR_CTRL_CHAR',
98
      'JSON_ERROR_SYNTAX',
99
      'JSON_ERROR_UTF8',
100
      );
101
  /** Indents a flat JSON string to make it more human-readable. */
102
    public $prettyPrintJson = true;
103
    protected $sError = '';
104
    protected $iErrNo = 0;
105

    
106
    /**
107
     * Main Constructor.
108
     *
109
     *  @param $basePath (string) URL to the %CKEditor installation directory (optional).
110
     */
111
    public function __construct($basePath = null) {
112
        if (!empty($basePath)) {
113
            $this->basePath = $basePath;
114

    
115
        }
116
    }
117

    
118
    public function __set($name, $value)
119
    {
120
        throw new Exception('Tried to set a readonly or nonexisting property ['.$name.']!!');
121
    }
122

    
123
    public function __get($sPropertyName)
124
    {
125
        throw new Exception('Tried to get nonexisting property ['.$sPropertyName.']');
126
    }
127
    public function set($name, $value = '')
128
    {
129
        if (property_exists($this, $name)) {
130
            $this->$name = $value;
131
            return true;
132
        } else {
133
             $this->setError(4, 'variable set' . $name);
134
            return false;
135
        }
136
    }
137
/**
138
 * check if an error occured
139
 * @return bool
140
 */
141
    public function isError()
142
    {
143
        return (bool)$this->iErrNo;
144
    }
145

    
146
/**
147
 * returns last occured error number
148
 * @return integer number of last error
149
 */
150
    public function getErrNo()
151
    {
152
        return $this->iErrNo;
153
    }
154

    
155
/**
156
 * returns last occured error message
157
 * @return string message of last error
158
 */
159
    public function getError()
160
    {
161
        return $this->sError;
162
    }
163

    
164
/* *********************************************************************
165
 *  internal methods
166
 * ********************************************************************/
167
/**
168
 * set occured error
169
 * @param int $iErr Number of the error
170
 * @param string $sError Error message
171
 */
172
    protected function setError($iErr = 0, $sError = 'unknown error')
173
    {
174
        $this->iErrNo = $iErr;
175
        $this->sError = $sError;
176
    }
177

    
178

    
179
    /**
180
     * Creates a %CKEditor instance.
181
     * In incompatible browsers %CKEditor will downgrade to plain HTML &lt;textarea&gt; element.
182
     *
183
     * @param $name (string) Name of the %CKEditor instance (this will be also the "name" attribute of textarea element).
184
     * @param $value (string) Initial value (optional).
185
     * @param $config (array) The specific configurations to apply to this editor instance (optional).
186
     * @param $events (array) Event listeners for this editor instance (optional).
187
     *
188
     * Example usage:
189
     * @code
190
     * $CKEditor = new CKEditor();
191
     * $CKEditor->editor("field1", "<p>Initial value.</p>");
192
     * @endcode
193
     *
194
     * Advanced example:
195
     * @code
196
     * $CKEditor = new CKEditor();
197
     * $config = array();
198
     * $config['toolbar'] = array(
199
     *     array( 'Source', '-', 'Bold', 'Italic', 'Underline', 'Strike' ),
200
     *     array( 'Image', 'Link', 'Unlink', 'Anchor' )
201
     * );
202
     * $events['instanceReady'] = 'function (ev) {
203
     *     alert("Loaded: " + ev.editor.name);
204
     * }';
205
     * $CKEditor->editor("field1", "<p>Initial value.</p>", $config, $events);
206
     * @endcode
207
     */
208
    public function editor($name, $value = "", $config = array(), $events = array())
209
    {
210
        $attr = "";
211
        foreach ($this->textareaAttributes as $key => $val) {
212
            $attr.= " " . $key . '="' . str_replace('"', '&quot;', $val) . '"';
213
        }
214
        $out = '<textarea id="' . $name . '" name="' . $name . '"'. $attr . '>' . htmlspecialchars($value) . '</textarea>'."\n";
215
        if (!$this->initialized) {
216
            $out .= $this->init();
217
        }
218
        $js = $this->returnGlobalEvents();
219
        $_config = $this->configSettings($config, $events);
220
        if (($_config)){
221
            $js .= "CKEDITOR.replace('".$name."', ".($this->jsEncode($_config)).");";
222
        } else {
223
            $js .= "CKEDITOR.replace('".$name."');";
224
        }
225
        $out .= $this->script($js);
226
        if (!$this->bOutputAsBuffer) {
227
            print $out;
228
            $out = "";
229
        }
230
        return $out;
231
    }
232

    
233
/**
234
 * Replaces a &lt;textarea&gt; with a %CKEditor instance.
235
 *
236
 * @param $id (string) The id or name of textarea element.
237
 * @param $config (array) The specific configurations to apply to this editor instance (optional).
238
 * @param $events (array) Event listeners for this editor instance (optional).
239
 *
240
 * Example 1: adding %CKEditor to &lt;textarea name="article"&gt;&lt;/textarea&gt; element:
241
 * @code
242
 * $CKEditor = new CKEditor();
243
 * $CKEditor->replace("article");
244
 * @endcode
245
 */
246
    public function replace($id, $config = array(), $events = array())
247
    {
248
        $out = "";
249
        if (!$this->initialized) {
250
            $out .= $this->init();
251
        }
252
        $_config = $this->configSettings($config, $events);
253
        $js = $this->returnGlobalEvents();
254
        if (($_config)) {
255
            $js .= "CKEDITOR.replace('".$id."', ".$this->jsEncode($_config).");";
256
        }
257
        else {
258
            $js .= "CKEDITOR.replace('".$id."');";
259
        }
260
        $out .= $this->script($js);
261
        if (!$this->bOutputAsBuffer) {
262
            print $out;
263
            $out = "";
264
        }
265
        return $out;
266
    }
267
/**
268
 * Replace all &lt;textarea&gt; elements available in the document with editor instances.
269
 *
270
 * @param $className (string) If set, replace all textareas with class className in the page.
271
 *
272
 * Example 1: replace all &lt;textarea&gt; elements in the page.
273
 * @code
274
 * $CKEditor = new CKEditor();
275
 * $CKEditor->replaceAll();
276
 * @endcode
277
 *
278
 * Example 2: replace all &lt;textarea class="myClassName"&gt; elements in the page.
279
 * @code
280
 * $CKEditor = new CKEditor();
281
 * $CKEditor->replaceAll( 'myClassName' );
282
 * @endcode
283
 */
284
    public function replaceAll($className = null)
285
    {
286
        $out = "";
287
        if (!$this->initialized) {
288
            $out .= $this->init();
289
        }
290
        $_config = $this->configSettings();
291
        $js = $this->returnGlobalEvents();
292
        if (empty($_config)) {
293
            if (empty($className)) {
294
                $js .= "CKEDITOR.replaceAll();";
295
            }
296
            else {
297
                $js .= "CKEDITOR.replaceAll('".$className."');";
298
            }
299
        } else {
300
            $classDetection = "";
301
            $js .= "CKEDITOR.replaceAll( function(textarea, config) {\n";
302
            if (!empty($className)) {
303
                $js .= "    var classRegex = new RegExp('(?:^| )' + '". $className ."' + '(?:$| )');\n";
304
                $js .= "    if (!classRegex.test(textarea.className))\n";
305
                $js .= "        return false;\n";
306
            }
307
            $js .= "    CKEDITOR.tools.extend(config, ". $this->jsEncode($_config) .", true);";
308
            $js .= "} );";
309
        }
310
        $out .= $this->script($js);
311
        if (!$this->bOutputAsBuffer) {
312
            print $out;
313
            $out = "";
314
        }
315
        return $out;
316
    }
317
/**
318
 * Adds event listener.
319
 * Events are fired by %CKEditor in various situations.
320
 *
321
 * @param $event (string) Event name.
322
 * @param $javascriptCode (string) Javascript anonymous function or function name.
323
 *
324
 * Example usage:
325
 * @code
326
 * $CKEditor->addEventHandler('instanceReady', 'function (ev) {
327
 *     alert("Loaded: " + ev.editor.name);
328
 * }');
329
 * @endcode
330
 */
331
    public function addEventHandler($event, $javascriptCode)
332
    {
333
        if (!isset($this->events[$event])) {
334
            $this->events[$event] = array();
335
        }
336
        // Avoid duplicates.
337
        if (!in_array($javascriptCode, $this->events[$event])) {
338
            $this->events[$event][] = $javascriptCode;
339
        }
340
    }
341
/**
342
 * Clear registered event handlers.
343
 * Note: this function will have no effect on already created editor instances.
344
 *
345
 * @param $event (string) Event name, if not set all event handlers will be removed (optional).
346
 */
347
    public function clearEventHandlers($event = null)
348
    {
349
        if (!empty($event)) {
350
            $this->events[$event] = array();
351
        }
352
        else {
353
            $this->events = array();
354
        }
355
    }
356
/**
357
 * Adds global event listener.
358
 *
359
 * @param $event (string) Event name.
360
 * @param $javascriptCode (string) Javascript anonymous function or function name.
361
 *
362
 * Example usage:
363
 * @code
364
 * $CKEditor->addGlobalEventHandler('dialogDefinition', 'function (ev) {
365
 *     alert("Loading dialog: " + ev.data.name);
366
 * }');
367
 * @endcode
368
 */
369
    public function addGlobalEventHandler($event, $javascriptCode)
370
    {
371
        if (!isset($this->globalEvents[$event])) {
372
            $this->globalEvents[$event] = array();
373
        }
374
        // Avoid duplicates.
375
        if (!in_array($javascriptCode, $this->globalEvents[$event])) {
376
            $this->globalEvents[$event][] = $javascriptCode;
377
        }
378
    }
379
/**
380
 * Clear registered global event handlers.
381
 * Note: this function will have no effect if the event handler has been already printed/returned.
382
 *
383
 * @param $event (string) Event name, if not set all event handlers will be removed (optional).
384
 */
385
    public function clearGlobalEventHandlers($event = null)
386
    {
387
        if (!empty($event)) {
388
            $this->globalEvents[$event] = array();
389
        }
390
        else {
391
            $this->globalEvents = array();
392
        }
393
    }
394
/**
395
 *
396
 *
397
 * @param
398
 */
399
    protected function loadBackendCss(  )
400
    {
401
        $sAddonName = basename(dirname(__DIR__));
402
        $out = ''
403
        . "<script type=\"text/javascript\">\n"
404
        . "if (document.querySelectorAll('.cke')) {LoadOnFly('head', "
405
        . "WB_URL+'/modules/".$sAddonName."/backend.css');}\n"
406
        . "</script>\n";
407
        return $out;
408
    }
409
/**
410
 * Prints javascript code.
411
 *
412
 * @param string $js
413
 */
414
    private function script($js)
415
    {
416
        $out  = "<script type=\"text/javascript\">";
417
        $out .= $js;
418
        $out .= "</script>\n";
419
        return $out;
420
    }
421
/**
422
 * Returns the configuration array (global and instance specific settings are merged into one array).
423
 *
424
 * @param $config (array) The specific configurations to apply to editor instance.
425
 * @param $events (array) Event listeners for editor instance.
426
 */
427
    private function configSettings($config = array(), $events = array())
428
    {
429
        $_config = $this->config;
430
        $_events = $this->events;
431
        if (is_array($config) && !empty($config)) {
432
            $_config = array_merge($_config, $config);
433
        }
434
        if (is_array($events) && !empty($events)) {
435
            foreach ($events as $eventName => $code) {
436
                if (!isset($_events[$eventName])) {
437
                    $_events[$eventName] = array();
438
                }
439
                if (!in_array($code, $_events[$eventName])) {
440
                    $_events[$eventName][] = $code;
441
                }
442
            }
443
        }
444
        if (!empty($_events)) {
445
            foreach($_events as $eventName => $handlers) {
446
                if (empty($handlers)) {
447
                    continue;
448
                } elseif (count($handlers) == 1) {
449
                    $_config['on'][$eventName] = '@@'.$handlers[0];
450
                } else {
451
                    $_config['on'][$eventName] = '@@function (ev){';
452
                    foreach ($handlers as $handler => $code) {
453
                        $_config['on'][$eventName] .= '('.$code.')(ev);';
454
                    }
455
                    $_config['on'][$eventName] .= '}';
456
                }
457
            }
458
        }
459
        return $_config;
460
    }
461
/**
462
 * CKEditor::setConfig()
463
 *
464
 * @param mixed $key
465
 * @param mixed $value
466
 * @return void
467
 */
468
    public function setConfig ( $key, $value ) {
469
        $this->config[$key] = $value;
470
    }
471
/**
472
 * Return global event handlers.
473
 */
474
    private function returnGlobalEvents()
475
    {
476
        static $returnedEvents;
477
        $out = "";
478
        if (!isset($returnedEvents)) {$returnedEvents = array();}
479
        if (!empty($this->globalEvents)) {
480
            foreach ($this->globalEvents as $eventName => $handlers) {
481
                foreach ($handlers as $handler => $code) {
482
                    if (!isset($returnedEvents[$eventName])) {
483
                        $returnedEvents[$eventName] = array();
484
                    }
485
                    // Return only new events
486
                    if (!in_array($code, $returnedEvents[$eventName])) {
487
                        $out .= ($code ? "\n" : "") . "CKEDITOR.on('". $eventName ."', $code);";
488
                        $returnedEvents[$eventName][] = $code;
489
                    }
490
                }
491
            }
492
        }
493
        return $out;
494
    }
495
/**
496
 * Initializes CKEditor (executed only once).
497
 */
498
    private function init()
499
    {
500
        static $initComplete;
501
        $out = "";
502
        if (!empty($initComplete)) {return "";}
503
        if ($this->initialized) {
504
            $initComplete = true;
505
            return "";
506
        }
507
        $out  = $this->loadBackendCss();
508
        $args = "";
509
        $ckeditorPath = $this->ckeditorPath();
510
        if (!empty($this->timestamp) && $this->timestamp != "%"."TIMESTAMP%") {
511
            $args = '?t=' . $this->timestamp;
512
        }
513
        // Skip relative paths...
514
        if (strpos($ckeditorPath, '..') !== 0) {
515
            $out .= $this->script("window.CKEDITOR_BASEPATH='". $ckeditorPath ."';");
516
        }
517
        $out .= "<script type=\"text/javascript\" src=\"" . $ckeditorPath . 'ckeditor.js' . $args . "\"></script>\n";
518
        $extraCode = "";
519
        if ($this->timestamp != self::timestamp) {
520
            $extraCode .= ($extraCode ? "\n" : "") . "CKEDITOR.timestamp = '". $this->timestamp ."';";
521
        }
522
        if ($extraCode) {
523
            $out .= $this->script($extraCode);
524
        }
525
        $initComplete = $this->initialized = true;
526
        return $out;
527
    }
528
/**
529
 * Return path to ckeditor.js.
530
 */
531
    private function ckeditorPath()
532
    {
533
        if (!empty($this->basePath)) {return $this->basePath;}
534
        /**
535
         * The absolute pathname of the currently executing script.
536
         * Note: If a script is executed with the CLI, as a relative path, such as file.php or ../file.php,
537
         * $_SERVER['SCRIPT_FILENAME'] will contain the relative path specified by the user.
538
         */
539
        if (isset($_SERVER['SCRIPT_FILENAME'])) {
540
            $realPath = dirname($_SERVER['SCRIPT_FILENAME']);
541
        } else {
542
            /**
543
             * realpath - Returns canonicalized absolute pathname
544
             */
545
            $realPath = realpath( './' ) ;
546
        }
547
        /**
548
         * The filename of the currently executing script, relative to the document root.
549
         * For instance, $_SERVER['PHP_SELF'] in a script at the address http://example.com/test.php/foo.bar
550
         * would be /test.php/foo.bar.
551
         */
552
        $selfPath = dirname($_SERVER['PHP_SELF']);
553
        $file = str_replace("\\", "/", __FILE__);
554
        if (!$selfPath || !$realPath || !$file) {return "/ckeditor/";}
555
        $documentRoot = substr($realPath, 0, strlen($realPath) - strlen($selfPath));
556
        $fileUrl = substr($file, strlen($documentRoot));
557
        $ckeditorUrl = str_replace("ckeditor_php5.php", "", $fileUrl);
558
        return $ckeditorUrl;
559
    }
560
  /**
561
   * CKEditor::setJsonEncode()
562
   * only works with UTF-8 encoded data.
563
   * in moment not in use was for test only
564
   *
565
   * @param mixed $obj Can be any type except a resource.
566
   * @param integer $iBitmask consisting of
567
   *                         PHP_JSON_HEX_TAG,
568
   *                         PHP_JSON_HEX_AMP,
569
   *                         PHP_JSON_HEX_APOS
570
   * @return string JSON representation of $obj
571
   *
572
   */
573
  public function setJsonEncode( $obj, $iBitmask = 0)
574
  {
575
    $iBitmask = JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT;
576
    //        $retJson = ( (version_compare(PHP_VERSION, '5.3.0') < 0 ) ? json_encode($obj) : json_encode($obj, $iBitmask ) );
577
    return '"'.str_replace( array(
578
      "\\",
579
      "/",
580
      "\n",
581
      "\t",
582
      "\r",
583
      "\x08",
584
      "\x0c",
585
      '"'), array(
586
      '\\\\',
587
      '\\/',
588
      '\\n',
589
      '\\t',
590
      '\\r',
591
      '\\b',
592
      '\\f',
593
      '\"'), json_encode( $obj)).'"';
594
  }
595
  /**
596
   * Format a flat JSON string to make it more human-readable
597
   * original code: http://www.daveperrett.com/articles/2008/03/11/format-json-with-php/
598
   * adapted to allow native functionality in php version >= 5.4.0
599
   *
600
   * @param string $json The original JSON string to process
601
   *        When the input is not a string it is assumed the input is RAW
602
   *        and should be converted to JSON first of all.
603
   * @return string Indented version of the original JSON string
604
   *
605
   */
606
  public function getPrettyPrintJson( $json)
607
  {
608
    if( !is_string( $json)) {
609
      if( phpversion() && ( phpversion() >= 5.4) && $this->prettyPrintJson) {
610
        return json_encode( $json, JSON_PRETTY_PRINT);
611
      }
612
      $json = json_encode( $json);
613
    }
614
    if( $this->prettyPrintJson === false) {
615
      return $json;
616
    }
617
    $result = '';
618
    $pos = 0; // indentation level
619
    $strLen = strlen( $json);
620
    $indentStr = "\t";
621
    $newLine = "\n";
622
    $prevChar = '';
623
    $outOfQuotes = true;
624
    for ( $i = 0; $i < $strLen; $i++)
625
    {
626
      // Grab the next character in the string
627
      $char = substr( $json, $i, 1);
628
      // Are we inside a quoted string?
629
      if( $char == '"' && $prevChar != '\\') {
630
        $outOfQuotes = !$outOfQuotes;
631
      } else
632
      // If this character is the end of an element,
633
      // output a new line and indent the next line
634
        if( ( $char == '}' || $char == ']') && $outOfQuotes) {
635
          $result .= $newLine;
636
          $pos--;
637
          for ( $j = 0; $j < $pos; $j++) {
638
            $result .= $indentStr;
639
          }
640
        } else
641
      // eat all non-essential whitespace in the input as we do our own here and it would only mess up our process
642
          if( $outOfQuotes && false !== strpos( " \t\r\n", $char)) {continue;}
643
      // Add the character to the result string
644
      $result .= $char;
645
      // always add a space after a field colon:
646
      if( $char == ':' && $outOfQuotes) {$result .= ' ';}
647
      // If the last character was the beginning of an element,
648
      // output a new line and indent the next line
649
      if( ( $char == ',' || $char == '{' || $char == '[') && $outOfQuotes) {
650
        $result .= $newLine;
651
        if( $char == '{' || $char == '[') {
652
          $pos++;
653
        }
654
        for ( $j = 0; $j < $pos; $j++) {
655
          $result .= $indentStr;
656
        }
657
      }
658
      $prevChar = $char;
659
    }
660
    return $result;
661
  }
662

    
663
  /**
664
   * Takes a JSON encoded string and converts it into a PHP variable
665
   * JSON::Decode()
666
   * @param mixed $json
667
   * @param bool $toAssoc
668
   * @return array
669
   */
670
    public function getJsonDecode( $json, $toAssoc = false)
671
    {
672
      $iError = 0;
673
      $retJson = json_decode( $json, $toAssoc);
674
      if( ( $iError = intval( json_last_error())) != 0) {
675
        throw new Exception( 'JSON Error: '.$this->aMessage[$iError]);
676
      }
677
      return $retJson;
678
    }
679
/**
680
 * This little function provides a basic JSON support.
681
 *
682
 * @param mixed $val
683
 * @return string
684
 */
685
    private function jsEncode($val)
686
    {
687
        return $this->getPrettyPrintJson( $val);
688
    }
689
}
(2-2/11)