1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace neon\cms\services\cmsEditor; |
4
|
|
|
|
5
|
|
|
|
6
|
|
|
use neon\core\helpers\Hash; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Class Cmp |
10
|
|
|
* |
11
|
|
|
* Holds the block structure in memory |
12
|
|
|
* |
13
|
|
|
* @since 3.8.0 |
14
|
|
|
*/ |
15
|
|
|
class Cmp |
16
|
|
|
{ |
17
|
|
|
/** |
18
|
|
|
* Name of block |
19
|
|
|
* |
20
|
|
|
* @example "core/paragraph" |
21
|
|
|
* |
22
|
|
|
* @since 3.8.0 |
23
|
|
|
* @var string |
24
|
|
|
*/ |
25
|
|
|
public $cmp; |
26
|
|
|
/** |
27
|
|
|
* Optional set of attributes from block comment delimiters |
28
|
|
|
* |
29
|
|
|
* @example null |
30
|
|
|
* @example array( 'columns' => 3 ) |
31
|
|
|
* |
32
|
|
|
* @since 3.8.0 |
33
|
|
|
* @var array|null |
34
|
|
|
*/ |
35
|
|
|
public $props; |
36
|
|
|
/** |
37
|
|
|
* List of inner blocks (of this same class) |
38
|
|
|
* |
39
|
|
|
* @since 3.8.0 |
40
|
|
|
* @var \neon\cms\components\WP_Block_Parser_Block[] |
41
|
|
|
*/ |
42
|
|
|
// public $innerBlocks; |
43
|
|
|
/** |
44
|
|
|
* Resultant HTML from inside block comment delimiters |
45
|
|
|
* after removing inner blocks |
46
|
|
|
* |
47
|
|
|
* @example "...Just <!-- wp:test /--> testing..." -> "Just testing..." |
48
|
|
|
* |
49
|
|
|
* @since 3.8.0 |
50
|
|
|
* @var string |
51
|
|
|
*/ |
52
|
|
|
// public $innerHTML; |
53
|
|
|
/** |
54
|
|
|
* List of string fragments and null markers where inner blocks were found |
55
|
|
|
* |
56
|
|
|
* @example array( |
57
|
|
|
* 'innerHTML' => 'BeforeInnerAfter', |
58
|
|
|
* 'innerBlocks' => array( block, block ), |
59
|
|
|
* 'innerContent' => array( 'Before', null, 'Inner', null, 'After' ), |
60
|
|
|
* ) |
61
|
|
|
* |
62
|
|
|
* @since 4.2.0 |
63
|
|
|
* @var array |
64
|
|
|
*/ |
65
|
|
|
// public $innerContent; |
66
|
|
|
|
67
|
|
|
public $uuid = ''; |
68
|
|
|
|
69
|
|
|
public $children = []; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Constructor. |
73
|
|
|
* |
74
|
|
|
* Will populate object properties from the provided arguments. |
75
|
|
|
* |
76
|
|
|
* @param string $name Name of block. |
77
|
|
|
* @param array $attrs Optional set of attributes from block comment delimiters. |
78
|
|
|
* @param array $innerBlocks List of inner blocks (of this same class). |
79
|
|
|
* @param string $innerHTML Resultant HTML from inside block comment delimiters after removing inner blocks. |
80
|
|
|
* @param array $innerContent List of string fragments and null markers where inner blocks were found. |
81
|
|
|
* @since 3.8.0 |
82
|
|
|
* |
83
|
|
|
*/ |
84
|
|
|
function __construct($name, $attrs, $innerBlocks, $innerHTML, $innerContent, $uuid=null) |
|
|
|
|
85
|
|
|
{ |
86
|
|
|
$this->cmp = $name; |
87
|
|
|
$this->props = $attrs; |
88
|
|
|
//$this->innerBlocks = $innerBlocks; |
89
|
|
|
//$this->innerHTML = $innerHTML; |
90
|
|
|
//$this->innerContent = $innerContent; |
91
|
|
|
if (isset($attrs['uuid']) ) { |
92
|
|
|
$uuid = $attrs['uuid']; |
93
|
|
|
} |
94
|
|
|
$this->uuid = $uuid ? $uuid : Hash::uuid64(); |
|
|
|
|
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Class CmpFrame |
100
|
|
|
* |
101
|
|
|
* Holds partial blocks in memory while parsing |
102
|
|
|
* |
103
|
|
|
* @internal |
104
|
|
|
* @since 3.8.0 |
105
|
|
|
*/ |
106
|
|
|
class CmpFrame |
107
|
|
|
{ |
108
|
|
|
/** |
109
|
|
|
* Full or partial block |
110
|
|
|
* |
111
|
|
|
* @since 3.8.0 |
112
|
|
|
* @var CmpFrame |
113
|
|
|
*/ |
114
|
|
|
public $block; |
115
|
|
|
/** |
116
|
|
|
* Byte offset into document for start of parse token |
117
|
|
|
* |
118
|
|
|
* @since 3.8.0 |
119
|
|
|
* @var int |
120
|
|
|
*/ |
121
|
|
|
public $token_start; |
122
|
|
|
/** |
123
|
|
|
* Byte length of entire parse token string |
124
|
|
|
* |
125
|
|
|
* @since 3.8.0 |
126
|
|
|
* @var int |
127
|
|
|
*/ |
128
|
|
|
public $token_length; |
129
|
|
|
/** |
130
|
|
|
* Byte offset into document for after parse token ends |
131
|
|
|
* (used during reconstruction of stack into parse production) |
132
|
|
|
* |
133
|
|
|
* @since 3.8.0 |
134
|
|
|
* @var int |
135
|
|
|
*/ |
136
|
|
|
public $prev_offset; |
137
|
|
|
/** |
138
|
|
|
* Byte offset into document where leading HTML before token starts |
139
|
|
|
* |
140
|
|
|
* @since 3.8.0 |
141
|
|
|
* @var int |
142
|
|
|
*/ |
143
|
|
|
public $leading_html_start; |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Constructor |
147
|
|
|
* |
148
|
|
|
* Will populate object properties from the provided arguments. |
149
|
|
|
* |
150
|
|
|
* @param Cmp $block Full or partial block. |
151
|
|
|
* @param int $token_start Byte offset into document for start of parse token. |
152
|
|
|
* @param int $token_length Byte length of entire parse token string. |
153
|
|
|
* @param int $prev_offset Byte offset into document for after parse token ends. |
154
|
|
|
* @param int $leading_html_start Byte offset into document where leading HTML before token starts. |
155
|
|
|
* @since 3.8.0 |
156
|
|
|
* |
157
|
|
|
*/ |
158
|
|
|
function __construct($block, $token_start, $token_length, $prev_offset = null, $leading_html_start = null) |
159
|
|
|
{ |
160
|
|
|
$this->block = $block; |
|
|
|
|
161
|
|
|
$this->token_start = $token_start; |
162
|
|
|
$this->token_length = $token_length; |
163
|
|
|
$this->prev_offset = isset($prev_offset) ? $prev_offset : $token_start + $token_length; |
164
|
|
|
$this->leading_html_start = $leading_html_start; |
165
|
|
|
} |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
|
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* Class Parser |
172
|
|
|
* |
173
|
|
|
* Parses a document and constructs a list of parsed block objects |
174
|
|
|
* |
175
|
|
|
* @since 3.8.0 |
176
|
|
|
* @since 4.0.0 returns arrays not objects, all attributes are arrays |
177
|
|
|
*/ |
178
|
|
|
class Parser |
179
|
|
|
{ |
180
|
|
|
/** |
181
|
|
|
* Input document being parsed |
182
|
|
|
* |
183
|
|
|
* @example "Pre-text\n<!-- wp:paragraph -->This is inside a block!<!-- /wp:paragraph -->" |
184
|
|
|
* |
185
|
|
|
* @since 3.8.0 |
186
|
|
|
* @var string |
187
|
|
|
*/ |
188
|
|
|
public $document; |
189
|
|
|
/** |
190
|
|
|
* Tracks parsing progress through document |
191
|
|
|
* |
192
|
|
|
* @since 3.8.0 |
193
|
|
|
* @var int |
194
|
|
|
*/ |
195
|
|
|
public $offset; |
196
|
|
|
/** |
197
|
|
|
* List of parsed blocks |
198
|
|
|
* |
199
|
|
|
* @since 3.8.0 |
200
|
|
|
* @var Cmp[] |
201
|
|
|
*/ |
202
|
|
|
public $output; |
203
|
|
|
/** |
204
|
|
|
* Stack of partially-parsed structures in memory during parse |
205
|
|
|
* |
206
|
|
|
* @since 3.8.0 |
207
|
|
|
* @var CmpFrame[] |
208
|
|
|
*/ |
209
|
|
|
public $stack; |
210
|
|
|
/** |
211
|
|
|
* Empty associative array, here due to PHP quirks |
212
|
|
|
* |
213
|
|
|
* @since 4.4.0 |
214
|
|
|
* @var array empty associative array |
215
|
|
|
*/ |
216
|
|
|
public $empty_attrs; |
217
|
|
|
|
218
|
|
|
public $componentNamespace = 'ni:'; |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Parses a document and returns a list of block structures |
222
|
|
|
* |
223
|
|
|
* When encountering an invalid parse will return a best-effort |
224
|
|
|
* parse. In contrast to the specification parser this does not |
225
|
|
|
* return an error on invalid inputs. |
226
|
|
|
* |
227
|
|
|
* @param string $document Input document being parsed. |
228
|
|
|
* @return Cmp[] |
229
|
|
|
* @since 3.8.0 |
230
|
|
|
* |
231
|
|
|
*/ |
232
|
|
|
function parse($document) |
233
|
|
|
{ |
234
|
|
|
$this->document = $document; |
235
|
|
|
$this->offset = 0; |
236
|
|
|
$this->output = array(); |
237
|
|
|
$this->stack = array(); |
238
|
|
|
$this->empty_attrs = json_decode('{}', true); |
239
|
|
|
do { |
240
|
|
|
// twiddle our thumbs. |
241
|
|
|
} while ($this->proceed()); |
242
|
|
|
return $this->output; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Processes the next token from the input document |
247
|
|
|
* and returns whether to proceed eating more tokens |
248
|
|
|
* |
249
|
|
|
* This is the "next step" function that essentially |
250
|
|
|
* takes a token as its input and decides what to do |
251
|
|
|
* with that token before descending deeper into a |
252
|
|
|
* nested block tree or continuing along the document |
253
|
|
|
* or breaking out of a level of nesting. |
254
|
|
|
* |
255
|
|
|
* @return bool |
256
|
|
|
* @since 3.8.0 |
257
|
|
|
* @internal |
258
|
|
|
*/ |
259
|
|
|
function proceed() |
|
|
|
|
260
|
|
|
{ |
261
|
|
|
$next_token = $this->next_token(); |
262
|
|
|
list($token_type, $block_name, $attrs, $start_offset, $token_length) = $next_token; |
263
|
|
|
$stack_depth = count($this->stack); |
264
|
|
|
// we may have some HTML soup before the next block. |
265
|
|
|
$leading_html_start = $start_offset > $this->offset ? $this->offset : null; |
266
|
|
|
switch ($token_type) { |
267
|
|
|
case 'no-more-tokens': |
268
|
|
|
// if not in a block then flush output. |
269
|
|
|
if (0 === $stack_depth) { |
270
|
|
|
$this->add_freeform(); |
271
|
|
|
return false; |
272
|
|
|
} |
273
|
|
|
/* |
274
|
|
|
* Otherwise we have a problem |
275
|
|
|
* This is an error |
276
|
|
|
* |
277
|
|
|
* we have options |
278
|
|
|
* - treat it all as freeform text |
279
|
|
|
* - assume an implicit closer (easiest when not nesting) |
280
|
|
|
*/ |
281
|
|
|
// for the easy case we'll assume an implicit closer. |
282
|
|
|
if (1 === $stack_depth) { |
283
|
|
|
$this->add_block_from_stack(); |
284
|
|
|
return false; |
285
|
|
|
} |
286
|
|
|
/* |
287
|
|
|
* for the nested case where it's more difficult we'll |
288
|
|
|
* have to assume that multiple closers are missing |
289
|
|
|
* and so we'll collapse the whole stack piecewise |
290
|
|
|
*/ |
291
|
|
|
while (0 < count($this->stack)) { |
292
|
|
|
$this->add_block_from_stack(); |
293
|
|
|
} |
294
|
|
|
return false; |
295
|
|
|
case 'void-block': |
296
|
|
|
/* |
297
|
|
|
* easy case is if we stumbled upon a void block |
298
|
|
|
* in the top-level of the document |
299
|
|
|
*/ |
300
|
|
|
if (0 === $stack_depth) { |
301
|
|
|
if (isset($leading_html_start)) { |
302
|
|
|
$uuid = Hash::uuid64(); |
303
|
|
|
$this->output[$uuid] = (array)self::freeform( |
|
|
|
|
304
|
|
|
substr( |
305
|
|
|
$this->document, |
306
|
|
|
$leading_html_start, |
307
|
|
|
$start_offset - $leading_html_start |
308
|
|
|
), $uuid |
309
|
|
|
); |
310
|
|
|
} |
311
|
|
|
$cmp = (array)new Cmp($block_name, $attrs, array(), '', array(), $uuid); |
|
|
|
|
312
|
|
|
$this->output[$cmp->uuid] = $cmp; |
313
|
|
|
$this->offset = $start_offset + $token_length; |
314
|
|
|
return true; |
315
|
|
|
} |
316
|
|
|
// otherwise we found an inner block. |
317
|
|
|
$this->add_inner_block( |
318
|
|
|
new Cmp($block_name, $attrs, array(), '', array()), |
319
|
|
|
$start_offset, |
320
|
|
|
$token_length |
321
|
|
|
); |
322
|
|
|
$this->offset = $start_offset + $token_length; |
323
|
|
|
return true; |
324
|
|
|
case 'block-opener': |
325
|
|
|
// track all newly-opened blocks on the stack. |
326
|
|
|
array_push( |
327
|
|
|
$this->stack, |
328
|
|
|
new CmpFrame( |
329
|
|
|
new Cmp($block_name, $attrs, array(), '', array()), |
330
|
|
|
$start_offset, |
331
|
|
|
$token_length, |
332
|
|
|
$start_offset + $token_length, |
333
|
|
|
$leading_html_start |
334
|
|
|
) |
335
|
|
|
); |
336
|
|
|
$this->offset = $start_offset + $token_length; |
337
|
|
|
return true; |
338
|
|
|
case 'block-closer': |
339
|
|
|
/* |
340
|
|
|
* if we're missing an opener we're in trouble |
341
|
|
|
* This is an error |
342
|
|
|
*/ |
343
|
|
|
if (0 === $stack_depth) { |
344
|
|
|
/* |
345
|
|
|
* we have options |
346
|
|
|
* - assume an implicit opener |
347
|
|
|
* - assume _this_ is the opener |
348
|
|
|
* - give up and close out the document |
349
|
|
|
*/ |
350
|
|
|
$this->add_freeform(); |
351
|
|
|
return false; |
352
|
|
|
} |
353
|
|
|
// if we're not nesting then this is easy - close the block. |
354
|
|
|
if (1 === $stack_depth) { |
355
|
|
|
$this->add_block_from_stack($start_offset); |
356
|
|
|
$this->offset = $start_offset + $token_length; |
357
|
|
|
return true; |
358
|
|
|
} |
359
|
|
|
/* |
360
|
|
|
* otherwise we're nested and we have to close out the current |
361
|
|
|
* block and add it as a new innerBlock to the parent |
362
|
|
|
*/ |
363
|
|
|
$stack_top = array_pop($this->stack); |
364
|
|
|
$html = substr($this->document, $stack_top->prev_offset, $start_offset - $stack_top->prev_offset); |
|
|
|
|
365
|
|
|
// $stack_top->block->innerHTML .= $html; |
366
|
|
|
// $stack_top->block->innerContent[] = $html; |
367
|
|
|
$stack_top->prev_offset = $start_offset + $token_length; |
368
|
|
|
$this->add_inner_block( |
369
|
|
|
$stack_top->block, |
370
|
|
|
$stack_top->token_start, |
371
|
|
|
$stack_top->token_length, |
372
|
|
|
$start_offset + $token_length |
373
|
|
|
); |
374
|
|
|
$this->offset = $start_offset + $token_length; |
375
|
|
|
return true; |
376
|
|
|
default: |
377
|
|
|
// This is an error. |
378
|
|
|
$this->add_freeform(); |
379
|
|
|
return false; |
380
|
|
|
} |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* Scans the document from where we last left off |
385
|
|
|
* and finds the next valid token to parse if it exists |
386
|
|
|
* |
387
|
|
|
* Returns the type of the find: kind of find, block information, attributes |
388
|
|
|
* |
389
|
|
|
* @return array |
390
|
|
|
* @since 3.8.0 |
391
|
|
|
* @since 4.6.1 fixed a bug in attribute parsing which caused catastrophic backtracking on invalid block comments |
392
|
|
|
* @internal |
393
|
|
|
*/ |
394
|
|
|
function next_token() |
|
|
|
|
395
|
|
|
{ |
396
|
|
|
$matches = null; |
397
|
|
|
/* |
398
|
|
|
* aye the magic |
399
|
|
|
* we're using a single RegExp to tokenize the block comment delimiters |
400
|
|
|
* we're also using a trick here because the only difference between a |
401
|
|
|
* block opener and a block closer is the leading `/` before `wp:` (and |
402
|
|
|
* a closer has no attributes). we can trap them both and process the |
403
|
|
|
* match back in PHP to see which one it was. |
404
|
|
|
*/ |
405
|
|
|
$has_match = preg_match( |
406
|
|
|
'/<\s+(?P<closer>\/)?'.$this->componentNamespace.'(?P<namespace>[a-z][a-z0-9_-]*\/)?(?P<name>[a-z][a-z0-9_-]*)\s+(?P<attrs>{(?:(?:[^}]+|}+(?=})|(?!}\s+\/?>).)*+)?}\s+)?(?P<void>\/)?>/s', |
407
|
|
|
$this->document, |
408
|
|
|
$matches, |
409
|
|
|
PREG_OFFSET_CAPTURE, |
410
|
|
|
$this->offset |
411
|
|
|
); |
412
|
|
|
// if we get here we probably have catastrophic backtracking or out-of-memory in the PCRE. |
413
|
|
|
if (false === $has_match) { |
414
|
|
|
return array('no-more-tokens', null, null, null, null); |
415
|
|
|
} |
416
|
|
|
// we have no more tokens. |
417
|
|
|
if (0 === $has_match) { |
418
|
|
|
return array('no-more-tokens', null, null, null, null); |
419
|
|
|
} |
420
|
|
|
list($match, $started_at) = $matches[0]; |
421
|
|
|
$length = strlen($match); |
422
|
|
|
$is_closer = isset($matches['closer']) && -1 !== $matches['closer'][1]; |
423
|
|
|
$is_void = isset($matches['void']) && -1 !== $matches['void'][1]; |
424
|
|
|
$namespace = $matches['namespace']; |
425
|
|
|
$namespace = (isset($namespace) && -1 !== $namespace[1]) ? $namespace[0] : ''; // 'core/'; |
426
|
|
|
$name = $namespace . $matches['name'][0]; |
427
|
|
|
$has_attrs = isset($matches['attrs']) && -1 !== $matches['attrs'][1]; |
428
|
|
|
/* |
429
|
|
|
* Fun fact! It's not trivial in PHP to create "an empty associative array" since all arrays |
430
|
|
|
* are associative arrays. If we use `array()` we get a JSON `[]` |
431
|
|
|
*/ |
432
|
|
|
$attrs = $has_attrs |
433
|
|
|
? json_decode($matches['attrs'][0], /* as-associative */ true) |
434
|
|
|
: $this->empty_attrs; |
435
|
|
|
/* |
436
|
|
|
* This state isn't allowed |
437
|
|
|
* This is an error |
438
|
|
|
*/ |
439
|
|
|
if ($is_closer && ($is_void || $has_attrs)) { |
440
|
|
|
// we can ignore them since they don't hurt anything. |
441
|
|
|
} |
442
|
|
|
if ($is_void) { |
443
|
|
|
return array('void-block', $name, $attrs, $started_at, $length); |
444
|
|
|
} |
445
|
|
|
if ($is_closer) { |
446
|
|
|
return array('block-closer', $name, null, $started_at, $length); |
447
|
|
|
} |
448
|
|
|
return array('block-opener', $name, $attrs, $started_at, $length); |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
/** |
452
|
|
|
* Returns a new block object for freeform HTML |
453
|
|
|
* |
454
|
|
|
* @param string $innerHTML HTML content of block. |
455
|
|
|
* @return Cmp freeform block object. |
456
|
|
|
* @internal |
457
|
|
|
* @since 3.9.0 |
458
|
|
|
* |
459
|
|
|
*/ |
460
|
|
|
function freeform($innerHTML, $uuid) |
|
|
|
|
461
|
|
|
{ |
462
|
|
|
return new Cmp(null, $this->empty_attrs, array(), $innerHTML, array($innerHTML), $uuid); |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
/** |
466
|
|
|
* Pushes a length of text from the input document |
467
|
|
|
* to the output list as a freeform block. |
468
|
|
|
* |
469
|
|
|
* @param null $length how many bytes of document text to output. |
|
|
|
|
470
|
|
|
* @since 3.8.0 |
471
|
|
|
* @internal |
472
|
|
|
*/ |
473
|
|
|
function add_freeform($length = null) |
|
|
|
|
474
|
|
|
{ |
475
|
|
|
$length = $length ? $length : strlen($this->document) - $this->offset; |
|
|
|
|
476
|
|
|
if (0 === $length) { |
477
|
|
|
return; |
478
|
|
|
} |
479
|
|
|
$uuid = Hash::uuid64(); |
480
|
|
|
$this->output[$uuid] = (array)self::freeform(substr($this->document, $this->offset, $length), $uuid); |
|
|
|
|
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
/** |
484
|
|
|
* Given a block structure from memory pushes |
485
|
|
|
* a new block to the output list. |
486
|
|
|
* |
487
|
|
|
* @param Cmp $block The block to add to the output. |
488
|
|
|
* @param int $token_start Byte offset into the document where the first token for the block starts. |
489
|
|
|
* @param int $token_length Byte length of entire block from start of opening token to end of closing token. |
490
|
|
|
* @param int|null $last_offset Last byte offset into document if continuing form earlier output. |
491
|
|
|
* @internal |
492
|
|
|
* @since 3.8.0 |
493
|
|
|
*/ |
494
|
|
|
function add_inner_block(Cmp $block, $token_start, $token_length, $last_offset = null) |
|
|
|
|
495
|
|
|
{ |
496
|
|
|
$parent = $this->stack[count($this->stack) - 1]; |
497
|
|
|
// $parent->block->innerBlocks[] = (array)$block; |
498
|
|
|
$html = substr($this->document, $parent->prev_offset, $token_start - $parent->prev_offset); |
499
|
|
|
if (!empty($html)) { |
500
|
|
|
// $parent->block->innerHTML .= $html; |
501
|
|
|
// $parent->block->innerContent[] = $html; |
502
|
|
|
} |
503
|
|
|
$parent->block->innerContent[] = null; |
|
|
|
|
504
|
|
|
$parent->prev_offset = $last_offset ? $last_offset : $token_start + $token_length; |
505
|
|
|
$parent->block->children[] = $block->uuid; |
|
|
|
|
506
|
|
|
$this->output[$block->uuid] = $block; |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
/** |
510
|
|
|
* Pushes the top block from the parsing stack to the output list. |
511
|
|
|
* |
512
|
|
|
* @param int|null $end_offset byte offset into document for where we should stop sending text output as HTML. |
513
|
|
|
* @since 3.8.0 |
514
|
|
|
* @internal |
515
|
|
|
*/ |
516
|
|
|
function add_block_from_stack($end_offset = null) |
|
|
|
|
517
|
|
|
{ |
518
|
|
|
$stack_top = array_pop($this->stack); |
519
|
|
|
$prev_offset = $stack_top->prev_offset; |
520
|
|
|
$html = isset($end_offset) |
521
|
|
|
? substr($this->document, $prev_offset, $end_offset - $prev_offset) |
522
|
|
|
: substr($this->document, $prev_offset); |
523
|
|
|
if (!empty($html)) { |
524
|
|
|
// $stack_top->block->innerHTML .= $html; |
525
|
|
|
// $stack_top->block->innerContent[] = $html; |
526
|
|
|
} |
527
|
|
|
if (isset($stack_top->leading_html_start)) { |
528
|
|
|
$uuid = Hash::uuid64(); |
529
|
|
|
$this->output[$uuid] = (array)self::freeform( |
|
|
|
|
530
|
|
|
substr( |
531
|
|
|
$this->document, |
532
|
|
|
$stack_top->leading_html_start, |
533
|
|
|
$stack_top->token_start - $stack_top->leading_html_start |
534
|
|
|
), $uuid |
535
|
|
|
); |
536
|
|
|
} |
537
|
|
|
$this->output[$stack_top->block->uuid] = (array)$stack_top->block; |
538
|
|
|
} |
539
|
|
|
} |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.