1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * |
||||
5 | * @package ElkArte Forum |
||||
6 | * @copyright ElkArte Forum contributors |
||||
7 | * @license BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file) |
||||
8 | * |
||||
9 | * This file contains code covered by: |
||||
10 | * copyright: 2011 Simple Machines (http://www.simplemachines.org) |
||||
11 | * |
||||
12 | * @version 2.0 dev |
||||
13 | * |
||||
14 | */ |
||||
15 | |||||
16 | namespace BBC; |
||||
17 | |||||
18 | /** |
||||
19 | * Class BBCParser |
||||
20 | * |
||||
21 | * One of the primary functions, parsing BBC to HTML |
||||
22 | * |
||||
23 | * @package BBC |
||||
24 | */ |
||||
25 | class BBCParser |
||||
26 | { |
||||
27 | /** The max number of iterations to perform while solving for out of order attributes */ |
||||
28 | public const MAX_PERMUTE_ITERATIONS = 5040; |
||||
29 | |||||
30 | /** @var string */ |
||||
31 | protected $message; |
||||
32 | |||||
33 | /** @var Codes */ |
||||
34 | protected $bbc; |
||||
35 | |||||
36 | /** @var array */ |
||||
37 | protected $bbc_codes; |
||||
38 | |||||
39 | /** @var array */ |
||||
40 | protected $item_codes; |
||||
41 | |||||
42 | /** @var array */ |
||||
43 | protected $tags; |
||||
44 | |||||
45 | /** @var int parser position in message */ |
||||
46 | protected $pos; |
||||
47 | |||||
48 | /** @var int */ |
||||
49 | protected $pos1; |
||||
50 | |||||
51 | /** @var int */ |
||||
52 | protected $pos2; |
||||
53 | |||||
54 | /** @var int */ |
||||
55 | protected $pos3; |
||||
56 | |||||
57 | /** @var int */ |
||||
58 | protected $last_pos; |
||||
59 | |||||
60 | /** @var bool */ |
||||
61 | protected $do_smileys = true; |
||||
62 | |||||
63 | /** @var array */ |
||||
64 | protected $open_tags = []; |
||||
65 | |||||
66 | /** @var string|null This is the actual tag that's open */ |
||||
67 | protected $inside_tag; |
||||
68 | |||||
69 | /** @var Autolink|null */ |
||||
70 | protected $autolinker; |
||||
71 | |||||
72 | /** @var bool */ |
||||
73 | protected $possible_html; |
||||
74 | |||||
75 | /** @var bool */ |
||||
76 | protected $possible_markdown; |
||||
77 | |||||
78 | /** @var HtmlParser|null */ |
||||
79 | protected $html_parser; |
||||
80 | |||||
81 | /** @var MarkdownParser|null */ |
||||
82 | protected $markdown_parser; |
||||
83 | |||||
84 | /** @var bool if we can cache the message or not (some tags disallow caching) */ |
||||
85 | protected $can_cache = true; |
||||
86 | 4 | ||||
87 | /** @var int footnote tracker */ |
||||
88 | 4 | protected $num_footnotes = 0; |
|||
89 | |||||
90 | 4 | /** @var string used to mark smiles in a message */ |
|||
91 | 4 | protected $smiley_marker = "\r"; |
|||
92 | |||||
93 | 4 | /** @var int */ |
|||
94 | 4 | protected $lastAutoPos = 0; |
|||
95 | |||||
96 | 4 | /** @var array content fo the footnotes */ |
|||
97 | 4 | protected $fn_content = []; |
|||
98 | |||||
99 | /** @var array */ |
||||
100 | protected $tag_possible = []; |
||||
101 | |||||
102 | 28 | /** @var int */ |
|||
103 | protected $fn_count = 0; |
||||
104 | 28 | ||||
105 | 28 | /** @var int */ |
|||
106 | 28 | protected $fn_num = 0; |
|||
107 | 28 | ||||
108 | 28 | /** |
|||
109 | 28 | * BBCParser constructor. |
|||
110 | 28 | * |
|||
111 | 28 | * @param Codes $bbc |
|||
112 | 28 | * @param Autolink|null $autolinker |
|||
113 | 28 | * @param HtmlParser|null $html_parser |
|||
114 | * @param MarkdownParser|null $markdown_parser |
||||
115 | */ |
||||
116 | public function __construct(Codes $bbc, Autolink $autolinker = null, HtmlParser $html_parser = null, MarkdownParser $markdown_parser = null) |
||||
117 | { |
||||
118 | $this->bbc = $bbc; |
||||
119 | |||||
120 | $this->bbc_codes = $this->bbc->getForParsing(); |
||||
121 | $this->item_codes = $this->bbc->getItemCodes(); |
||||
122 | 32 | ||||
123 | $this->autolinker = $autolinker; |
||||
124 | $this->loadAutolink(); |
||||
125 | |||||
126 | 32 | $this->html_parser = $html_parser; |
|||
127 | $this->markdown_parser = $markdown_parser; |
||||
128 | 32 | } |
|||
129 | |||||
130 | /** |
||||
131 | 32 | * Reset the parser's properties for a new message |
|||
132 | */ |
||||
133 | 4 | public function resetParser() |
|||
134 | { |
||||
135 | $this->pos = -1; |
||||
136 | $this->pos1 = null; |
||||
137 | 28 | $this->pos2 = null; |
|||
138 | $this->last_pos = null; |
||||
139 | $this->open_tags = array(); |
||||
140 | $this->inside_tag = null; |
||||
141 | $this->lastAutoPos = 0; |
||||
142 | 28 | $this->can_cache = true; |
|||
143 | $this->num_footnotes = 0; |
||||
144 | } |
||||
145 | 28 | ||||
146 | /** |
||||
147 | * Parse the BBC in a string/message |
||||
148 | 28 | * |
|||
149 | * @param string $message |
||||
150 | 28 | * |
|||
151 | * @return string |
||||
152 | */ |
||||
153 | 28 | public function parse($message) |
|||
154 | { |
||||
155 | 2 | // Allow addons access before entering the main parse loop, formally |
|||
156 | // called as integrate_pre_parsebbc |
||||
157 | call_integration_hook('integrate_pre_bbc_parser', array(&$message, &$this->bbc)); |
||||
158 | |||||
159 | 28 | $this->message = (string) $message; |
|||
160 | |||||
161 | // Don't waste cycles |
||||
162 | 28 | if ($this->message === '') |
|||
163 | { |
||||
164 | 2 | return ''; |
|||
165 | } |
||||
166 | |||||
167 | 28 | // @todo remove from here and make the caller figure it out |
|||
168 | if (!$this->parsingEnabled()) |
||||
169 | { |
||||
170 | return $this->message; |
||||
171 | } |
||||
172 | |||||
173 | $this->resetParser(); |
||||
174 | 28 | ||||
175 | $this->message = str_replace("\n", '<br />', $this->message); |
||||
176 | |||||
177 | 28 | // Check if the message might have a link or email to save a bunch of parsing in autolink() |
|||
178 | $this->autolinker->setPossibleAutolink($this->message); |
||||
0 ignored issues
–
show
|
|||||
179 | 2 | ||||
180 | $this->possible_html = !empty($GLOBALS['modSettings']['enablePostHTML']) && strpos($message, '<') !== false; |
||||
181 | |||||
182 | // Don't load the HTML Parser unless we have to |
||||
183 | if ($this->possible_html && $this->html_parser === null) |
||||
184 | 28 | { |
|||
185 | 28 | $this->loadHtmlParser(); |
|||
186 | 28 | } |
|||
187 | |||||
188 | 28 | $this->possible_markdown = !empty($GLOBALS['modSettings']['enablePostMarkdown']); |
|||
189 | |||||
190 | // Don't load the Markdown Parser unless we have to |
||||
191 | if ($this->possible_markdown) |
||||
192 | { |
||||
193 | if ($this->markdown_parser === null) |
||||
194 | { |
||||
195 | $this->loadMarkdownParser(); |
||||
196 | 28 | } |
|||
197 | |||||
198 | 28 | // To protect bbc tags inside `icode` and ```code``` blocks, we need to process them up front |
|||
199 | $this->message = $this->markdown_parser->inlineCodeTags($this->message); |
||||
200 | 28 | } |
|||
201 | 28 | ||||
202 | // Allow addons access before entering the main parse loop |
||||
203 | call_integration_hook('integrate_pre_bbc_parser_loop', array(&$this->message, &$this->bbc)); |
||||
204 | 28 | ||||
205 | // This handles pretty much all the parsing. It is a separate method, so it is easier to override and profile. |
||||
206 | 28 | $this->parse_loop(); |
|||
207 | |||||
208 | // Close any remaining tags. |
||||
209 | while ($tag = $this->closeOpenedTag()) |
||||
210 | 28 | { |
|||
211 | $this->message .= $this->noSmileys($tag[Codes::ATTR_AFTER]); |
||||
212 | 28 | } |
|||
213 | |||||
214 | if (isset($this->message[0]) && $this->message[0] === ' ') |
||||
215 | { |
||||
216 | 28 | $this->message = substr_replace($this->message, ' ', 0, 1); |
|||
0 ignored issues
–
show
It seems like
substr_replace($this->message, ' ', 0, 1) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
217 | //$this->message = ' ' . substr($this->message, 1); |
||||
218 | 28 | } |
|||
219 | |||||
220 | // Cleanup whitespace. |
||||
221 | 16 | $this->message = str_replace(array(' ', '<br /> ', ' '), array(' ', '<br /> ', "\n"), $this->message); |
|||
222 | |||||
223 | // Finish footnotes if we have any. |
||||
224 | 16 | if ($this->num_footnotes > 0) |
|||
225 | { |
||||
226 | 16 | $this->handleFootnotes(); |
|||
227 | } |
||||
228 | 14 | ||||
229 | // Allow addons access to what the parser created, formally |
||||
230 | // called as integrate_post_parsebbc |
||||
231 | $message = $this->message; |
||||
232 | 16 | call_integration_hook('integrate_post_bbc_parser', array(&$message)); |
|||
233 | $this->message = $message; |
||||
234 | |||||
235 | return $this->message; |
||||
236 | 16 | } |
|||
237 | |||||
238 | 2 | /** |
|||
239 | * The BBC parsing loop-o-love |
||||
240 | * |
||||
241 | 16 | * Walks the string to parse, looking for BBC tags and passing items to the required translation functions |
|||
242 | */ |
||||
243 | 16 | protected function parse_loop() |
|||
244 | { |
||||
245 | while ($this->pos !== false) |
||||
246 | 2 | { |
|||
247 | $this->last_pos = $this->last_pos !== null ? max($this->pos, $this->last_pos) : $this->pos; |
||||
248 | $this->pos = strpos($this->message, '[', $this->pos + 1); |
||||
249 | 2 | ||||
250 | // Failsafe. |
||||
251 | if ($this->pos === false || $this->last_pos > $this->pos) |
||||
252 | { |
||||
253 | 2 | $this->pos = strlen($this->message) + 1; |
|||
254 | } |
||||
255 | |||||
256 | // Can't have a one letter smiley, URL, or email! (sorry.) |
||||
257 | 16 | if ($this->last_pos < $this->pos - 1) |
|||
258 | { |
||||
259 | $this->betweenTags(); |
||||
260 | } |
||||
261 | 16 | ||||
262 | // Are we there yet? Are we there yet? |
||||
263 | if ($this->pos >= strlen($this->message) - 1) |
||||
264 | { |
||||
265 | return; |
||||
266 | } |
||||
267 | |||||
268 | $next_char = strtolower($this->message[$this->pos + 1]); |
||||
269 | |||||
270 | 16 | // Possibly a closer? |
|||
271 | if ($next_char === '/') |
||||
272 | 2 | { |
|||
273 | if ($this->hasOpenTags()) |
||||
274 | { |
||||
275 | $this->handleOpenTags(); |
||||
276 | 14 | } |
|||
277 | |||||
278 | // We don't allow / to be used for anything but the closing character, so this can't be a tag |
||||
279 | continue; |
||||
280 | } |
||||
281 | |||||
282 | 14 | // No tags for this character, so just keep going (fastest possible course.) |
|||
283 | if (!isset($this->bbc_codes[$next_char])) |
||||
284 | { |
||||
285 | continue; |
||||
286 | } |
||||
287 | |||||
288 | 14 | $this->inside_tag = $this->hasOpenTags() ? $this->getLastOpenedTag() : null; |
|||
289 | |||||
290 | 6 | if (isset($this->message[$this->pos + 2]) && $this->isItemCode($next_char) && $this->message[$this->pos + 2] === ']' && !$this->bbc->isDisabled('list') && !$this->bbc->isDisabled('li')) |
|||
291 | { |
||||
292 | // Itemcodes cannot be 0 and must be proceeded by a semi-colon, space, tab, new line, or greater than sign |
||||
293 | if (!($this->message[$this->pos + 1] === '0' && !in_array($this->message[$this->pos - 1], array(';', ' ', "\t", "\n", '>')))) |
||||
294 | 14 | { |
|||
295 | // Item codes are complicated buggers... they are implicit [li]s and can make [list]s! |
||||
296 | $this->handleItemCode(); |
||||
297 | } |
||||
298 | |||||
299 | // No matter what, we have to continue here. |
||||
300 | 14 | continue; |
|||
301 | } |
||||
302 | 2 | ||||
303 | $tag = $this->findTag($this->bbc_codes[$next_char]); |
||||
304 | |||||
305 | // Implicitly close lists and tables if something other than what's required is in them. This is needed for itemcode. |
||||
306 | 14 | if ($tag === null && $this->inside_tag !== null && !empty($this->inside_tag[Codes::ATTR_REQUIRE_CHILDREN])) |
|||
307 | { |
||||
308 | 4 | $this->closeOpenedTag(); |
|||
309 | $tmp = $this->noSmileys($this->inside_tag[Codes::ATTR_AFTER]); |
||||
310 | $this->message = substr_replace($this->message, $tmp, $this->pos, 0); |
||||
0 ignored issues
–
show
It seems like
substr_replace($this->me...e, $tmp, $this->pos, 0) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
311 | $this->pos += strlen($tmp) - 1; |
||||
312 | } |
||||
313 | |||||
314 | // No tag? Keep looking, then. Silly people using brackets without actual tags. |
||||
315 | if ($tag === null) |
||||
316 | 14 | { |
|||
317 | continue; |
||||
318 | } |
||||
319 | 14 | ||||
320 | // Propagate the list to the child (so wrapping the disallowed tag won't work either.) |
||||
321 | if (isset($this->inside_tag[Codes::ATTR_DISALLOW_CHILDREN])) |
||||
322 | 14 | { |
|||
323 | $tag[Codes::ATTR_DISALLOW_CHILDREN] = isset($tag[Codes::ATTR_DISALLOW_CHILDREN]) ? $tag[Codes::ATTR_DISALLOW_CHILDREN] + $this->inside_tag[Codes::ATTR_DISALLOW_CHILDREN] : $this->inside_tag[Codes::ATTR_DISALLOW_CHILDREN]; |
||||
324 | } |
||||
325 | |||||
326 | // Is this tag disabled? |
||||
327 | if ($this->bbc->isDisabled($tag[Codes::ATTR_TAG])) |
||||
328 | 14 | { |
|||
329 | 14 | $this->handleDisabled($tag); |
|||
330 | 14 | } |
|||
331 | |||||
332 | // The only special case is 'html', which doesn't need to close things. |
||||
333 | if ($tag[Codes::ATTR_BLOCK_LEVEL] && $tag[Codes::ATTR_TAG] !== 'html' && empty($this->inside_tag[Codes::ATTR_BLOCK_LEVEL])) |
||||
334 | { |
||||
335 | 14 | $this->closeNonBlockLevel(); |
|||
336 | } |
||||
337 | |||||
338 | 14 | // This is the part where we actually handle the tags. I know, crazy how long it took. |
|||
339 | if ($this->handleTag($tag)) |
||||
340 | { |
||||
341 | continue; |
||||
342 | } |
||||
343 | 14 | ||||
344 | // If this is block level, eat any breaks after it. |
||||
345 | if ($tag[Codes::ATTR_BLOCK_LEVEL] && isset($this->message[$this->pos + 1]) && substr_compare($this->message, '<br />', $this->pos + 1, 6) === 0) |
||||
0 ignored issues
–
show
It seems like
$this->message can also be of type array ; however, parameter $haystack of substr_compare() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
346 | 4 | { |
|||
347 | $this->message = substr_replace($this->message, '', $this->pos + 1, 6); |
||||
348 | } |
||||
349 | |||||
350 | // Are we trimming outside this tag? |
||||
351 | if (!empty($tag[Codes::ATTR_TRIM]) && $tag[Codes::ATTR_TRIM] !== Codes::TRIM_OUTSIDE) |
||||
352 | { |
||||
353 | 4 | $this->trimWhiteSpace($this->pos + 1); |
|||
354 | } |
||||
355 | 4 | } |
|||
356 | } |
||||
357 | 4 | ||||
358 | /** |
||||
359 | 4 | * Process a tag once the closing character / has been found |
|||
360 | 4 | */ |
|||
361 | protected function handleOpenTags() |
||||
362 | { |
||||
363 | // Next closing bracket after the first character |
||||
364 | $this->pos2 = strpos($this->message, ']', $this->pos + 1); |
||||
365 | 4 | ||||
366 | // Playing games? string = [/] |
||||
367 | if ($this->pos2 === $this->pos + 2) |
||||
368 | { |
||||
369 | return; |
||||
370 | } |
||||
371 | |||||
372 | // Get everything between [/ and ] |
||||
373 | 14 | $look_for = strtolower(substr($this->message, $this->pos + 2, $this->pos2 - $this->pos - 2)); |
|||
374 | 14 | $to_close = array(); |
|||
375 | $block_level = null; |
||||
376 | |||||
377 | 14 | do |
|||
378 | { |
||||
379 | // Get the last opened tag |
||||
380 | $tag = $this->closeOpenedTag(); |
||||
381 | |||||
382 | // No open tags |
||||
383 | 14 | if (!$tag) |
|||
384 | { |
||||
385 | break; |
||||
386 | } |
||||
387 | |||||
388 | if (!empty($tag[Codes::ATTR_BLOCK_LEVEL])) |
||||
389 | { |
||||
390 | // Only find out if we need to. |
||||
391 | if ($block_level === false) |
||||
392 | { |
||||
393 | $this->addOpenTag($tag); |
||||
394 | break; |
||||
395 | } |
||||
396 | |||||
397 | // The idea is, if we are LOOKING for a block level tag, we can close them on the way. |
||||
398 | if (isset($look_for[1], $this->bbc_codes[$look_for[0]])) |
||||
399 | { |
||||
400 | foreach ($this->bbc_codes[$look_for[0]] as $temp) |
||||
401 | { |
||||
402 | if ($temp[Codes::ATTR_TAG] === $look_for) |
||||
403 | { |
||||
404 | $block_level = $temp[Codes::ATTR_BLOCK_LEVEL]; |
||||
405 | break; |
||||
406 | } |
||||
407 | } |
||||
408 | } |
||||
409 | 14 | ||||
410 | if ($block_level !== true) |
||||
411 | 14 | { |
|||
412 | 14 | $block_level = false; |
|||
413 | 14 | $this->addOpenTag($tag); |
|||
414 | 14 | break; |
|||
415 | } |
||||
416 | } |
||||
417 | 14 | ||||
418 | $to_close[] = $tag; |
||||
419 | } while (isset($tag[Codes::ATTR_TAG]) && $tag[Codes::ATTR_TAG] !== $look_for); |
||||
420 | |||||
421 | // Did we just eat through everything and not find it? |
||||
422 | if ((empty($tag) || $tag[Codes::ATTR_TAG] !== $look_for) && !$this->hasOpenTags()) |
||||
423 | 14 | { |
|||
424 | $this->open_tags = $to_close; |
||||
425 | 9 | ||||
426 | return; |
||||
427 | } |
||||
428 | |||||
429 | 14 | if (!empty($to_close) && isset($tag[Codes::ATTR_TAG]) && $tag[Codes::ATTR_TAG] !== $look_for) |
|||
430 | { |
||||
431 | 14 | if ($block_level === null && isset($look_for[0], $this->bbc_codes[$look_for[0]])) |
|||
432 | { |
||||
433 | 14 | foreach ($this->bbc_codes[$look_for[0]] as $temp) |
|||
434 | { |
||||
435 | if ($temp[Codes::ATTR_TAG] === $look_for) |
||||
436 | { |
||||
437 | $block_level = !empty($temp[Codes::ATTR_BLOCK_LEVEL]); |
||||
438 | break; |
||||
439 | } |
||||
440 | } |
||||
441 | } |
||||
442 | |||||
443 | // We're not looking for a block level tag (or maybe even a tag that exists...) |
||||
444 | if (!$block_level) |
||||
0 ignored issues
–
show
The expression
$block_level of type boolean|null is loosely compared to false ; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.
If an expression can have both $a = canBeFalseAndNull();
// Instead of
if ( ! $a) { }
// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
![]() |
|||||
445 | { |
||||
446 | foreach ($to_close as $tag) |
||||
447 | { |
||||
448 | $this->addOpenTag($tag); |
||||
449 | } |
||||
450 | |||||
451 | return; |
||||
452 | } |
||||
453 | 28 | } |
|||
454 | |||||
455 | 28 | foreach ($to_close as $tag) |
|||
456 | { |
||||
457 | $tmp = $this->noSmileys($tag[Codes::ATTR_AFTER]); |
||||
458 | $this->message = substr_replace($this->message, $tmp, $this->pos, $this->pos2 + 1 - $this->pos); |
||||
0 ignored issues
–
show
It seems like
substr_replace($this->me...>pos2 + 1 - $this->pos) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
459 | $this->pos += strlen($tmp); |
||||
460 | $this->pos2 = $this->pos - 1; |
||||
461 | 2 | ||||
462 | // See the comment at the end of the big loop - just eating whitespace ;). |
||||
463 | 2 | if (!empty($tag[Codes::ATTR_BLOCK_LEVEL]) && isset($this->message[$this->pos]) && substr_compare($this->message, '<br />', $this->pos, 6) === 0) |
|||
0 ignored issues
–
show
It seems like
$this->message can also be of type array ; however, parameter $haystack of substr_compare() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
464 | 2 | { |
|||
465 | 2 | $this->message = substr_replace($this->message, '', $this->pos, 6); |
|||
466 | 2 | } |
|||
467 | // Trim inside whitespace |
||||
468 | if (empty($tag[Codes::ATTR_TRIM])) |
||||
469 | { |
||||
470 | continue; |
||||
471 | } |
||||
472 | if ($tag[Codes::ATTR_TRIM] === Codes::TRIM_INSIDE) |
||||
473 | { |
||||
474 | continue; |
||||
475 | 2 | } |
|||
476 | $this->trimWhiteSpace($this->pos); |
||||
477 | 2 | } |
|||
478 | |||||
479 | if (!empty($to_close)) |
||||
480 | { |
||||
481 | $this->pos--; |
||||
482 | } |
||||
483 | } |
||||
484 | |||||
485 | /** |
||||
486 | * Turn smiley parsing on/off |
||||
487 | 28 | * |
|||
488 | * @param bool $toggle |
||||
489 | 28 | * @return BBCParser |
|||
490 | */ |
||||
491 | 20 | public function doSmileys($toggle) |
|||
492 | { |
||||
493 | $this->do_smileys = (bool) $toggle; |
||||
494 | |||||
495 | 14 | return $this; |
|||
496 | } |
||||
497 | 14 | ||||
498 | /** |
||||
499 | 14 | * Check if parsing is enabled |
|||
500 | * |
||||
501 | 13 | * @return bool |
|||
502 | */ |
||||
503 | public function parsingEnabled() |
||||
504 | { |
||||
505 | return !empty($GLOBALS['modSettings']['enableBBC']); |
||||
506 | 14 | } |
|||
507 | |||||
508 | /** |
||||
509 | * Load the HTML parsing engine |
||||
510 | */ |
||||
511 | public function loadHtmlParser() |
||||
512 | 4 | { |
|||
513 | $parser = new HtmlParser(); |
||||
514 | 4 | call_integration_hook('integrate_bbc_load_html_parser', array(&$parser)); |
|||
515 | $this->html_parser = $parser; |
||||
516 | 2 | } |
|||
517 | |||||
518 | 4 | /** |
|||
519 | * Parse the HTML in a string |
||||
520 | * |
||||
521 | * @param string $data |
||||
522 | * |
||||
523 | * @return string |
||||
524 | */ |
||||
525 | protected function parseHTML($data) |
||||
526 | { |
||||
527 | 16 | return $this->html_parser->parse($data); |
|||
0 ignored issues
–
show
The method
parse() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||
528 | } |
||||
529 | 16 | ||||
530 | 16 | /** |
|||
531 | * Load the Markdown parsing engine |
||||
532 | 16 | */ |
|||
533 | public function loadMarkdownParser() |
||||
534 | { |
||||
535 | 16 | $parser = new MarkdownParser(); |
|||
536 | call_integration_hook('integrate_bbc_load_markdown_parser', [&$parser]); |
||||
537 | 6 | $this->markdown_parser = $parser; |
|||
538 | } |
||||
539 | |||||
540 | /** |
||||
541 | 16 | * Parse any Markdown in a string |
|||
542 | * |
||||
543 | * @param string $data |
||||
544 | 16 | * |
|||
545 | * @return string |
||||
546 | */ |
||||
547 | protected function parseMarkdown($data) |
||||
548 | { |
||||
549 | return $this->markdown_parser->parse($data); |
||||
0 ignored issues
–
show
The method
parse() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||
550 | 16 | } |
|||
551 | |||||
552 | 16 | /** |
|||
553 | 16 | * Parse URIs and email addresses in a string to url and email BBC tags to be parsed by the BBC parser |
|||
554 | * |
||||
555 | * @param string $data |
||||
556 | * |
||||
557 | 16 | * @return null|string|string[] |
|||
558 | */ |
||||
559 | 4 | protected function autoLink($data) |
|||
560 | 4 | { |
|||
561 | if ($data === '' || $data === $this->smiley_marker || !$this->autolinker->hasPossible()) |
||||
562 | { |
||||
563 | 16 | return $data; |
|||
564 | 16 | } |
|||
565 | |||||
566 | 16 | // Are we inside tags that should be auto linked? |
|||
567 | if ($this->hasOpenTags()) |
||||
568 | { |
||||
569 | foreach ($this->getOpenedTags() as $open_tag) |
||||
570 | 14 | { |
|||
571 | if (empty($open_tag[Codes::ATTR_AUTOLINK])) |
||||
572 | 2 | { |
|||
573 | return $data; |
||||
574 | } |
||||
575 | 14 | } |
|||
576 | } |
||||
577 | |||||
578 | return $this->autolinker->parse($data); |
||||
579 | 16 | } |
|||
580 | |||||
581 | 14 | /** |
|||
582 | * Load the autolink regular expression to be used in autoLink() |
||||
583 | */ |
||||
584 | protected function loadAutolink() |
||||
585 | 16 | { |
|||
586 | if ($this->autolinker === null) |
||||
587 | 2 | { |
|||
588 | $this->autolinker = new Autolink($this->bbc); |
||||
589 | } |
||||
590 | 16 | } |
|||
591 | |||||
592 | /** |
||||
593 | * Find if the current character is the start of a tag and get it |
||||
594 | * |
||||
595 | * @param array $possible_codes |
||||
596 | * |
||||
597 | * @return null|array the tag that was found or null if no tag found |
||||
598 | 2 | */ |
|||
599 | protected function findTag(array $possible_codes) |
||||
600 | { |
||||
601 | 2 | $tag = null; |
|||
602 | 2 | $last_check = null; |
|||
603 | |||||
604 | foreach ($possible_codes as $possible) |
||||
605 | 2 | { |
|||
606 | // Skip tags that didn't match the next X characters |
||||
607 | 2 | if ($possible[Codes::ATTR_TAG] === $last_check) |
|||
608 | { |
||||
609 | continue; |
||||
610 | } |
||||
611 | |||||
612 | // The character after the possible tag or nothing |
||||
613 | $next_c = $this->message[$this->pos + 1 + $possible[Codes::ATTR_LENGTH]] ?? ''; |
||||
614 | |||||
615 | 2 | // This only happens if the tag is the last character of the string |
|||
616 | 2 | if ($next_c === '') |
|||
617 | 2 | { |
|||
618 | break; |
||||
619 | } |
||||
620 | |||||
621 | // The next character must be one of these or it's not a tag |
||||
622 | if ($next_c !== ' ' && $next_c !== ']' && $next_c !== '=' && $next_c !== '/') |
||||
623 | { |
||||
624 | $last_check = $possible[Codes::ATTR_TAG]; |
||||
625 | continue; |
||||
626 | 16 | } |
|||
627 | |||||
628 | // Not a match? |
||||
629 | 16 | if (substr_compare($this->message, $possible[Codes::ATTR_TAG], $this->pos + 1, $possible[Codes::ATTR_LENGTH], true) !== 0) |
|||
630 | { |
||||
631 | 4 | $last_check = $possible[Codes::ATTR_TAG]; |
|||
632 | continue; |
||||
633 | 4 | } |
|||
634 | |||||
635 | $tag = $this->checkCodeAttributes($next_c, $possible); |
||||
636 | if ($tag === null) |
||||
637 | 16 | { |
|||
638 | continue; |
||||
639 | 8 | } |
|||
640 | |||||
641 | 8 | // Quotes can have alternate styling, we do this php-side due to all the permutations of quotes. |
|||
642 | if ($tag[Codes::ATTR_TAG] === 'quote') |
||||
643 | { |
||||
644 | $this->alternateQuoteStyle($tag); |
||||
645 | } |
||||
646 | |||||
647 | 16 | break; |
|||
648 | } |
||||
649 | 4 | ||||
650 | // If there is a code that says you can't cache, the message can't be cached |
||||
651 | if ($tag !== null && $this->can_cache) |
||||
0 ignored issues
–
show
|
|||||
652 | 16 | { |
|||
653 | $this->can_cache = empty($tag[Codes::ATTR_NO_CACHE]); |
||||
654 | } |
||||
655 | 16 | ||||
656 | // If its a footnote, keep track of the number |
||||
657 | 12 | if (isset($tag[Codes::ATTR_TAG]) && $tag[Codes::ATTR_TAG] === 'footnote') |
|||
658 | { |
||||
659 | $this->num_footnotes++; |
||||
660 | 16 | } |
|||
661 | |||||
662 | return $tag; |
||||
663 | } |
||||
664 | |||||
665 | /** |
||||
666 | * Just alternates the applied class for quotes for themes that want to distinguish them (legacy) |
||||
667 | * |
||||
668 | 16 | * - The alternating should be covered with CSS odd/even attributes |
|||
669 | * - Now used to detect long quotes and wrap them in a container div with an input checkbox. |
||||
670 | * - The wrapper and checkbox are styled by the CSS with a fade out effect and a read more click. |
||||
671 | * |
||||
672 | * @param array $tag |
||||
673 | 16 | */ |
|||
674 | protected function alternateQuoteStyle(array &$tag) |
||||
675 | 6 | { |
|||
676 | // Start with standard |
||||
677 | $quote_alt = false; |
||||
678 | $first_quote = 0; |
||||
679 | |||||
680 | foreach ($this->open_tags as $open_quote) |
||||
681 | 6 | { |
|||
682 | // Every parent quote this quote has flips the styling |
||||
683 | if (isset($open_quote[Codes::ATTR_TAG]) && $open_quote[Codes::ATTR_TAG] === 'quote') |
||||
684 | { |
||||
685 | $quote_alt = !$quote_alt; |
||||
0 ignored issues
–
show
|
|||||
686 | $first_quote++; |
||||
687 | 6 | } |
|||
688 | } |
||||
689 | |||||
690 | // First quote (of nested or single) receives a wrapper so its markup will be: |
||||
691 | // <div class="quote-read-more"> .. relative |
||||
692 | // <input type="checkbox" class="quote-show-more">.. absolute over the below blockquote |
||||
693 | // <blockquote class="bbc_quote"> |
||||
694 | // <cite>Peter Parker says</cite> |
||||
695 | // No man can win every battle, but no man should fall without a struggle. |
||||
696 | // </blockquote> .. with a max height that is removed on input click |
||||
697 | // </div> |
||||
698 | if ($first_quote === 0) |
||||
699 | 16 | { |
|||
700 | $tag[Codes::ATTR_BEFORE] = str_replace('<blockquote class="bbc_quote">', '<div class="quote-read-more"><input type="checkbox" title="show" class="quote-show-more"><blockquote class="bbc_quote">', $tag[Codes::ATTR_BEFORE]); |
||||
701 | 6 | $tag[Codes::ATTR_AFTER] = str_replace('</blockquote>', '</blockquote></div>', $tag[Codes::ATTR_AFTER]); |
|||
702 | } |
||||
703 | // Add a class to every other blockquote to style alternating blockquotes |
||||
704 | // This allows simpler CSS for themes (like default) which do not use the alternate styling, |
||||
705 | 16 | // but still allow it for legacy themes that want it. |
|||
706 | elseif ($quote_alt) |
||||
707 | { |
||||
708 | 16 | $tag[Codes::ATTR_BEFORE] = str_replace('<blockquote class="bbc_quote">', '<blockquote class="bbc_quote bbc_alternate_quote">', $tag[Codes::ATTR_BEFORE]); |
|||
709 | } |
||||
710 | 4 | } |
|||
711 | |||||
712 | /** |
||||
713 | 4 | * Parses BBC codes attributes for codes that may have them |
|||
714 | * |
||||
715 | 4 | * @param string $next_c |
|||
716 | * @param array $possible |
||||
717 | * @return array|null |
||||
718 | 2 | */ |
|||
719 | protected function checkCodeAttributes($next_c, array $possible) |
||||
720 | { |
||||
721 | 14 | // Do we want parameters? |
|||
722 | if (!empty($possible[Codes::ATTR_PARAM])) |
||||
723 | { |
||||
724 | if ($next_c !== ' ') |
||||
725 | { |
||||
726 | return null; |
||||
727 | } |
||||
728 | } |
||||
729 | // parsed_content demands an immediate ] without parameters! |
||||
730 | elseif ($possible[Codes::ATTR_TYPE] === Codes::TYPE_PARSED_CONTENT) |
||||
731 | 8 | { |
|||
732 | if ($next_c !== ']') |
||||
733 | 8 | { |
|||
734 | return null; |
||||
735 | } |
||||
736 | } |
||||
737 | else |
||||
738 | { |
||||
739 | 2 | // Do we need an equal sign? |
|||
740 | if ($next_c !== '=' && in_array($possible[Codes::ATTR_TYPE], array(Codes::TYPE_UNPARSED_EQUALS, Codes::TYPE_UNPARSED_COMMAS, Codes::TYPE_UNPARSED_COMMAS_CONTENT, Codes::TYPE_UNPARSED_EQUALS_CONTENT, Codes::TYPE_PARSED_EQUALS))) |
||||
741 | 2 | { |
|||
742 | return null; |
||||
743 | } |
||||
744 | |||||
745 | if ($next_c !== ']') |
||||
746 | 2 | { |
|||
747 | // An immediate ]? |
||||
748 | if ($possible[Codes::ATTR_TYPE] === Codes::TYPE_UNPARSED_CONTENT) |
||||
749 | 2 | { |
|||
750 | return null; |
||||
751 | 2 | } |
|||
752 | 2 | ||||
753 | // Maybe we just want a /... |
||||
754 | 2 | if ($possible[Codes::ATTR_TYPE] === Codes::TYPE_CLOSED && substr_compare($this->message, '/]', $this->pos + 1 + $possible[Codes::ATTR_LENGTH], 2) !== 0 && substr_compare($this->message, ' /]', $this->pos + 1 + $possible[Codes::ATTR_LENGTH], 3) !== 0) |
|||
755 | { |
||||
756 | return null; |
||||
757 | 2 | } |
|||
758 | 2 | } |
|||
759 | } |
||||
760 | |||||
761 | 2 | // Check allowed tree? |
|||
762 | if (isset($possible[Codes::ATTR_REQUIRE_PARENTS]) && ($this->inside_tag === null || !isset($possible[Codes::ATTR_REQUIRE_PARENTS][$this->inside_tag[Codes::ATTR_TAG]]))) |
||||
763 | { |
||||
764 | 2 | return null; |
|||
765 | } |
||||
766 | 2 | ||||
767 | 2 | if ($this->inside_tag !== null) |
|||
768 | { |
||||
769 | if (isset($this->inside_tag[Codes::ATTR_REQUIRE_CHILDREN]) && !isset($this->inside_tag[Codes::ATTR_REQUIRE_CHILDREN][$possible[Codes::ATTR_TAG]])) |
||||
770 | { |
||||
771 | return null; |
||||
772 | } |
||||
773 | |||||
774 | // If this is in the list of disallowed child tags, don't parse it. |
||||
775 | 2 | if (isset($this->inside_tag[Codes::ATTR_DISALLOW_CHILDREN]) && isset($this->inside_tag[Codes::ATTR_DISALLOW_CHILDREN][$possible[Codes::ATTR_TAG]])) |
|||
776 | 2 | { |
|||
777 | return null; |
||||
778 | 2 | } |
|||
779 | |||||
780 | // Not allowed in this parent, replace the tags or show it like regular text |
||||
781 | 2 | if (isset($possible[Codes::ATTR_DISALLOW_PARENTS][$this->inside_tag[Codes::ATTR_TAG]])) |
|||
782 | { |
||||
783 | 2 | if (!isset($possible[Codes::ATTR_DISALLOW_BEFORE], $possible[Codes::ATTR_DISALLOW_AFTER])) |
|||
784 | { |
||||
785 | return null; |
||||
786 | } |
||||
787 | 2 | ||||
788 | $possible[Codes::ATTR_BEFORE] = $possible[Codes::ATTR_DISALLOW_BEFORE] ?? $possible[Codes::ATTR_BEFORE]; |
||||
789 | 2 | $possible[Codes::ATTR_AFTER] = $possible[Codes::ATTR_DISALLOW_AFTER] ?? $possible[Codes::ATTR_AFTER]; |
|||
790 | 2 | } |
|||
791 | 2 | } |
|||
792 | |||||
793 | if (isset($possible[Codes::ATTR_TEST]) && $this->handleTest($possible)) |
||||
794 | 2 | { |
|||
795 | 2 | return null; |
|||
796 | } |
||||
797 | 2 | ||||
798 | 2 | // +1 for [, then the length of the tag, then a space |
|||
799 | $this->pos1 = $this->pos + 1 + $possible[Codes::ATTR_LENGTH] + 1; |
||||
800 | |||||
801 | // If we need to reset content attr, this is where we step in |
||||
802 | if (isset($possible[Codes::ATTR_RESET])) |
||||
803 | { |
||||
804 | $possible[Codes::ATTR_CONTENT] = $possible[Codes::ATTR_RESET]; |
||||
805 | } |
||||
806 | |||||
807 | // This is long, but it makes things much easier and cleaner. |
||||
808 | if (!empty($possible[Codes::ATTR_PARAM])) |
||||
809 | { |
||||
810 | $match = $this->matchParameters($possible, $matches); |
||||
811 | |||||
812 | // Didn't match our parameter list, try the next possible. |
||||
813 | if (!$match) |
||||
814 | 2 | { |
|||
815 | 2 | return null; |
|||
816 | } |
||||
817 | 2 | ||||
818 | return $this->setupTagParameters($possible, $matches); |
||||
819 | } |
||||
820 | |||||
821 | return $possible; |
||||
822 | } |
||||
823 | |||||
824 | /** |
||||
825 | * Called when a code has defined a test parameter |
||||
826 | 6 | * |
|||
827 | * @param array $possible |
||||
828 | * |
||||
829 | 6 | * @return bool |
|||
830 | 6 | */ |
|||
831 | 6 | protected function handleTest(array $possible) |
|||
832 | 6 | { |
|||
833 | return preg_match('~^' . $possible[Codes::ATTR_TEST] . '\]$~', substr($this->message, $this->pos + 2 + $possible[Codes::ATTR_LENGTH], strpos($this->message, ']', $this->pos) - ($this->pos + 1 + $possible[Codes::ATTR_LENGTH]))) === 0; |
||||
834 | 6 | } |
|||
835 | |||||
836 | /** |
||||
837 | * Handles item codes by converting them to lists |
||||
838 | */ |
||||
839 | protected function handleItemCode() |
||||
840 | { |
||||
841 | if (!isset($this->item_codes[$this->message[$this->pos + 1]])) |
||||
842 | { |
||||
843 | return; |
||||
844 | 6 | } |
|||
845 | |||||
846 | $tag = $this->item_codes[$this->message[$this->pos + 1]]; |
||||
847 | 6 | ||||
848 | // First let's set up the tree: it needs to be in a list, or after an li. |
||||
849 | if ($this->inside_tag === null || (isset($this->inside_tag[Codes::ATTR_TAG]) && $this->inside_tag[Codes::ATTR_TAG] !== 'list' && $this->inside_tag[Codes::ATTR_TAG] !== 'li')) |
||||
850 | 6 | { |
|||
851 | $this->addOpenTag(array( |
||||
852 | Codes::ATTR_TAG => 'list', |
||||
853 | Codes::ATTR_TYPE => Codes::TYPE_PARSED_CONTENT, |
||||
854 | Codes::ATTR_AFTER => '</ul>', |
||||
855 | 6 | Codes::ATTR_BLOCK_LEVEL => true, |
|||
856 | Codes::ATTR_REQUIRE_CHILDREN => array('li' => 'li'), |
||||
857 | 6 | Codes::ATTR_DISALLOW_CHILDREN => $this->inside_tag[Codes::ATTR_DISALLOW_CHILDREN] ?? null, |
|||
858 | Codes::ATTR_LENGTH => 4, |
||||
859 | Codes::ATTR_AUTOLINK => true, |
||||
860 | )); |
||||
861 | |||||
862 | 6 | $code = '<ul class="bbc_list">'; |
|||
863 | } |
||||
864 | 6 | // We're in a list item already: another itemcode? Close it first. |
|||
865 | elseif (isset($this->inside_tag[Codes::ATTR_TAG]) && $this->inside_tag[Codes::ATTR_TAG] === 'li') |
||||
866 | { |
||||
867 | 6 | $this->closeOpenedTag(); |
|||
868 | 6 | $code = '</li>'; |
|||
869 | 6 | } |
|||
870 | 6 | else |
|||
871 | 6 | { |
|||
872 | $code = ''; |
||||
873 | 6 | } |
|||
874 | |||||
875 | // Now we open a new tag. |
||||
876 | $this->addOpenTag(array( |
||||
877 | Codes::ATTR_TAG => 'li', |
||||
878 | Codes::ATTR_TYPE => Codes::TYPE_PARSED_CONTENT, |
||||
879 | Codes::ATTR_AFTER => '</li>', |
||||
880 | Codes::ATTR_TRIM => Codes::TRIM_OUTSIDE, |
||||
881 | Codes::ATTR_BLOCK_LEVEL => true, |
||||
882 | Codes::ATTR_DISALLOW_CHILDREN => $this->inside_tag[Codes::ATTR_DISALLOW_CHILDREN] ?? null, |
||||
883 | 2 | Codes::ATTR_AUTOLINK => true, |
|||
884 | Codes::ATTR_LENGTH => 2, |
||||
885 | )); |
||||
886 | 2 | ||||
887 | // First, open the tag... |
||||
888 | $code .= '<li style="list-style-type: ' . $tag . '">'; |
||||
889 | $tmp = $this->noSmileys($code); |
||||
890 | $this->message = substr_replace($this->message, $tmp, $this->pos, 3); |
||||
0 ignored issues
–
show
It seems like
substr_replace($this->me...e, $tmp, $this->pos, 3) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
891 | $this->pos += strlen($tmp) - 1; |
||||
892 | |||||
893 | // Next, find the next break (if any.) If there's more itemcode after it, keep it going - otherwise close! |
||||
894 | $this->pos2 = strpos($this->message, '<br />', $this->pos); |
||||
0 ignored issues
–
show
It seems like
$this->message can also be of type array ; however, parameter $haystack of strpos() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
895 | $this->pos3 = strpos($this->message, '[/', $this->pos); |
||||
896 | |||||
897 | $num_open_tags = count($this->open_tags); |
||||
898 | if ($this->pos2 !== false && ($this->pos3 === false || $this->pos2 <= $this->pos3)) |
||||
899 | { |
||||
900 | // Can't use offset because of the ^ |
||||
901 | 2 | preg_match('~^(<br />| |\s|\[)+~', substr($this->message, $this->pos2 + 6), $matches); |
|||
0 ignored issues
–
show
It seems like
$this->message can also be of type array ; however, parameter $string of substr() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
902 | //preg_match('~(<br />| |\s|\[)+~', $this->message, $matches, 0, $this->pos2 + 6); |
||||
903 | |||||
904 | 2 | // Keep the list open if the next character after the break is a [. Otherwise, close it. |
|||
905 | 2 | $replacement = !empty($matches[0]) && substr_compare($matches[0], '[', -1, 1) === 0 ? '[/li]' : '[/li][/list]'; |
|||
906 | |||||
907 | $this->message = substr_replace($this->message, $replacement, $this->pos2, 0); |
||||
908 | $this->open_tags[$num_open_tags - 2][Codes::ATTR_AFTER] = '</ul>'; |
||||
909 | } |
||||
910 | 2 | // Tell the [list] that it needs to close specially. |
|||
911 | 2 | else |
|||
912 | { |
||||
913 | // Move the li over, because we're not sure what we'll hit. |
||||
914 | $this->open_tags[$num_open_tags - 1][Codes::ATTR_AFTER] = ''; |
||||
915 | $this->open_tags[$num_open_tags - 2][Codes::ATTR_AFTER] = '</li></ul>'; |
||||
916 | } |
||||
917 | 2 | } |
|||
918 | 2 | ||||
919 | /** |
||||
920 | * Handle codes that are of the parsed context type |
||||
921 | 2 | * |
|||
922 | * @param array $tag |
||||
923 | * |
||||
924 | * @return bool |
||||
925 | */ |
||||
926 | protected function handleTypeParsedContext(array $tag) |
||||
927 | 2 | { |
|||
928 | // @todo Check for end tag first, so people can say "I like that [i] tag"? |
||||
929 | 2 | $this->addOpenTag($tag); |
|||
930 | $tmp = $this->noSmileys($tag[Codes::ATTR_BEFORE]); |
||||
931 | $this->message = substr_replace($this->message, $tmp, $this->pos, $this->pos1 - $this->pos); |
||||
0 ignored issues
–
show
It seems like
substr_replace($this->me...his->pos1 - $this->pos) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
932 | 2 | $this->pos += strlen($tmp) - 1; |
|||
933 | 2 | ||||
934 | 2 | return false; |
|||
935 | 2 | } |
|||
936 | |||||
937 | 2 | /** |
|||
938 | * Handle codes that are of the unparsed context type |
||||
939 | * |
||||
940 | * @param array $tag |
||||
941 | * |
||||
942 | * @return bool |
||||
943 | */ |
||||
944 | protected function handleTypeUnparsedContext(array $tag) |
||||
945 | { |
||||
946 | // Find the next closer |
||||
947 | 4 | $this->pos2 = stripos($this->message, '[/' . $tag[Codes::ATTR_TAG] . ']', $this->pos1); |
|||
948 | |||||
949 | 4 | // Account for basic tag nesting |
|||
950 | 4 | $this->handleUnparsedContentNesting($tag); |
|||
951 | 4 | ||||
952 | 4 | // No closer |
|||
953 | if ($this->pos2 === false) |
||||
954 | 4 | { |
|||
955 | return true; |
||||
956 | } |
||||
957 | |||||
958 | $data = substr($this->message, $this->pos1, $this->pos2 - $this->pos1); |
||||
959 | |||||
960 | if (!empty($tag[Codes::ATTR_BLOCK_LEVEL]) && isset($data[0]) && substr_compare($data, '<br />', 0, 6) === 0) |
||||
961 | { |
||||
962 | $data = substr($data, 6); |
||||
963 | } |
||||
964 | |||||
965 | if (isset($tag[Codes::ATTR_VALIDATE])) |
||||
966 | { |
||||
967 | $this->filterData($tag, $data); |
||||
968 | } |
||||
969 | |||||
970 | $code = strtr($tag[Codes::ATTR_CONTENT], array('$1' => $data)); |
||||
971 | $tmp = $this->noSmileys($code); |
||||
972 | $this->message = substr_replace($this->message, $tmp, $this->pos, $this->pos2 + 3 + $tag[Codes::ATTR_LENGTH] - $this->pos); |
||||
0 ignored issues
–
show
It seems like
substr_replace($this->me...R_LENGTH] - $this->pos) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
973 | $this->pos += strlen($tmp) - 1; |
||||
974 | $this->last_pos = $this->pos + 1; |
||||
975 | |||||
976 | return false; |
||||
977 | } |
||||
978 | |||||
979 | /** |
||||
980 | * Account for the most basic nesting of same unparsed tags |
||||
981 | * - [code][code]x[/code][/code] |
||||
982 | * - [code][code]x[/code]<br />[/code] |
||||
983 | */ |
||||
984 | protected function handleUnparsedContentNesting($tag) |
||||
985 | { |
||||
986 | $nest_advance = $this->pos2 + $tag[Codes::ATTR_LENGTH] + 3; |
||||
987 | |||||
988 | $nest_check = stripos($this->message, '<br />[/' . $tag[Codes::ATTR_TAG] . ']', $nest_advance); |
||||
989 | if ($nest_check && $nest_check === $nest_advance) |
||||
990 | { |
||||
991 | $this->pos2 = $nest_advance + 6; |
||||
992 | } |
||||
993 | else |
||||
994 | { |
||||
995 | $nest_check = stripos($this->message, '[/' . $tag[Codes::ATTR_TAG] . ']', $nest_advance); |
||||
996 | if ($nest_check && $nest_check === $nest_advance) |
||||
997 | { |
||||
998 | $this->pos2 = $nest_advance; |
||||
999 | } |
||||
1000 | } |
||||
1001 | } |
||||
1002 | |||||
1003 | /** |
||||
1004 | * Handle codes that are of the unparsed equals context type |
||||
1005 | * |
||||
1006 | * @param array $tag |
||||
1007 | * |
||||
1008 | * @return bool |
||||
1009 | */ |
||||
1010 | protected function handleUnparsedEqualsContext(array $tag) |
||||
1011 | { |
||||
1012 | // The value may be quoted for some tags - check. |
||||
1013 | if (isset($tag[Codes::ATTR_QUOTED])) |
||||
1014 | { |
||||
1015 | $quoted = substr_compare($this->message, '"', $this->pos1, 6) === 0; |
||||
1016 | if ($tag[Codes::ATTR_QUOTED] !== Codes::OPTIONAL && !$quoted) |
||||
1017 | { |
||||
1018 | return true; |
||||
1019 | } |
||||
1020 | |||||
1021 | if ($quoted) |
||||
1022 | { |
||||
1023 | $this->pos1 += 6; |
||||
1024 | } |
||||
1025 | } |
||||
1026 | else |
||||
1027 | { |
||||
1028 | $quoted = false; |
||||
1029 | } |
||||
1030 | |||||
1031 | $this->pos2 = strpos($this->message, $quoted ? '"]' : ']', $this->pos1); |
||||
1032 | if ($this->pos2 === false) |
||||
1033 | { |
||||
1034 | return true; |
||||
1035 | } |
||||
1036 | |||||
1037 | $this->pos3 = stripos($this->message, '[/' . $tag[Codes::ATTR_TAG] . ']', $this->pos2); |
||||
1038 | if ($this->pos3 === false) |
||||
1039 | { |
||||
1040 | return true; |
||||
1041 | } |
||||
1042 | |||||
1043 | $data = array( |
||||
1044 | substr($this->message, $this->pos2 + ($quoted ? 7 : 1), $this->pos3 - ($this->pos2 + ($quoted ? 7 : 1))), |
||||
1045 | substr($this->message, $this->pos1, $this->pos2 - $this->pos1) |
||||
1046 | ); |
||||
1047 | |||||
1048 | if (!empty($tag[Codes::ATTR_BLOCK_LEVEL]) && substr_compare($data[0], '<br />', 0, 6) === 0) |
||||
1049 | { |
||||
1050 | $data[0] = substr($data[0], 6); |
||||
1051 | 14 | } |
|||
1052 | |||||
1053 | // Validation for my parking, please! |
||||
1054 | 14 | if (isset($tag[Codes::ATTR_VALIDATE])) |
|||
1055 | { |
||||
1056 | 4 | $this->filterData($tag, $data); |
|||
1057 | 4 | } |
|||
1058 | |||||
1059 | $code = strtr($tag[Codes::ATTR_CONTENT], array('$1' => $data[0], '$2' => $data[1])); |
||||
1060 | $tmp = $this->noSmileys($code); |
||||
1061 | $this->message = substr_replace($this->message, $tmp, $this->pos, $this->pos3 + 3 + $tag[Codes::ATTR_LENGTH] - $this->pos); |
||||
0 ignored issues
–
show
It seems like
substr_replace($this->me...R_LENGTH] - $this->pos) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
1062 | 4 | $this->pos += strlen($tmp) - 1; |
|||
1063 | |||||
1064 | 4 | return false; |
|||
1065 | } |
||||
1066 | |||||
1067 | /** |
||||
1068 | * Handle codes that are of the closed type |
||||
1069 | 14 | * |
|||
1070 | * @param array $tag |
||||
1071 | * |
||||
1072 | 14 | * @return bool |
|||
1073 | 14 | */ |
|||
1074 | protected function handleTypeClosed(array $tag) |
||||
1075 | { |
||||
1076 | $this->pos2 = strpos($this->message, ']', $this->pos); |
||||
1077 | $tmp = $this->noSmileys($tag[Codes::ATTR_CONTENT]); |
||||
1078 | 14 | $this->message = substr_replace($this->message, $tmp, $this->pos, $this->pos2 + 1 - $this->pos); |
|||
0 ignored issues
–
show
It seems like
substr_replace($this->me...>pos2 + 1 - $this->pos) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
1079 | $this->pos += strlen($tmp) - 1; |
||||
1080 | |||||
1081 | 14 | return false; |
|||
1082 | } |
||||
1083 | 12 | ||||
1084 | /** |
||||
1085 | * Handle codes that are of the unparsed commas context type |
||||
1086 | * |
||||
1087 | 14 | * @param array $tag |
|||
1088 | * |
||||
1089 | 2 | * @return bool |
|||
1090 | */ |
||||
1091 | protected function handleUnparsedCommasContext(array $tag) |
||||
1092 | 14 | { |
|||
1093 | $this->pos2 = strpos($this->message, ']', $this->pos1); |
||||
1094 | 14 | if ($this->pos2 === false) |
|||
1095 | { |
||||
1096 | 14 | return true; |
|||
1097 | 14 | } |
|||
1098 | 14 | ||||
1099 | 14 | $this->pos3 = stripos($this->message, '[/' . $tag[Codes::ATTR_TAG] . ']', $this->pos2); |
|||
1100 | if ($this->pos3 === false) |
||||
1101 | 14 | { |
|||
1102 | return true; |
||||
1103 | } |
||||
1104 | |||||
1105 | // We want $1 to be the content, and the rest to be csv. |
||||
1106 | $data = explode(',', ',' . substr($this->message, $this->pos1, $this->pos2 - $this->pos1)); |
||||
1107 | $data[0] = substr($this->message, $this->pos2 + 1, $this->pos3 - $this->pos2 - 1); |
||||
1108 | |||||
1109 | if (isset($tag[Codes::ATTR_VALIDATE])) |
||||
1110 | { |
||||
1111 | 14 | $this->filterData($tag, $data); |
|||
1112 | } |
||||
1113 | 14 | ||||
1114 | $code = $tag[Codes::ATTR_CONTENT]; |
||||
1115 | 7 | foreach ($data as $k => $d) |
|||
1116 | 6 | { |
|||
1117 | $code = strtr($code, array('$' . ($k + 1) => trim($d))); |
||||
1118 | } |
||||
1119 | 7 | ||||
1120 | 6 | $tmp = $this->noSmileys($code); |
|||
1121 | $this->message = substr_replace($this->message, $tmp, $this->pos, $this->pos3 + 3 + $tag[Codes::ATTR_LENGTH] - $this->pos); |
||||
0 ignored issues
–
show
It seems like
substr_replace($this->me...R_LENGTH] - $this->pos) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
1122 | $this->pos += strlen($tmp) - 1; |
||||
1123 | 7 | ||||
1124 | 2 | return false; |
|||
1125 | } |
||||
1126 | |||||
1127 | 7 | /** |
|||
1128 | 4 | * Handle codes that are of the unparsed commas type |
|||
1129 | * |
||||
1130 | * @param array $tag |
||||
1131 | 7 | * |
|||
1132 | * @return bool |
||||
1133 | */ |
||||
1134 | protected function handleUnparsedCommas(array $tag) |
||||
1135 | 7 | { |
|||
1136 | $this->pos2 = strpos($this->message, ']', $this->pos1); |
||||
1137 | if ($this->pos2 === false) |
||||
1138 | { |
||||
1139 | 7 | return true; |
|||
1140 | 7 | } |
|||
1141 | 14 | ||||
1142 | $data = explode(',', substr($this->message, $this->pos1, $this->pos2 - $this->pos1)); |
||||
1143 | |||||
1144 | if (isset($tag[Codes::ATTR_VALIDATE])) |
||||
1145 | { |
||||
1146 | $this->filterData($tag, $data); |
||||
1147 | } |
||||
1148 | |||||
1149 | // Fix after, for disabled code mainly. |
||||
1150 | foreach ($data as $k => $d) |
||||
1151 | { |
||||
1152 | 28 | $tag[Codes::ATTR_AFTER] = strtr($tag[Codes::ATTR_AFTER], array('$' . ($k + 1) => trim($d))); |
|||
1153 | } |
||||
1154 | |||||
1155 | 28 | $this->addOpenTag($tag); |
|||
1156 | |||||
1157 | // Replace them out, $1, $2, $3, $4, etc. |
||||
1158 | 28 | $code = $tag[Codes::ATTR_BEFORE]; |
|||
1159 | foreach ($data as $k => $d) |
||||
1160 | { |
||||
1161 | $code = strtr($code, array('$' . ($k + 1) => trim($d))); |
||||
1162 | 28 | } |
|||
1163 | |||||
1164 | 4 | $tmp = $this->noSmileys($code); |
|||
1165 | $this->message = substr_replace($this->message, $tmp, $this->pos, $this->pos2 + 1 - $this->pos); |
||||
0 ignored issues
–
show
It seems like
substr_replace($this->me...>pos2 + 1 - $this->pos) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
1166 | $this->pos += strlen($tmp) - 1; |
||||
1167 | |||||
1168 | 28 | return false; |
|||
1169 | } |
||||
1170 | |||||
1171 | 2 | /** |
|||
1172 | * Handle codes that are of the equals type |
||||
1173 | * |
||||
1174 | 28 | * @param array $tag |
|||
1175 | * |
||||
1176 | 28 | * @return bool |
|||
1177 | */ |
||||
1178 | protected function handleEquals(array $tag) |
||||
1179 | { |
||||
1180 | 28 | // The value may be quoted for some tags - check. |
|||
1181 | if (isset($tag[Codes::ATTR_QUOTED])) |
||||
1182 | { |
||||
1183 | 28 | $quoted = substr_compare($this->message, '"', $this->pos1, 6) === 0; |
|||
1184 | if ($tag[Codes::ATTR_QUOTED] !== Codes::OPTIONAL && !$quoted) |
||||
1185 | 4 | { |
|||
1186 | return true; |
||||
1187 | } |
||||
1188 | 4 | ||||
1189 | 4 | if ($quoted) |
|||
1190 | 4 | { |
|||
1191 | $this->pos1 += 6; |
||||
1192 | 28 | } |
|||
1193 | } |
||||
1194 | else |
||||
1195 | { |
||||
1196 | $quoted = false; |
||||
1197 | 2 | } |
|||
1198 | |||||
1199 | 2 | $this->pos2 = strpos($this->message, $quoted ? '"]' : ']', $this->pos1); |
|||
1200 | if ($this->pos2 === false) |
||||
1201 | { |
||||
1202 | 2 | return true; |
|||
1203 | } |
||||
1204 | 2 | ||||
1205 | 2 | $data = substr($this->message, $this->pos1, $this->pos2 - $this->pos1); |
|||
1206 | 2 | ||||
1207 | // Validation for my parking, please! |
||||
1208 | if (isset($tag[Codes::ATTR_VALIDATE])) |
||||
1209 | { |
||||
1210 | 2 | $this->filterData($tag, $data); |
|||
1211 | 2 | } |
|||
1212 | 2 | ||||
1213 | // For parsed content, we must recurse to avoid security problems. |
||||
1214 | if ($tag[Codes::ATTR_TYPE] === Codes::TYPE_PARSED_EQUALS) |
||||
1215 | 2 | { |
|||
1216 | $data = $this->recursiveParser($data, $tag); |
||||
1217 | 2 | } |
|||
1218 | |||||
1219 | 2 | $tag[Codes::ATTR_AFTER] = strtr($tag[Codes::ATTR_AFTER], array('$1' => $data)); |
|||
1220 | |||||
1221 | $this->addOpenTag($tag); |
||||
1222 | |||||
1223 | $code = strtr($tag[Codes::ATTR_BEFORE], array('$1' => $data)); |
||||
1224 | $tmp = $this->noSmileys($code); |
||||
1225 | $this->message = substr_replace($this->message, $tmp, $this->pos, $this->pos2 + ($quoted ? 7 : 1) - $this->pos); |
||||
0 ignored issues
–
show
It seems like
substr_replace($this->me...d ? 7 : 1 - $this->pos) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
1226 | $this->pos += strlen($tmp) - 1; |
||||
1227 | |||||
1228 | 2 | return false; |
|||
1229 | } |
||||
1230 | 2 | ||||
1231 | 2 | /** |
|||
1232 | * Handles a tag by its type. Offloads the actual handling to handle*() method |
||||
1233 | 2 | * |
|||
1234 | * @param array $tag |
||||
1235 | * |
||||
1236 | * @return bool true if there was something wrong and the parser should advance |
||||
1237 | */ |
||||
1238 | protected function handleTag(array $tag) |
||||
1239 | { |
||||
1240 | return match ($tag[Codes::ATTR_TYPE]) |
||||
1241 | { |
||||
1242 | Codes::TYPE_PARSED_CONTENT => $this->handleTypeParsedContext($tag), |
||||
1243 | Codes::TYPE_UNPARSED_CONTENT => $this->handleTypeUnparsedContext($tag), |
||||
1244 | Codes::TYPE_UNPARSED_EQUALS_CONTENT => $this->handleUnparsedEqualsContext($tag), |
||||
1245 | Codes::TYPE_CLOSED => $this->handleTypeClosed($tag), |
||||
1246 | Codes::TYPE_UNPARSED_COMMAS_CONTENT => $this->handleUnparsedCommasContext($tag), |
||||
1247 | Codes::TYPE_UNPARSED_COMMAS => $this->handleUnparsedCommas($tag), |
||||
1248 | Codes::TYPE_PARSED_EQUALS, Codes::TYPE_UNPARSED_EQUALS => $this->handleEquals($tag), |
||||
1249 | default => false, |
||||
1250 | }; |
||||
1251 | |||||
1252 | } |
||||
1253 | |||||
1254 | /** |
||||
1255 | * Text between tags |
||||
1256 | * |
||||
1257 | * @todo I don't know what else to call this. It's the area that isn't a tag. |
||||
1258 | */ |
||||
1259 | protected function betweenTags() |
||||
1260 | { |
||||
1261 | // Make sure the $this->last_pos is not negative. |
||||
1262 | $this->last_pos = max($this->last_pos, 0); |
||||
1263 | |||||
1264 | // Pick a block of data to do some raw fixing on. |
||||
1265 | $data = substr($this->message, $this->last_pos, $this->pos - $this->last_pos); |
||||
1266 | |||||
1267 | 4 | // This happens when the pos is > last_pos and there is a trailing \n from one of the tags having "AFTER" |
|||
1268 | // In micro-optimization tests, using substr() here doesn't prove to be slower. This is much easier to read so leave it. |
||||
1269 | 4 | if ($data === $this->smiley_marker) |
|||
1270 | { |
||||
1271 | 4 | return; |
|||
1272 | 4 | } |
|||
1273 | 4 | ||||
1274 | // Take care of some HTML! |
||||
1275 | 4 | if ($this->possible_html && strpos($data, '<') !== false) |
|||
1276 | { |
||||
1277 | 4 | // @todo new \Parser\BBC\HTML; |
|||
1278 | 4 | $data = $this->parseHTML($data); |
|||
1279 | 4 | } |
|||
1280 | 4 | ||||
1281 | // Take care of some Markdown! |
||||
1282 | if ($this->possible_markdown) |
||||
1283 | 4 | { |
|||
1284 | $data = $this->parseMarkdown($data); |
||||
1285 | } |
||||
1286 | |||||
1287 | 4 | if (!empty($GLOBALS['modSettings']['autoLinkUrls'])) |
|||
1288 | { |
||||
1289 | $data = $this->autoLink($data); |
||||
1290 | } |
||||
1291 | 4 | ||||
1292 | // This cannot be moved earlier. It breaks tests |
||||
1293 | $data = str_replace("\t", ' ', $data); |
||||
1294 | 4 | ||||
1295 | // If it wasn't changed, no copying or other boring stuff has to happen! |
||||
1296 | if (substr_compare($this->message, $data, $this->last_pos, $this->pos - $this->last_pos) !== 0) |
||||
1297 | { |
||||
1298 | 4 | $this->message = substr_replace($this->message, $data, $this->last_pos, $this->pos - $this->last_pos); |
|||
0 ignored issues
–
show
It seems like
substr_replace($this->me...>pos - $this->last_pos) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
1299 | |||||
1300 | // Since we changed it, look again in case we added or removed a tag. But we don't want to skip any. |
||||
1301 | 4 | $old_pos = strlen($data) + $this->last_pos; |
|||
1302 | $this->pos = strpos($this->message, '[', $this->last_pos); |
||||
0 ignored issues
–
show
It seems like
$this->message can also be of type array ; however, parameter $haystack of strpos() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
1303 | $this->pos = $this->pos === false ? $old_pos : min($this->pos, $old_pos); |
||||
1304 | } |
||||
1305 | } |
||||
1306 | 4 | ||||
1307 | 4 | /** |
|||
1308 | * Handles special [footnote] tag processing as the tag is not rendered "inline" |
||||
1309 | 4 | */ |
|||
1310 | protected function handleFootnotes() |
||||
1311 | 4 | { |
|||
1312 | static $fn_total = 0; |
||||
1313 | |||||
1314 | 4 | // @todo temporary until we have nesting |
|||
1315 | 4 | $this->message = str_replace(array('[footnote]', '[/footnote]'), '', $this->message); |
|||
1316 | |||||
1317 | 4 | $this->fn_num = 0; |
|||
1318 | $this->fn_content = array(); |
||||
1319 | $this->fn_count = $fn_total; |
||||
1320 | |||||
1321 | // Replace our footnote text with a [1] link, save the text for use at the end of the message |
||||
1322 | $this->message = preg_replace_callback('~(%fn%(.*?)%fn%)~is', fn(array $matches) => $this->footnoteCallback($matches), $this->message); |
||||
1323 | $fn_total += $this->fn_num; |
||||
1324 | |||||
1325 | // If we have footnotes, add them in at the end of the message |
||||
1326 | if ($this->fn_num !== 0) // Set in callback |
||||
1327 | { |
||||
1328 | 4 | $this->message .= $this->smiley_marker . '<div class="bbc_footnotes">' . implode('', $this->fn_content) . '</div>' . $this->smiley_marker; |
|||
1329 | } |
||||
1330 | 4 | } |
|||
1331 | 4 | ||||
1332 | /** |
||||
1333 | * Final footnote conversions, builds the proper link code to footnote at base of post |
||||
1334 | 4 | * |
|||
1335 | * @param array $matches |
||||
1336 | 4 | * |
|||
1337 | * @return string |
||||
1338 | 4 | */ |
|||
1339 | protected function footnoteCallback(array $matches) |
||||
1340 | 3 | { |
|||
1341 | $this->fn_num++; |
||||
1342 | $this->fn_content[] = '<div class="target" id="fn' . $this->fn_num . '_' . $this->fn_count . '"><sup>' . $this->fn_num . ' </sup>' . $matches[2] . '<a class="footnote_return" href="#ref' . $this->fn_num . '_' . $this->fn_count . '">↵</a></div>'; |
||||
1343 | |||||
1344 | 4 | return '<a class="target" href="#fn' . $this->fn_num . '_' . $this->fn_count . '" id="ref' . $this->fn_num . '_' . $this->fn_count . '">[' . $this->fn_num . ']</a>'; |
|||
1345 | } |
||||
1346 | 4 | ||||
1347 | /** |
||||
1348 | * Parse a tag that is disabled |
||||
1349 | * |
||||
1350 | * @param array $tag |
||||
1351 | */ |
||||
1352 | protected function handleDisabled(array &$tag) |
||||
1353 | { |
||||
1354 | if (!isset($tag[Codes::ATTR_DISABLED_BEFORE]) && !isset($tag[Codes::ATTR_DISABLED_AFTER]) && !isset($tag[Codes::ATTR_DISABLED_CONTENT])) |
||||
1355 | { |
||||
1356 | $tag[Codes::ATTR_BEFORE] = empty($tag[Codes::ATTR_BLOCK_LEVEL]) ? '' : '<div>'; |
||||
1357 | $tag[Codes::ATTR_AFTER] = empty($tag[Codes::ATTR_BLOCK_LEVEL]) ? '' : '</div>'; |
||||
1358 | $tag[Codes::ATTR_CONTENT] = $tag[Codes::ATTR_TYPE] === Codes::TYPE_CLOSED ? '' : (empty($tag[Codes::ATTR_BLOCK_LEVEL]) ? '$1' : '<div>$1</div>'); |
||||
1359 | } |
||||
1360 | 4 | elseif (isset($tag[Codes::ATTR_DISABLED_BEFORE]) || isset($tag[Codes::ATTR_DISABLED_AFTER])) |
|||
1361 | { |
||||
1362 | $tag[Codes::ATTR_BEFORE] = $tag[Codes::ATTR_DISABLED_BEFORE] ?? (empty($tag[Codes::ATTR_BLOCK_LEVEL]) ? '' : '<div>'); |
||||
1363 | 4 | $tag[Codes::ATTR_AFTER] = $tag[Codes::ATTR_DISABLED_AFTER] ?? (empty($tag[Codes::ATTR_BLOCK_LEVEL]) ? '' : '</div>'); |
|||
1364 | } |
||||
1365 | else |
||||
1366 | 4 | { |
|||
1367 | $tag[Codes::ATTR_CONTENT] = $tag[Codes::ATTR_DISABLED_CONTENT]; |
||||
1368 | } |
||||
1369 | 4 | } |
|||
1370 | |||||
1371 | 2 | /** |
|||
1372 | * Map required / optional tag parameters to the found tag |
||||
1373 | 2 | * |
|||
1374 | * @param array &$possible |
||||
1375 | * @param array &$matches |
||||
1376 | * @return bool |
||||
1377 | 3 | */ |
|||
1378 | protected function matchParameters(array &$possible, &$matches) |
||||
1379 | { |
||||
1380 | if (!isset($possible['regex_cache'])) |
||||
1381 | 4 | { |
|||
1382 | $possible['regex_cache'] = array(); |
||||
1383 | $possible['param_check'] = array(); |
||||
1384 | $possible['optionals'] = array(); |
||||
1385 | |||||
1386 | foreach ($possible[Codes::ATTR_PARAM] as $param => $info) |
||||
1387 | { |
||||
1388 | $quote = empty($info[Codes::PARAM_ATTR_QUOTED]) ? '' : '"'; |
||||
1389 | $possible['optionals'][] = !empty($info[Codes::PARAM_ATTR_OPTIONAL]); |
||||
1390 | $possible['param_check'][] = ' ' . $param . '=' . $quote; |
||||
1391 | $possible['regex_cache'][] = '(\s+' . $param . '=' . $quote . ($info[Codes::PARAM_ATTR_MATCH] ?? '(.+?)') . $quote . ')'; |
||||
1392 | } |
||||
1393 | 4 | ||||
1394 | $possible['regex_size'] = count($possible[Codes::ATTR_PARAM]) - 1; |
||||
1395 | } |
||||
1396 | 4 | ||||
1397 | // Tag setup for this loop |
||||
1398 | $this->tag_possible = $possible; |
||||
1399 | 4 | ||||
1400 | // Okay, this may look ugly and it is, but it's not going to happen much and it is the best way |
||||
1401 | 4 | // of allowing any order of parameters but still parsing them right. |
|||
1402 | $message_stub = $this->messageStub(); |
||||
1403 | |||||
1404 | 4 | // Set regex optional flags only if the param *is* optional and it *was not used* in this tag |
|||
1405 | $this->optionalParam($message_stub); |
||||
1406 | |||||
1407 | // If an addon adds many parameters we can exceed max_execution time, lets prevent that |
||||
1408 | // 5040 = 7, 40,320 = 8, (N!) etc |
||||
1409 | $max_iterations = self::MAX_PERMUTE_ITERATIONS; |
||||
1410 | |||||
1411 | // Use the same range to start each time. Most BBC is in the order that it should be in when it starts. |
||||
1412 | $keys = $this->setKeys(); |
||||
1413 | |||||
1414 | // Step, one by one, through all possible permutations of the parameters until we have a match |
||||
1415 | do |
||||
1416 | 2 | { |
|||
1417 | $match_preg = '~^'; |
||||
1418 | foreach ($keys as $key) |
||||
1419 | 2 | { |
|||
1420 | $match_preg .= $this->tag_possible['regex_cache'][$key]; |
||||
1421 | 2 | } |
|||
1422 | |||||
1423 | 2 | $match_preg .= '\]~i'; |
|||
1424 | |||||
1425 | // Check if this combination of parameters matches the user input |
||||
1426 | $match = preg_match($match_preg, $message_stub, $matches) !== 0; |
||||
1427 | 2 | } while (!$match && --$max_iterations && ($keys = pc_next_permutation($keys, $possible['regex_size']))); |
|||
1428 | 2 | ||||
1429 | 2 | return $match; |
|||
1430 | } |
||||
1431 | 2 | ||||
1432 | /** |
||||
1433 | 2 | * Sorts the params so they are in a required to optional order. |
|||
1434 | * |
||||
1435 | * Supports the assumption that the params as defined in CODES is the preferred / common |
||||
1436 | * order they are found in, and inserted by, the editor toolbar. |
||||
1437 | * |
||||
1438 | * @return array |
||||
1439 | */ |
||||
1440 | private function setKeys() |
||||
1441 | { |
||||
1442 | $control_order = array(); |
||||
1443 | $this->tag_possible['regex_keys'] = range(0, $this->tag_possible['regex_size']); |
||||
1444 | |||||
1445 | // Push optional params to the end of the stack but maintain current order of required ones |
||||
1446 | foreach (array_keys($this->tag_possible['regex_keys']) as $index) |
||||
1447 | { |
||||
1448 | $control_order[$index] = $index; |
||||
1449 | |||||
1450 | if ($this->tag_possible['optionals'][$index]) |
||||
1451 | { |
||||
1452 | $control_order[$index] = $index + $this->tag_possible['regex_size']; |
||||
1453 | 2 | } |
|||
1454 | } |
||||
1455 | 2 | ||||
1456 | array_multisort($control_order, SORT_ASC, $this->tag_possible['regex_cache']); |
||||
0 ignored issues
–
show
BBC\SORT_ASC cannot be passed to array_multisort() as the parameter $rest expects a reference.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
1457 | 2 | ||||
1458 | return $this->tag_possible['regex_keys']; |
||||
1459 | } |
||||
1460 | |||||
1461 | /** |
||||
1462 | * Sets the optional parameter search flag only where needed. |
||||
1463 | * |
||||
1464 | * What it does: |
||||
1465 | 14 | * |
|||
1466 | * - Sets the optional ()? flag only for optional params that were not actually used |
||||
1467 | 14 | * - This makes the permutation function match all required *and* passed parameters |
|||
1468 | 14 | * - Returns false if an non optional tag was not found |
|||
1469 | * |
||||
1470 | * @param string $message_stub |
||||
1471 | */ |
||||
1472 | protected function optionalParam($message_stub) |
||||
1473 | { |
||||
1474 | // Set optional flag only if the param is optional and it was not used in this tag |
||||
1475 | 28 | foreach ($this->tag_possible['optionals'] as $index => $optional) |
|||
1476 | { |
||||
1477 | 28 | // @todo more robust, and slower, check would be a preg_match on $possible['regex_cache'][$index] |
|||
1478 | $param_exists = stripos($message_stub, (string) $this->tag_possible['param_check'][$index]) !== false; |
||||
1479 | 28 | ||||
1480 | // Only make unused optional tags as optional |
||||
1481 | if ($optional) |
||||
1482 | { |
||||
1483 | if ($param_exists) |
||||
1484 | { |
||||
1485 | $this->tag_possible['optionals'][$index] = false; |
||||
1486 | } |
||||
1487 | else |
||||
1488 | { |
||||
1489 | $this->tag_possible['regex_cache'][$index] .= '?'; |
||||
1490 | } |
||||
1491 | } |
||||
1492 | } |
||||
1493 | } |
||||
1494 | |||||
1495 | 16 | /** |
|||
1496 | * Given the position in a message, extracts the tag for analysis |
||||
1497 | 16 | * |
|||
1498 | * What it does: |
||||
1499 | * |
||||
1500 | * - Given ' width=100 height=100 alt=image]....[/img]more text and [tags]...' |
||||
1501 | * - Returns ' width=100 height=100 alt=image]....[/img]' |
||||
1502 | * |
||||
1503 | * @return string |
||||
1504 | */ |
||||
1505 | 6 | protected function messageStub() |
|||
1506 | { |
||||
1507 | 6 | // For parameter searching, swap in \n's to reduce any regex greediness |
|||
1508 | $message_stub = str_replace('<br />', "\n", substr($this->message, $this->pos1 - 1)) . "\n"; |
||||
1509 | |||||
1510 | // Attempt to pull out just this tag |
||||
1511 | if (preg_match('~^(?:.+?)\](?>.|(?R))*?\[\/' . $this->tag_possible[Codes::ATTR_TAG] . '\](?:.|\s)~i', $message_stub, $matches) === 1) |
||||
1512 | { |
||||
1513 | return $matches[0]; |
||||
1514 | } |
||||
1515 | |||||
1516 | return $message_stub; |
||||
1517 | 14 | } |
|||
1518 | |||||
1519 | 14 | /** |
|||
1520 | * Recursively call the parser with a new Codes object |
||||
1521 | 14 | * This allows to parse BBC in parameters like [quote author="[url]www.quotes.com[/url]"]Something famous.[/quote] |
|||
1522 | * |
||||
1523 | * @param string $data |
||||
1524 | * @param array $tag |
||||
1525 | * |
||||
1526 | * @return string |
||||
1527 | */ |
||||
1528 | protected function recursiveParser($data, array $tag) |
||||
1529 | { |
||||
1530 | // @todo if parsed tags allowed is empty, return? |
||||
1531 | $bbc = clone $this->bbc; |
||||
1532 | |||||
1533 | if (!empty($tag[Codes::ATTR_PARSED_TAGS_ALLOWED])) |
||||
1534 | { |
||||
1535 | $bbc->setParsedTags($tag[Codes::ATTR_PARSED_TAGS_ALLOWED]); |
||||
1536 | } |
||||
1537 | |||||
1538 | 4 | // Do not use $this->autolinker. For some reason it causes a recursive loop |
|||
1539 | $autolinker = null; |
||||
1540 | 4 | $html = null; |
|||
1541 | call_integration_hook('integrate_recursive_bbc_parser', array(&$autolinker, &$html)); |
||||
1542 | |||||
1543 | $parser = new BBCParser($bbc, $autolinker, $html); |
||||
1544 | 4 | ||||
1545 | return $parser->enableSmileys(empty($tag[Codes::ATTR_PARSED_TAGS_ALLOWED]))->parse($data); |
||||
1546 | } |
||||
1547 | |||||
1548 | /** |
||||
1549 | * Return the BBC codes in the system |
||||
1550 | * |
||||
1551 | * @return array |
||||
1552 | 2 | */ |
|||
1553 | public function getBBC() |
||||
1554 | 2 | { |
|||
1555 | 2 | return $this->bbc_codes; |
|||
1556 | } |
||||
1557 | 2 | ||||
1558 | /** |
||||
1559 | 2 | * Enable the parsing of smileys |
|||
1560 | * |
||||
1561 | 2 | * @param bool $enable |
|||
1562 | * |
||||
1563 | 2 | * @return $this |
|||
1564 | */ |
||||
1565 | 2 | public function enableSmileys($enable = true) |
|||
1566 | { |
||||
1567 | $this->do_smileys = (bool) $enable; |
||||
1568 | |||||
1569 | 2 | return $this; |
|||
1570 | } |
||||
1571 | |||||
1572 | /** |
||||
1573 | 2 | * Open a tag |
|||
1574 | * |
||||
1575 | * @param array $tag |
||||
1576 | 2 | */ |
|||
1577 | protected function addOpenTag(array $tag) |
||||
1578 | 2 | { |
|||
1579 | $this->open_tags[] = $tag; |
||||
1580 | 2 | } |
|||
1581 | |||||
1582 | /** |
||||
1583 | * @param string|bool $tag = false False closes the last open tag. Anything else finds that tag LIFO |
||||
1584 | * |
||||
1585 | 2 | * @return mixed |
|||
1586 | */ |
||||
1587 | protected function closeOpenedTag($tag = false) |
||||
1588 | 2 | { |
|||
1589 | if ($tag === false) |
||||
1590 | 2 | { |
|||
1591 | return array_pop($this->open_tags); |
||||
1592 | } |
||||
1593 | 2 | ||||
1594 | if (isset($this->open_tags[$tag])) |
||||
1595 | 2 | { |
|||
1596 | $return = $this->open_tags[$tag]; |
||||
1597 | unset($this->open_tags[$tag]); |
||||
1598 | 2 | ||||
1599 | return $return; |
||||
1600 | 2 | } |
|||
1601 | } |
||||
1602 | |||||
1603 | 2 | /** |
|||
1604 | * Check if there are any tags that are open |
||||
1605 | 2 | * |
|||
1606 | * @return bool |
||||
1607 | */ |
||||
1608 | protected function hasOpenTags() |
||||
1609 | { |
||||
1610 | return !empty($this->open_tags); |
||||
1611 | } |
||||
1612 | |||||
1613 | /** |
||||
1614 | * Get the last opened tag |
||||
1615 | * |
||||
1616 | * @return string |
||||
1617 | */ |
||||
1618 | protected function getLastOpenedTag() |
||||
1619 | { |
||||
1620 | return end($this->open_tags); |
||||
1621 | } |
||||
1622 | |||||
1623 | /** |
||||
1624 | * Get the currently opened tags |
||||
1625 | * |
||||
1626 | * @param bool|false $tags_only True if you want just the tag or false for the whole code |
||||
1627 | * |
||||
1628 | * @return array |
||||
1629 | */ |
||||
1630 | protected function getOpenedTags($tags_only = false) |
||||
1631 | { |
||||
1632 | if (!$tags_only) |
||||
1633 | { |
||||
1634 | return $this->open_tags; |
||||
1635 | 16 | } |
|||
1636 | |||||
1637 | 16 | $tags = array(); |
|||
1638 | foreach ($this->open_tags as $tag) |
||||
1639 | { |
||||
1640 | $tags[] = $tag[Codes::ATTR_TAG]; |
||||
1641 | } |
||||
1642 | |||||
1643 | return $tags; |
||||
1644 | 6 | } |
|||
1645 | |||||
1646 | 6 | /** |
|||
1647 | 6 | * Does what it says, removes whitespace |
|||
1648 | * |
||||
1649 | * @param null|int $offset = null |
||||
1650 | */ |
||||
1651 | protected function trimWhiteSpace($offset = null) |
||||
1652 | { |
||||
1653 | 6 | if (preg_match('~(<br />| |\s)*~', $this->message, $matches, 0, $offset) === 0) |
|||
0 ignored issues
–
show
It seems like
$offset can also be of type null ; however, parameter $offset of preg_match() does only seem to accept integer , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
1654 | { |
||||
1655 | return; |
||||
1656 | } |
||||
1657 | if (!isset($matches[0])) |
||||
1658 | { |
||||
1659 | return; |
||||
1660 | } |
||||
1661 | if ($matches[0] === '') |
||||
1662 | { |
||||
1663 | return; |
||||
1664 | } |
||||
1665 | |||||
1666 | $this->message = substr_replace($this->message, '', $offset, strlen($matches[0])); |
||||
0 ignored issues
–
show
It seems like
$offset can also be of type null ; however, parameter $offset of substr_replace() does only seem to accept array|integer , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like
substr_replace($this->me...t, strlen($matches[0])) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
1667 | } |
||||
1668 | |||||
1669 | /** |
||||
1670 | * Substitutes parameter attribute values in to the tag context |
||||
1671 | * e.g. tag width={width} => tag width=300px |
||||
1672 | * |
||||
1673 | * @param array $possible |
||||
1674 | 6 | * @param array $matches |
|||
1675 | * |
||||
1676 | * @return array |
||||
1677 | */ |
||||
1678 | protected function setupTagParameters(array $possible, array $matches) |
||||
1679 | { |
||||
1680 | $params = array(); |
||||
1681 | for ($i = 1, $n = count($matches); $i < $n; $i += 2) |
||||
1682 | { |
||||
1683 | 14 | $key = strtok(ltrim($matches[$i]), '='); |
|||
1684 | |||||
1685 | 14 | if (isset($possible[Codes::ATTR_PARAM][$key][Codes::PARAM_ATTR_VALUE])) |
|||
1686 | { |
||||
1687 | $params['{' . $key . '}'] = strtr($possible[Codes::ATTR_PARAM][$key][Codes::PARAM_ATTR_VALUE], array('$1' => $matches[$i + 1])); |
||||
1688 | } |
||||
1689 | elseif (isset($possible[Codes::ATTR_PARAM][$key][Codes::PARAM_ATTR_VALIDATE])) |
||||
1690 | { |
||||
1691 | $params['{' . $key . '}'] = $possible[Codes::ATTR_PARAM][$key][Codes::PARAM_ATTR_VALIDATE]($matches[$i + 1]); |
||||
1692 | } |
||||
1693 | else |
||||
1694 | { |
||||
1695 | $params['{' . $key . '}'] = $matches[$i + 1]; |
||||
1696 | } |
||||
1697 | |||||
1698 | // Just to make sure: replace any $ or { so they can't interpolate wrongly. |
||||
1699 | $params['{' . $key . '}'] = str_replace(array('$', '{'), array('$', '{'), $params['{' . $key . '}']); |
||||
1700 | } |
||||
1701 | |||||
1702 | foreach ($possible[Codes::ATTR_PARAM] as $p => $info) |
||||
1703 | { |
||||
1704 | 14 | if (!isset($params['{' . $p . '}'])) |
|||
1705 | { |
||||
1706 | 14 | $params['{' . $p . '}'] = ''; |
|||
1707 | 14 | } |
|||
1708 | } |
||||
1709 | |||||
1710 | // We found our tag |
||||
1711 | $tag = $possible; |
||||
1712 | |||||
1713 | // Put the parameters into the string. |
||||
1714 | if (isset($tag[Codes::ATTR_BEFORE])) |
||||
1715 | { |
||||
1716 | $tag[Codes::ATTR_BEFORE] = strtr($tag[Codes::ATTR_BEFORE], $params); |
||||
1717 | } |
||||
1718 | |||||
1719 | if (isset($tag[Codes::ATTR_AFTER])) |
||||
1720 | { |
||||
1721 | $tag[Codes::ATTR_AFTER] = strtr($tag[Codes::ATTR_AFTER], $params); |
||||
1722 | } |
||||
1723 | |||||
1724 | if (isset($tag[Codes::ATTR_CONTENT])) |
||||
1725 | { |
||||
1726 | $tag[Codes::ATTR_CONTENT] = strtr($tag[Codes::ATTR_CONTENT], $params); |
||||
1727 | } |
||||
1728 | |||||
1729 | $this->pos1 += strlen($matches[0]) - 1; |
||||
1730 | |||||
1731 | return $tag; |
||||
1732 | } |
||||
1733 | |||||
1734 | /** |
||||
1735 | * Check if a tag (not a code) is open |
||||
1736 | * |
||||
1737 | * @param string $tag |
||||
1738 | * |
||||
1739 | * @return bool |
||||
1740 | */ |
||||
1741 | protected function isOpen($tag) |
||||
1742 | { |
||||
1743 | foreach ($this->open_tags as $open) |
||||
1744 | { |
||||
1745 | if ($open[Codes::ATTR_TAG] === $tag) |
||||
1746 | { |
||||
1747 | return true; |
||||
1748 | } |
||||
1749 | } |
||||
1750 | |||||
1751 | return false; |
||||
1752 | } |
||||
1753 | |||||
1754 | /** |
||||
1755 | * Check if a character is an item code |
||||
1756 | * |
||||
1757 | * @param string $char |
||||
1758 | * |
||||
1759 | * @return bool |
||||
1760 | */ |
||||
1761 | protected function isItemCode($char) |
||||
1762 | { |
||||
1763 | return isset($this->item_codes[$char]); |
||||
1764 | } |
||||
1765 | |||||
1766 | /** |
||||
1767 | * Close any open codes that aren't block level. |
||||
1768 | * Used before opening a code that *is* block level |
||||
1769 | */ |
||||
1770 | protected function closeNonBlockLevel() |
||||
1771 | { |
||||
1772 | $n = count($this->open_tags) - 1; |
||||
1773 | while (empty($this->open_tags[$n][Codes::ATTR_BLOCK_LEVEL]) && $n >= 0) |
||||
1774 | { |
||||
1775 | $n--; |
||||
1776 | } |
||||
1777 | |||||
1778 | // Close all the non block level tags so this tag isn't surrounded by them. |
||||
1779 | for ($i = count($this->open_tags) - 1; $i > $n; $i--) |
||||
1780 | { |
||||
1781 | $tmp = isset($this->open_tags[$i][Codes::ATTR_AFTER]) |
||||
1782 | ? $this->noSmileys($this->open_tags[$i][Codes::ATTR_AFTER]) |
||||
1783 | : $this->noSmileys(''); |
||||
1784 | $this->message = substr_replace($this->message, $tmp, $this->pos, 0); |
||||
0 ignored issues
–
show
It seems like
substr_replace($this->me...e, $tmp, $this->pos, 0) can also be of type array . However, the property $message is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||||
1785 | $ot_strlen = strlen($tmp); |
||||
1786 | $this->pos += $ot_strlen; |
||||
1787 | $this->pos1 += $ot_strlen; |
||||
1788 | |||||
1789 | // Trim or eat trailing stuff... see comment at the end of the big loop. |
||||
1790 | if (!empty($this->open_tags[$i][Codes::ATTR_BLOCK_LEVEL]) && substr_compare($this->message, '<br />', $this->pos, 6) === 0) |
||||
0 ignored issues
–
show
It seems like
$this->message can also be of type array ; however, parameter $haystack of substr_compare() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
1791 | { |
||||
1792 | $this->message = substr_replace($this->message, '', $this->pos, 6); |
||||
1793 | } |
||||
1794 | |||||
1795 | if (isset($tag[Codes::ATTR_TRIM]) && $tag[Codes::ATTR_TRIM] !== Codes::TRIM_INSIDE) |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
1796 | { |
||||
1797 | $this->trimWhiteSpace($this->pos); |
||||
1798 | } |
||||
1799 | |||||
1800 | $this->closeOpenedTag(); |
||||
1801 | } |
||||
1802 | } |
||||
1803 | |||||
1804 | /** |
||||
1805 | * Add markers around a string to denote that smileys should not be parsed |
||||
1806 | * |
||||
1807 | * @param string $string |
||||
1808 | * |
||||
1809 | * @return string |
||||
1810 | */ |
||||
1811 | protected function noSmileys($string) |
||||
1812 | { |
||||
1813 | return $this->smiley_marker . $string . $this->smiley_marker; |
||||
1814 | } |
||||
1815 | |||||
1816 | /** |
||||
1817 | * Checks if we can cache, some codes prevent this and require parsing each time |
||||
1818 | * |
||||
1819 | * @return bool |
||||
1820 | */ |
||||
1821 | public function canCache() |
||||
1822 | { |
||||
1823 | return $this->can_cache; |
||||
1824 | } |
||||
1825 | |||||
1826 | /** |
||||
1827 | * This calls Codes::ATTR_VALIDATE. |
||||
1828 | * |
||||
1829 | * @param array $tag |
||||
1830 | * @param $data |
||||
1831 | */ |
||||
1832 | protected function filterData(array &$tag, &$data) |
||||
1833 | { |
||||
1834 | $tag[Codes::ATTR_VALIDATE]($data, $this->bbc->getDisabled(), $tag); |
||||
1835 | } |
||||
1836 | } |
||||
1837 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.