Passed
Push — feature/6.x ( 53903f...32797a )
by Schlaefer
05:38 queued 02:15
created

Parser::_initParser()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 19
nc 5
nop 1
dl 0
loc 33
rs 9.0111
c 1
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * Saito - The Threaded Web Forum
6
 *
7
 * @copyright Copyright (c) the Saito Project Developers
8
 * @link https://github.com/Schlaefer/Saito
9
 * @license http://opensource.org/licenses/MIT
10
 */
11
12
namespace BbcodeParser\Lib;
13
14
use Cake\View\Helper;
15
use BbcodeParser\Lib\jBBCode\Visitors;
16
use Saito\Markup\MarkupSettings;
17
18
class Parser
19
{
20
    /**
21
     * @var \JBBCode\Parser
22
     */
23
    protected $_Parser;
24
25
    protected $_Preprocessors;
26
27
    protected $_Postprocessors;
28
29
    /**
30
     * @var bool
31
     */
32
    private $areCombinedClassFilesLoaded = false;
33
34
    /**
35
     * @var array
36
     *
37
     * [
38
     *    <set title> => [
39
     *      <arbitrary definition title> => [ <definition> ]
40
     *    ]
41
     * ]
42
     *
43
     */
44
    protected $_tags = [
45
        'basic' => [
46
            // strong
47
            'b' => [
48
                'type' => 'replacement',
49
                'title' => 'b',
50
                'replacement' => '<strong>{param}</strong>',
51
            ],
52
            // code
53
            'codeWithAttributes' => [
54
                'type' => 'class',
55
                'title' => 'CodeWithAttributes',
56
            ],
57
            'codeWithoutAttributes' => [
58
                'type' => 'class',
59
                'title' => 'CodeWithoutAttributes',
60
            ],
61
            // edit marker
62
            'e' => [
63
                'type' => 'replacement',
64
                'title' => 'e',
65
                'replacement' => '<span class="richtext-editMark"></span>{param}',
66
            ],
67
            // float
68
            'float' => [
69
                'type' => 'replacement',
70
                'title' => 'float',
71
                'replacement' => '<div class="clearfix"></div><div class="richtext-float">{param}</div>',
72
            ],
73
            // email
74
            'email' => [
75
                'type' => 'class',
76
                'title' => 'email',
77
            ],
78
            'emailWithAttributes' => [
79
                'type' => 'class',
80
                'title' => 'emailWithAttributes',
81
            ],
82
            // hr
83
            'hr' => [
84
                'type' => 'replacement',
85
                'title' => 'hr',
86
                'replacement' => '<hr>{param}',
87
            ],
88
            '---' => [
89
                'type' => 'replacement',
90
                'title' => '---',
91
                'replacement' => '<hr>{param}',
92
            ],
93
            // emphasis
94
            'i' => [
95
                'type' => 'replacement',
96
                'title' => 'i',
97
                'replacement' => '<em>{param}</em>',
98
            ],
99
            // list
100
            'list' => [
101
                'type' => 'class',
102
                'title' => 'ulList',
103
            ],
104
            // spoiler
105
            'spoiler' => [
106
                'type' => 'class',
107
                'title' => 'spoiler',
108
            ],
109
            // strike
110
            's' => [
111
                'type' => 'replacement',
112
                'title' => 's',
113
                'replacement' => '<del>{param}</del>',
114
            ],
115
            'strike' => [
116
                'type' => 'replacement',
117
                'title' => 'strike',
118
                'replacement' => '<del>{param}</del>',
119
            ],
120
            // url
121
            'link' => [
122
                'type' => 'class',
123
                'title' => 'link',
124
            ],
125
            'linkWithAttributes' => [
126
                'type' => 'class',
127
                'title' => 'linkWithAttributes',
128
            ],
129
            'url' => [
130
                'type' => 'class',
131
                'title' => 'url',
132
            ],
133
            'urlWithAttributes' => [
134
                'type' => 'class',
135
                'title' => 'urlWithAttributes',
136
            ],
137
            // quotes
138
            'quote' => [
139
                'type' => 'replacement',
140
                'title' => 'quote',
141
                'replacement' => '<blockquote>{param}</blockquote>',
142
            ],
143
        ],
144
        'multimedia' => [
145
            'image' => [
146
                'type' => 'class',
147
                'title' => 'Image',
148
            ],
149
            'imageWithAttributes' => [
150
                'type' => 'class',
151
                'title' => 'ImageWithAttributes',
152
            ],
153
            'html5audio' => [
154
                'type' => 'class',
155
                'title' => 'Html5Audio',
156
            ],
157
            'html5audioWithAttributes' => [
158
                'type' => 'class',
159
                'title' => 'Html5AudioWithAttributes',
160
            ],
161
            'html5video' => [
162
                'type' => 'class',
163
                'title' => 'Html5Video',
164
            ],
165
            'html5videoWithAttributes' => [
166
                'type' => 'class',
167
                'title' => 'Html5VideoWithAttributes',
168
            ],
169
            'upload' => [
170
                'type' => 'class',
171
                'title' => 'Upload',
172
            ],
173
            'uploadWithAttributes' => [
174
                'type' => 'class',
175
                'title' => 'UploadWithAttributes',
176
            ],
177
            'fileWithAttributes' => [
178
                'type' => 'class',
179
                'title' => 'fileWithAttributes',
180
            ],
181
        ],
182
        'embed' => [
183
            'embed' => [
184
                'type' => 'class',
185
                'title' => 'Embed',
186
            ],
187
            'flash' => [
188
                'type' => 'class',
189
                'title' => 'Flash',
190
            ],
191
            'iframe' => [
192
                'type' => 'class',
193
                'title' => 'Iframe',
194
            ],
195
        ],
196
    ];
197
198
    /**
199
     * Initialized parsers
200
     *
201
     * @var array
202
     */
203
    protected $_initializedParsers = [];
204
205
    /**
206
     * @var \Saito\Markup\MarkupSettings cache for app settings
207
     */
208
    protected $_cSettings;
209
210
    /**
211
     * @var \Cake\View\Helper Helper usually the ParseHelper
212
     */
213
    protected $_Helper;
214
215
    /**
216
     * Constructor
217
     *
218
     * @param \Cake\View\Helper $Helper helper
219
     * @param \Saito\Markup\MarkupSettings $settings settings
220
     */
221
    public function __construct(Helper $Helper, MarkupSettings $settings)
222
    {
223
        $this->_Helper = $Helper;
224
        $this->_cSettings = $settings;
225
    }
226
227
    /**
228
     * Parses BBCode
229
     *
230
     * @param string $string string to parse
231
     * @param array $options options
232
     * - `return` string "html"|"text" result type
233
     * - `multimedia` bool true|false parse or ignore multimedia content
234
     * - `embed` bool true|false parse or ignore embed content
235
     *
236
     * @return mixed|string
237
     */
238
    public function parse($string, array $options = [])
239
    {
240
        $options += [
241
            'embed' => true,
242
            'multimedia' => true,
243
            'return' => 'html',
244
        ];
245
246
        $this->_initParser($options);
247
248
        $string = $this->_Preprocessors->process($string);
249
250
        $this->_Parser->parse($string);
251
252
        $this->_Parser->accept(
253
            new Visitors\JbbCodeNl2BrVisitor($this->_Helper, $this->_cSettings)
254
        );
255
        if ($this->_cSettings->get('autolink')) {
256
            $this->_Parser->accept(
257
                new Visitors\JbbCodeAutolinkVisitor($this->_Helper, $this->_cSettings)
258
            );
259
        }
260
        if ($this->_cSettings->get('smilies')) {
261
            $this->_Parser->accept(
262
                new Visitors\JbbCodeSmileyVisitor($this->_Helper, $this->_cSettings)
263
            );
264
        }
265
266
        switch ($options['return']) {
267
            case 'text':
268
                $html = $this->_Parser->getAsText();
269
                break;
270
            default:
271
                $html = $this->_Parser->getAsHtml();
272
        }
273
274
        $html = $this->_Postprocessors->process($html);
275
276
        return $html;
277
    }
278
279
    /**
280
     * Init parser
281
     *
282
     * @param array $options merged MarkupSettings and parse-run-option
283
     *
284
     * @return void
285
     * @throws \Exception
286
     */
287
    protected function _initParser($options)
288
    {
289
        // serializing complex objects kills PHP
290
        $serializable = array_filter(
291
            $options,
292
            function ($value) {
293
                return !is_object($value);
294
            }
295
        );
296
        $parserId = md5(serialize($serializable));
297
        if (isset($this->_initializedParsers[$parserId])) {
298
            $this->_Parser = $this->_initializedParsers[$parserId];
299
300
            return;
301
        }
302
303
        $this->_Parser = new \JBBCode\Parser();
304
        $this->_addDefinitionSet('basic', $this->_cSettings);
305
306
        if ($this->_cSettings->get('bbcode_img') && $options['multimedia']) {
307
            $this->_addDefinitionSet('multimedia', $this->_cSettings);
308
        }
309
310
        if ($this->_cSettings->get('bbcode_img') && $options['embed']) {
311
            $this->_addDefinitionSet('embed', $this->_cSettings);
312
        }
313
314
        $this->_Preprocessors = new Processors\BbcodeProcessorCollection();
315
        $this->_Preprocessors->add(new Processors\BbcodePreparePreprocessor($this->_cSettings));
316
        $this->_Postprocessors = new Processors\BbcodeProcessorCollection();
317
        $this->_Postprocessors->add(new Processors\BbcodeQuotePostprocessor($this->_cSettings));
318
319
        $this->_initializedParsers[$parserId] = $this->_Parser;
320
    }
321
322
    /**
323
     * Add definitin set
324
     *
325
     * @param string $set set
326
     * @param \Saito\Markup\MarkupSettings $options options
327
     *
328
     * @return void
329
     * @throws \Exception
330
     */
331
    protected function _addDefinitionSet($set, MarkupSettings $options)
332
    {
333
        $this->loadCombinedClassFiles();
334
335
        foreach ($this->_tags[$set] as $definition) {
336
            $title = $definition['title'];
337
            switch ($definition['type']) {
338
                case 'replacement':
339
                    $builder = new \JBBCode\CodeDefinitionBuilder(
340
                        $title,
341
                        $definition['replacement']
342
                    );
343
                    $this->_Parser->addCodeDefinition($builder->build());
344
                    break;
345
                case 'class':
346
                    $class = '\BbcodeParser\Lib\jBBCode\Definitions\\' . ucfirst($title);
347
                    $this->_Parser->addCodeDefinition(new $class($this->_Helper, $options));
348
                    break;
349
                default:
350
                    throw new \Exception();
351
            }
352
        }
353
    }
354
355
    /**
356
     * Class combined definition class files before first usage
357
     *
358
     * @return void
359
     */
360
    protected function loadCombinedClassFiles()
361
    {
362
        if ($this->areCombinedClassFilesLoaded) {
363
            return;
364
        }
365
366
        $folder = __DIR__ . '/jBBCode/Definitions/';
367
        require_once $folder . 'JbbCodeDefinitions.php';
368
        require_once $folder . 'JbbHtml5MediaCodeDefinition.php';
369
        require_once $folder . 'JbbCodeCodeDefinition.php';
370
371
        $this->areCombinedClassFilesLoaded = true;
372
    }
373
}
374