1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
textparser.php
См. документацию.
1<?php
8
17use Bitrix\Socialnetwork\Item\Workgroup\Type;
19
21{
22 public $type = 'html';
23 public $serverName = '';
24 public $preg;
25
26 public $imageWidth = 800;
27 public $imageHeight = 800;
28 public $maxStringLen = 0;
29 public $maxAnchorLength = 40;
30
31 //https://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
32 public $arFontSize = [
33 1 => 'xx-small',
34 2 => 'small',
35 3 => 'medium',
36 4 => 'large',
37 5 => 'x-large',
38 6 => 'xx-large',
39 ];
40 public $allow = [
41 'HTML' => 'N',
42 'ANCHOR' => 'Y',
43 'BIU' => 'Y',
44 'IMG' => 'Y',
45 'QUOTE' => 'Y',
46 'CODE' => 'Y',
47 'FONT' => 'Y',
48 'LIST' => 'Y',
49 'EMOJI' => 'Y',
50 'SMILES' => 'Y',
51 'CLEAR_SMILES' => 'N',
52 'NL2BR' => 'N',
53 'VIDEO' => 'Y',
54 'TABLE' => 'Y',
55 'CUT_ANCHOR' => 'N',
56 'SHORT_ANCHOR' => 'N',
57 'TEXT_ANCHOR' => 'Y',
58 'ALIGN' => 'Y',
59 'USERFIELDS' => 'N',
60 'USER' => 'Y',
61 'PROJECT' => 'Y',
62 'DEPARTMENT' => 'Y',
63 'P' => 'Y',
64 'TAG' => 'N',
65 'SPOILER' => 'Y',
66 ];
67 public $anchorType = 'html';
68 public $smiles = null;
69 public $smilesGallery = CSmileGallery::GALLERY_DEFAULT;
70 public $bMobile = false;
71 public $LAZYLOAD = 'N';
72 public $parser_nofollow = 'N';
73 public $link_target = '_blank';
74 public $authorName = '';
75 public $pathToUser = '';
76 public $pathToUserEntityType = false;
77 public $pathToUserEntityId = false;
78 public $useTypography = false;
79 protected $tagClasses = [
80 'p' => 'ui-typography-paragraph',
81 'b' => 'ui-typography-text-bold',
82 'i' => 'ui-typography-text-italic',
83 'u' => 'ui-typography-text-underline',
84 's' => 'ui-typography-text-strikethrough',
85 'code' => 'ui-typography-code',
86 'quote' => 'ui-typography-quote',
87 'url' => 'ui-typography-link',
88 'image-container' => 'ui-typography-image-container',
89 'image' => 'ui-typography-image',
90 'ol' => 'ui-typography-ol',
91 'ul' => 'ui-typography-ul',
92 'li' => 'ui-typography-li',
93 'table' => 'ui-typography-table',
94 'td' => 'ui-typography-table-cell',
95 'tr' => 'ui-typography-table-row',
96 'th' => 'ui-typography-table-cell ui-typography-table-cell-header',
97 'mention' => 'ui-typography-mention',
98 'smiley' => 'ui-typography-smiley',
99 'hashtag' => 'ui-typography-hashtag',
100 ];
101
102 protected $wordSeparator = "\\s.,;:!?\\#\\-\\*\\|\\[\\]\\(\\)\\{\\}";
103 protected $smilePatterns = null;
104 protected $smileReplaces = null;
105 protected static $repoSmiles = [];
106 protected $defended_urls = [];
107 protected $anchorSchemes = null;
108 protected $userField;
109 protected $tagPattern = "/([\s]+|^)#([^\s,\.\[\]<>]+)/is";
110 protected $pathToSmile = '';
111 protected $ajaxPage;
112
113 protected $code_open = 0;
114 protected $code_error = 0;
115 protected $code_closed = 0;
116 protected $quote_open = 0;
117 protected $quote_error = 0;
118 protected $quote_closed = 0;
119
120 public function __construct()
121 {
122 global $APPLICATION;
123
124 $this->ajaxPage = $APPLICATION->GetCurPageParam('', ['bxajaxid', 'logout']);
125 }
126
127 public function getAnchorSchemes()
128 {
129 if ($this->anchorSchemes === null)
130 {
131 static $schemes = null;
132 if ($schemes === null)
133 {
134 $schemes = Option::get('main', '~parser_anchor_schemes', 'http|https|news|ftp|aim|mailto|file|tel|callto|skype|viber');
135 }
136 $this->anchorSchemes = $schemes;
137 }
139 }
140
141 public function setAnchorSchemes($schemes)
142 {
143 $this->anchorSchemes = $schemes;
144 }
145
146 protected function initSmiles()
147 {
148 if (!array_key_exists($this->smilesGallery, self::$repoSmiles))
149 {
150 $smiles = CSmile::getByGalleryId(CSmile::TYPE_SMILE, $this->smilesGallery);
151 $arSmiles = [];
152 foreach ($smiles as $smile)
153 {
154 $arTypings = explode(' ', $smile['TYPING']);
155 foreach ($arTypings as $typing)
156 {
157 $arSmiles[] = array_merge($smile, [
158 'TYPING' => $typing,
159 'IMAGE' => CSmile::PATH_TO_SMILE . $smile['SET_ID'] . '/' . $smile['IMAGE'],
160 'DESCRIPTION' => $smile['NAME'],
161 'DESCRIPTION_DECODE' => 'Y',
162 ]);
163 }
164 }
165 self::$repoSmiles[$this->smilesGallery] = $arSmiles;
166 }
167 $this->smiles = self::$repoSmiles[$this->smilesGallery];
168 }
169
170 protected function initSmilePatterns()
171 {
172 $this->smilePatterns = [];
173 $this->smileReplaces = [];
174
175 $pre = '';
176 foreach ($this->smiles as $row)
177 {
178 if (preg_match("/\\w\$/", $row['TYPING']))
179 {
180 $pre .= '|' . preg_quote($row['TYPING'], '/');
181 }
182 }
183
184 foreach ($this->smiles as $row)
185 {
186 if ($row['TYPING'] != '' && $row['IMAGE'] != '')
187 {
188 $code = str_replace(["'", "<", ">"], ["\\'", "&lt;", "&gt;"], $row["TYPING"]);
189 $patt = preg_quote($code, "/");
190 $code = preg_quote(str_replace(["\x5C"], ["&#092;"], $code));
191 $image = preg_quote(str_replace("'", "\\'", $row["IMAGE"]));
192 $description = preg_quote(htmlspecialcharsbx(str_replace(["\x5C"], ["&#092;"], $row["DESCRIPTION"]), ENT_QUOTES), "/");
193
194 $patternName = 'pattern' . count($this->smilePatterns);
195
196 $this->smilePatterns[] = "/(?<=^|\\>|[" . $this->wordSeparator . "\\&]" . $pre . ")(?P<" . $patternName . ">$patt)(?=$|\\<|[" . $this->wordSeparator . "\\&])/su";
197
198 $this->smileReplaces[$patternName] = [
199 'code' => $code,
200 'image' => $image,
201 'description' => $description,
202 'width' => intval($row['IMAGE_WIDTH']),
203 'height' => intval($row['IMAGE_HEIGHT']),
204 'descriptionDecode' => $row['DESCRIPTION_DECODE'] == 'Y',
205 'imageDefinition' => $row['IMAGE_DEFINITION'] ?: CSmile::IMAGE_SD,
206 ];
207 }
208 }
209 usort($this->smilePatterns, function($a, $b) { return (mb_strlen($a) > mb_strlen($b) ? -1 : 1); });
210 }
211
212 // Added $attributes for links
213 public function convertText($text, $attributes = [])
214 {
215 if (!is_string($text) || $text == '')
216 {
217 return '';
218 }
219
220 $attributes = is_array($attributes) ? $attributes : [];
221
222 $text = preg_replace(["#([?&;])PHPSESSID=([0-9a-zA-Z]{32})#i", "/\\x{00A0}/u"], ["\\1PHPSESSID1=", " "], $text);
223
224 $this->defended_urls = [];
225
226 if ($this->serverName == '' && $this->type == 'rss')
227 {
228 $dbSite = CSite::GetByID(SITE_ID);
229 $arSite = $dbSite->Fetch();
230 $serverName = $arSite['SERVER_NAME'];
231 if ($serverName == '')
232 {
233 if (defined('SITE_SERVER_NAME') && SITE_SERVER_NAME != '')
234 {
235 $serverName = SITE_SERVER_NAME;
236 }
237 else
238 {
239 $serverName = COption::GetOptionString('main', 'server_name');
240 }
241 }
242 if ($serverName != '')
243 {
244 $this->serverName = 'http://' . $serverName;
245 }
246 }
247
248 $this->preg = ['counter' => 0, 'pattern' => [], 'replace' => [], 'cache' => []];
249
250 foreach (GetModuleEvents('main', 'TextParserBefore', true) as $arEvent)
251 {
252 ExecuteModuleEventEx($arEvent, [&$text, &$this]);
253 }
254
255 if (($this->allow['CODE'] ?? '') == 'Y')
256 {
257 $text = preg_replace_callback(
258 [
259 "#(\\[code(?:\\s+[^]]*]|]))(.+?)(\\[/code(?:\\s+[^]]*]|]))#isu",
260 "#(<code(?:\\s+[^>]*>|>))(.+?)(</code(?:\\s+[^>]*>|>))#isu",
261 ],
262 [$this, 'convertCode'],
263 $text
264 );
265 }
266 if (($this->allow['HTML'] ?? '') != 'Y' && ($this->allow['NL2BR'] ?? '') == 'Y')
267 {
268 $text = preg_replace("#<br(.*?)>#is", "\n", $text);
269 }
270 if (($this->allow['HTML'] ?? '') != 'Y')
271 {
272 // тут она превращается!
273 if (($this->allow['ANCHOR'] ?? '') == 'Y')
274 {
275 $text = preg_replace(
276 [
277 "#<a[^>]+href\\s*=\\s*(['\"])(.+?)(?:\\1)[^>]*>(.*?)</a[^>]*>#isu",
278 "#<a[^>]+href(\\s*=\\s*)([^'\">]+)>(.*?)</a[^>]*>#isu",
279 ],
280 "[url=\\2]\\3[/url]", $text
281 );
282 }
283 if (($this->allow['BIU'] ?? '') == 'Y')
284 {
285 $replaced = 0;
286 do
287 {
288 $text = preg_replace(
289 "/<([busi])[^>a-z]*>(.+?)<\\/(\\1)[^>a-z]*>/isu",
290 "[\\1]\\2[/\\1]",
291 $text, -1, $replaced);
292 }
293 while ($replaced > 0);
294 }
295 if (($this->allow['P'] ?? '') == 'Y')
296 {
297 $replaced = 0;
298 do
299 {
300 $text = preg_replace(
301 "/<p[^>a-z]*>(.+?)<\\/p[^>a-z]*>/isu",
302 "[p]\\1[/p]",
303 $text, -1, $replaced);
304 }
305 while ($replaced > 0);
306 }
307 if (($this->allow['IMG'] ?? '') == 'Y')
308 {
309 $text = preg_replace(
310 "#<img[^>]+src\\s*=[\\s'\"]*(((http|https|ftp)://[.\\-_:a-z0-9@]+)*(/[-_/=:.a-z0-9@{}&?%]+)+)[\\s'\"]*[^>]*>#isu",
311 "[img]\\1[/img]", $text
312 );
313 }
314 if (($this->allow['FONT'] ?? '') == 'Y')
315 {
316 $text = preg_replace(
317 [
318 "/<font[^>]+size\\s*=[\\s'\"]*([0-9]+)[\\s'\"]*[^>]*>(.+?)<\\/font[^>]*>/isu",
319 "/<font[^>]+color\\s*=[\\s'\"]*(#[a-f0-9]{6})[^>]*>(.+?)<\\/font[^>]*>/isu",
320 "/<font[^>]+face\\s*=[\\s'\"]*([a-z\\s\\-]+)[\\s'\"]*[^>]*>(.+?)<\\/font[^>]*>/isu",
321 ],
322 [
323 "[size=\\1]\\2[/size]",
324 "[color=\\1]\\2[/color]",
325 "[font=\\1]\\2[/font]",
326 ],
327 $text
328 );
329 }
330 if (($this->allow['LIST'] ?? '') == 'Y')
331 {
332 $text = preg_replace(
333 [
334 "/<ul((\\s[^>]*)|(\\s*))>(.+?)<\\/ul([^>]*)>/isu",
335 "/<ol((\\s[^>]*)|(\\s*))>(.+?)<\\/ol([^>]*)>/isu",
336 "/<li((\\s[^>]*)|(\\s*))>(.+?)<\\/li([^>]*)>/isu",
337 "/<li((\\s[^>]*)|(\\s*))>/isu",
338 ],
339 [
340 "[list]\\4[/list]",
341 "[list=1]\\4[/list]",
342 "[*]\\4",
343 "[*]",
344 ],
345 $text
346 );
347 }
348 if (($this->allow['TABLE'] ?? '') == 'Y')
349 {
350 $text = preg_replace(
351 [
352 "/<table((\\s[^>]*)|(\\s*))>/isu",
353 "/<\\/table([^>]*)>/isu",
354 "/<tr((\\s[^>]*)|(\\s*))>/isu",
355 "/<\\/tr([^>]*)>/isu",
356 "/<td((\\s[^>]*)|(\\s*))>/isu",
357 "/<\\/td([^>]*)>/isu",
358 "/<th((\\s[^>]*)|(\\s*))>/isu",
359 "/<\\/th([^>]*)>/isu",
360 ],
361 [
362 "[table]",
363 "[/table]",
364 "[tr]",
365 "[/tr]",
366 "[td]",
367 "[/td]",
368 "[th]",
369 "[/th]",
370 ],
371 $text
372 );
373 }
374 if (($this->allow['QUOTE'] ?? '') == 'Y')
375 {
376 $text = preg_replace("#<(/?)quote(.*?)>#is", "[\\1quote]", $text);
377 }
378
379 if (preg_match("/<cut/isu", $text, $matches))
380 {
381 $text = preg_replace(
382 "/<cut([^>]*)>(.+?)<\/cut>/isu",
383 "[cut=\\1]\\2[/cut]",
384 $text);
385 }
386
387 if ($text != '')
388 {
389 if ($this->preg['counter'] > 0)
390 {
391 $res = mb_strlen((string)$this->preg['counter']);
392 $p = ['\d'];
393 while (($res--) > 1)
394 {
395 $p[] = '\d{' . ($res + 1) . '}';
396 }
397 $text = preg_replace(
398 ["/<(?!\017#(" . implode(")|(", $p) . ")>)/", "/(?<!<\017#(" . implode(")|(", $p) . "))>/", "/\"/"],
399 ["&lt;", "&gt;", "&quot;"],
400 $text
401 );
402 }
403 else
404 {
405 $text = str_replace(
406 ["<", ">", "/\"/"],
407 ["&lt;", "&gt;", "&quot;"],
408 $text
409 );
410
411 }
412 }
413 }
414 $patt = [];
415 if (($this->allow['VIDEO'] ?? '') == 'Y')
416 {
417 $patt[] = "/\\[video([^\\]]*)\\](.+?)\\[\\/video[\\s]*\\]/isu";
418 }
419 if (($this->allow['IMG'] ?? '') == 'Y')
420 {
421 $patt[] = "/\\[img([^\\]]*)\\](.+?)\\[\\/img\\]/isu";
422 }
423
424 foreach (GetModuleEvents('main', 'TextParserBeforeAnchorTags', true) as $arEvent)
425 {
426 ExecuteModuleEventEx($arEvent, [&$text, &$this]);
427 }
428
429 if (($this->allow['ANCHOR'] ?? '') == 'Y')
430 {
431 $patt[] = "/\\[url\\](.*?)\\[\\/url\\]/iu";
432 $patt[] = "/\\[url\\s*=\\s*(
433 (?:
434 [^\\[\\]]++
435 |\\[ (?: (?>[^\\[\\]]+) | (?:\\1) )* \\]
436 )+
437 )\\s*\\](.*?)\\[\\/url\\]/ixsu";
438
439 $text = preg_replace_callback(
440 $patt,
441 fn($matches) => $this->preconvertAnchor($matches, $attributes['ANCHOR'] ?? []),
442 $text,
443 );
444
445 if (($this->allow['TEXT_ANCHOR'] ?? 'Y') == 'Y')
446 {
447 $schemes = $this->getAnchorSchemes();
448
449 $boundaries = [
450 '&lt;' => '&gt;',
451 '(' => ')', // doubtful
452 "“" => "”",
453 "‘" => "’",
454 "«" => "»",
455 ];
456
457 $patt = [];
458 foreach ($boundaries as $start => $end)
459 {
460 if (str_contains($text, $start))
461 {
462 $patt[] = "/(?<=" . preg_quote($start) . ")(?<!\\[nomodify\\]|<nomodify>)((((" . $schemes . "):\\/\\/)|www\\.)[._:a-z0-9@-].*?)(?=" . preg_quote($end) . ")/isu";
463 }
464 }
465
466 $patt[] = "/(?<=^|[" . $this->wordSeparator . "])(?<!\\[nomodify\\]|<nomodify>)((((" . $schemes . "):\\/\\/)|www\\.)[._:a-z0-9@-].*?)(?=[\\s\"{}]|&quot;|\$)/isu";
467
468 $text = preg_replace_callback(
469 $patt,
470 fn($matches) => $this->preconvertUrl($matches, $attributes['TEXT_ANCHOR'] ?? []),
471 $text,
472 );
473 }
474 }
475 elseif (!empty($patt))
476 {
477 $text = preg_replace_callback($patt, [$this, 'preconvertAnchor'], $text);
478 }
479
480 $text = preg_replace("/<\\/?nomodify>/iu", '', $text);
481
482 if (($this->allow['SPOILER'] ?? '') === 'Y')
483 {
484 if (preg_match("/\\[(cut|spoiler)/isu", $text, $matches))
485 {
486 $text = preg_replace(
487 [
488 "/\\[(cut|spoiler)(([^]])*)]/isu",
489 "/\\[\\/(cut|spoiler)]/isu",
490 ],
491 [
492 "\001\\2\002",
493 "\003",
494 ],
495 $text
496 );
497
498 while (preg_match("/(\001([^\002]*)\002([^\001\002\003]+)\003)/isu", $text, $matches))
499 {
500 $text = preg_replace_callback("/\001([^\002]*)\002([^\001\002\003]+)\003/isu", [ $this, 'convert_spoiler_tag' ], $text);
501 }
502
503 $text = preg_replace(
504 [
505 "/\001([^\002]+)\002/",
506 "/\001\002/",
507 "/\003/",
508 ],
509 [
510 "[spoiler\\1]",
511 "[spoiler]",
512 "[/spoiler]",
513 ],
514 $text
515 );
516 }
517 }
518
519 foreach (GetModuleEvents('main', 'TextParserBeforeTags', true) as $arEvent)
520 {
521 ExecuteModuleEventEx($arEvent, [&$text, &$this]);
522 }
523
524 if (
525 ($this->allow['SMILES'] ?? '') == 'Y'
526 || ($this->allow['CLEAR_SMILES'] ?? '') == 'Y'
527 )
528 {
529 if (str_contains($text, "<nosmile>"))
530 {
531 $text = preg_replace_callback(
532 "/<nosmile>(.*?)<\\/nosmile>/isu",
533 [$this, "defendTags"],
534 $text
535 );
536 }
537 if ($this->smiles === null)
538 {
539 $this->initSmiles();
540 }
541 if (!empty($this->smiles))
542 {
543 if ($this->smilePatterns === null)
544 {
545 $this->initSmilePatterns();
546 }
547
548 if (!empty($this->smilePatterns))
549 {
550 $text = preg_replace_callback($this->smilePatterns, [$this, 'convertEmoticon'], ' ' . $text . ' ');
551 }
552 }
553 }
554
556
557 if (!isset($this->allow['EMOJI']) || $this->allow['EMOJI'] != 'N')
558 {
560 }
561
562 $res = array_merge(
563 [
564 'VIDEO' => 'N',
565 'IMG' => 'N',
566 'ANCHOR' => 'N',
567 'BIU' => 'N',
568 'LIST' => 'N',
569 'FONT' => 'N',
570 'TABLE' => 'N',
571 'ALIGN' => 'N',
572 'QUOTE' => 'N',
573
574 // TODO: change to N
575 // This option was added in 2016 as a default value for cases like this
576 // $textParser = new CTextParser(); $textParser->allow = [...];
577 'P' => 'Y',
578 ],
579 $this->allow
580 );
581 foreach ($res as $tag => $val)
582 {
583 if ($val != 'Y')
584 {
585 continue;
586 }
587
588 if (str_contains($text, '<nomodify>'))
589 {
590 $text = preg_replace_callback(
591 "/<nomodify>(.*?)<\\/nomodify>/isu",
592 [$this, "defendTags"],
593 $text
594 );
595 }
596
597 switch ($tag)
598 {
599 case 'VIDEO':
600 $text = preg_replace_callback(
601 "/\\[video([^]]*)](.+?)\\[\\/video\\s*]/isu",
602 [$this, 'convertVideo'],
603 $text
604 );
605 break;
606 case 'IMG':
607 $text = preg_replace_callback(
608 "/\\[img([^]]*)](.+?)\\[\\/img]/isu",
609 [$this, 'convertImage'],
610 $text
611 );
612 break;
613 case 'ANCHOR':
614 $arUrlPatterns = [
615 "/\\[url\\](.*?)\\[\\/url\\]/iu",
616 "/\\[url\\s*=\\s*(
617 (?:
618 [^\\[\\]]++
619 |\\[ (?: (?>[^\\[\\]]+) | (?:\\1) )* \\]
620 )+
621 )\\s*\\](.*?)\\[\\/url\\]/ixsu",
622 ];
623
624 if (($this->allow['CUT_ANCHOR'] ?? '') != 'Y')
625 {
626 $text = preg_replace_callback(
627 $arUrlPatterns,
628 fn($matches) => $this->convertAnchor($matches, $attributes['ANCHOR'] ?? []),
629 $text,
630 );
631 }
632 else
633 {
634 $text = preg_replace($arUrlPatterns, '', $text);
635 }
636 break;
637 case 'BIU':
638 $replaced = 0;
639 do
640 {
641 $text = preg_replace_callback(
642 "/\\[([busi])](.*?)\\[\\/(\\1)]/isu",
643 function($matches) {
644 $tag = strtolower($matches[1]);
645 $className = $this->useTypography ? ' class="' . $this->tagClasses[$tag] . '"' : '';
646
647 return "<$tag$className>" . $matches[2]. "</$tag>";
648 },
649 $text,
650 -1,
651 $replaced
652 );
653 }
654 while ($replaced > 0);
655 break;
656 case 'P':
657 $replaced = 0;
658 do
659 {
660 $text = preg_replace_callback(
661 "/\\[p](.*?)\\[\\/p](([ \r\t]*)\n?)/isu",
662 function($matches) {
663 $className = $this->useTypography ? ' class="' . $this->tagClasses['p'] . '"' : '';
664
665 return "<p$className>". self::trimLineBreaks($matches[1]) . '</p>';
666 },
667 $text,
668 -1,
669 $replaced
670 );
671 }
672 while ($replaced > 0);
673 break;
674 case 'LIST':
675 $olClassName = $this->useTypography ? ' class="' . $this->tagClasses['ol'] . '"' : '';
676 $ulClassName = $this->useTypography ? ' class="' . $this->tagClasses['ul'] . '"' : '';
677 $liClassName = $this->useTypography ? ' class="' . $this->tagClasses['li'] . '"' : '';
678
679 while (preg_match("/\\[list\\s*=\\s*([1a])\\s*](.+?)\\[\\/list]/isu", $text))
680 {
681 $text = preg_replace(
682 [
683 "/\\[list\\s*=\\s*1\\s*](\\s*)(.+?)\\[\\/list](([\040\\r\\t]*)\\n?)/isu",
684 "/\\[list\\s*=\\s*a\\s*](\\s*)(.+?)\\[\\/list](([\040\\r\\t]*)\\n?)/isu",
685 "/\\[\\*]/u",
686 ],
687 [
688 "<ol$olClassName>\\2</ol>",
689 "<ol type=\"a\"$olClassName>\\2</ol>",
690 "<li$liClassName>",
691 ],
692 $text
693 );
694 }
695 while (preg_match("/\\[list](.+?)\\[\\/list](([\\040\\r\\t]*)\\n?)/isu", $text))
696 {
697 $text = preg_replace(
698 [
699 "/\\[list](\\s*)(.+?)\\[\\/list](([\\040\\r\\t]*)\\n?)/isu",
700 "/\\[\\*]/u",
701 ],
702 [
703 "<ul$ulClassName>\\2</ul>",
704 "<li$liClassName>",
705 ],
706 $text
707 );
708 }
709 break;
710 case 'FONT':
711 while (preg_match("/\\[size\\s*=\\s*([^]]+)](.*?)\\[\\/size]/isu", $text))
712 {
713 $text = preg_replace_callback(
714 "/\\[size\\s*=\\s*([^]]+)](.*?)\\[\\/size]/isu",
715 [$this, 'convertFontSize'],
716 $text
717 );
718 }
719 while (preg_match("/\\[font\\s*=\\s*([^]]+)](.*?)\\[\\/font]/isu", $text))
720 {
721 $text = preg_replace_callback(
722 "/\\[font\\s*=\\s*([^]]+)](.*?)\\[\\/font]/isu",
723 [$this, 'convertFont'],
724 $text
725 );
726 }
727 while (preg_match("/\\[color\\s*=\\s*([^]]+)](.*?)\\[\\/color]/isu", $text))
728 {
729 $text = preg_replace_callback(
730 "/\\[color\\s*=\\s*([^]]+)](.*?)\\[\\/color]/isu",
731 [$this, 'convertFontColor'],
732 $text
733 );
734 }
735 break;
736 case 'TABLE':
737 while (preg_match("/\\[table]/isu", $text))
738 {
739 $tagToCheckList = ['td', 'th', 'tr', 'table'];
740 foreach ($tagToCheckList as $tagToCheck)
741 {
742 preg_match_all("/\\[" . $tagToCheck . "]/isu", $text, $matches);
743 $opentags = count($matches['0']);
744
745 preg_match_all("/\\[\\/" . $tagToCheck . "]/is", $text, $matches);
746 $closetags = count($matches['0']);
747
748 $unclosed = $opentags - $closetags;
749 if ($unclosed > 0)
750 {
751 $text .= str_repeat('[/' . $tagToCheck . ']', $unclosed);
752 }
753 }
754
755 $tableClass = $this->useTypography ? $this->tagClasses['table'] : 'data-table';
756 $openTableTag = (
757 $this->bMobile
758 ? "<div style=\"overflow-x: auto;\"><table class=\"$tableClass\">"
759 : "<table class=\"$tableClass\">"
760 );
761 $closeTableTag = (
762 $this->bMobile
763 ? '</table></div>'
764 : '</table>'
765 );
766
767 $trClass = $this->useTypography ? ' class="' . $this->tagClasses['tr'] . '"' : '';
768 $tdClass = $this->useTypography ? ' class="' . $this->tagClasses['td'] . '"' : '';
769 $thClass = $this->useTypography ? ' class="' . $this->tagClasses['th'] . '"' : '';
770
771 $text = preg_replace(
772 [
773 "/\\[table]/isu",
774 "/\\[\\/table](?:(?:[\\040\\r\\t]*)\\n?)/isu",
775 "/(\\s*?)\\[tr]/isu",
776 "/\\[\\/tr](\\s*?)/isu",
777 "/(\\s*?)\\[td]/isu",
778 "/\\[\\/td](\\s*?)/isu",
779 "/(\\s*?)\\[th]/isu",
780 "/\\[\\/th](\\s*?)/isu",
781 ],
782 [
783 $openTableTag,
784 $closeTableTag,
785 "<tr$trClass>",
786 '</tr>',
787 "<td$tdClass>",
788 '</td>',
789 "<th$thClass>",
790 '</th>',
791 ],
792 $text
793 );
794 }
795 break;
796 case 'ALIGN':
797 $paragraph = "<p class=\"{$this->tagClasses['p']}\">\\1</p>";
798
799 $replaced = 0;
800 do
801 {
802 $text = preg_replace(
803 [
804 "/\\[left](.*?)\\[\\/left](([\\040\\r\\t]*)\\n?)/isu",
805 "/\\[right](.*?)\\[\\/right](([\\040\\r\\t]*)\\n?)/isu",
806 "/\\[center](.*?)\\[\\/center](([\\040\\r\\t]*)\\n?)/isu",
807 "/\\[justify](.*?)\\[\\/justify](([\\040\\r\\t]*)\\n?)/isu",
808 ],
809 [
810 $this->useTypography ? $paragraph : "<div align=\"left\">\\1</div>",
811 $this->useTypography ? $paragraph : "<div align=\"right\">\\1</div>",
812 $this->useTypography ? $paragraph : "<div align=\"center\">\\1</div>",
813 $this->useTypography ? $paragraph : "<div align=\"justify\">\\1</div>",
814 ],
815 $text,
816 -1,
817 $replaced
818 );
819 }
820 while ($replaced > 0);
821 break;
822 case 'QUOTE':
823 while (preg_match("/\\[quote[^]]*](.*?)\\[\\/quote[^]]*]/isu", $text))
824 {
825 $text = preg_replace_callback(
826 "/\\[quote[^]]*](.*?)\\[\\/quote[^]]*](([\\040\\r\\t]*)\\n?)/isu",
827 [$this, 'convertQuote'],
828 $text
829 );
830 }
831 break;
832 }
833 }
834
835 if (preg_match("/\[cut/isu", $text, $matches))
836 {
837 $text = preg_replace(
838 [
839 "/\[cut(([^]])*)]/isu",
840 "/\[\/cut]/isu",
841 ],
842 [
843 "\001\\1\002",
844 "\003",
845 ],
846 $text
847 );
848
849 $text = preg_replace_callback(
850 "/(\001([^\002]*)\002([^\001\002\003]+)\003)/isu",
851 function($matches) {
852 return $this->convert_cut_tag($matches[3], $matches[2]);
853 },
854 $text
855 );
856
857 $text = preg_replace(
858 [
859 "/\001([^\002]+)\002/",
860 "/\001\002/",
861 "/\003/",
862 ],
863 [
864 "[cut\\1]",
865 "[cut]",
866 "[/cut]",
867 ],
868 $text
869 );
870 }
871
872 if (str_contains($text, '<nomodify>'))
873 {
874 $text = preg_replace_callback(
875 "/<nomodify>(.*?)<\\/nomodify>/isu",
876 [$this, 'defendTags'],
877 $text
878 );
879 }
880
881 if (isset($this->allow['USERFIELDS']) && is_array($this->allow['USERFIELDS']))
882 {
883 foreach ($this->allow['USERFIELDS'] as $userField)
884 {
885 if (is_array($userField['USER_TYPE']) && array_key_exists('TAG', $userField['USER_TYPE']) )
886 {
887 $userField['TAG'] = $userField['USER_TYPE']['TAG'];
888 }
889 if (empty($userField['TAG']))
890 {
891 switch($userField['USER_TYPE_ID'])
892 {
893 case 'webdav_element' :
894 $userField['TAG'] = 'DOCUMENT ID';
895 break;
896 case 'vote' :
897 $userField['TAG'] = 'VOTE ID';
898 break;
899 }
900 }
901
902 if (!empty($userField['TAG']) && array_key_exists('VALUE', $userField) && !empty($userField['VALUE']) &&
903 method_exists($userField['USER_TYPE']['CLASS_NAME'], 'GetPublicViewHTML') )
904 {
905 $userField['VALUE'] = (is_array($userField['VALUE']) ? $userField['VALUE'] : [$userField['VALUE']]);
906 $this->userField = $userField;
907 $text = preg_replace_callback(
908 "/\\[(" . (is_array($userField["TAG"]) ? implode("|", $userField["TAG"]) : $userField["TAG"]) . ")\\s*=\\s*([a-z0-9]+)([^]]*)]/isu",
909 [$this, 'convert_userfields'],
910 $text
911 );
912 }
913 }
914 }
915
916 if (!isset($this->allow['USER']) || $this->allow['USER'] != 'N')
917 {
918 do
919 {
920 $textOriginal = $text;
921 $text = preg_replace_callback(
922 "/\[user\s*=\s*([^]]*)]((?:(?!\[user\s*=\s*[^]]*]).)+?)\[\/user]/isu",
923 [$this, 'convert_user'],
924 $text
925 );
926 }
927 while ($textOriginal !== $text);
928 }
929
930 if (!isset($this->allow['PROJECT']) || $this->allow['PROJECT'] !== 'N')
931 {
932 $text = preg_replace_callback(
933 "/\[project\s*=\s*([^]]*)](.+?)\[\/project]/isu",
934 [ $this, 'convert_project' ],
935 $text
936 );
937 }
938
939 if (!isset($this->allow['DEPARTMENT']) || $this->allow['DEPARTMENT'] !== 'N')
940 {
941 $text = preg_replace_callback(
942 "/\[department\s*=\s*([^]]*)](.+?)\[\/department]/isu",
943 [ $this, 'convert_department' ],
944 $text
945 );
946 }
947
948 if (isset($this->allow['TAG']) && $this->allow['TAG'] == 'Y')
949 {
950 $text = preg_replace_callback(
951 $this->getTagPattern(),
952 [$this, 'convert_tag'],
953 $text
954 );
955 }
956
957 foreach (GetModuleEvents('main', 'TextParserAfterTags', true) as $arEvent)
958 {
959 ExecuteModuleEventEx($arEvent, [&$text, &$this]);
960 }
961
962 if (($this->allow['HTML'] ?? '') != 'Y' || ($this->allow['NL2BR'] ?? '') == 'Y')
963 {
964 $text = str_replace(["\r\n", "\n"], "<br />", $text);
965 $text = preg_replace(
966 [
967 "/<br \\/>[\\t\\s]*(<\\/table[^>]*>)/isu",
968 "/<br \\/>[\\t\\s]*(<thead[^>]*>)/isu",
969 "/<br \\/>[\\t\\s]*(<\\/thead[^>]*>)/isu",
970 "/<br \\/>[\\t\\s]*(<tfoot[^>]*>)/isu",
971 "/<br \\/>[\\t\\s]*(<\\/tfoot[^>]*>)/isu",
972 "/<br \\/>[\\t\\s]*(<tbody[^>]*>)/isu",
973 "/<br \\/>[\\t\\s]*(<\\/tbody[^>]*>)/isu",
974 "/<br \\/>[\\t\\s]*(<tr[^>]*>)/isu",
975 "/<br \\/>[\\t\\s]*(<\\/tr[^>]*>)/isu",
976 "/<br \\/>[\\t\\s]*(<td[^>]*>)/isu",
977 "/<br \\/>[\\t\\s]*(<\\/td[^>]*>)/isu",
978 ],
979 "\\1",
980 $text
981 );
982 }
983
984 $text = str_replace(
985 [
986 "(c)", "(C)",
987 "(tm)", "(TM)", "(Tm)", "(tM)",
988 "(r)", "(R)",
989 ],
990 [
991 "&#169;", "&#169;",
992 "&#153;", "&#153;", "&#153;", "&#153;",
993 "&#174;", "&#174;",
994 ],
995 $text
996 );
997
998 if (($this->allow['HTML'] ?? '') != 'Y')
999 {
1000 if ($this->maxStringLen > 0)
1001 {
1002 $text = preg_replace("/(&#\\d{1,3};)/isu", "<\019\\1>", $text);
1003 $text = preg_replace_callback("/(?<=^|>)([^<>\\[]+?)(?=<|\\[|$)/isu", [$this, "partWords"], $text);
1004 $text = preg_replace("/(<\019((&#\\d{1,3};))>)/isu", "\\2", $text);
1005 }
1006 $text = preg_replace_callback("/(?<=^|>)([^<>\\[]+?)(?=<|\\[|$)/isu", [$this, "parseSpaces"], $text);
1007 }
1008
1009 foreach (GetModuleEvents('main', 'TextParserBeforePattern', true) as $arEvent)
1010 {
1011 ExecuteModuleEventEx($arEvent, [&$text, &$this]);
1012 }
1013
1014 if ($this->preg['counter'] > 0)
1015 {
1016 $text = str_replace(array_reverse($this->preg['pattern']), array_reverse($this->preg['replace']), $text);
1017 }
1018
1019 foreach (GetModuleEvents('main', 'TextParserAfter', true) as $arEvent)
1020 {
1021 ExecuteModuleEventEx($arEvent, [&$text, &$this]);
1022 }
1023
1024 return trim($text);
1025 }
1026
1027 public function defendTags($matches)
1028 {
1029 return $this->defended_tags($matches[1]);
1030 }
1031
1032 public function defended_tags($text, $tag = 'replace')
1033 {
1034 $text = str_replace("\\\"", "\"", $text);
1035 switch ($tag)
1036 {
1037 case 'replace':
1038 if (($k = array_search($text, $this->preg['replace'])) !== false)
1039 {
1040 $text = "<\017#" . $k . ">";
1041 break;
1042 }
1043 $this->preg["pattern"][] = "<\017#" . $this->preg["counter"] . ">";
1044 $this->preg["replace"][] = $text;
1045 $text = "<\017#" . $this->preg["counter"] . ">";
1046 $this->preg["counter"]++;
1047 break;
1048 }
1049 return $text;
1050 }
1051
1052 public function convert4mail($text)
1053 {
1054 $text = trim($text);
1055 if ($text == '')
1056 {
1057 return '';
1058 }
1059
1060 $arPattern = [];
1061 $arReplace = [];
1062
1063 $arPattern[] = "/\\[(code|quote)(.*?)\\]/isu";
1064 $arReplace[] = "\n>================== \\1 ===================\n";
1065
1066 $arPattern[] = "/\\[\\/(code|quote)(.*?)\\]/isu";
1067 $arReplace[] = "\n>===========================================\n";
1068
1069 $arPattern[] = "/\\<WBR[\\s\\/]?\\>/isu";
1070 $arReplace[] = "";
1071
1072 $arPattern[] = "/\\[\\*\\]/isu";
1073 $arReplace[] = "- ";
1074
1075 $arPattern[] = "/^(\r|\n)+?(.*)$/";
1076 $arReplace[] = "\\2";
1077
1078 $arPattern[] = "/\\[b\\](.+?)\\[\\/b\\]/isu";
1079 $arReplace[] = "\\1";
1080
1081 $arPattern[] = "/\\[p\\](.*?)\\[\\/p\\]/isu";
1082 $arReplace[] = "\\1";
1083
1084 $arPattern[] = "/\\[i\\](.+?)\\[\\/i\\]/isu";
1085 $arReplace[] = "\\1";
1086
1087 $arPattern[] = "/\\[u\\](.+?)\\[\\/u\\]/isu";
1088 $arReplace[] = "_\\1_";
1089
1090 $arPattern[] = "/\\[s\\](.+?)\\[\\/s\\]/isu";
1091 $arReplace[] = "_\\1_";
1092
1093 $arPattern[] = "/\\[(\\/?)(color|font|size|left|right|center)([^\\]]*)\\]/isu";
1094 $arReplace[] = "";
1095
1096 $arPattern[] = "/\\[url\\](\\S+?)\\[\\/url\\]/isu";
1097 $arReplace[] = "(URL: \\1 )";
1098
1099 $arPattern[] = "/\\[url\\s*=\\s*(\\S+?)\\s*\\](.*?)\\[\\/url\\]/isu";
1100 $arReplace[] = "\\2 (URL: \\1 )";
1101
1102 $arPattern[] = "/\\[img([^\\]]*)\\](.+?)\\[\\/img\\]/isu";
1103 $arReplace[] = "(IMAGE: \\2)";
1104
1105 $arPattern[] = "/\\[video([^\\]]*)\\](.+?)\\[\\/video[\\s]*\\]/isu";
1106 $arReplace[] = "(VIDEO: \\2)";
1107
1108 $arPattern[] = "/\\[(\\/?)list(.*?)\\]/isu";
1109 $arReplace[] = "\n";
1110
1111 $arPattern[] = "/\\[user([^\\]]*)\\](.+?)\\[\\/user\\]/isu";
1112 $arReplace[] = "\\2";
1113
1114 $arPattern[] = "/\\[project([^\\]]*)\\](.+?)\\[\\/project\\]/isu";
1115 $arReplace[] = "\\2";
1116
1117 $arPattern[] = "/\\[department([^\\]]*)\\](.+?)\\[\\/department\\]/isu";
1118 $arReplace[] = "\\2";
1119
1120 $arPattern[] = "/\\[DOCUMENT([^\\]]*)\\]/isu";
1121 $arReplace[] = "";
1122
1123 $arPattern[] = "/\\[DISK(.+?)\\]/isu";
1124 $arReplace[] = "";
1125
1126 $arPattern[] = "/\\[(table)(.*?)\\]/isu";
1127 $arReplace[] = "\n>================== \\1 ===================";
1128
1129 $arPattern[] = "/\\[\\/table(.*?)\\]/isu";
1130 $arReplace[] = "\n>===========================================\n";
1131
1132 $arPattern[] = "/\\[tr\\]\\s*/isu";
1133 $arReplace[] = "\n";
1134
1135 $arPattern[] = "/\\[(\\/?)(tr|td)\\]/isu";
1136 $arReplace[] = "";
1137
1138 $text = preg_replace($arPattern, $arReplace, $text);
1139
1140 $text = str_replace('&shy;', '', $text);
1141 if (preg_match("/\[cut(([^]])*)]/isu", $text))
1142 {
1143 $text = preg_replace(
1144 [
1145 "/\[cut(([^]])*)]/isu",
1146 "/\[\/cut]/isu",
1147 ],
1148 [
1149 "\001\\1\002",
1150 "\003",
1151 ],
1152 $text
1153 );
1154 while (preg_match("/(\001([^\002]*)\002([^\001\002\003]+)\003)/isu", $text))
1155 {
1156 $text = preg_replace(
1157 "/(\001([^\002]*)\002([^\001\002\003]+)\003)/isu",
1158 "\n>================== CUT ===================\n\\3\n>==========================================\n",
1159 $text
1160 );
1161 }
1162 $text = preg_replace(
1163 [
1164 "/\001([^\002]+)\002/",
1165 "/\001\002/",
1166 "/\003/",
1167 ],
1168 [
1169 "[cut\\1]",
1170 "[cut]",
1171 "[/cut]",
1172 ],
1173 $text
1174 );
1175 }
1176
1177 static $replacements = [
1178 "&nbsp;" => " ",
1179 "&quot;" => "\"",
1180 "&#092;" => "\\",
1181 "&#036;" => "\$",
1182 "&#33;" => "!",
1183 "&#91;" => "[",
1184 "&#93;" => "]",
1185 "&#39;" => "'",
1186 "&lt;" => "<",
1187 "&gt;" => ">",
1188 "&#124;" => '|',
1189 "&amp;" => "&",
1190 ];
1191 $text = strtr($text, $replacements);
1192
1193 return $text;
1194 }
1195
1196 public function convertVideo($matches)
1197 {
1198 $params = $matches[1];
1199 $path = $matches[2];
1200
1201 if ($path == '')
1202 {
1203 return '';
1204 }
1205
1206 $width = '';
1207 $height = '';
1208 $preview = '';
1209 $provider = '';
1210 $type = '';
1211 preg_match("/width=([0-9]+)/isu", $params, $width);
1212 preg_match("/height=([0-9]+)/isu", $params, $height);
1213
1214 preg_match("/preview='([^']+)'/isu", $params, $preview);
1215 if (empty($preview))
1216 {
1217 preg_match("/preview=\"([^\"]+)\"/isu", $params, $preview);
1218 }
1219
1220 preg_match("/type=(YOUTUBE|RUTUBE|VIMEO|VK|FACEBOOK|INSTAGRAM)/isu", $params, $provider);
1221 preg_match("/mimetype='([^']+)'/isu", $params, $type);
1222
1223 $width = intval($width[1] ?? 0);
1224 $width = ($width > 0 ? $width : 400);
1225 $height = intval($height[1] ?? 0);
1226 $height = ($height > 0 ? $height : 300);
1227 $preview = trim($preview[1] ?? '');
1228 $preview = ($preview != '' ? $preview : '');
1229 $provider = isset($provider[1])? mb_strtoupper(trim($provider[1])) : '';
1230 $type = trim($type[1] ?? '');
1231
1232 $arFields = [
1233 'PATH' => $path,
1234 'WIDTH' => $width,
1235 'HEIGHT' => $height,
1236 'PREVIEW' => $preview,
1237 'TYPE' => $provider,
1238 'MIME_TYPE' => $type,
1239 'PARSER_OBJECT' => $this,
1240 ];
1241
1242 foreach (GetModuleEvents('main', 'TextParserVideoConvert', true) as $arEvent)
1243 {
1244 ExecuteModuleEventEx($arEvent, [&$arFields, &$this]);
1245 }
1246
1247 $video = $this->convert_video($arFields);
1248
1249 return $this->defended_tags($video);
1250 }
1251
1252 protected function convert_video($arParams)
1253 {
1254 global $APPLICATION;
1255
1256 if (
1257 !is_array($arParams)
1258 || $arParams["PATH"] == ''
1259 )
1260 {
1261 return false;
1262 }
1263 $trustedProviders = [
1264 'YOUTUBE',
1265 'RUTUBE',
1266 'VIMEO',
1267 'VK',
1268 'FACEBOOK',
1269 'INSTAGRAM',
1270 ];
1271
1272 ob_start();
1273
1274 if ($this->type == 'mail')
1275 {
1276 $pathEncoded = htmlspecialcharsbx($arParams['PATH']);
1277
1278 ?><a href="<?=$pathEncoded?>"><?=$pathEncoded?></a><?
1279 }
1280 elseif (in_array($arParams['TYPE'], $trustedProviders))
1281 {
1282 // add missing protocol to get host
1283 if (str_starts_with($arParams['PATH'], '//'))
1284 {
1285 $arParams['PATH'] = 'http:' . $arParams['PATH'];
1286 }
1287 $uri = new Uri($arParams['PATH']);
1288 if (UrlPreview::isHostTrusted($uri) || $uri->getHost() == Application::getInstance()->getContext()->getServer()->getServerName())
1289 {
1290 // Replace http://someurl, https://someurl by //someurl
1291 $arParams['PATH'] = preg_replace("/https?:\\/\\//i", '//', $arParams['PATH']);
1292
1293 $pathEncoded = htmlspecialcharsbx($arParams['PATH']);
1294
1295 if ($this->bMobile)
1296 {
1297 ?><iframe class="bx-mobile-video-frame" src="<?=$pathEncoded?>" allowfullscreen="" frameborder="0" height="100%" width="100%" style="max-width: 600px; min-height: 300px;"></iframe><?
1298 }
1299 else
1300 {
1301 ?><iframe src="<?=$pathEncoded?>" allowfullscreen="" frameborder="0" height="<?=intval($arParams["HEIGHT"])?>" width="<?=intval($arParams["WIDTH"])?>" style="max-width: 100%;"></iframe><?
1302 }
1303 }
1304 }
1305 else
1306 {
1307 $playerParams = $arParams;
1308 $playerParams['TYPE'] = $arParams['MIME_TYPE'];
1309 $playerComponent = 'bitrix:player';
1310 if ($this->bMobile)
1311 {
1312 $playerComponent = 'bitrix:mobile.player';
1313 }
1314
1315 $APPLICATION->IncludeComponent(
1316 $playerComponent, '', $playerParams,
1317 null,
1318 [
1319 'HIDE_ICONS' => 'Y',
1320 ]
1321 );
1322 }
1323
1324 return ob_get_clean();
1325 }
1326
1327 public function convertEmoticon($matches)
1328 {
1329 $array = array_intersect_key($this->smileReplaces, $matches);
1330 $replacement = reset($array);
1331 if (!empty($replacement))
1332 {
1333 if (($this->allow['CLEAR_SMILES'] ?? '') == 'Y')
1334 {
1335 return $this->convert_emoticon($replacement['code']);
1336 }
1337 else
1338 {
1339 return $this->convert_emoticon(
1340 $replacement['code'],
1341 $replacement['image'],
1342 $replacement['description'],
1343 $replacement['width'],
1344 $replacement['height'],
1345 $replacement['descriptionDecode'],
1346 $replacement['imageDefinition']
1347 );
1348 }
1349 }
1350 return $matches[0];
1351 }
1352
1353 public function convert_emoticon($code = '', $image = '', $description = '', $width = '', $height = '', $descriptionDecode = false, $imageDefinition = CSmile::IMAGE_SD)
1354 {
1355 if ($code == '' || $image == '')
1356 {
1357 return '';
1358 }
1359 $code = stripslashes($code);
1360 $description = stripslashes($description);
1361 $image = stripslashes($image);
1362 $width = intval($width);
1363 $height = intval($height);
1364 if ($descriptionDecode)
1365 {
1367 }
1368
1369 $html = '<img src="' . htmlspecialcharsbx($this->serverName) . $this->pathToSmile . $image . '"'
1370 . ' border="0"'
1371 . ' data-code="' . $code . '"'
1372 . ' data-definition="' . $imageDefinition . '"'
1373 . ' alt="' . $code . '"'
1374 . ' style="' . ($width > 0 ? 'width:' . $width . 'px;' : '') . ($height > 0 ? 'height:' . $height . 'px;' : '') . '"'
1375 . ' title="' . $description . '"'
1376 . ' class="' . ($this->useTypography ? $this->tagClasses['smiley'] : 'bx-smile') . '" />';
1377 $cacheKey = md5($html);
1378 if (!isset($this->preg['cache'][$cacheKey]))
1379 {
1380 $this->preg['cache'][$cacheKey] = $this->defended_tags($html);
1381 }
1382
1383 return $this->preg['cache'][$cacheKey];
1384 }
1385
1386 public function convertCode($matches)
1387 {
1388 $text = $matches[2];
1389
1390 if ($text == '')
1391 {
1392 return '';
1393 }
1394
1395 $text = str_replace(
1396 ["[nomodify]", "[/nomodify]", "&#91;", "&#93;", "&", "<", ">", "\\r", "\\n", "\\\"", "\\", "[", "]", " ", "\t"],
1397 ["", "", "[", "]", "&#38;", "&#60;", "&#62;", "&#92;r", "&#92;n", '&#92;"', "&#92;", "&#91;", "&#93;", "&nbsp;&nbsp;", "&nbsp;&nbsp;&nbsp;"],
1398 $text
1399 );
1400
1401 $text = stripslashes($text);
1402 $text = $this->useTypography ? self::trimLineBreaks($text) : "<pre>" . $text . "</pre>";
1403
1404 return $this->defended_tags($this->convert_open_tag('code') . $text . $this->convert_close_tag('code'));
1405 }
1406
1407 public function convertQuote($matches)
1408 {
1409 return $this->convert_quote_tag($matches[1]);
1410 }
1411
1412 public function convert_quote_tag($text = '')
1413 {
1414 if ($text == '')
1415 {
1416 return '';
1417 }
1418
1419 $text = str_replace("\\\"", "\"", $text);
1420
1421 if ($this->useTypography)
1422 {
1423 $text = self::trimLineBreaks($text);
1424 }
1425
1426 return $this->convert_open_tag() . $text . $this->convert_close_tag();
1427 }
1428
1429 public function convert_spoiler_tag($text, $title = '')
1430 {
1431 if (is_array($text))
1432 {
1433 $title = $text[1];
1434 $text = $text[2];
1435 }
1436
1437 if (empty($text))
1438 {
1439 return '';
1440 }
1441
1443 if ($this->type === 'mail')
1444 {
1445 return "<dl><dt>" . ($title ?: Loc::getMessage("MAIN_TEXTPARSER_SPOILER")) . "</dt><dd>" . htmlspecialcharsbx($text) . "</dd></dl>";
1446 }
1447
1448 return self::renderSpoiler($text, $title, $this->useTypography);
1449 }
1450
1452 {
1453 if (empty($text))
1454 {
1455 return '';
1456 }
1457
1458 $title = trim($title);
1459 $title = ltrim($title, '=');
1460 $title = trim($title);
1461
1462 return self::renderSpoiler($text, $title, $this->useTypography);
1463 }
1464
1465 public static function renderSpoiler($text, $title = '', $useTypography = false)
1466 {
1467 $title = (empty($title) ? Loc::getMessage("MAIN_TEXTPARSER_HIDDEN_TEXT") : $title);
1468
1469 if ($useTypography)
1470 {
1471 return (
1472 '<details class="ui-typography-spoiler ui-icon-set__scope">' .
1473 '<summary class="ui-typography-spoiler-title">' . htmlspecialcharsbx($title) . '</summary>' .
1474 '<div class="ui-typography-spoiler-content" data-spoiler-content="true">' .
1475 self::trimLineBreaks($text) .
1476 '</div>' .
1477 '</details>'
1478 );
1479 }
1480
1481 ob_start();
1482
1483 ?><table class="forum-spoiler"><?
1484 ?><thead onclick="if (this.nextSibling.style.display=='none') { this.nextSibling.style.display=''; BX.addClass(this, 'forum-spoiler-head-open'); } else { this.nextSibling.style.display='none'; BX.removeClass(this, 'forum-spoiler-head-open'); } BX.onCustomEvent('BX.Forum.Spoiler:toggle', [{node: this}]); event.stopPropagation();"><?
1485 ?><tr><?
1486 ?><th><?
1487 ?><div><?=htmlspecialcharsbx($title)?></div><?
1488 ?></th><?
1489 ?></tr><?
1490 ?></thead><?
1491 ?><tbody class="forum-spoiler" style="display:none;"><?
1492 ?><tr><?
1493 ?><td><?=self::trimLineBreaks($text)?></td><?
1494 ?></tr><?
1495 ?></tbody><?
1496 ?></table><?
1497
1498 return ob_get_clean();
1499 }
1500
1501 public function convert_open_tag($marker = 'quote')
1502 {
1503 $marker = (mb_strtolower($marker) == 'code' ? 'code' : 'quote');
1504
1505 $this->{$marker . '_open'}++;
1506 if ($this->type == 'rss')
1507 {
1508 return "\n====" . $marker . "====\n";
1509 }
1510
1511 if ($this->useTypography)
1512 {
1513 return (
1514 $marker === 'quote'
1515 ? '<blockquote class="' . $this->tagClasses['quote'] . '">'
1516 : '<code class="' . $this->tagClasses['code'] . '">'
1517 );
1518 }
1519
1520 return "<div class='" . $marker . "'><table class='" . $marker . "'><tr><td>";
1521 }
1522
1523 public function convert_close_tag($marker = 'quote')
1524 {
1525 $marker = (mb_strtolower($marker) == 'code' ? 'code' : 'quote');
1526
1527 if ($this->{$marker . '_open'} == 0)
1528 {
1529 $this->{$marker . '_error'}++;
1530 return '';
1531 }
1532 $this->{$marker . '_closed'}++;
1533
1534 if ($this->type == 'rss')
1535 {
1536 return "\n=============\n";
1537 }
1538
1539 if ($this->useTypography)
1540 {
1541 return (
1542 $marker === 'quote'
1543 ? '</blockquote>'
1544 : '</code>'
1545 );
1546 }
1547
1548 return '</td></tr></table></div>';
1549 }
1550
1551 public function convertImage($matches)
1552 {
1553 return $this->convert_image_tag($matches[2], $matches[1]);
1554 }
1555
1556 public function convert_image_tag($url = '', $params = '')
1557 {
1558 $url = trim($url);
1559 if ($url == '')
1560 {
1561 return '';
1562 }
1563
1564 preg_match("/width=([0-9]+)/isu", $params, $width);
1565 preg_match("/height=([0-9]+)/isu", $params, $height);
1566 $width = intval($width[1] ?? 0);
1567 $height = intval($height[1] ?? 0);
1568
1569 $bErrorIMG = false;
1570 if (!preg_match("/^(http|https|ftp|\\/)/iu", $url))
1571 {
1572 $bErrorIMG = true;
1573 }
1574
1576 if ($bErrorIMG)
1577 {
1578 return '[img]' . $url . '[/img]';
1579 }
1580
1581 $strPar = '';
1582 if ($width > 0)
1583 {
1584 if ($width > $this->imageWidth)
1585 {
1586 $height = intval($height * ($this->imageWidth / $width));
1588 }
1589 }
1590 if ($height > 0)
1591 {
1592 if ($height > $this->imageHeight)
1593 {
1594 $width = intval($width * ($this->imageHeight / $height));
1595 $height = $this->imageHeight;
1596 }
1597 }
1598 if ($width > 0)
1599 {
1600 $strPar = " width=\"" . $width . "\"";
1601 }
1602 if ($height > 0)
1603 {
1604 $strPar .= " height=\"" . $height . "\"";
1605 }
1606
1607 $serverName = htmlspecialcharsbx($this->serverName);
1608 if ($this->useTypography)
1609 {
1610 $src = $serverName . $url;
1611 if ($this->serverName == '' || preg_match("/^(http|https|ftp):\\/\\//iu", $url))
1612 {
1613 $src = $url;
1614 }
1615
1616 $attrs = ' loading="lazy"';
1617 if ($width > 0)
1618 {
1619 $attrs .= " width=\"" . $width . "\"";
1620 if ($height > 0)
1621 {
1622 $attrs .= ' style="aspect-ratio: ' . ($width / $height) . '"';
1623 }
1624 }
1625
1626 $image = (
1627 '<span class="' . $this->tagClasses['image-container'] . '">' .
1628 '<img src="' . $src . '" class="' . $this->tagClasses['image'] . '"'. $attrs .'>' .
1629 '</span>'
1630 );
1631
1632 return $this->defended_tags($image);
1633 }
1634
1635 $image = '<img src="' . $serverName . $url . '" border="0"' . $strPar . ' data-bx-image="' . $serverName . $url . '" data-bx-onload="Y" />';
1636 if ($this->serverName == '' || preg_match("/^(http|https|ftp):\\/\\//iu", $url))
1637 {
1638 $image = '<img src="' . $url . '" border="0"' . $strPar . ' data-bx-image="' . $url . '" data-bx-onload="Y" />';
1639 }
1640 return $this->defended_tags($image);
1641 }
1642
1643 public function convertFont($matches)
1644 {
1645 return $this->convert_font_attr('font', $matches[1], $matches[2]);
1646 }
1647
1648 public function convertFontSize($matches)
1649 {
1650 return $this->convert_font_attr('size', $matches[1], $matches[2]);
1651 }
1652
1654 {
1655 return $this->convert_font_attr('color', $matches[1], $matches[2]);
1656 }
1657
1658 public function stripAllTags($text)
1659 {
1660 return preg_replace('|[[/!]*?[^\\[\\]]*?]|i', '', $text);
1661 }
1662
1663 public function convert_font_attr($attr, $value = "", $text = "")
1664 {
1665 if ($text == '')
1666 {
1667 return '';
1668 }
1669
1670 $text = str_replace("\\\"", "\"", $text);
1671
1672 if ($value == '')
1673 {
1674 return $text;
1675 }
1676
1677 if ($attr == 'size')
1678 {
1679 if (mb_strlen($value) > 2 && str_ends_with($value, 'pt'))
1680 {
1681 $value = intval(substr($value, 0, -2));
1682 if ($value <= 0)
1683 {
1684 return $text;
1685 }
1686 return '<span class="bx-font" style="font-size:' . $value . 'pt; line-height: normal;">' . $text . '</span>';
1687 }
1688
1689 $count = count($this->arFontSize);
1690 if ($count <= 0)
1691 {
1692 return $text;
1693 }
1694 $value = intval($value > $count ? ($count - 1) : $value);
1695 if (!isset($this->arFontSize[$value]))
1696 {
1697 return $text;
1698 }
1699
1700 //compatibility with old percent values
1701 $size = (is_numeric($this->arFontSize[$value])? $this->arFontSize[$value] . '%' : $this->arFontSize[$value]);
1702 return '<span class="bx-font" style="font-size:' . $size . ';">' . $text . '</span>';
1703 }
1704 elseif ($attr == 'color')
1705 {
1706 $value = preg_replace("/[^\\w#]/", "" , $value);
1707 return '<span class="bx-font" style="color:' . $value . '">' . $text . '</span>';
1708 }
1709 elseif ($attr == 'font')
1710 {
1711 $value = preg_replace("/[^\\w\\s\\-,()]/", "" , $value);
1712 return '<span class="bx-font" style="font-family:' . $value . '">' . $text . '</span>';
1713 }
1714 return '';
1715 }
1716
1718 {
1719 $vars = get_object_vars($this);
1720 $vars['TEMPLATE'] = ($this->bMobile ? 'mobile' : $this->type);
1721 $vars['LAZYLOAD'] = $this->LAZYLOAD;
1722
1724
1725 $id = $matches[2];
1726
1727 foreach (GetModuleEvents('main', 'TextParserUserField', true) as $arEvent)
1728 {
1729 ExecuteModuleEventEx($arEvent, [$id, &$userField, &$vars, &$this]);
1730 }
1731
1732 if ($userField['USER_TYPE']['USER_TYPE_ID'] == 'disk_file' || in_array($id, $userField['VALUE']))
1733 {
1734 if (defined('BX_COMP_MANAGED_CACHE'))
1735 {
1736 global $CACHE_MANAGER;
1737 $CACHE_MANAGER->RegisterTag('webdav_element_internal_' . $id);
1738 }
1739
1740 return call_user_func_array(
1741 [$userField['USER_TYPE']['CLASS_NAME'], 'GetPublicViewHTML'],
1742 [$userField, $id, $matches[3], $vars, $matches]
1743 );
1744 }
1745 return $matches[0];
1746 }
1747
1748 public function convert_user($userId = 0, $userName = '')
1749 {
1750 static $userTypeList = [];
1751
1752 if (is_array($userId))
1753 {
1754 $userName = $userId[2];
1755 $userId = $userId[1];
1756 }
1757
1758 $userId = (int)$userId;
1759
1760 $renderParams = [
1761 'USER_ID' => $userId,
1762 'USER_NAME' => $userName,
1763 ];
1764
1765 if ($userId > 0)
1766 {
1767 $status = false;
1768
1769 if (isset($userTypeList[$userId]))
1770 {
1771 $status = $userTypeList[$userId];
1772 }
1773 else
1774 {
1775 if (Loader::includeModule('intranet'))
1776 {
1777 $status = \Bitrix\Intranet\Util::getUserStatus($userId);
1778 }
1779
1780 $userTypeList[$userId] = $status;
1781 }
1782
1783 $pathToUser = (
1784 !empty($this->userPath)
1785 ? $this->userPath // forum
1787 );
1788
1789 if (empty($pathToUser))
1790 {
1791 $pathToUser = COption::GetOptionString('main', 'TOOLTIP_PATH_TO_USER', '', SITE_ID);
1792 }
1793
1794 switch($status)
1795 {
1796 case 'extranet':
1797 $classAdditional = ' blog-p-user-name-extranet';
1798 break;
1799 case 'email':
1800 $classAdditional = ' blog-p-user-name-email';
1801 $pathToUser .= '';
1802 if (
1803 $this->pathToUserEntityType && $this->pathToUserEntityType != ''
1804 && (int)$this->pathToUserEntityId > 0
1805 )
1806 {
1807 $pathToUser .= (!str_contains($pathToUser, '?') ? '?' : '&') . 'entityType=' . $this->pathToUserEntityType . '&entityId=' . intval($this->pathToUserEntityId);
1808 }
1809 break;
1810 case 'collaber':
1811 $classAdditional = ' blog-p-user-name-collaber';
1812 break;
1813 default:
1814 $classAdditional = '';
1815 }
1816
1817 $renderParams = [
1818 'CLASS_ADDITIONAL' => $classAdditional,
1819 'PATH_TO_USER' => $pathToUser,
1820 'USER_ID' => $userId,
1821 'USER_NAME' => $userName,
1822 ];
1823
1824 if (
1825 $status === 'email'
1826 && !empty($this->pathToUserEntityType)
1827 && !empty($this->pathToUserEntityId)
1828 )
1829 {
1830 $renderParams['TOOLTIP_PARAMS'] = \Bitrix\Main\Web\Json::encode([
1831 'entityType' => $this->pathToUserEntityType,
1832 'entityId' => (int)$this->pathToUserEntityId,
1833 ]);
1834 }
1835 }
1836
1837 $res = $this->render_user($renderParams);
1838
1839 return $this->defended_tags($res);
1840 }
1841
1842 protected function render_user($fields)
1843 {
1844 $classAdditional = (!empty($fields['CLASS_ADDITIONAL']) ? $fields['CLASS_ADDITIONAL'] : '');
1845 $pathToUser = (!empty($fields['PATH_TO_USER']) ? $fields['PATH_TO_USER'] : '');
1846 $userId = (!empty($fields['USER_ID']) ? $fields['USER_ID'] : '');
1847 $userName = (!empty($fields['USER_NAME']) ? $fields['USER_NAME'] : '');
1848
1849 $className = $this->useTypography ? $this->tagClasses['mention'] : 'blog-p-user-name';
1850 if (empty($userId))
1851 {
1852 return "<span class=\"{$className}\">{$userName}</span>";
1853 }
1854
1855 return '<a class="'. $className . $classAdditional . '"'
1856 . ' href="' . CComponentEngine::MakePathFromTemplate($pathToUser, ["user_id" => $userId]) . '"'
1857 . ' bx-tooltip-user-id="' . (!$this->bMobile ? $userId : '') . '"'
1858 . (!empty($fields['TOOLTIP_PARAMS']) ? ' bx-tooltip-params="' . htmlspecialcharsbx($fields['TOOLTIP_PARAMS']) . '"' : '') . '>'
1859 . $userName . '</a>';
1860 }
1861
1862 public function convert_project(array $matches): string
1863 {
1864 static $projectTypeList = [];
1865 static $extranetProjectIdList = null;
1866
1867 $projectName = $matches[2];
1868 $projectId = (int)$matches[1];
1869
1870 $renderParams = [
1871 'PROJECT_ID' => $projectId,
1872 'PROJECT_NAME' => $projectName,
1873 ];
1874
1875 if ($projectId > 0)
1876 {
1877 if ($extranetProjectIdList === null)
1878 {
1879 $extranetSiteId = (Loader::includeModule('extranet') ? CExtranet::getExtranetSiteId() : '');
1880 if (!empty($extranetSiteId))
1881 {
1882 $res = \Bitrix\Socialnetwork\WorkgroupSiteTable::getList([
1883 'filter' => [
1884 '=SITE_ID' => $extranetSiteId,
1885 ],
1886 'select' => [ 'GROUP_ID' ],
1887 ]);
1888
1889 while ($projectSiteFields = $res->fetch())
1890 {
1891 $extranetProjectIdList[] = (int)$projectSiteFields['GROUP_ID'];
1892 }
1893 }
1894 }
1895
1896 $type = false;
1897
1898 if (isset($projectTypeList[$projectId]))
1899 {
1900 $type = $projectTypeList[$projectId];
1901 }
1902 else
1903 {
1904 if (!empty($extranetProjectIdList) && in_array($projectId, $extranetProjectIdList, true))
1905 {
1906 $type = $this->getExternalGroupType($projectId);
1907 }
1908
1909 $projectTypeList[$projectId] = $type;
1910 }
1911
1912 $pathToProject = $this->getGroupPath($projectId, (array)$extranetProjectIdList);
1913
1914 switch ($type)
1915 {
1916 case 'extranet':
1917 $classAdditional = ' blog-p-user-name-extranet';
1918 break;
1919 case 'collab':
1920 $classAdditional = ' blog-p-user-name-collab';
1921 break;
1922 default:
1923 $classAdditional = '';
1924 }
1925
1926 $renderParams['CLASS_ADDITIONAL'] = $classAdditional;
1927 $renderParams['PATH_TO_PROJECT'] = $pathToProject;
1928 }
1929
1930 $res = $this->render_project($renderParams);
1931
1932 return $this->defended_tags($res);
1933 }
1934
1935 protected function render_project($fields): string
1936 {
1937 $classAdditional = ($fields['CLASS_ADDITIONAL'] ?? '');
1938 $pathToProject = ($fields['PATH_TO_PROJECT'] ?? '');
1939 $projectId = (int)($fields['PROJECT_ID'] ?? 0);
1940 $projectName = (string)($fields['PROJECT_NAME'] ?? '');
1941
1942 $className = $this->useTypography ? $this->tagClasses['mention'] : 'blog-p-user-name';
1943 if ($projectId <= 0)
1944 {
1945 return "<span class=\"{$className}\">{$projectName}</span>";
1946 }
1947
1948 return '<a class="' . $className . $classAdditional . '" href="' . CComponentEngine::MakePathFromTemplate($pathToProject, [ 'group_id' => $projectId ]) . '" >' . $projectName . '</a>';
1949 }
1950
1951 public function convert_department(array $matches): string
1952 {
1953 static $pathToDepartment = null;
1954
1955 $departmentName = $matches[2];
1956 $departmentId = (int)$matches[1];
1957
1958 $renderParams = [
1959 'DEPARTMENT_ID' => $departmentId,
1960 'DEPARTMENT_NAME' => $departmentName,
1961 ];
1962
1963 if ($departmentId > 0)
1964 {
1965 if ($pathToDepartment === null)
1966 {
1967 // then replace to \Bitrix\Socialnetwork\Helper\Path::get('department_path_template')
1968 $pathToDepartment = Option::get('main', 'TOOLTIP_PATH_TO_CONPANY_DEPARTMENT', SITE_DIR . 'company/structure.php?set_filter_structure=Y&structure_UF_DEPARTMENT=#ID#', SITE_ID);
1969 }
1970
1971 $renderParams['PATH_TO_DEPARTMENT'] = $pathToDepartment;
1972 }
1973
1974 $res = $this->render_department($renderParams);
1975
1976 return $this->defended_tags($res);
1977 }
1978
1979 protected function render_department($fields): string
1980 {
1981 $pathToDepartment = ($fields['PATH_TO_DEPARTMENT'] ?? '');
1982 $departmentId = (int)($fields['DEPARTMENT_ID'] ?? 0);
1983 $departmentName = (string)($fields['DEPARTMENT_NAME'] ?? '');
1984
1985 $className = $this->useTypography ? $this->tagClasses['mention'] : 'blog-p-user-name';
1986 if ($departmentId <= 0)
1987 {
1988 return "<span class=\"{$className}\">{$departmentName}</span>";
1989 }
1990
1991 return '<a class="' . $className . '" href="' . CComponentEngine::MakePathFromTemplate($pathToDepartment, [ 'ID' => $departmentId ]) . '" >' . $departmentName . '</a>';
1992 }
1993
1994 public function getTagPattern()
1995 {
1996 return $this->tagPattern . 'u';
1997 }
1998
1999 public static function cleanTag($tag)
2000 {
2001 return trim(html_entity_decode(str_replace('&nbsp;', ' ', $tag), (ENT_COMPAT | ENT_HTML401), SITE_CHARSET));
2002 }
2003
2004 public function detectTags($text)
2005 {
2006 $result = [];
2007
2008 $text = str_replace("\xC2\xA0", ' ', $text);
2009
2010 if (preg_match_all($this->getTagPattern(), ' ' . $text, $matches))
2011 {
2012 $result = array_unique($matches[2]);
2013 }
2014
2015 $result = array_map(
2016 function($tag) { return CTextParser::cleanTag($tag); },
2017 $result
2018 );
2019
2020 $result = array_filter(
2021 $result,
2022 function($tag) { return $tag != ''; }
2023 );
2024
2025 return $result;
2026 }
2027
2028 public function convert_tag($tag = [])
2029 {
2030 $res = '';
2031
2032 if (
2033 !is_array($tag)
2034 || $tag[2] == ''
2035 )
2036 {
2037 return $res;
2038 }
2039
2040 $tagText = self::cleanTag($tag[2]);
2041
2042 if ($tagText == '')
2043 {
2044 return $tag[0];
2045 }
2046
2047 $res = htmlentities($tagText, (ENT_COMPAT | ENT_HTML401), SITE_CHARSET);
2048
2049 $className = $this->useTypography ? $this->tagClasses['hashtag'] : 'bx-inline-tag';
2050 $res = '<span class="' . $className . '" bx-tag-value="' . $res . '">#' . $res . '</span>';
2051
2052 return $tag[1].$this->defended_tags($res);
2053 }
2054
2055 // Only for public using
2056 public function wrap_long_words($text = '')
2057 {
2058 if ($this->maxStringLen > 0 && !empty($text))
2059 {
2060 $text = str_replace([chr(11), chr(12), chr(34), chr(39)], ["", "", chr(11), chr(12)], $text);
2061 $text = preg_replace_callback("/(?<=^|>)([^<]+)(?=<|$)/isu", [$this, "partWords"], $text);
2062 $text = str_replace([chr(11), chr(12)], [chr(34), chr(39)], $text);
2063 }
2064 return $text;
2065 }
2066
2067 public function partWords($matches)
2068 {
2069 return $this->part_long_words($matches[1]);
2070 }
2071
2072 public function part_long_words($str)
2073 {
2074 $word_separator = $this->wordSeparator;
2075 if (($this->maxStringLen > 0) && (trim($str) != ''))
2076 {
2077 $str = str_replace(
2078 [chr(1), chr(2), chr(3), chr(4), chr(5), chr(6), chr(7), chr(8),
2079 "&amp;", "&lt;", "&gt;", "&quot;", "&nbsp;", "&copy;", "&reg;", "&trade;",
2080 chr(34), chr(39),
2081 ],
2082 ["", "", "", "", "", "", "", "",
2083 chr(1), "<", ">", chr(2), chr(3), chr(4), chr(5), chr(6),
2084 chr(7), chr(8),
2085 ],
2086 $str
2087 );
2088 $str = preg_replace_callback(
2089 "/(?<=[" . $word_separator . "]|^)(([^" . $word_separator . "]+))(?=[" . $word_separator . "]|$)/isu",
2090 [$this, "cutWords"],
2091 $str
2092 );
2093 $str = str_replace(
2094 [chr(1), "<", ">", chr(2), chr(3), chr(4), chr(5), chr(6), chr(7), chr(8), "&lt;WBR/&gt;", "&lt;WBR&gt;", "&amp;shy;"],
2095 ["&amp;", "&lt;", "&gt;", "&quot;", "&nbsp;", "&copy;", "&reg;", "&trade;", chr(34), chr(39), "<WBR/>", "<WBR/>", "&shy;"],
2096 $str
2097 );
2098 }
2099 return $str;
2100 }
2101
2102 public function cutWords($matches)
2103 {
2104 return $this->cut_long_words($matches[2]);
2105 }
2106
2107 public function cut_long_words($str)
2108 {
2109 if (($this->maxStringLen > 0) && ($str != ''))
2110 {
2111 $str = preg_replace("/([^ \n\r\t\x01]{" . $this->maxStringLen . "})/isu", "\\1<WBR/>&shy;", $str);
2112 }
2113 return $str;
2114 }
2115
2116 protected function parseSpaces($matches)
2117 {
2118 if ($matches[1] != '')
2119 {
2120 return preg_replace("/\x20{2}/", "\x20&nbsp;", $matches[1]);
2121 }
2122 return $matches[1];
2123 }
2124
2125 public function convertAnchor($matches, $attributes = [])
2126 {
2127 return $this->convert_anchor_tag($matches[1], (!empty($matches[2]) ? $matches[2] : $matches[1]), $attributes);
2128 }
2129
2130 public function convert_anchor_tag($url, $text, $attributes = [])
2131 {
2132 $url = trim(str_replace(['[nomodify]', '[/nomodify]'], '', $url));
2133 $text = trim(str_replace(['[nomodify]', '[/nomodify]'], '', $text));
2134 $text = ($text == '' ? $url : $text);
2135
2136 $bTextUrl = ($text == $url);
2137 $bShortUrl = (($this->allow['SHORT_ANCHOR'] ?? '') == 'Y');
2138
2139 $text = str_replace("\\\"", "\"", $text);
2140 $postfix = "";
2141 $pattern = "/([.,?!;]|&#33;)$/u";
2142 if ($bTextUrl && preg_match($pattern, $url, $match))
2143 {
2144 $postfix = $match[1];
2145 $url = preg_replace($pattern, '', $url);
2146 $text = preg_replace($pattern, '', $text);
2147 }
2148
2149 $url = preg_replace(
2150 [
2151 "/&amp;/u",
2152 "/javascript:/iu",
2153 "/[" . chr(12) . "']/u",
2154 "/&#91;/u",
2155 "/&#93;/u",
2156 ],
2157 [
2158 "&",
2159 "java script&#58; ",
2160 "%27",
2161 "[",
2162 "]",
2163 ],
2164 $url
2165 );
2166
2167 if (!str_starts_with($url, '/') && !preg_match("/^(" . $this->getAnchorSchemes() . "):/iu", $url))
2168 {
2169 $url = 'http://' . $url;
2170 }
2171 $text = preg_replace(
2172 ["/&amp;/iu", "/javascript:/iu"],
2173 ["&", "javascript&#58; "],
2174 $text
2175 );
2176
2177 if ($bShortUrl &&
2178 mb_strlen($text) > $this->maxAnchorLength &&
2179 preg_match("/^(" . $this->getAnchorSchemes() . "):\\/\\/(\\S+)$/iu", $text, $matches))
2180 {
2181 $uri_type = $matches[1];
2182 $stripped = $matches[2];
2183 $text = $uri_type . '://' . (mb_strlen($stripped) > $this->maxAnchorLength ?
2184 mb_substr($stripped, 0, $this->maxAnchorLength - 10) . '...' . mb_substr($stripped, -10) :
2185 $stripped
2186 );
2187 }
2188
2189 if ($this->anchorType === 'bbcode')
2190 {
2191 if ($url === $text)
2192 {
2193 $link = '[URL]' . $url . '[/URL]';
2194 }
2195 else
2196 {
2197 $link = '[URL=' . $url . ']' . $text . '[/URL]';
2198 }
2199 }
2200 else
2201 {
2202 $url = $this->defended_tags(htmlspecialcharsbx($url, ENT_COMPAT, false));
2203
2204 if (!str_contains($text, "<\017"))
2205 {
2206 // it could be "defended" tag inside URL code
2207 $text = htmlspecialcharsbx($text, ENT_COMPAT, false);
2208 }
2209
2210 $noFollowAttribute = $this->parser_nofollow == 'Y'? ' rel="nofollow"': '';
2211
2212 $className = $this->useTypography ? ' class="' . $this->tagClasses['url'] . '"' : '';
2213
2214 $link = '<a' . $className . ' href="' . $url . '" target="' . $this->link_target . '"' . $noFollowAttribute . $this->convertAttributes($attributes) . ' >' . $text . '</a>';
2215
2216 if ($noFollowAttribute)
2217 {
2218 $link = '<noindex>' . $link . '</noindex>';
2219 }
2220 }
2221
2222 return $link . $postfix;
2223 }
2224
2225 protected function convertAttributes($attributes)
2226 {
2227 $result = '';
2228 if (!is_array($attributes))
2229 {
2230 return $result;
2231 }
2232
2233 foreach ($attributes as $key => $value)
2234 {
2235 if (preg_match('#[^a-zA-Z\d-]#', $key))
2236 {
2237 continue;
2238 }
2239
2240 $result .= ' ' . $key . '="' . htmlspecialcharsbx(\Bitrix\Main\Web\Json::encode($value)). '"';
2241 }
2242
2243 return $result;
2244 }
2245
2246 private function preconvertUrl($matches, $attributes = [])
2247 {
2248 return $this->pre_convert_anchor_tag($matches[0], $matches[0], '[url]' . $matches[0] . '[/url]', $attributes);
2249 }
2250
2251 public function preconvertAnchor($matches, $attributes = [])
2252 {
2253 return $this->pre_convert_anchor_tag($matches[1], $matches[2] ?? '', $matches[0] ?? '', $attributes);
2254 }
2255
2256 public function pre_convert_anchor_tag($url, $text = '', $str = '', $attributes = [])
2257 {
2258 if (stripos($str, '[url') !== 0)
2259 {
2260 $url = $str;
2261 }
2262 elseif ($text != '')
2263 {
2264 $url = str_replace(['[', ']'], ['%5B', '%5D'], $url);
2265 $url = '[url=' . $url . ']' . $text . '[/url]';
2266 }
2267 else
2268 {
2269 $url = '[url]' . $url . '[/url]';
2270 }
2271
2272 if (isset($this->defended_urls[$url]))
2273 {
2274 return $this->defended_urls[$url];
2275 }
2276 else
2277 {
2278 $tag = "<\x18#" . count($this->defended_urls) . " " . $this->convertAttributes($attributes) . " " . ">";
2279 $this->defended_urls[$url] = $tag;
2280
2281 return $tag;
2282 }
2283 }
2284
2286 {
2287 if (!empty($this->defended_urls))
2288 {
2289 return str_replace(array_reverse(array_values($this->defended_urls)), array_reverse(array_keys($this->defended_urls)), $str);
2290 }
2291 return $str;
2292 }
2293
2294 public function strip_words($string, $count)
2295 {
2296 $splice_pos = null;
2297
2298 $ar = preg_split("/(<.*?>|\\s+)/s", $string, -1, PREG_SPLIT_DELIM_CAPTURE);
2299 foreach ($ar as $i => $s)
2300 {
2301 if (!str_starts_with($s, '<'))
2302 {
2303 $count -= mb_strlen($s);
2304 if ($count <= 0)
2305 {
2306 $splice_pos = $i;
2307 break;
2308 }
2309 }
2310 }
2311
2312 if (isset($splice_pos))
2313 {
2314 array_splice($ar, $splice_pos+1);
2315 return implode('', $ar);
2316 }
2317 return $string;
2318 }
2319
2320 public static function closeTags($html)
2321 {
2322 preg_match_all("#<([a-z0-9]+)([^>]*)(?<!/)>#iu", $html, $result);
2323 $openedtags = array_map('strtolower', $result[1]);
2324
2325 preg_match_all("#</([a-z0-9]+)>#iu", $html, $result);
2326 $closedtags = array_map('strtolower', $result[1]);
2327
2328 $len_opened = count($openedtags);
2329
2330 if (count($closedtags) == $len_opened)
2331 {
2332 return $html;
2333 }
2334
2335 $openedtags = array_reverse($openedtags);
2336
2337 static $tagsWithoutClose = ['input'=>1, 'img'=>1, 'br'=>1, 'hr'=>1, 'meta'=>1, 'area'=>1, 'base'=>1, 'col'=>1, 'embed'=>1, 'keygen'=>1, 'link'=>1, 'param'=>1, 'source'=>1, 'track'=>1, 'wbr'=>1];
2338
2339 for ($i = 0; $i < $len_opened; $i++)
2340 {
2341 if (isset($tagsWithoutClose[$openedtags[$i]]))
2342 {
2343 continue;
2344 }
2345 if (!in_array($openedtags[$i], $closedtags))
2346 {
2347 $html .= '</' . $openedtags[$i] . '>';
2348 }
2349 else
2350 {
2351 unset($closedtags[array_search($openedtags[$i], $closedtags)]);
2352 }
2353 }
2354
2355 return $html;
2356 }
2357
2358 public static function clearAllTags($text)
2359 {
2360 $text = strip_tags(trim($text));
2361 if ($text == '')
2362 {
2363 return '';
2364 }
2365
2366 if (mb_stripos($text, '<cut') !== false || mb_stripos($text, '[cut') !== false)
2367 {
2368 $text = preg_replace([
2369 "/^(.+?)<cut(.*?)>/isu",
2370 "/^(.+?)\\[cut(.*?)]/isu",
2371 ], "\\1", $text);
2372 }
2373 if (mb_stripos($text, '[quote') !== false)
2374 {
2375 while (preg_match("/\\[(?:quote)(?:.*?)](.*?)\\[\\/quote(.*?)]/isu", $text))
2376 {
2377 $text = preg_replace(
2378 [
2379 "/\\[quote(?:.*?)](.*?)\\[\\/quote(.*?)]/isu",
2380 "/<quote(?:.*?)>(.*?)<\\/quote(.*?)>/isu",
2381 ],
2382 "\"\\1\"",
2383 $text
2384 );
2385 }
2386 }
2387
2388 $text = preg_replace("/\\[url\\s*=\\s*(\\S+?)\\s*](.*?)\\[\\/url]/isu", "\\2", $text);
2389
2390 $arPattern = [];
2391 $arReplace = [];
2392
2393 $arPattern[] = "/\\<WBR[\\s\\/]?\\>/isu";
2394 $arReplace[] = "";
2395
2396 $arPattern[] = "/^(\r|\n)+?(.*)$/";
2397 $arReplace[] = "\\2";
2398
2399 $arPattern[] = "/\\<(\\/?)(code|font|color|video)(.*?)\\>/isu";
2400 $arReplace[] = "";
2401 $arPattern[] = "/\\[\\/td(.*?)\\]\\[td(.*?)\\]/isu";
2402 $arReplace[] = " ";
2403 $arPattern[] = "/\\[(\\/?)(p|b|i|u|s|list|code|quote|size|font|color|url|img|video|td|tr|table|file|document id|disk file id|user|project|left|right|center|justify|\\*)(.*?)\\]/isu";
2404 $arReplace[] = "";
2405
2406 return preg_replace($arPattern, $arReplace, $text);
2407 }
2408
2409 public function html_cut($html, $size)
2410 {
2411 $symbols = strip_tags($html);
2412 $symbols_len = mb_strlen($symbols);
2413
2414 if ($symbols_len < mb_strlen($html))
2415 {
2416 $strip_text = $this->strip_words($html, $size);
2417
2418 if ($symbols_len > $size)
2419 {
2420 $strip_text = $strip_text . '...';
2421 }
2422
2423 $final_text = $this->closetags($strip_text);
2424 }
2425 elseif ($symbols_len > $size)
2426 {
2427 $final_text = mb_substr($html, 0, $size) . '...';
2428 }
2429 else
2430 {
2431 $final_text = $html;
2432 }
2433
2434 return $final_text;
2435 }
2436
2437 public function convertHTMLToBB($html = '', $allow = null)
2438 {
2439 if (empty($html))
2440 {
2441 return $html;
2442 }
2443
2444 $handler = AddEventHandler('main', 'TextParserBeforeTags', ['CTextParser', 'TextParserHTMLToBBHack']);
2445
2446 $this->allow = array_merge(
2447 is_array($allow) ? $allow : [
2448 'ANCHOR' => 'Y',
2449 'BIU' => 'Y',
2450 'IMG' => 'Y',
2451 'QUOTE' => 'Y',
2452 'CODE' => 'Y',
2453 'FONT' => 'Y',
2454 'LIST' => 'Y',
2455 'SMILES' => 'Y',
2456 'NL2BR' => 'Y',
2457 'VIDEO' => 'Y',
2458 'TABLE' => 'Y',
2459 'ALIGN' => 'Y',
2460 'P' => 'Y',
2461 ],
2462 ['HTML' => 'N']
2463 );
2464
2465 $html = $this->convertText($html);
2466
2467 $html = preg_replace("/<br\s*\\/*>/isu", "\n", $html);
2468 $html = preg_replace("/&nbsp;/isu", '', $html);
2469
2470 RemoveEventHandler('main', 'TextParserBeforeTags', $handler);
2471
2472 return $html;
2473 }
2474
2475 public static function TextParserHTMLToBBHack($text, $TextParser)
2476 {
2477 // Workaround for the wrong default value (see above 'TODO: change to N')
2478 $TextParser->allow = [
2479 'P' => 'N',
2480 ];
2481
2482 return true;
2483 }
2484
2485 private static function trimLineBreaks(string $text): string
2486 {
2487 return preg_replace("/^\r?\n|\r?\n$/", '', $text);
2488 }
2489
2490 private function getExternalGroupType(int $groupId): string
2491 {
2492 if (
2493 !Loader::includeModule('socialnetwork')
2494 || !class_exists(GroupProvider::class)
2495 )
2496 {
2497 return 'extranet';
2498 }
2499
2500 return GroupProvider::getInstance()->getGroupType($groupId) === Type::Collab ? 'collab' : 'extranet';
2501 }
2502
2503 private function getGroupPath(int $groupId, array $extranetGroupIds): string
2504 {
2505 $path = Option::get('socialnetwork', 'group_path_template', SITE_DIR . 'workgroups/group/#group_id#/', SITE_ID);
2506
2507 if (
2508 !Loader::includeModule('socialnetwork')
2509 || !class_exists(UrlManager::class)
2510 )
2511 {
2512 return $path;
2513 }
2514
2515 if (!in_array($groupId, $extranetGroupIds, true))
2516 {
2517 return $path;
2518 }
2519
2520 $type = GroupProvider::getInstance()->getGroupType($groupId);
2521 if ($type !== Type::Collab)
2522 {
2523 return $path;
2524 }
2525
2526 $chatId = Workgroup::getChatData(['group_id' => $groupId])[$groupId] ?? null;
2527
2528 return UrlManager::getCollabUrlById($groupId, ['chatId' => $chatId]);
2529 }
2530}
$arParams
Определения access_dialog.php:21
$path
Определения access_edit.php:21
$count
Определения admin_tab.php:4
if(!Loader::includeModule('messageservice')) $provider
Определения callback_ednaruimhpx.php:21
global $APPLICATION
Определения include.php:80
if(!is_object($USER)||! $USER->IsAuthorized()) $userId
Определения check_mail.php:18
Определения loader.php:13
static decode($text)
Определения emoji.php:24
static encode($data, $options=null)
Определения json.php:22
Определения uri.php:17
Определения textparser.php:21
$parser_nofollow
Определения textparser.php:72
convertImage($matches)
Определения textparser.php:1551
wrap_long_words($text='')
Определения textparser.php:2056
convert_spoiler_tag($text, $title='')
Определения textparser.php:1429
$pathToUser
Определения textparser.php:75
__construct()
Определения textparser.php:120
convert4mail($text)
Определения textparser.php:1052
$userField
Определения textparser.php:108
$code_error
Определения textparser.php:114
setAnchorSchemes($schemes)
Определения textparser.php:141
$smileReplaces
Определения textparser.php:104
static $repoSmiles
Определения textparser.php:105
$arFontSize
Определения textparser.php:32
convert_department(array $matches)
Определения textparser.php:1951
convert_open_tag($marker='quote')
Определения textparser.php:1501
$pathToUserEntityType
Определения textparser.php:76
convertFontColor($matches)
Определения textparser.php:1653
getTagPattern()
Определения textparser.php:1994
$quote_error
Определения textparser.php:117
convertEmoticon($matches)
Определения textparser.php:1327
$link_target
Определения textparser.php:73
render_user($fields)
Определения textparser.php:1842
cut_long_words($str)
Определения textparser.php:2107
convert_emoticon($code='', $image='', $description='', $width='', $height='', $descriptionDecode=false, $imageDefinition=CSmile::IMAGE_SD)
Определения textparser.php:1353
part_long_words($str)
Определения textparser.php:2072
static TextParserHTMLToBBHack($text, $TextParser)
Определения textparser.php:2475
defended_tags($text, $tag='replace')
Определения textparser.php:1032
$quote_open
Определения textparser.php:116
pre_convert_anchor_tag($url, $text='', $str='', $attributes=[])
Определения textparser.php:2256
$maxAnchorLength
Определения textparser.php:29
$defended_urls
Определения textparser.php:106
convertFont($matches)
Определения textparser.php:1643
convert_project(array $matches)
Определения textparser.php:1862
convertQuote($matches)
Определения textparser.php:1407
convert_anchor_tag($url, $text, $attributes=[])
Определения textparser.php:2130
convert_user($userId=0, $userName='')
Определения textparser.php:1748
$preg
Определения textparser.php:24
post_convert_anchor_tag($str)
Определения textparser.php:2285
$quote_closed
Определения textparser.php:118
$authorName
Определения textparser.php:74
$anchorType
Определения textparser.php:67
convertAnchor($matches, $attributes=[])
Определения textparser.php:2125
static cleanTag($tag)
Определения textparser.php:1999
$ajaxPage
Определения textparser.php:111
static closeTags($html)
Определения textparser.php:2320
$wordSeparator
Определения textparser.php:102
html_cut($html, $size)
Определения textparser.php:2409
initSmilePatterns()
Определения textparser.php:170
getAnchorSchemes()
Определения textparser.php:127
$imageHeight
Определения textparser.php:27
$useTypography
Определения textparser.php:78
$pathToUserEntityId
Определения textparser.php:77
convert_userfields($matches)
Определения textparser.php:1717
partWords($matches)
Определения textparser.php:2067
render_project($fields)
Определения textparser.php:1935
cutWords($matches)
Определения textparser.php:2102
$tagPattern
Определения textparser.php:109
detectTags($text)
Определения textparser.php:2004
parseSpaces($matches)
Определения textparser.php:2116
convertText($text, $attributes=[])
Определения textparser.php:213
convertCode($matches)
Определения textparser.php:1386
$type
Определения textparser.php:22
$smiles
Определения textparser.php:68
convertFontSize($matches)
Определения textparser.php:1648
convert_cut_tag($text, $title='')
Определения textparser.php:1451
$allow
Определения textparser.php:40
defendTags($matches)
Определения textparser.php:1027
$maxStringLen
Определения textparser.php:28
convert_quote_tag($text='')
Определения textparser.php:1412
initSmiles()
Определения textparser.php:146
$serverName
Определения textparser.php:23
static renderSpoiler($text, $title='', $useTypography=false)
Определения textparser.php:1465
$smilePatterns
Определения textparser.php:103
convertHTMLToBB($html='', $allow=null)
Определения textparser.php:2437
$smilesGallery
Определения textparser.php:69
$imageWidth
Определения textparser.php:26
strip_words($string, $count)
Определения textparser.php:2294
$code_open
Определения textparser.php:113
convert_close_tag($marker='quote')
Определения textparser.php:1523
$anchorSchemes
Определения textparser.php:107
render_department($fields)
Определения textparser.php:1979
$bMobile
Определения textparser.php:70
$tagClasses
Определения textparser.php:79
stripAllTags($text)
Определения textparser.php:1658
preconvertAnchor($matches, $attributes=[])
Определения textparser.php:2251
convert_video($arParams)
Определения textparser.php:1252
convert_image_tag($url='', $params='')
Определения textparser.php:1556
convertAttributes($attributes)
Определения textparser.php:2225
convertVideo($matches)
Определения textparser.php:1196
$LAZYLOAD
Определения textparser.php:71
static clearAllTags($text)
Определения textparser.php:2358
convert_font_attr($attr, $value="", $text="")
Определения textparser.php:1663
convert_tag($tag=[])
Определения textparser.php:2028
$pathToSmile
Определения textparser.php:110
$code_closed
Определения textparser.php:115
global $CACHE_MANAGER
Определения clear_component_cache.php:7
$str
Определения commerceml2.php:63
$arFields
Определения dblapprove.php:5
hidden PROPERTY[<?=$propertyIndex?>][CODE]<?=htmlspecialcharsEx( $propertyCode)?> height
Определения file_new.php:759
bx popup label bx width30 PAGE_NEW_MENU_NAME text width
Определения file_new.php:677
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$res
Определения filter_act.php:7
$result
Определения get_property_values.php:14
$start
Определения get_search.php:9
$p
Определения group_list_element_edit.php:23
if(Loader::includeModule( 'bitrix24')) elseif(Loader::includeModule('intranet') &&CIntranetUtils::getPortalZone() !=='ru') $description
Определения .description.php:24
if(!is_null($config))($config as $configItem)(! $configItem->isVisible()) $code
Определения options.php:195
if(file_exists($_SERVER['DOCUMENT_ROOT'] . "/urlrewrite.php")) $uri
Определения urlrewrite.php:61
const SITE_DIR(!defined('LANG'))
Определения include.php:72
const SITE_CHARSET
Определения include.php:62
$status
Определения session.php:10
ExecuteModuleEventEx($arEvent, $arParams=[])
Определения tools.php:5214
htmlspecialcharsback($str)
Определения tools.php:2693
AddEventHandler($FROM_MODULE_ID, $MESSAGE_ID, $CALLBACK, $SORT=100, $FULL_PATH=false)
Определения tools.php:5165
htmlspecialcharsbx($string, $flags=ENT_COMPAT, $doubleEncode=true)
Определения tools.php:2701
GetModuleEvents($MODULE_ID, $MESSAGE_ID, $bReturnArray=false)
Определения tools.php:5177
RemoveEventHandler($FROM_MODULE_ID, $MESSAGE_ID, $iEventHandlerKey)
Определения tools.php:5171
int $chatId
Определения Param.php:36
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
$ar
Определения options.php:199
if(empty($signedUserToken)) $key
Определения quickway.php:257
$text
Определения template_pdf.php:79
$i
Определения factura.php:643
font style
Определения invoice.php:442
else $userName
Определения order_form.php:75
</p ></td >< td valign=top style='border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0cm 2.0pt 0cm 2.0pt;height:9.0pt'>< p class=Normal align=center style='margin:0cm;margin-bottom:.0001pt;text-align:center;line-height:normal'>< a name=ТекстовоеПоле54 ></a ><?=($taxRate > count( $arTaxList) > 0) ? $taxRate."%"
Определения waybill.php:936
$width
Определения html.php:68
if($inWords) echo htmlspecialcharsbx(Number2Word_Rus(roundEx($totalVatSum $params['CURRENCY']
Определения template.php:799
else $a
Определения template.php:137
$title
Определения pdf.php:123
$val
Определения options.php:1793
if(!Loader::includeModule('sale')) $pattern
Определения index.php:20
$matches
Определения index.php:22
const SITE_ID
Определения sonet_set_content_view.php:12
$k
Определения template_pdf.php:567
$url
Определения iframe.php:7
$fields
Определения yandex_run.php:501