Passed
Push — main ( b65dce...bff77a )
by Stefan
04:12
created

FormCKEdit::readAdditionalXML()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace SKien\Formgenerator;
5
6
/**
7
 * WYSIWYG - HTML input using CKEditor.
8
 * uses CKEditor Version 4.15
9
 *
10
 * For more information about download, install and integration of the CKEditor, see
11
 * CKEditorIntegration.md
12
 *
13
 * To enable filebrowsing on the server for the insert mage and insert link functionality
14
 * the RichFilemanager is used. For more information see
15
 * RichFilemanager.md
16
 *
17
 * @package Formgenerator
18
 * @author Stefanius <[email protected]>
19
 * @copyright MIT License - see the LICENSE file for details
20
 */
21
class FormCKEdit extends FormTextArea
22
{
23
    /** 'Source' - Button   */
24
    public const TB_SOURCE           = 0x00000002;
25
    /** Basic Styles:  Bold, Italic, Underline, Subscript, Superscript, RemoveFormat    */
26
    public const TB_BASIC_STYLES     = 0x00000004;
27
    /** Paragraph Formation: NumberedList, BulletedList, Outdent, Indent, JustifyLeft, -Center, -Right' */
28
    public const TB_PARAGRAPH        = 0x00000008;
29
    /** Links: link, Unlink */
30
    public const TB_LINKS            = 0x00000010;
31
    /** Insert Image    */
32
    public const TB_IMAGE            = 0x00000020;
33
    /** Colors: Text-, Backgroundcolor  */
34
    public const TB_COLOR            = 0x00000040;
35
    /** insert Table   */
36
    public const TB_TABLE            = 0x00000080;
37
    /** SelectBox for defined Styles    */
38
    public const TB_STYLES_SELECT    = 0x00000100;
39
    /** Select predefined Templates */
40
    public const TB_TEMPLATES        = 0x00000200;
41
    /** SelectBox for Placeholders  */
42
    public const TB_PLACEHOLDERS     = 0x00000400;
43
    /** Insert Codesnippet  */
44
    public const TB_SNIPPET          = 0x00000800;
45
    /** Insert Special Chars  */
46
    public const TB_SPECIAL_CHAR     = 0x00001000;
47
    /** Insert Iframe  */
48
    public const TB_IFRAME           = 0x00002000;
49
50
    /** small toolbar (only basic styles)   */
51
    public const TB_SMALL    = 0x00000004; // TB_BASIC_STYLES;
52
    /** insert objects   */
53
    public const TB_INSERT   = 0x000038A0; // TB_IMAGE | TB_TABLE | TB_SNIPPET | TB_SPECIAL_CHAR | TB_IFRAME
54
    /** toolbar for content edit (no colors, templates and objects)   */
55
    public const TB_CONTENT  = 0xfffff53d; // 0xffffffff & ~(TB_COLOR | TB_TEMPLATES | TB_INSERT | TB_SOURCE);
56
    /** full toolbar (no templates)   */
57
    public const TB_FULL     = 0xfffffdfd; // 0xffffffff & ~(TB_TEMPLATES | TB_SOURCE);
58
59
    /** custom button only with text   */
60
    public const BTN_TEXT           = 0x01;
61
    /** custom button only with icon   */
62
    public const BTN_ICON           = 0x02;
63
    /** custom button with text and icon  */
64
    public const BTN_TEXT_ICON      = 0x03;
65
66
    /** @var string the CSS file used inside the editarea    */
67
    protected string $strContentsCss = '';
68
    /** @var string the id of the editarea   */
69
    protected string $strBodyID;
70
    /** @var array custom button definition ["func" => <buttonhandler>, "name" => <buttonname>]    */
71
    protected array $aCustomBtn = [];
72
    /** @var string allowed content    */
73
    protected string $strAllowedContent = '';
74
    /** @var int toolbar mask    */
75
    protected int $lToolbar;
76
    /** @var string initial folder to expand in filemanager for links   */
77
    protected string $strBrowseFolderLinkURL = '';
78
    /** @var string initial folder to expand in filemanager for images   */
79
    protected string $strBrowseFolderImageURL = '';
80
    /** @var string initial folder to expand in filemanager for image links   */
81
    protected string $strBrowseFolderImageLinkURL = '';
82
83
    /**
84
     * Creates a WYSIWYG editor.
85
     * @param string $strName
86
     * @param int $iRows
87
     * @param string $strWidth
88
     * @param int $wFlags
89
     */
90
    public function __construct(string $strName, int $iRows, string $strWidth = '100%', int $wFlags = 0)
91
    {
92
        // add 2 rows to increase height for toolbar
93
        parent::__construct($strName, 0, $iRows + 2, $strWidth, $wFlags);
94
        $this->strBodyID = 'editarea';
95
        $this->lToolbar = self::TB_CONTENT;
96
    }
97
98
    /**
99
     * {@inheritDoc}
100
     * @see \SKien\Formgenerator\FormElement::fromXML()
101
     */
102
    static public function fromXML(\DOMElement $oXMLElement, FormCollection $oFormParent) : ?FormElement
103
    {
104
        $strName = self::getAttribString($oXMLElement, 'name', '');
105
        $iRows = self::getAttribInt($oXMLElement, 'rows', 10);
106
        $strWidth = self::getAttribString($oXMLElement, 'width', '100%');
107
        $wFlags = self::getAttribFlags($oXMLElement);
108
        $oFormElement = new self($strName, $iRows, $strWidth, $wFlags);
109
        $oFormParent->add($oFormElement);
110
        $oFormElement->readAdditionalXML($oXMLElement);
111
        return $oFormElement;
112
    }
113
114
    /**
115
     * {@inheritDoc}
116
     * @see \SKien\Formgenerator\FormElement::readAdditionalXML()
117
     */
118
    public function readAdditionalXML(\DOMElement $oXMLElement) : void
119
    {
120
        parent::readAdditionalXML($oXMLElement);
121
        $this->setContentsCss(self::getAttribString($oXMLElement, 'content-css', ''));
122
        $this->setBodyID(self::getAttribString($oXMLElement, 'body-id', ''));
123
    }
124
125
    /**
126
     * Set the CSS file to use in the edit area.
127
     * @param string $strContentsCss
128
     */
129
    public function setContentsCss(string $strContentsCss) : void
130
    {
131
        $this->strContentsCss = $strContentsCss;
132
    }
133
134
    /**
135
     * Set the ID of the body.
136
     * This is the ID of the 'Container' element in which the text to be edited here
137
     * should be displayed. This ID is required so that the correct CSS selectors are
138
     * used for the display here in the editor.
139
     * @param string $strBodyID
140
     */
141
    public function setBodyID(string $strBodyID) : void
142
    {
143
        $this->strBodyID = $strBodyID;
144
    }
145
146
    /**
147
     * Add custom button to the beginning of the toolbar.
148
     * If icon specified take care it the path is absolute or relative to the script that
149
     * containing this CKEditor.
150
     * @param string $strName       Name (Text) of the Button
151
     * @param string $strFunction   JS-Function to handle click (func gets editor as paramter)
152
     * @param string $strIcon       Icon for the button
153
     * @param int $iType            Type of the button (FormCKEdit::BTN_TEXT, FormCKEdit::BTN_ICON or FormCKEdit::BTN_TXET_ICON)
154
     */
155
    public function addCustomButton(string $strName, string $strFunction, string $strIcon = '', int $iType = self::BTN_TEXT) : void
156
    {
157
        if (empty($strIcon)) {
158
            $iType = self::BTN_TEXT;
159
        }
160
        $this->aCustomBtn[] = [
161
            'func' => $strFunction,
162
            'name' => $strName,
163
            'icon' => $strIcon,
164
            'type' => $iType,
165
        ];
166
    }
167
168
    /**
169
     * Specify allowed content (see documentation of CKEdit for details)
170
     * @param string $strAllowedContent leave empty to allow everything (default)
171
     */
172
    public function setAllowedContent(string $strAllowedContent = '') : void
173
    {
174
        $this->strAllowedContent = $strAllowedContent;
175
    }
176
177
    /**
178
     * @param string $strBrowseFolderLinkURL
179
     */
180
    public function setBrowseFolderLinkURL(string $strBrowseFolderLinkURL) : void
181
    {
182
        $this->strBrowseFolderLinkURL = $strBrowseFolderLinkURL;
183
    }
184
185
    /**
186
     * @param string $strBrowseFolderImageURL
187
     */
188
    public function setBrowseFolderImageURL(string $strBrowseFolderImageURL) : void
189
    {
190
        $this->strBrowseFolderImageURL = $strBrowseFolderImageURL;
191
    }
192
193
    /**
194
     * @param string $strBrowseFolderImageLinkURL
195
     */
196
    public function setBrowseFolderImageLinkURL(string $strBrowseFolderImageLinkURL) : void
197
    {
198
        $this->strBrowseFolderImageLinkURL = $strBrowseFolderImageLinkURL;
199
    }
200
201
    /**
202
     * Load some configuratin after parent set.
203
     * {@inheritDoc}
204
     * @see \SKien\Formgenerator\FormElement::onParentSet()
205
     */
206
    protected function onParentSet() : void
207
    {
208
        $aCKEditor = [
209
            'Path' => $this->oFG->getConfig()->getString('CKEditor.Path'),
210
            'editorID' => $this->strName,
211
            'editorOptions' => $this->buildEditorOptions(),
212
            'customButtons' => $this->aCustomBtn,
213
        ];
214
        // pass the content through the JSON data
215
        if ($this->oFlags->isSet(FormFlags::SET_JSON_DATA)) {
216
            $aCKEditor['editorData'] = $this->oFG->getData()->getValue($this->strName);
217
        }
218
        $this->oFG->addConfigForJS('CKEditor', $aCKEditor);
219
220
        $strRfmPath = $this->oFG->getConfig()->getString('RichFilemanager.Path');
221
        if ($strRfmPath != '') {
222
            if (!file_exists($_SERVER['DOCUMENT_ROOT'] . $strRfmPath)) {
223
                if ($this->oFG->getDebugMode()) {
224
                    trigger_error('Can not find Rich Filemanager at [' . $_SERVER['DOCUMENT_ROOT'] . $strRfmPath . ']', E_USER_WARNING);
225
                }
226
            }
227
228
            $strBrowseFolderLinkURL = $this->getBrowseFolder($this->strBrowseFolderLinkURL, 'RichFilemanager.expandFolder.browseLinkURL');
229
            $strBrowseFolderImageURL = $this->getBrowseFolder($this->strBrowseFolderImageURL, 'RichFilemanager.expandFolder.browseImageURL');
230
            $strBrowseFolderImageLinkURL = $this->getBrowseFolder($this->strBrowseFolderImageLinkURL, 'RichFilemanager.expandFolder.browseImageLinkURL');
231
            $aRFN = [
232
                'Path' => $strRfmPath,
233
                'language' => $this->oFG->getConfig()->getString('RichFilemanager.language'),
234
                'expandFolder' => [
235
                    'browseLinkURL' => $strBrowseFolderLinkURL,
236
                    'browseImageURL' => $strBrowseFolderImageURL,
237
                    'browseImageLinkURL' => $strBrowseFolderImageLinkURL ?: $strBrowseFolderLinkURL
238
                ]
239
            ];
240
            $this->oFG->addConfigForJS('RichFilemanager', $aRFN);
241
        }
242
    }
243
244
    /**
245
     * Get the startfolder for different purposes.
246
     * @param string $strFolder
247
     * @param string $strConfig
248
     * @return string
249
     */
250
    protected function getBrowseFolder(string $strFolder, string $strConfig) : string
251
    {
252
        if (strlen($strFolder) > 0) {
253
            return $strFolder;
254
        }
255
        return $this->oFG->getConfig()->getString($strConfig);
256
    }
257
258
    /**
259
     * Build CKEditor specific styles.
260
     * @return string
261
     */
262
    public function getStyle() : string
263
    {
264
        // If custom toolbar buttons defined, for each button dependent on the his
265
        // type (TEXT, ICON, TEXT+ICON) some styles have to be set.
266
        $strStyle = '';
267
        foreach ($this->aCustomBtn as $aBtn) {
268
            $strBtn = strtolower($aBtn['func']);
269
            $strDisplayLabel = (($aBtn['type'] & self::BTN_TEXT) != 0) ? 'inline' : 'none';
270
            $strDisplayIcon = (($aBtn['type'] & self::BTN_ICON) != 0) ? 'inline' : 'none';
271
            $strStyle .= '.cke_button__' . $strBtn . '_icon { display: ' . $strDisplayIcon . ' !important; }' . PHP_EOL;
272
            $strStyle .= '.cke_button__' . $strBtn . '_label { display: ' . $strDisplayLabel . ' !important; }' . PHP_EOL;
273
        }
274
275
        if ($this->oFG->getConfig()->getString('RichFilemanager.Path')) {
276
            $strStyle .= PHP_EOL .
277
                ".fm-modal {" . PHP_EOL .
278
                "    z-index: 10011; /** Because CKEditor image dialog was at 10010 */" . PHP_EOL .
279
                "    width:80%;" . PHP_EOL .
280
                "    height:80%;" . PHP_EOL .
281
                "    top: 10%;" . PHP_EOL .
282
                "    left:10%;" . PHP_EOL .
283
                "    border:0;" . PHP_EOL .
284
                "    position:fixed;" . PHP_EOL .
285
                "}";
286
        }
287
288
        return $strStyle;
289
    }
290
291
    /**
292
     * Define toolbar to display.
293
     * @param int $lToolbar
294
     */
295
    public function setToolbar(int $lToolbar) : void
296
    {
297
        $this->lToolbar = $lToolbar;
298
    }
299
300
    /**
301
     * Returns currently defined toolbar.
302
     * @return int
303
     */
304
    public function getToolbar() : int
305
    {
306
        return $this->lToolbar;
307
    }
308
309
    /**
310
     * Build the options to create the CKEditor instance.
311
     * @return array
312
     */
313
    protected function buildEditorOptions() : array
314
    {
315
        if (strlen($this->strContentsCss) == 0) {
316
            trigger_error('No CSS Stylesheet set!', E_USER_WARNING);
317
        }
318
        $aCKEditor = [
319
            'contentsCss' => $this->strContentsCss,
320
            'skin' => $this->oFG->getConfig()->getString('CKEditor.skin', 'moonocolor'),
321
            'bodyId' => $this->strBodyID,
322
            'toolbar' => $this->buildToolbarDef(),
323
            'toolbarCanCollapse' => false,
324
            'uiColor' => $this->oFG->getConfig()->getString('CKEditor.uiColor', '#F8F8F8'),
325
            'pasteFilter' => $this->oFG->getConfig()->getString('CKEditor.pasteFilter', 'plain-text'),
326
            'colorButton_enableAutomatic' => $this->oFG->getConfig()->getBool('CKEditor.colorbutton.enableAutomatic', true),
327
            'colorButton_enableMore' => $this->oFG->getConfig()->getBool('CKEditor.colorbutton.enableMore', true),
328
            'allowedContent' => ($this->strAllowedContent ?: true),
329
            'resize_enabled' => false,
330
        ];
331
        $this->buildSelectableColors($aCKEditor);
332
        $this->buildPlaceholderSelect($aCKEditor);
333
334
        return $aCKEditor;
335
    }
336
337
    /**
338
     * Build config settings for the selectable colors.
339
     * @param array $aCKEditor
340
     */
341
    protected function buildSelectableColors(array &$aCKEditor) : void
342
    {
343
        $aSelectableColorsRaw = $this->oFG->getConfig()->getArray('CKEditor.colorbutton.selectableColors');
344
        $aSelectableColors = [];
345
        foreach ($aSelectableColorsRaw as $color) {
346
            $aSelectableColors[] = ltrim($color, '#');
347
        }
348
        if (($this->lToolbar & self::TB_COLOR) != 0 && count($aSelectableColors) > 0) {
349
            $strColors = '';
350
            $strSep = '';
351
            foreach ($aSelectableColors as $strColor) {
352
                $strColors .= $strSep . $strColor;
353
                $strSep = ',';
354
            }
355
            $aCKEditor['colorButton_colors'] = $strColors;
356
            $aCKEditor['colorButton_colorsPerRow'] = $this->oFG->getConfig()->getInt('CKEditor.colorbutton.colorsPerRow', 6);
357
        }
358
    }
359
360
    /**
361
     * Build config for available placeholders in the placeholder-combobox.
362
     * @param array $aCKEditor
363
     */
364
    protected function buildPlaceholderSelect(array &$aCKEditor) : void
365
    {
366
        $aPlaceholderselect = $this->oFG->getConfig()->getArray('CKEditor.placeholder');
367
        if (($this->lToolbar & self::TB_PLACEHOLDERS) != 0 && count($aPlaceholderselect) > 0) {
368
            $aCKEditor['placeholder_select'] = ['placeholders' => $aPlaceholderselect];
369
        }
370
    }
371
372
    /**
373
     * Returns currently defined toolbar as array for JSON-encoding.
374
     * @link https://ckeditor.com/latest/samples/toolbarconfigurator/index.html
375
     * @return array
376
     */
377
    protected function buildToolbarDef() : array
378
    {
379
        $aToolbar = [];
380
        $this->addCustomBtns($aToolbar);
381
        $this->addBasicStyleBtns($aToolbar);
382
        $this->addParagraphBtns($aToolbar);
383
        $this->addLinkBtns($aToolbar);
384
        $this->addInsertBtns($aToolbar);
385
        $this->addColorBtns($aToolbar);
386
        $this->addStyleSelect($aToolbar);
387
        $this->addTemplateSelect($aToolbar);
388
        $this->addPlaceholderSelect($aToolbar);
389
        $this->addSourceBtn($aToolbar);
390
391
        return $aToolbar;
392
    }
393
394
    /**
395
     * Add all custom buttons at start of the toolbar.
396
     * @param array $aToolbar reference to the toolbar array
397
     */
398
    protected function addCustomBtns(array &$aToolbar) : void
399
    {
400
        foreach ($this->aCustomBtn as $aBtn) {
401
            $aToolbar[] = ['items' => [$aBtn['func']]];
402
        }
403
    }
404
405
    /**
406
     * Add button group for basic styles.
407
     * @param array $aToolbar reference to the toolbar array
408
     */
409
    protected function addBasicStyleBtns(array &$aToolbar) : void
410
    {
411
        if (($this->lToolbar & self::TB_BASIC_STYLES) != 0) {
412
            $aToolbar[] = [
413
                'name' => 'basicstyles',
414
                'items' => [
415
                    'Bold',
416
                    'Italic',
417
                    'Underline',
418
                    'Subscript',
419
                    'Superscript',
420
                    '-',
421
                    'RemoveFormat',
422
                ]
423
            ];
424
        }
425
    }
426
427
    /**
428
     * Add button group for paragraph formating.
429
     * @param array $aToolbar reference to the toolbar array
430
     */
431
    protected function addParagraphBtns(array &$aToolbar) : void
432
    {
433
        if (($this->lToolbar & self::TB_PARAGRAPH) != 0) {
434
            $aToolbar[] = [
435
                'name' => 'paragraph',
436
                'items' => [
437
                    'NumberedList',
438
                    'BulletedList',
439
                    '-',
440
                    'Outdent',
441
                    'Indent',
442
                    '-',
443
                    'JustifyLeft',
444
                    'JustifyCenter',
445
                    'JustifyRight',
446
                ]
447
            ];
448
        }
449
    }
450
451
    /**
452
     * Add button group for links.
453
     * @param array $aToolbar reference to the toolbar array
454
     */
455
    protected function addLinkBtns(array &$aToolbar) : void
456
    {
457
        if (($this->lToolbar & self::TB_LINKS) != 0) {
458
            $aToolbar[] = [
459
                'name' => 'links',
460
                'items' => ['Link', 'Unlink']
461
            ];
462
        }
463
    }
464
465
    /**
466
     * Add button group to insert objects.
467
     * - Images
468
     * - Snippets
469
     * - Tables
470
     * - Special Chars
471
     * - IFrames
472
     * @param array $aToolbar reference to the toolbar array
473
     */
474
    protected function addInsertBtns(array &$aToolbar) : void
475
    {
476
        if (($this->lToolbar & self::TB_INSERT) != 0) {
477
            $aInsert = array();
478
            if (($this->lToolbar & self::TB_IMAGE) != 0) {
479
                $aInsert[] = 'Image';
480
            }
481
            if (($this->lToolbar & self::TB_SNIPPET) != 0) {
482
                $aInsert[] = 'CodeSnippet';
483
            }
484
            if (($this->lToolbar & self::TB_TABLE) != 0) {
485
                $aInsert[] = 'Table';
486
            }
487
            if (($this->lToolbar & self::TB_SPECIAL_CHAR) != 0) {
488
                $aInsert[] = 'SpecialChar';
489
            }
490
            if (($this->lToolbar & self::TB_IFRAME) != 0) {
491
                $aInsert[] = 'Iframe';
492
            }
493
            $aToolbar[] = ['name' => 'insert', 'items' => $aInsert];
494
        }
495
    }
496
497
    /**
498
     * Add button group for colors
499
     * @param array $aToolbar reference to the toolbar array
500
     */
501
    protected function addColorBtns(array &$aToolbar) : void
502
    {
503
        if (($this->lToolbar & self::TB_COLOR) != 0) {
504
            $aToolbar[] = ['name' => 'color', 'items' => ['TextColor', 'BGColor']];
505
        }
506
    }
507
508
    /**
509
     * Add select list for styles
510
     * @param array $aToolbar reference to the toolbar array
511
     */
512
    protected function addStyleSelect(array &$aToolbar) : void
513
    {
514
        if (($this->lToolbar & self::TB_STYLES_SELECT) != 0) {
515
            $aToolbar[] = ['items' => ['Styles']];
516
        }
517
    }
518
519
    /**
520
     * Add select list for templates
521
     * @param array $aToolbar reference to the toolbar array
522
     */
523
    protected function addTemplateSelect(array &$aToolbar) : void
524
    {
525
        if (($this->lToolbar & self::TB_TEMPLATES) != 0) {
526
            $aToolbar[] = ['items' => ['Templates']];
527
        }
528
    }
529
530
    /**
531
     * Add select list for placeholders
532
     * @param array $aToolbar reference to the toolbar array
533
     */
534
    protected function addPlaceholderSelect(array &$aToolbar) : void
535
    {
536
        if (($this->lToolbar & self::TB_PLACEHOLDERS) != 0 && count($this->oFG->getConfig()->getArray('CKEditor.placeholder')) > 0) {
537
            $aToolbar[] = ['items' => ['placeholder_select']];
538
        }
539
    }
540
541
    /**
542
     * Add button to switch in the source mode
543
     * @param array $aToolbar reference to the toolbar array
544
     */
545
    protected function addSourceBtn(array &$aToolbar) : void
546
    {
547
        if (($this->lToolbar & self::TB_SOURCE) != 0) {
548
            $aToolbar[] = ['name' => 'document', 'items' => ['Source']];
549
        }
550
    }
551
}
552