1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
search.php
См. документацию.
1<?php
3
4if (!defined('START_EXEC_TIME'))
5{
6 define('START_EXEC_TIME', microtime(true));
7}
8
9class CAllSearch extends CDBResult
10{
11 var $Query; //Query parset
12 var $Statistic; //Search statistic
13 var $strQueryText = false; //q
14 var $strTagsText = false; //tags
15 var $strSqlWhere = ''; //additional sql filter
16 var $strTags = ''; //string of tags in double quotes separated by commas
17 var $errorno = 0;
18 var $error = false;
19 var $arParams = [];
20 var $url_add_params = []; //additional url params (OnSearch event)
21 var $tf_hwm = 0;
25 var $offset = false;
26 var $limit = false;
27 var $bUseRatingSort = false;
30 var $formatter = null;
31
32 function __construct($strQuery = false, $SITE_ID = false, $MODULE_ID = false, $ITEM_ID = false, $PARAM1 = false, $PARAM2 = false, $aSort = [], $aParamsEx = [], $bTagsCloud = false)
33 {
34 $this->limit = (int)COption::GetOptionInt('search', 'max_result_size');
35 if ($this->limit < 1)
36 {
37 $this->limit = 500;
38 }
39
40 $this->CSearch($strQuery, $SITE_ID, $MODULE_ID, $ITEM_ID, $PARAM1, $PARAM2, $aSort, $aParamsEx, $bTagsCloud);
41 }
42
43 function CSearch($strQuery = false, $LID = false, $MODULE_ID = false, $ITEM_ID = false, $PARAM1 = false, $PARAM2 = false, $aSort = [], $aParamsEx = [], $bTagsCloud = false)
44 {
45 if ($strQuery === false)
46 {
47 return $this;
48 }
49
50 $arParams['QUERY'] = $strQuery;
51 $arParams['SITE_ID'] = $LID;
52 $arParams['MODULE_ID'] = $MODULE_ID;
53 $arParams['ITEM_ID'] = $ITEM_ID;
54 $arParams['PARAM1'] = $PARAM1;
55 $arParams['PARAM2'] = $PARAM2;
56
57 $this->Search($arParams, $aSort, $aParamsEx, $bTagsCloud);
58 }
59
60 //combination ($MODULE_ID, $PARAM1, $PARAM2, $PARAM3) is used to narrow search
61 //returns recordset with search results
62 function Search($arParams, $aSort = [], $aParamsEx = [], $bTagsCloud = false)
63 {
64 $DB = CDatabase::GetModuleConnection('search');
65
66 if (!is_array($arParams))
67 {
68 $arParams = ['QUERY' => $arParams];
69 }
70
71 if (!is_set($arParams, 'SITE_ID') && is_set($arParams, 'LID'))
72 {
73 $arParams['SITE_ID'] = $arParams['LID'];
74 unset($arParams['LID']);
75 }
76
77 if (array_key_exists('TAGS', $arParams))
78 {
79 $this->strTagsText = $arParams['TAGS'];
80 $arTags = explode(',', $arParams['TAGS']);
81 foreach ($arTags as $i => $strTag)
82 {
83 $strTag = trim($strTag);
84 if ($strTag <> '')
85 {
86 $arTags[$i] = str_replace('"', '\\"', $strTag);
87 }
88 else
89 {
90 unset($arTags[$i]);
91 }
92 }
93
94 if (count($arTags))
95 {
96 $arParams['TAGS'] = '"' . implode('","', $arTags) . '"';
97 }
98 else
99 {
100 unset($arParams['TAGS']);
101 }
102 }
103
104 $this->strQueryText = $strQuery = trim($arParams['QUERY']);
105 $this->strTags = $strTags = $arParams['TAGS'] ?? '';
106
107 if (($strQuery == '') && ($strTags <> ''))
108 {
109 $strQuery = $strTags;
110 $bTagsSearch = true;
111 }
112 else
113 {
114 if ($strTags <> '')
115 {
116 $strQuery .= ' ' . $strTags;
117 }
118 $strQuery = preg_replace_callback('/&#(\\d+);/', [$this, 'chr'], $strQuery);
119 $bTagsSearch = false;
120 }
121
122 if (!array_key_exists('STEMMING', $aParamsEx))
123 {
124 $aParamsEx['STEMMING'] = COption::GetOptionString('search', 'use_stemming') == 'Y';
125 }
126 $this->Query = new CSearchQuery('and', 'yes', 0, $arParams['SITE_ID']);
127 if ($this->_opt_NO_WORD_LOGIC)
128 {
129 $this->Query->no_bool_lang = true;
130 }
131 $query = $this->Query->GetQueryString('sct.SEARCHABLE_CONTENT', $strQuery, $bTagsSearch, $aParamsEx['STEMMING'], $this->_opt_ERROR_ON_EMPTY_STEM);
132
133 $fullTextParams = $aParamsEx;
134 if (!isset($fullTextParams['LIMIT']))
135 {
136 $fullTextParams['LIMIT'] = $this->limit;
137 }
138 $fullTextParams['OFFSET'] = $this->offset;
139 $fullTextParams['QUERY_OBJECT'] = $this->Query;
140 $result = CSearchFullText::getInstance()->search($arParams, $aSort, $fullTextParams, $bTagsCloud);
141 if (is_array($result))
142 {
143 $this->error = CSearchFullText::getInstance()->getErrorText();
144 $this->errorno = CSearchFullText::getInstance()->getErrorNumber();
145 $this->formatter = CSearchFullText::getInstance()->getRowFormatter();
146 if ($this->errorno > 0)
147 {
148 return;
149 }
150 }
151 else
152 {
153 if (!$query || trim($query) == '')
154 {
155 if ($bTagsCloud)
156 {
157 $query = '1=1';
158 }
159 else
160 {
161 $this->error = $this->Query->error;
162 $this->errorno = $this->Query->errorno;
163 return;
164 }
165 }
166
167 if (mb_strlen($query) > 2000)
168 {
169 $this->error = GetMessage('SEARCH_ERROR4');
170 $this->errorno = 4;
171 return;
172 }
173 }
174
175 foreach (GetModuleEvents('search', 'OnSearch', true) as $arEvent)
176 {
177 $r = '';
178 if ($bTagsSearch)
179 {
180 if ($strTags <> '')
181 {
182 $r = ExecuteModuleEventEx($arEvent, ['tags:' . $strTags]);
183 }
184 }
185 else
186 {
187 $r = ExecuteModuleEventEx($arEvent, [$strQuery]);
188 }
189 if ($r <> '')
190 {
191 $this->url_add_params[] = $r;
192 }
193 }
194
195 if (is_array($result))
196 {
197 $r = new CDBResult;
198 $r->InitFromArray($result);
199 }
200 elseif (
201 !empty($this->Query->m_stemmed_words_id)
202 && is_array($this->Query->m_stemmed_words_id)
203 && array_sum($this->Query->m_stemmed_words_id) === 0
204 )
205 {
206 $r = new CDBResult;
207 $r->InitFromArray([]);
208 }
209 else
210 {
211 $this->strSqlWhere = '';
212 $bIncSites = false;
213
214 $arSqlWhere = [];
215 if (is_array($aParamsEx) && !empty($aParamsEx))
216 {
217 foreach ($aParamsEx as $aParamEx)
218 {
219 $strSqlWhere = CSearch::__PrepareFilter($aParamEx, $bIncSites);
220 if ($strSqlWhere != '')
221 {
222 $arSqlWhere[] = $strSqlWhere;
223 }
224 }
225 }
226 if (!empty($arSqlWhere))
227 {
228 $arSqlWhere = [
229 "\n\t\t\t\t(" . implode(")\n\t\t\t\t\tOR(", $arSqlWhere) . "\n\t\t\t\t)",
230 ];
231 }
232
234 if ($strSqlWhere != '')
235 {
236 array_unshift($arSqlWhere, $strSqlWhere);
237 }
238
239 $strSqlOrder = $this->__PrepareSort($aSort, 'sc.', $bTagsCloud);
240
241 if (!array_key_exists('USE_TF_FILTER', $aParamsEx))
242 {
243 $aParamsEx['USE_TF_FILTER'] = COption::GetOptionString('search', 'use_tf_cache') == 'Y';
244 }
245
246 $bStem = !$bTagsSearch && count($this->Query->m_stemmed_words) > 0;
247 //calculate freq of the word on the whole site_id
248 if ($bStem && count($this->Query->m_stemmed_words))
249 {
250 $arStat = $this->GetFreqStatistics($this->Query->m_lang, $this->Query->m_stemmed_words, $arParams['SITE_ID']);
251 $this->tf_hwm_site_id = ($arParams['SITE_ID'] <> '' ? $arParams['SITE_ID'] : '');
252
253 //we'll make filter by it's contrast
254 if (!$bTagsCloud && $aParamsEx['USE_TF_FILTER'])
255 {
256 $hwm = false;
257 foreach ($this->Query->m_stemmed_words as $i => $stem)
258 {
259 if (!array_key_exists($stem, $arStat))
260 {
261 $hwm = 0;
262 break;
263 }
264 elseif ($hwm === false)
265 {
266 $hwm = $arStat[$stem]['TF'];
267 }
268 elseif ($hwm > $arStat[$stem]['TF'])
269 {
270 $hwm = $arStat[$stem]['TF'];
271 }
272 }
273
274 if ($hwm > 0)
275 {
276 $arSqlWhere[] = 'st.TF >= ' . number_format($hwm, 2, '.', '');
277 $this->tf_hwm = $hwm;
278 }
279 }
280 }
281
282 if (!empty($arSqlWhere))
283 {
284 $this->strSqlWhere = "\n\t\t\t\tAND (\n\t\t\t\t\t(" . implode(")\n\t\t\t\t\tAND(", $arSqlWhere) . ")\n\t\t\t\t)";
285 }
286
287 if ($bTagsCloud)
288 {
289 $strSql = $this->tagsMakeSQL($query, $this->strSqlWhere, $strSqlOrder, $bIncSites, $bStem, $aParamsEx['LIMIT']);
290 }
291 else
292 {
293 $strSql = $this->MakeSQL($query, $this->strSqlWhere, $strSqlOrder, $bIncSites, $bStem);
294 }
295
296 $r = $DB->Query($strSql);
297 }
298 parent::__construct($r);
299 }
300
302 {
303 if (array_key_exists('ERROR_ON_EMPTY_STEM', $arOptions))
304 {
305 $this->_opt_ERROR_ON_EMPTY_STEM = $arOptions['ERROR_ON_EMPTY_STEM'] === true;
306 }
307
308 if (array_key_exists('NO_WORD_LOGIC', $arOptions))
309 {
310 $this->_opt_NO_WORD_LOGIC = $arOptions['NO_WORD_LOGIC'] === true;
311 }
312 }
313
315 {
316 $this->offset = (int)$offset;
317 }
318
319 function SetLimit($limit)
320 {
321 $this->limit = (int)$limit;
322 }
323
324 function GetFilterMD5()
325 {
327 $sql = preg_replace("/(DATE_FROM|DATE_TO|DATE_CHANGE)(\\s+IS\\s+NOT\\s+NULL|\\s+IS\\s+NULL|\\s*[<>!=]+\\s*'.*?')/im", '', $this->strSqlWhere);
328 return md5($perm . $sql . $this->strTags);
329 }
330
331 public static function chr($a)
332 {
333 return chr($a[1]);
334 }
335
336 function GetFreqStatistics($lang_id, $arStem, $site_id = '')
337 {
338 $DB = CDatabase::GetModuleConnection('search');
339 $sql_site_id = $DB->ForSQL($site_id);
340 $sql_lang_id = $DB->ForSQL($lang_id);
341 $sql_stem = [];
342 foreach ($arStem as $stem)
343 {
344 $sql_stem[] = $DB->ForSQL($stem);
345 }
346
347 $limit = COption::GetOptionInt('search', 'max_result_size');
348 if ($limit < 1)
349 {
350 $limit = 500;
351 }
352
353 $arResult = [];
354 foreach ($arStem as $stem)
355 {
356 $arResult[$stem] = [
357 'STEM' => false,
358 'FREQ' => 0,
359 'TF' => 0,
360 'STEM_COUNT' => 0,
361 'TF_SUM' => 0,
362 ];
363 }
364
365 $strSql = "
366 SELECT s.ID, s.STEM, FREQ, TF
367 FROM b_search_content_freq f
368 inner join b_search_stem s on s.ID = f.STEM
369 WHERE LANGUAGE_ID = '" . $sql_lang_id . "'
370 AND s.STEM in ('" . implode("','", $sql_stem) . "')
371 AND " . ($site_id <> '' ? "SITE_ID = '" . $sql_site_id . "'" : 'SITE_ID IS NULL') . '
372 ORDER BY STEM
373 ';
374
375 $rs = $DB->Query($strSql);
376 while ($ar = $rs->Fetch())
377 {
378 if ($ar['TF'] <> '')
379 {
380 $arResult[$ar['STEM']] = $ar;
381 }
382 }
383
384 $arMissed = [];
385 foreach ($arResult as $stem => $ar)
386 {
387 if (!$ar['STEM'])
388 {
389 $arMissed[] = $DB->ForSQL($stem);
390 }
391 }
392
393 if (count($arMissed) > 0)
394 {
395 $strSql = '
396 SELECT s.ID, s.STEM, floor(st.TF/100) BUCKET, sum(st.TF/10000) TF_SUM, count(*) STEM_COUNT
397 FROM
398 b_search_content_stem st
399 inner join b_search_stem s on s.ID = st.STEM
400 ' . ($site_id <> '' ? "INNER JOIN b_search_content_site scsite ON scsite.SEARCH_CONTENT_ID = st.SEARCH_CONTENT_ID AND scsite.SITE_ID = '" . $sql_site_id . "'" : '') . "
401 WHERE st.LANGUAGE_ID = '" . $sql_lang_id . "'
402 AND s.STEM in ('" . implode("','", $arMissed) . "')
403 GROUP BY s.ID, s.STEM, floor(st.TF/100)
404 ORDER BY s.ID, s.STEM, floor(st.TF/100) DESC
405 ";
406
407 $rs = $DB->Query($strSql);
408 while ($ar = $rs->Fetch())
409 {
410 $stem = $ar['STEM'];
411 if ($arResult[$stem]['STEM_COUNT'] < $limit)
412 {
413 $arResult[$stem]['TF'] = $ar['BUCKET'] / 100.0;
414 }
415 $arResult[$stem]['STEM_COUNT'] += $ar['STEM_COUNT'];
416 $arResult[$stem]['TF_SUM'] += $ar['TF_SUM'];
417 $arResult[$stem]['DO_INSERT'] = true;
418 $arResult[$stem]['ID'] = $ar['ID'];
419 }
420 }
421
422 foreach ($arResult as $stem => $ar)
423 {
424 if (isset($ar['DO_INSERT']) && $ar['DO_INSERT'])
425 {
426 $FREQ = intval(defined('search_range_by_sum_tf') ? $ar['TF_SUM'] : $ar['STEM_COUNT']);
427 $strSql = '
428 UPDATE b_search_content_freq
429 SET FREQ=' . $FREQ . ', TF=' . number_format($ar['TF'], 2, '.', '') . "
430 WHERE LANGUAGE_ID='" . $sql_lang_id . "'
431 AND " . ($site_id <> '' ? "SITE_ID = '" . $sql_site_id . "'" : 'SITE_ID IS NULL') . "
432 AND STEM='" . $DB->ForSQL($ar['ID']) . "'
433 ";
434 $rsUpdate = $DB->Query($strSql);
435 if ($rsUpdate->AffectedRowsCount() <= 0)
436 {
437 $strSql = "
438 INSERT INTO b_search_content_freq
439 (STEM, LANGUAGE_ID, SITE_ID, FREQ, TF)
440 VALUES
441 ('" . $DB->ForSQL($ar['ID']) . "', '" . $sql_lang_id . "', " . ($site_id <> '' ? "'" . $sql_site_id . "'" : 'NULL') . ', ' . $FREQ . ', ' . number_format($ar['TF'], 2, '.', '') . ')
442 ';
443 $rsInsert = $DB->Query($strSql, true);
444 }
445 }
446 }
447
448 return $arResult;
449 }
450
451 function Repl($strCond, $strType, $strWh)
452 {
453 $l = mb_strlen($strCond);
454
455 if ($this->Query->bStemming)
456 {
457 $arStemInfo = stemming_init($this->Query->m_lang);
458 $pcreLettersClass = '[' . $arStemInfo['pcre_letters'] . ']';
459 $strWhUpp = stemming_upper($strWh, $this->Query->m_lang);
460 }
461 else
462 {
463 $strWhUpp = mb_strtoupper($strWh);
464 }
465
466 $strCondUpp = mb_strtoupper($strCond);
467
468 $pos = 0;
469 do
470 {
471 $pos = mb_strpos($strWhUpp, $strCondUpp, $pos);
472
473 //Check if we are in the middle of the numeric entity
474 while (
475 $pos !== false &&
476 preg_match('/^[0-9]+;/', mb_substr($strWh, $pos)) &&
477 preg_match('/^[0-9]+#&/', strrev(mb_substr($strWh, 0, $pos + mb_strlen($strCond))))
478 )
479 {
480 $pos = mb_strpos($strWhUpp, $strCondUpp, $pos + 1);
481 }
482
483 if ($pos === false)
484 {
485 break;
486 }
487
488 if ($strType == 'STEM')
489 {
490 $lw = mb_strlen($strWhUpp);
491 for ($s = $pos; $s >= 0; $s--)
492 {
493 if (!preg_match('/' . $pcreLettersClass . '/u', mb_substr($strWhUpp, $s, 1)))
494 {
495 break;
496 }
497 }
498 $s++;
499 for ($e = $pos; $e < $lw; $e++)
500 {
501 if (!preg_match('/' . $pcreLettersClass . '/u', mb_substr($strWhUpp, $e, 1)))
502 {
503 break;
504 }
505 }
506 $e--;
507 $a = stemming(mb_substr($strWhUpp, $s, $e - $s + 1), $this->Query->m_lang, true);
508 foreach ($a as $stem => $cnt)
509 {
510 if ($stem == $strCondUpp)
511 {
512 $strWh = mb_substr($strWh, 0, $pos) . '%^%' . mb_substr($strWh, $pos, $e - $pos + 1) . '%/^%' . mb_substr($strWh, $e + 1);
513 $strWhUpp = mb_substr($strWhUpp, 0, $pos) . '%^%' . str_repeat(' ', $e - $pos + 1) . '%/^%' . mb_substr($strWhUpp, $e + 1);
514 $pos += 7 + $e - $pos + 1;
515 }
516 }
517 }
518 else
519 {
520 $strWh = mb_substr($strWh, 0, $pos) . '%^%' . mb_substr($strWh, $pos, $l) . '%/^%' . mb_substr($strWh, $pos + $l);
521 $strWhUpp = mb_substr($strWhUpp, 0, $pos) . '%^%' . str_repeat(' ', $l) . '%/^%' . mb_substr($strWhUpp, $pos + $l);
522 $pos += 7 + $l;
523 }
524 $pos += 1;
525 } while ($pos < mb_strlen($strWhUpp));
526
527 return $strWh;
528 }
529
531 {
532 //$words - contains what we will highlight
533 $words = [];
534 foreach ($this->Query->m_words as $v)
535 {
536 $v = mb_strtoupper($v);
537 $words[$v] = 'KAV';
538 if (mb_strpos($v, '"') !== false)
539 {
540 $words[str_replace('"', '&QUOT;', $v)] = 'KAV';
541 }
542 }
543
544 foreach ($this->Query->m_stemmed_words as $v)
545 {
546 $words[mb_strtoupper($v)] = 'STEM';
547 }
548
549 //Prepare upper case version of the string
550 if ($this->Query->bStemming)
551 {
552 //And add missing stemming words
553 $arStemInfo = stemming_init($this->Query->m_lang);
554 $a = stemming($this->Query->m_query, $this->Query->m_lang, true);
555 foreach ($a as $stem => $cnt)
556 {
557 if (!preg_match('/cut[56]/i', $stem))
558 {
559 $words[$stem] = 'STEM';
560 }
561 }
562 $pcreLettersClass = '[' . $arStemInfo['pcre_letters'] . ']';
563 $strUpp = stemming_upper($str, $this->Query->m_lang);
564 }
565 else
566 {
567 $strUpp = mb_strtoupper($str);
568 $pcreLettersClass = '';
569 }
570
571 $wordsCount = count($words);
572
573 //We'll use regexp to find positions of the words in the text
574 $pregMask = '';
575 foreach ($words as $search => $type)
576 {
577 if ($type == 'STEM')
578 {
579 $pregMask = '(?<!' . $pcreLettersClass . ')' . preg_quote($search, '/') . $pcreLettersClass . '*|' . $pregMask;
580 }
581 else
582 {
583 $pregMask = $pregMask . '|' . preg_quote($search, '/');
584 }
585 }
586 $pregMask = trim($pregMask, '|');
587
588 $arPos = []; //This will contain positions of the first occurrence
589 $arPosW = []; //This is "running" words array
590 $arPosP = []; //and their positions
591 $arPosLast = false; //Best found combination of the positions
592 $matches = [];
593 if (preg_match_all('/(' . $pregMask . ')/iu', $strUpp, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE))
594 {
595 foreach ($matches as $oneCase)
596 {
597 $search = null;
598 if (isset($words[$oneCase[0][0]]))
599 {
600 $search = $oneCase[0][0];
601 }
602 else
603 {
604 $a = stemming($oneCase[0][0], $this->Query->m_lang, true);
605 foreach ($a as $stem => $cnt)
606 {
607 if (isset($words[$stem]))
608 {
609 $search = $stem;
610 break;
611 }
612 }
613 }
614
615 if (isset($search))
616 {
617 $p = $oneCase[0][1];
618 if (!isset($arPos[$search]))
619 {
620 $arPos[$search] = $p;
621 }
622 //Add to the tail of the running window
623 $arPosP[] = $p;
624 $arPosW[] = $search;
625 $cc = count($arPosW);
626 if ($cc >= $wordsCount)
627 {
628 //This cuts the tail of the running window
629 while ($cc > $wordsCount)
630 {
631 array_shift($arPosW);
632 array_shift($arPosP);
633 $cc--;
634 }
635 //Check if all the words present in the current window
636 if (count(array_unique($arPosW)) == $wordsCount)
637 {
638 //And check if positions is the best
639 if (
640 !$arPosLast
641 || (
642 (max($arPosP) - min($arPosP)) < (max($arPosLast) - min($arPosLast))
643 )
644 )
645 {
646 $arPosLast = $arPosP;
647 }
648 }
649 }
650 }
651 }
652 }
653
654 if ($arPosLast)
655 {
656 $arPos = $arPosLast;
657 }
658
659 //Nothing found just cut some text
660 if (empty($arPos))
661 {
662 $str_len = mb_strlen($str);
663 $pos_end = 500;
664 while (($pos_end < $str_len) && (mb_strpos(" ,.\n\r", mb_substr($str, $pos_end, 1)) === false))
665 {
666 $pos_end++;
667 }
668 return mb_substr($str, 0, $pos_end) . ($pos_end < $str_len ? '...' : '');
669 }
670
671 sort($arPos);
672
673 $str_len = strlen($str);
674 $delta = 250 / count($arPos);
675 $arOtr = [];
676 //Have to do it two times because Positions eat each other
677 for ($i = 0; $i < 2; $i++)
678 {
679 $arOtr = [];
680 $last_pos = -1;
681 foreach ($arPos as $pos_mid)
682 {
683 //Find where sentence begins
684 $pos_beg = $pos_mid - $delta;
685 if ($pos_beg <= 0)
686 {
687 $pos_beg = 0;
688 }
689 while (($pos_beg > 0) && (mb_strpos(" ,.!?\n\r", substr($str, $pos_beg, 1)) === false))
690 {
691 $pos_beg--;
692 }
693
694 //Find where sentence ends
695 $pos_end = $pos_mid + $delta;
696 if ($pos_end > $str_len)
697 {
698 $pos_end = $str_len;
699 }
700 while (($pos_end < $str_len) && (mb_strpos(" ,.!?\n\r", substr($str, $pos_end, 1)) === false))
701 {
702 $pos_end++;
703 }
704
705 if ($pos_beg <= $last_pos)
706 {
707 $arOtr[count($arOtr) - 1][1] = $pos_end;
708 }
709 else
710 {
711 $arOtr[] = [$pos_beg, $pos_end];
712 }
713
714 $last_pos = $pos_end;
715 }
716 //Adjust length of the text
717 $delta = 250 / count($arOtr);
718 }
719
720 $str_result = '';
721 foreach ($arOtr as $borders)
722 {
723 $str_result .= ($borders[0] <= 0 ? '' : ' ...')
724 . substr($str, $borders[0], $borders[1] - $borders[0] + 1)
725 . ($borders[1] >= $str_len ? '' : '... ');
726 }
727
728 foreach ($words as $search => $type)
729 {
730 $str_result = $this->Repl($search, $type, $str_result);
731 }
732
733 $str_result = str_replace('%/^%', '</b>', str_replace('%^%', '<b>', $str_result));
734
735 return $str_result;
736 }
737
738 function NavStart($nPageSize = 0, $bShowAll = true, $iNumPage = false)
739 {
740 parent::NavStart($nPageSize, $bShowAll, $iNumPage);
741 if (COption::GetOptionString('search', 'stat_phrase') == 'Y')
742 {
743 $this->Statistic = new CSearchStatistic($this->strQueryText, $this->strTagsText);
744 $this->Statistic->PhraseStat($this->NavRecordCount, $this->NavPageNomer);
745 if ($this->Statistic->phrase_id)
746 {
747 $this->url_add_params[] = 'sphrase_id=' . $this->Statistic->phrase_id;
748 }
749 }
750 }
751
752 function Fetch()
753 {
754 static $arSite = [];
755 $DB = CDatabase::GetModuleConnection('search');
756
757 $r = parent::Fetch();
758
759 if ($r && $this->formatter)
760 {
761 $r = $this->formatter->format($r);
762 if (!$r)
763 {
764 return $this->Fetch();
765 }
766 }
767
768 if ($r)
769 {
770 $site_id = $r['SITE_ID'] ?? null;
771 if (!isset($arSite[$site_id]))
772 {
773 $rsSite = CSite::GetList('', '', ['ID' => $site_id]);
774 $arSite[$site_id] = $rsSite->Fetch();
775 }
776 $r['DIR'] = $arSite[$site_id]['DIR'];
777 $r['SERVER_NAME'] = $arSite[$site_id]['SERVER_NAME'];
778
779 if (!empty($r['SITE_URL']))
780 {
781 $r['URL'] = $r['SITE_URL'];
782 }
783
784 if (isset($r['URL']) && mb_substr($r['URL'], 0, 1) == '=')
785 {
786 foreach (GetModuleEvents('search', 'OnSearchGetURL', true) as $arEvent)
787 {
788 $newUrl = ExecuteModuleEventEx($arEvent, [$r]);
789 if (isset($newUrl))
790 {
791 $r['URL'] = $newUrl;
792 }
793 }
794 }
795
796 $r['URL'] = str_replace(
797 ['#LANG#', '#SITE_DIR#', '#SERVER_NAME#'],
798 [$r['DIR'], $r['DIR'], $r['SERVER_NAME']],
799 ($r['URL'] ?? '')
800 );
801 $r['URL'] = preg_replace("'(?<!:)/+'s", '/', $r['URL']);
802 $r['URL_WO_PARAMS'] = $r['URL'];
803
804 $w = $this->Query->m_words;
805 if (count($this->url_add_params))
806 {
807 $p1 = mb_strpos($r['URL'], '?');
808 if ($p1 === false)
809 {
810 $ch = '?';
811 }
812 else
813 {
814 $ch = '&';
815 }
816
817 $p2 = mb_strpos($r['URL'], '#', $p1);
818 if ($p2 === false)
819 {
820 $r['URL'] = $r['URL'] . $ch . implode('&', $this->url_add_params);
821 }
822 else
823 {
824 $r['URL'] = mb_substr($r['URL'], 0, $p2) . $ch . implode('&', $this->url_add_params) . mb_substr($r['URL'], $p2);
825 }
826 }
827
828 if (!array_key_exists('TITLE_FORMATED', $r) && array_key_exists('TITLE', $r))
829 {
830 $r['TITLE_FORMATED'] = $this->PrepareSearchResult(htmlspecialcharsEx($r['TITLE']));
831 $r['TITLE_FORMATED_TYPE'] = 'html';
832 $r['TAGS_FORMATED'] = tags_prepare($r['TAGS'], SITE_ID);
833 if (!empty($r['BODY']))
834 {
835 $r['BODY_FORMATED'] = $this->PrepareSearchResult(htmlspecialcharsEx($r['BODY']));
836 $r['BODY_FORMATED_TYPE'] = 'html';
837 }
838 else
839 {
840 $max_body_size = COption::GetOptionInt('search', 'max_body_size');
841 $sqlBody = $max_body_size > 0 ? 'left(BODY,' . $max_body_size . ') as BODY' : 'BODY';
842 $rsBody = $DB->Query('select ' . $sqlBody . ' from b_search_content where ID=' . $r['ID']);
843 if ($arBody = $rsBody->Fetch())
844 {
845 $r['BODY_FORMATED'] = $this->PrepareSearchResult(htmlspecialcharsEx($arBody['BODY']));
846 $r['BODY_FORMATED_TYPE'] = 'html';
847 }
848 }
849 }
850 }
851
852 return $r;
853 }
854
855 public static function CheckPath($path)
856 {
857 static $SEARCH_MASKS_CACHE = false;
858
859 if (!is_array($SEARCH_MASKS_CACHE))
860 {
861 $arSearch = ['\\', '.', '?', '*', "'"];
862 $arReplace = ['/', '\\.', '.', '.*?', "\\'"];
863
864 $arInc = [];
865 $inc = str_replace(
866 $arSearch,
867 $arReplace,
868 COption::GetOptionString('search', 'include_mask')
869 );
870 $arIncTmp = explode(';', $inc);
871 foreach ($arIncTmp as $mask)
872 {
873 $mask = trim($mask);
874 if ($mask <> '')
875 {
876 $arInc[] = "'^" . $mask . "$'";
877 }
878 }
879
880 $arFullExc = [];
881 $arExc = [];
882 $exc = str_replace(
883 $arSearch,
884 $arReplace,
885 COption::GetOptionString('search', 'exclude_mask')
886 );
887 $arExcTmp = explode(';', $exc);
888 foreach ($arExcTmp as $mask)
889 {
890 $mask = trim($mask);
891 if ($mask <> '')
892 {
893 if (preg_match('#^/[a-z0-9_.\\\\]+/#i', $mask))
894 {
895 $arFullExc[] = "'^" . $mask . "$'u";
896 }
897 else
898 {
899 $arExc[] = "'^" . $mask . "$'u";
900 }
901 }
902 }
903
904 $SEARCH_MASKS_CACHE = [
905 'full_exc' => $arFullExc,
906 'exc' => $arExc,
907 'inc' => $arInc
908 ];
909 }
910
911 $file = end(explode('/', $path)); //basename
912 if (strncmp($file, '.', 1) == 0)
913 {
914 return 0;
915 }
916
917 foreach ($SEARCH_MASKS_CACHE['full_exc'] as $mask)
918 {
919 if (preg_match($mask, $path))
920 {
921 return false;
922 }
923 }
924
925 foreach ($SEARCH_MASKS_CACHE['exc'] as $mask)
926 {
927 if (preg_match($mask, $path))
928 {
929 return 0;
930 }
931 }
932
933 foreach ($SEARCH_MASKS_CACHE['inc'] as $mask)
934 {
935 if (preg_match($mask, $path))
936 {
937 return true;
938 }
939 }
940
941 return 0;
942 }
943
944 public static function GetGroupCached()
945 {
946 static $SEARCH_CACHED_GROUPS = false;
947
948 if (!is_array($SEARCH_CACHED_GROUPS))
949 {
950 $SEARCH_CACHED_GROUPS = [];
951 $db_groups = CGroup::GetList('id', 'asc');
952 while ($g = $db_groups->Fetch())
953 {
954 $group_id = intval($g['ID']);
955 if ($group_id > 1)
956 {
957 $SEARCH_CACHED_GROUPS[$group_id] = $group_id;
958 }
959 }
960 }
961
962 return $SEARCH_CACHED_GROUPS;
963 }
964
965 public static function QueryMnogoSearch(&$xml)
966 {
967 $SITE = COption::GetOptionString('search', 'mnogosearch_url', 'www.mnogosearch.org');
968 $PATH = COption::GetOptionString('search', 'mnogosearch_path', '');
969 $PORT = COption::GetOptionString('search', 'mnogosearch_port', '80');
970
971 $QUERY_STR = 'document=' . urlencode($xml);
972
973 $strRequest = 'POST ' . $PATH . " HTTP/1.0\r\n";
974 $strRequest .= "User-Agent: BitrixSM\r\n";
975 $strRequest .= "Accept: */*\r\n";
976 $strRequest .= 'Host: ' . $SITE . "\r\n";
977 $strRequest .= "Accept-Language: en\r\n";
978 $strRequest .= "Content-type: application/x-www-form-urlencoded\r\n";
979 $strRequest .= 'Content-length: ' . mb_strlen($QUERY_STR) . "\r\n";
980 $strRequest .= "\r\n";
981 $strRequest .= $QUERY_STR;
982 $strRequest .= "\r\n";
983
984 $arAll = '';
985 $errno = 0;
986 $errstr = '';
987
988 $FP = fsockopen($SITE, $PORT, $errno, $errstr, 120);
989 if ($FP)
990 {
991 fputs($FP, $strRequest);
992
993 while (($line = fgets($FP, 4096)) && $line != "\r\n");
994 while ($line = fread($FP, 4096))
995 {
996 $arAll .= $line;
997 }
998 fclose($FP);
999 }
1000
1001 return $arAll;
1002 }
1003
1005 //reindex the whole server content
1006 //$bFull = true - no not check change_date. all index tables will be truncated
1007 // = false - add new ones. update changed and delete deleted.
1008 public static function ReIndexAll($bFull = false, $max_execution_time = 0, $NS = [], $clear_suggest = false)
1009 {
1010 global $APPLICATION;
1011 $DB = CDatabase::GetModuleConnection('search');
1012
1013 @set_time_limit(0);
1014 if (!is_array($NS))
1015 {
1016 $NS = [];
1017 }
1018 if ($max_execution_time <= 0)
1019 {
1020 $NS_OLD = $NS;
1021 $NS = ['CLEAR' => 'N', 'MODULE' => '', 'ID' => '', 'SESS_ID' => md5(uniqid(''))];
1022 if ($NS_OLD['SITE_ID'] != '')
1023 {
1024 $NS['SITE_ID'] = $NS_OLD['SITE_ID'];
1025 }
1026 if ($NS_OLD['MODULE_ID'] != '')
1027 {
1028 $NS['MODULE_ID'] = $NS_OLD['MODULE_ID'];
1029 }
1030 }
1031 $NS['CNT'] = intval($NS['CNT']);
1032 if (!$bFull && (!isset($NS['SESS_ID']) || mb_strlen($NS['SESS_ID']) != 32))
1033 {
1034 $NS['SESS_ID'] = md5(uniqid(''));
1035 }
1036
1037 $p1 = microtime(true);
1038
1039 $DB->StartTransaction();
1041
1042 if (!isset($NS['CLEAR']) || $NS['CLEAR'] != 'Y')
1043 {
1044 if ($bFull)
1045 {
1046 foreach (GetModuleEvents('search', 'OnBeforeFullReindexClear', true) as $arEvent)
1047 {
1048 ExecuteModuleEventEx($arEvent);
1049 }
1050
1052 $DB->Query('TRUNCATE TABLE b_search_content_param');
1053 $DB->Query('TRUNCATE TABLE b_search_content_site');
1054 $DB->Query('TRUNCATE TABLE b_search_content_right');
1055 $DB->Query('TRUNCATE TABLE b_search_content_title');
1056 $DB->Query('TRUNCATE TABLE b_search_tags');
1057 $DB->Query('TRUNCATE TABLE b_search_content_freq');
1058 $DB->Query('TRUNCATE TABLE b_search_content');
1059 $DB->Query('TRUNCATE TABLE b_search_suggest');
1060 $DB->Query('TRUNCATE TABLE b_search_user_right');
1061 CSearchFullText::getInstance()->truncate();
1062 CAdminNotify::DeleteByTag('SEARCH_REINDEX');
1063 }
1064 elseif ($clear_suggest)
1065 {
1066 $DB->Query('TRUNCATE TABLE b_search_suggest');
1067 $DB->Query('TRUNCATE TABLE b_search_user_right');
1068 $DB->Query('TRUNCATE TABLE b_search_content_freq');
1069 }
1070 }
1071
1072 $NS['CLEAR'] = 'Y';
1073
1074 clearstatcache();
1075
1076 if (
1077 (!isset($NS['MODULE']) || $NS['MODULE'] == '' || $NS['MODULE'] == 'main')
1078 && (!isset($NS['MODULE_ID']) || $NS['MODULE_ID'] == '' || $NS['MODULE_ID'] == 'main')
1079 )
1080 {
1081 $arLangDirs = [];
1082 $arFilter = ['ACTIVE' => 'Y'];
1083 if (isset($NS['SITE_ID']) && $NS['SITE_ID'] != '')
1084 {
1085 $arFilter['ID'] = $NS['SITE_ID'];
1086 }
1087 $r = CSite::GetList('', '', $arFilter);
1088 while ($arR = $r->Fetch())
1089 {
1090 $path = rtrim($arR['DIR'], '/');
1091 $arLangDirs[$arR['ABS_DOC_ROOT'] . '/' . $path . '/'] = $arR;
1092 }
1093
1094 //get rid of duplicates
1095 $dub = [];
1096 foreach ($arLangDirs as $path => $arR)
1097 {
1098 foreach ($arLangDirs as $path2 => $arR2)
1099 {
1100 if ($path == $path2)
1101 {
1102 continue;
1103 }
1104 if (mb_substr($path, 0, mb_strlen($path2)) == $path2)
1105 {
1106 $dub[] = $path;
1107 }
1108 }
1109 }
1110
1111 foreach ($dub as $p)
1112 {
1113 unset($arLangDirs[$p]);
1114 }
1115
1116 foreach ($arLangDirs as $arR)
1117 {
1118 $site = $arR['ID'];
1119 $path = rtrim($arR['DIR'], '/');
1120 $site_path = $site . '|' . $path . '/';
1121
1122 if (
1124 && isset($NS['MODULE'])
1125 && $NS['MODULE'] == 'main'
1126 && mb_substr($NS['ID'] . '/', 0, mb_strlen($site_path)) != $site_path
1127 )
1128 {
1129 continue;
1130 }
1131
1132 //for every folder
1134 if (
1136 && $NS['MODULE'] <> ''
1137 )
1138 {
1139 $DB->Commit();
1140 return $NS;
1141 }
1142 }
1143 }
1144
1145 $p1 = microtime(true);
1146
1147 //for every who wants to reindex
1148 $oCallBack = new CSearchCallback;
1149 $oCallBack->max_execution_time = $max_execution_time;
1150 foreach (GetModuleEvents('search', 'OnReindex', true) as $arEvent)
1151 {
1152 if (
1153 isset($NS['MODULE_ID'])
1154 && $NS['MODULE_ID'] != ''
1155 && $NS['MODULE_ID'] != $arEvent['TO_MODULE_ID']
1156 )
1157 {
1158 continue;
1159 }
1160 if (
1162 && isset($NS['MODULE'])
1163 && $NS['MODULE'] <> ''
1164 && $NS['MODULE'] != 'main'
1165 && $NS['MODULE'] != $arEvent['TO_MODULE_ID']
1166 )
1167 {
1168 continue;
1169 }
1170 //here we get recordset
1171 $oCallBack->MODULE = $arEvent['TO_MODULE_ID'];
1172 $oCallBack->CNT = &$NS['CNT'];
1173 $oCallBack->SESS_ID = $NS['SESS_ID'] ?? '';
1174 $r = &$oCallBack;
1175 $arResult = ExecuteModuleEventEx($arEvent, [$NS, $r, 'Index']);
1176 if (is_array($arResult)) //old way
1177 {
1178 foreach ($arResult as $arFields)
1179 {
1180 $ID = $arFields['ID'];
1181 if ($ID <> '')
1182 {
1183 unset($arFields['ID']);
1184 $NS['CNT']++;
1185 CSearch::Index($arEvent['TO_MODULE_ID'], $ID, $arFields, false, $NS['SESS_ID']);
1186 }
1187 }
1188 }
1189 else //new method
1190 {
1191 if ($max_execution_time > 0 && $arResult !== false && mb_strlen('.' . $arResult) > 1)
1192 {
1193 $DB->Commit();
1194 return [
1195 'MODULE' => $arEvent['TO_MODULE_ID'],
1196 'CNT' => $oCallBack->CNT,
1197 'ID' => $arResult,
1198 'CLEAR' => $NS['CLEAR'],
1199 'SESS_ID' => $NS['SESS_ID'],
1200 'SITE_ID' => $NS['SITE_ID'],
1201 'MODULE_ID' => $NS['MODULE_ID'],
1202 ];
1203 }
1204 }
1205 $NS['MODULE'] = '';
1206 }
1207
1208 if (!$bFull)
1209 {
1210 CSearch::DeleteOld($NS['SESS_ID'], $NS['MODULE_ID'], $NS['SITE_ID']);
1211 }
1212
1213 $DB->Commit();
1214
1215 return $NS['CNT'];
1216 }
1217
1218 public static function ReindexModule($MODULE_ID, $bFull = false)
1219 {
1220 if ($bFull)
1221 {
1222 CSearch::DeleteForReindex($MODULE_ID);
1223 }
1224
1225 $NS = ['CLEAR' => 'N', 'MODULE' => '', 'ID' => '', 'SESS_ID' => md5(uniqid(''))];
1226 //for every who wants to be reindexed
1227 foreach (GetModuleEvents('search', 'OnReindex', true) as $arEvent)
1228 {
1229 if ($arEvent['TO_MODULE_ID'] != $MODULE_ID)
1230 {
1231 continue;
1232 }
1233
1234 $oCallBack = new CSearchCallback;
1235 $oCallBack->MODULE = $arEvent['TO_MODULE_ID'];
1236 $oCallBack->CNT = &$NS['CNT'];
1237 $oCallBack->SESS_ID = $NS['SESS_ID'];
1238 $r = &$oCallBack;
1239
1240 $arResult = ExecuteModuleEventEx($arEvent, [$NS, $r, 'Index']);
1241 if (is_array($arResult)) //old way
1242 {
1243 foreach ($arResult as $arFields)
1244 {
1245 $ID = $arFields['ID'];
1246 if ($ID <> '')
1247 {
1248 unset($arFields['ID']);
1249 $NS['CNT']++;
1250 CSearch::Index($arEvent['TO_MODULE_ID'], $ID, $arFields, false, $NS['SESS_ID']);
1251 }
1252 }
1253 }
1254 else //new way
1255 {
1256 return ['MODULE' => $arEvent['TO_MODULE_ID'], 'CNT' => $oCallBack->CNT, 'ID' => $arResult, 'CLEAR' => $NS['CLEAR'], 'SESS_ID' => $NS['SESS_ID']];
1257 }
1258 }
1259
1260 if (!$bFull)
1261 {
1262 CSearch::DeleteOld($NS['SESS_ID'], $MODULE_ID, $NS['SITE_ID']);
1263 }
1264 }
1265
1266 public static function GetIndex($MODULE_ID, $ITEM_ID)
1267 {
1268 $DB = CDatabase::GetModuleConnection('search');
1269 $rs = $DB->Query("select * from b_search_content where MODULE_ID = '" . $DB->ForSql($MODULE_ID) . "' and ITEM_ID = '" . $DB->ForSql($ITEM_ID) . "'");
1270 $arFields = $rs->Fetch();
1271 if (!$arFields)
1272 {
1273 return false;
1274 }
1275
1276 $arFields['SITE_ID'] = [];
1277 $rs = $DB->Query('select * from b_search_content_site where SEARCH_CONTENT_ID = ' . $DB->ForSql($arFields['ID']));
1278 while ($ar = $rs->Fetch())
1279 {
1280 $arFields['SITE_ID'][$ar['SITE_ID']] = $ar['URL'];
1281 }
1282
1283 $arFields['PERMISSIONS'] = [];
1284 $rs = $DB->Query('select * from b_search_content_right where SEARCH_CONTENT_ID = ' . $DB->ForSql($arFields['ID']));
1285 while ($ar = $rs->Fetch())
1286 {
1287 $arFields['PERMISSIONS'][] = $ar['GROUP_CODE'];
1288 }
1289
1290 $arFields['PARAMS'] = [];
1291 $rs = $DB->Query('select * from b_search_content_param where SEARCH_CONTENT_ID = ' . $DB->ForSql($arFields['ID']));
1292 while ($ar = $rs->Fetch())
1293 {
1294 $arFields['PARAMS'][$ar['PARAM_NAME']][] = $ar['PARAM_VALUE'];
1295 }
1296
1297 return $arFields;
1298 }
1299
1300 //index one item (forum message, news, etc.)
1301 //combination of ($MODULE_ID, $ITEM_ID) is used to determine the documents
1302 public static function Index($MODULE_ID, $ITEM_ID, $arFields, $bOverWrite = false, $SEARCH_SESS_ID = '')
1303 {
1304 $DB = CDatabase::GetModuleConnection('search');
1305
1306 $arFields['MODULE_ID'] = $MODULE_ID;
1307 $arFields['ITEM_ID'] = $ITEM_ID;
1308 foreach (GetModuleEvents('search', 'BeforeIndex', true) as $arEvent)
1309 {
1310 $arEventResult = ExecuteModuleEventEx($arEvent, [$arFields]);
1311 if (is_array($arEventResult))
1312 {
1313 $arFields = $arEventResult;
1314 }
1315 }
1316 unset($arFields['MODULE_ID']);
1317 unset($arFields['ITEM_ID']);
1318
1319 $bTitle = array_key_exists('TITLE', $arFields);
1320 if ($bTitle)
1321 {
1322 $arFields['TITLE'] = trim($arFields['TITLE']);
1323 }
1324 $bBody = array_key_exists('BODY', $arFields);
1325 if ($bBody)
1326 {
1327 $arFields['BODY'] = trim($arFields['BODY']);
1328 }
1329 $bTags = array_key_exists('TAGS', $arFields);
1330 if ($bTags)
1331 {
1332 $arFields['TAGS'] = trim($arFields['TAGS']);
1333 }
1334
1335 if (!array_key_exists('SITE_ID', $arFields) && array_key_exists('LID', $arFields))
1336 {
1337 $arFields['SITE_ID'] = $arFields['LID'];
1338 }
1339
1340 if (array_key_exists('SITE_ID', $arFields))
1341 {
1342 if (!is_array($arFields['SITE_ID']))
1343 {
1344 $arFields['SITE_ID'] = [$arFields['SITE_ID'] => ''];
1345 }
1346 else
1347 {
1348 $bNotAssoc = true;
1349 $i = 0;
1350 foreach ($arFields['SITE_ID'] as $k => $val)
1351 {
1352 if ('' . $k != '' . $i)
1353 {
1354 $bNotAssoc = false;
1355 break;
1356 }
1357 $i++;
1358 }
1359 if ($bNotAssoc)
1360 {
1361 $x = $arFields['SITE_ID'];
1362 $arFields['SITE_ID'] = [];
1363 foreach ($x as $val)
1364 {
1365 $arFields['SITE_ID'][$val] = '';
1366 }
1367 }
1368 }
1369
1370 if (count($arFields['SITE_ID']) <= 0)
1371 {
1372 return 0;
1373 }
1374
1375 reset($arFields['SITE_ID']);
1376 $arFields['LID'] = current($arFields['SITE_ID']);
1377
1378 $arSites = [];
1379 foreach ($arFields['SITE_ID'] as $site => $url)
1380 {
1381 $arSites[] = $DB->ForSQL($site, 2);
1382 }
1383
1384 $strSql = "
1385 SELECT CR.RANK
1386 FROM b_search_custom_rank CR
1387 WHERE CR.SITE_ID in ('" . implode("', '", $arSites) . "')
1388 AND CR.MODULE_ID='" . $DB->ForSQL($MODULE_ID) . "'
1389 " . (is_set($arFields, 'PARAM1') ? "AND (CR.PARAM1 IS NULL OR CR.PARAM1='' OR CR.PARAM1='" . $DB->ForSQL($arFields['PARAM1']) . "')" : '') . '
1390 ' . (is_set($arFields, 'PARAM2') ? "AND (CR.PARAM2 IS NULL OR CR.PARAM2='' OR CR.PARAM2='" . $DB->ForSQL($arFields['PARAM2']) . "')" : '') . '
1391 ' . ($ITEM_ID <> '' ? "AND (CR.ITEM_ID IS NULL OR CR.ITEM_ID='' OR CR.ITEM_ID='" . $DB->ForSQL($ITEM_ID) . "')" : '') . '
1392 ORDER BY
1393 PARAM1 DESC, PARAM2 DESC, ITEM_ID DESC
1394 ';
1395 $r = $DB->Query($strSql);
1396 $arFields['CUSTOM_RANK_SQL'] = $strSql;
1397 if ($arResult = $r->Fetch())
1398 {
1399 $arFields['CUSTOM_RANK'] = $arResult['RANK'];
1400 }
1401 }
1402
1403 $arGroups = [];
1404 if (is_set($arFields, 'PERMISSIONS'))
1405 {
1406 foreach ($arFields['PERMISSIONS'] as $group_id)
1407 {
1408 if (is_numeric($group_id))
1409 {
1410 $arGroups[$group_id] = 'G' . intval($group_id);
1411 }
1412 else
1413 {
1414 $arGroups[$group_id] = $group_id;
1415 }
1416 }
1417 }
1418
1419 $strSqlSelect = '';
1420 if ($bBody)
1421 {
1422 $strSqlSelect .= ',BODY';
1423 }
1424 if ($bTitle)
1425 {
1426 $strSqlSelect .= ',TITLE';
1427 }
1428 if ($bTags)
1429 {
1430 $strSqlSelect .= ',TAGS';
1431 }
1432
1433 $strSql =
1434 'SELECT ID, MODULE_ID, ITEM_ID, ' . $DB->DateToCharFunction('DATE_CHANGE') . ' as DATE_CHANGE
1435 ' . $strSqlSelect . "
1436 FROM b_search_content
1437 WHERE MODULE_ID = '" . $DB->ForSQL($MODULE_ID) . "'
1438 AND ITEM_ID = '" . $DB->ForSQL($ITEM_ID) . "' ";
1439
1440 $r = $DB->Query($strSql);
1441
1442 if ($arResult = $r->Fetch())
1443 {
1444 $ID = $arResult['ID'];
1445
1446 if ($bTitle && $bBody && $arFields['BODY'] == '' && $arFields['TITLE'] == '')
1447 {
1448 foreach (GetModuleEvents('search', 'OnBeforeIndexDelete', true) as $arEvent)
1449 {
1450 ExecuteModuleEventEx($arEvent, ['SEARCH_CONTENT_ID = ' . $ID]);
1451 }
1452
1455 $DB->Query('DELETE FROM b_search_content_param WHERE SEARCH_CONTENT_ID = ' . $ID);
1456 $DB->Query('DELETE FROM b_search_content_right WHERE SEARCH_CONTENT_ID = ' . $ID);
1457 $DB->Query('DELETE FROM b_search_content_site WHERE SEARCH_CONTENT_ID = ' . $ID);
1458 $DB->Query('DELETE FROM b_search_content_title WHERE SEARCH_CONTENT_ID = ' . $ID);
1459 $DB->Query('DELETE FROM b_search_tags WHERE SEARCH_CONTENT_ID = ' . $ID);
1460 CSearchFullText::getInstance()->deleteById($ID);
1461 $DB->Query('DELETE FROM b_search_content WHERE ID = ' . $ID);
1462
1463 return 0;
1464 }
1465
1466 if (is_set($arFields, 'PARAMS'))
1467 {
1469 }
1470
1471 if (count($arGroups) > 0)
1472 {
1474 }
1475
1476 if (is_set($arFields, 'SITE_ID'))
1477 {
1478 CSearch::UpdateSite($ID, $arFields['SITE_ID']);
1479 }
1480
1481 if (array_key_exists('LAST_MODIFIED', $arFields))
1482 {
1483 $arFields['~DATE_CHANGE'] = $arFields['DATE_CHANGE'] = $DATE_CHANGE = $arFields['LAST_MODIFIED'];
1484 }
1485 elseif (array_key_exists('DATE_CHANGE', $arFields))
1486 {
1487 $arFields['~DATE_CHANGE'] = $arFields['DATE_CHANGE'] = $DATE_CHANGE = $DB->FormatDate($arFields['DATE_CHANGE'], 'DD.MM.YYYY HH:MI:SS', CLang::GetDateFormat());
1488 }
1489 else
1490 {
1491 $DATE_CHANGE = '';
1492 }
1493
1494 if (!$bOverWrite && $DATE_CHANGE == $arResult['DATE_CHANGE'])
1495 {
1496 if ($SEARCH_SESS_ID <> '')
1497 {
1498 $DB->Query("UPDATE b_search_content SET UPD='" . $DB->ForSql($SEARCH_SESS_ID) . "' WHERE ID = " . $ID);
1499 }
1500
1501 return $ID;
1502 }
1503
1504 unset($arFields['MODULE_ID']);
1505 unset($arFields['ITEM_ID']);
1506
1507 if ($bBody || $bTitle || $bTags)
1508 {
1509 if (array_key_exists('INDEX_TITLE', $arFields) && $arFields['INDEX_TITLE'] === false)
1510 {
1511 $content = '';
1512 }
1513 else
1514 {
1515 if ($bTitle)
1516 {
1517 $content = $arFields['TITLE'] . "\r\n";
1518 }
1519 else
1520 {
1521 $content = $arResult['TITLE'] . "\r\n";
1522 }
1523 }
1524
1525 if ($bBody)
1526 {
1527 $content .= $arFields['BODY'] . "\r\n";
1528 }
1529 else
1530 {
1531 $content .= $arResult['BODY'] . "\r\n";
1532 }
1533
1534 if ($bTags)
1535 {
1536 $content .= $arFields['TAGS'];
1537 }
1538 else
1539 {
1540 $content .= $arResult['TAGS'] ?? '';
1541 }
1542
1543 $content = preg_replace_callback('/&#(\\d+);/', ['CSearch', 'chr'], $content);
1544 $arFields['SEARCHABLE_CONTENT'] = CSearch::KillEntities(mb_strtoupper($content));
1545 }
1546
1547 if ($SEARCH_SESS_ID <> '')
1548 {
1549 $arFields['UPD'] = $SEARCH_SESS_ID;
1550 }
1551
1552 if (array_key_exists('TITLE', $arFields))
1553 {
1554 if (
1555 !array_key_exists('INDEX_TITLE', $arFields)
1556 || $arFields['INDEX_TITLE'] !== false
1557 )
1558 {
1559 CSearch::IndexTitle($arFields['SITE_ID'], $ID, $arFields['TITLE']);
1560 }
1561 }
1562
1563 if ($bTags && ($arResult['TAGS'] != $arFields['TAGS']))
1564 {
1566 $DB->Query('DELETE FROM b_search_tags WHERE SEARCH_CONTENT_ID = ' . $ID);
1567 CSearch::TagsIndex($arFields['SITE_ID'], $ID, $arFields['TAGS']);
1568 }
1569
1570 foreach (GetModuleEvents('search', 'OnBeforeIndexUpdate', true) as $arEvent)
1571 {
1572 ExecuteModuleEventEx($arEvent, [$ID, $arFields]);
1573 }
1574
1575 // Avoid double update of related tables
1576 unset($arFields['PARAMS']);
1577 unset($arFields['PERMISSIONS']);
1578 unset($arFields['SITE_ID']);
1579
1580 // This fields are needed for full text update
1581 $arFields['MODULE_ID'] = $arResult['MODULE_ID'];
1582 $arFields['ITEM_ID'] = $arResult['ITEM_ID'];
1583
1585 }
1586 else
1587 {
1588 if ($bTitle && $bBody && $arFields['BODY'] == '' && $arFields['TITLE'] == '')
1589 {
1590 return 0;
1591 }
1592
1593 $arFields['MODULE_ID'] = $MODULE_ID;
1594 $arFields['ITEM_ID'] = $ITEM_ID;
1595
1596 if (array_key_exists('INDEX_TITLE', $arFields) && $arFields['INDEX_TITLE'] === false)
1597 {
1598 $content = $arFields['BODY'] . "\r\n" . ($arFields['TAGS'] ?? '');
1599 }
1600 else
1601 {
1602 $content = $arFields['TITLE'] . "\r\n" . $arFields['BODY'] . "\r\n" . ($arFields['TAGS'] ?? '');
1603 }
1604
1605 $content = preg_replace_callback('/&#(\\d+);/', ['CSearch', 'chr'], $content);
1606 $arFields['SEARCHABLE_CONTENT'] = CSearch::KillEntities(mb_strtoupper($content));
1607
1608 if ($SEARCH_SESS_ID != '')
1609 {
1610 $arFields['UPD'] = $SEARCH_SESS_ID;
1611 }
1612
1614 //We failed to add this record to the search index
1615 if ($ID === false)
1616 {
1617 //Check if item was added
1618 $strSql = "SELECT ID FROM b_search_content WHERE MODULE_ID = '" . $DB->ForSQL($MODULE_ID) . "' AND ITEM_ID = '" . $DB->ForSQL($ITEM_ID) . "' ";
1619 $rs = $DB->Query($strSql);
1620 $ar = $rs->Fetch();
1621 if ($ar)
1622 {
1623 return $ar['ID'];
1624 }
1625 else
1626 {
1627 return $ID;
1628 }
1629 }
1631
1632 foreach (GetModuleEvents('search', 'OnAfterIndexAdd', true) as $arEvent)
1633 {
1634 ExecuteModuleEventEx($arEvent, [$ID, $arFields]);
1635 }
1636
1637 if (is_set($arFields, 'PARAMS'))
1638 {
1640 }
1641
1643
1644 CSearch::UpdateSite($ID, $arFields['SITE_ID']);
1645
1646 if (
1647 !array_key_exists('INDEX_TITLE', $arFields)
1648 || $arFields['INDEX_TITLE'] !== false
1649 )
1650 {
1651 CSearch::IndexTitle($arFields['SITE_ID'], $ID, $arFields['TITLE']);
1652 }
1653
1654 if ($bTags)
1655 {
1656 CSearch::TagsIndex($arFields['SITE_ID'], $ID, $arFields['TAGS']);
1657 }
1658 }
1659
1660 return $ID;
1661 }
1662
1663 public static function KillEntities($str)
1664 {
1665 static $arAllEntities = [
1666 'UMLYA' => [
1667 '&IQUEST;', '&AGRAVE;', '&AACUTE;', '&ACIRC;', '&ATILDE;',
1668 '&AUML;', '&ARING;', '&AELIG;', '&CCEDIL;', '&EGRAVE;',
1669 '&EACUTE;', '&ECIRC;', '&EUML;', '&IGRAVE;', '&IACUTE;',
1670 '&ICIRC;', '&IUML;', '&ETH;', '&NTILDE;', '&OGRAVE;',
1671 '&OACUTE;', '&OCIRC;', '&OTILDE;', '&OUML;', '&TIMES;',
1672 '&OSLASH;', '&UGRAVE;', '&UACUTE;', '&UCIRC;', '&UUML;',
1673 '&YACUTE;', '&THORN;', '&SZLIG;', '&AGRAVE;', '&AACUTE;',
1674 '&ACIRC;', '&ATILDE;', '&AUML;', '&ARING;', '&AELIG;',
1675 '&CCEDIL;', '&EGRAVE;', '&EACUTE;', '&ECIRC;', '&EUML;',
1676 '&IGRAVE;', '&IACUTE;', '&ICIRC;', '&IUML;', '&ETH;',
1677 '&NTILDE;', '&OGRAVE;', '&OACUTE;', '&OCIRC;', '&OTILDE;',
1678 '&OUML;', '&DIVIDE;', '&OSLASH;', '&UGRAVE;', '&UACUTE;',
1679 '&UCIRC;', '&UUML;', '&YACUTE;', '&THORN;', '&YUML;',
1680 '&OELIG;', '&OELIG;', '&SCARON;', '&SCARON;', '&YUML;',
1681 ],
1682 'GREEK' => [
1683 '&ALPHA;', '&BETA;', '&GAMMA;', '&DELTA;', '&EPSILON;',
1684 '&ZETA;', '&ETA;', '&THETA;', '&IOTA;', '&KAPPA;',
1685 '&LAMBDA;', '&MU;', '&NU;', '&XI;', '&OMICRON;',
1686 '&PI;', '&RHO;', '&SIGMA;', '&TAU;', '&UPSILON;',
1687 '&PHI;', '&CHI;', '&PSI;', '&OMEGA;', '&ALPHA;',
1688 '&BETA;', '&GAMMA;', '&DELTA;', '&EPSILON;', '&ZETA;',
1689 '&ETA;', '&THETA;', '&IOTA;', '&KAPPA;', '&LAMBDA;',
1690 '&MU;', '&NU;', '&XI;', '&OMICRON;', '&PI;',
1691 '&RHO;', '&SIGMAF;', '&SIGMA;', '&TAU;', '&UPSILON;',
1692 '&PHI;', '&CHI;', '&PSI;', '&OMEGA;', '&THETASYM;',
1693 '&UPSIH;', '&PIV;',
1694 ],
1695 'OTHER' => [
1696 '&IEXCL;', '&CENT;', '&POUND;', '&CURREN;', '&YEN;',
1697 '&BRVBAR;', '&SECT;', '&UML;', '&COPY;', '&ORDF;',
1698 '&LAQUO;', '&NOT;', '&REG;', '&MACR;', '&DEG;',
1699 '&PLUSMN;', '&SUP2;', '&SUP3;', '&ACUTE;', '&MICRO;',
1700 '&PARA;', '&MIDDOT;', '&CEDIL;', '&SUP1;', '&ORDM;',
1701 '&RAQUO;', '&FRAC14;', '&FRAC12;', '&FRAC34;', '&CIRC;',
1702 '&TILDE;', '&ENSP;', '&EMSP;', '&THINSP;', '&ZWNJ;',
1703 '&ZWJ;', '&LRM;', '&RLM;', '&NDASH;', '&MDASH;',
1704 '&LSQUO;', '&RSQUO;', '&SBQUO;', '&LDQUO;', '&RDQUO;',
1705 '&BDQUO;', '&DAGGER;', '&DAGGER;', '&PERMIL;', '&LSAQUO;',
1706 '&RSAQUO;', '&EURO;', '&BULL;', '&HELLIP;', '&PRIME;',
1707 '&PRIME;', '&OLINE;', '&FRASL;', '&WEIERP;', '&IMAGE;',
1708 '&REAL;', '&TRADE;', '&ALEFSYM;', '&LARR;', '&UARR;',
1709 '&RARR;', '&DARR;', '&HARR;', '&CRARR;', '&LARR;',
1710 '&UARR;', '&RARR;', '&DARR;', '&HARR;', '&FORALL;',
1711 '&PART;', '&EXIST;', '&EMPTY;', '&NABLA;', '&ISIN;',
1712 '&NOTIN;', '&NI;', '&PROD;', '&SUM;', '&MINUS;',
1713 '&LOWAST;', '&RADIC;', '&PROP;', '&INFIN;', '&ANG;',
1714 '&AND;', '&OR;', '&CAP;', '&CUP;', '&INT;',
1715 '&THERE4;', '&SIM;', '&CONG;', '&ASYMP;', '&NE;',
1716 '&EQUIV;', '&LE;', '&GE;', '&SUB;', '&SUP;',
1717 '&NSUB;', '&SUBE;', '&SUPE;', '&OPLUS;', '&OTIMES;',
1718 '&PERP;', '&SDOT;', '&LCEIL;', '&RCEIL;', '&LFLOOR;',
1719 '&RFLOOR;', '&LANG;', '&RANG;', '&LOZ;', '&SPADES;',
1720 '&CLUBS;', '&HEARTS;', '&DIAMS;',
1721 ],
1722 ];
1723 static $pregEntities = false;
1724 if (!$pregEntities)
1725 {
1726 $pregEntities = [];
1727 foreach ($arAllEntities as $key => $entities)
1728 {
1729 $pregEntities[$key] = implode('|', $entities);
1730 }
1731 }
1732 return preg_replace('/(' . implode('|', $pregEntities) . ')/i', '', $str);
1733 }
1734
1735 public static function ReindexFile($path, $SEARCH_SESS_ID = '')
1736 {
1737 global $APPLICATION;
1739 $DB = CDatabase::GetModuleConnection('search');
1740
1741 if (!is_array($path))
1742 {
1743 return 0;
1744 }
1745
1746 $file_doc_root = CSite::GetSiteDocRoot($path[0]);
1747 $file_rel_path = $path[1];
1748 $file_abs_path = preg_replace('#[\\\\\\/]+#', '/', $file_doc_root . '/' . $file_rel_path);
1749 $f = $io->GetFile($file_abs_path);
1750
1751 if (!$f->IsExists() || !$f->IsReadable())
1752 {
1753 return 0;
1754 }
1755
1756 if (!CSearch::CheckPath($file_rel_path))
1757 {
1758 return 0;
1759 }
1760
1761 $max_file_size = COption::GetOptionInt('search', 'max_file_size', 0);
1762 if (
1763 $max_file_size > 0
1764 && $f->GetFileSize() > ($max_file_size * 1024)
1765 )
1766 {
1767 return 0;
1768 }
1769
1770 $file_site = '';
1771 $rsSites = CSite::GetList('lendir', 'desc');
1772 while ($arSite = $rsSites->Fetch())
1773 {
1774 $site_path = preg_replace('#[\\\\\\/]+#', '/', $arSite['ABS_DOC_ROOT'] . '/' . $arSite['DIR'] . '/');
1775 if (mb_strpos($file_abs_path, $site_path) === 0)
1776 {
1777 $file_site = $arSite['ID'];
1778 break;
1779 }
1780 }
1781
1782 if ($file_site == '')
1783 {
1784 return 0;
1785 }
1786
1787 $item_id = $file_site . '|' . $file_rel_path;
1788 if (mb_strlen($item_id) > 255)
1789 {
1790 return 0;
1791 }
1792
1793 if ($SEARCH_SESS_ID <> '')
1794 {
1795 $DATE_CHANGE = $DB->CharToDateFunction(
1796 FormatDate(
1797 $DB->DateFormatToPHP(CLang::GetDateFormat('FULL')), $f->GetModificationTime() + CTimeZone::GetOffset()
1798 )
1799 );
1800 $strSql = "
1801 SELECT ID
1802 FROM b_search_content
1803 WHERE MODULE_ID = 'main'
1804 AND ITEM_ID = '" . $DB->ForSQL($item_id) . "'
1805 AND DATE_CHANGE = " . $DATE_CHANGE . '
1806 ';
1807
1808 $r = $DB->Query($strSql);
1809 if ($arR = $r->Fetch())
1810 {
1811 $strSql = "UPDATE b_search_content SET UPD='" . $DB->ForSQL($SEARCH_SESS_ID) . "' WHERE ID = " . $arR['ID'];
1812 $DB->Query($strSql);
1813 return $arR['ID'];
1814 }
1815 }
1816
1817 $arrFile = false;
1818 foreach (GetModuleEvents('search', 'OnSearchGetFileContent', true) as $arEvent)
1819 {
1820 if ($arrFile = ExecuteModuleEventEx($arEvent, [$file_abs_path, $SEARCH_SESS_ID]))
1821 {
1822 break;
1823 }
1824 }
1825 if (!is_array($arrFile))
1826 {
1827 $sFile = $APPLICATION->GetFileContent($file_abs_path);
1828 $sHeadEndPos = mb_strpos($sFile, '</head>');
1829 if ($sHeadEndPos === false)
1830 {
1831 $sHeadEndPos = mb_strpos($sFile, '</HEAD>');
1832 }
1833 if ($sHeadEndPos !== false)
1834 {
1835 //html header detected try to get document charset
1836 $arMetaMatch = [];
1837 if (preg_match("/<(meta)\\s+([^>]*)(content)\\s*=\\s*(['\"]).*?(charset)\\s*=\\s*(.*?)(\\4)/is", mb_substr($sFile, 0, $sHeadEndPos), $arMetaMatch))
1838 {
1839 $doc_charset = $arMetaMatch[6];
1840 if (strtoupper($doc_charset) != 'UTF-8')
1841 {
1842 $sFile = \Bitrix\Main\Text\Encoding::convertEncoding($sFile, $doc_charset, 'UTF-8');
1843 }
1844 }
1845 }
1846 $arrFile = ParseFileContent($sFile);
1847 }
1848
1849 $title = CSearch::KillTags(trim($arrFile['TITLE']));
1850 $title = mb_convert_encoding($title, 'UTF-8', 'UTF-8'); // Remove invalid utf
1851 if ($title == '')
1852 {
1853 return 0;
1854 }
1855
1856 //strip out all the tags
1857 $filesrc = CSearch::KillTags($arrFile['CONTENT']);
1858 $filesrc = mb_convert_encoding($filesrc, 'UTF-8', 'UTF-8'); // Remove invalid utf
1860 $arGPerm = [];
1861 foreach ($arGroups as $group_id)
1862 {
1863 $p = $APPLICATION->GetFileAccessPermission([$file_site, $file_rel_path], [$group_id]);
1864 if ($p >= 'R')
1865 {
1866 $arGPerm[] = $group_id;
1867 if ($group_id == 2)
1868 {
1869 break;
1870 }
1871 }
1872 }
1873
1874 $tags = COption::GetOptionString('search', 'page_tag_property');
1875
1876 //save to database
1877 $ID = CSearch::Index('main', $item_id,
1878 [
1879 'SITE_ID' => $file_site,
1880 'DATE_CHANGE' => date('d.m.Y H:i:s', $f->GetModificationTime() + 1),
1881 'PARAM1' => '',
1882 'PARAM2' => '',
1883 'URL' => $file_rel_path,
1884 'PERMISSIONS' => $arGPerm,
1885 'TITLE' => $title,
1886 'BODY' => $filesrc,
1887 'TAGS' => array_key_exists($tags, $arrFile['PROPERTIES']) ? $arrFile['PROPERTIES'][$tags] : '',
1888 ], false, $SEARCH_SESS_ID
1889 );
1890
1891 return $ID;
1892 }
1893
1894 public static function RecurseIndex($path = [], $max_execution_time = 0, &$NS)
1895 {
1896 if (!is_array($path))
1897 {
1898 return 0;
1899 }
1900
1901 $site = $path[0];
1902 $path = $path[1];
1903
1904 $DOC_ROOT = CSite::GetSiteDocRoot($site);
1906
1908
1909 if (!$io->DirectoryExists($abs_path))
1910 {
1911 return 0;
1912 }
1913
1914 $f = $io->GetFile($abs_path);
1915 if (!$f->IsReadable())
1916 {
1917 return 0;
1918 }
1919
1920 $d = $io->GetDirectory($abs_path);
1921 foreach ($d->GetChildren() as $dir_entry)
1922 {
1923 $path_file = $path . '/' . $dir_entry->GetName();
1924
1925 if ($dir_entry->IsDirectory())
1926 {
1927 if ($path_file == '/bitrix')
1928 {
1929 continue;
1930 }
1931
1932 //this is not first step and we had stopped here, so go on to reindex
1933 if (
1935 || !isset($NS['MODULE'])
1936 || $NS['MODULE'] == ''
1937 || (
1938 $NS['MODULE'] == 'main'
1939 && mb_substr($NS['ID'] . '/', 0, mb_strlen($site . '|' . $path_file . '/')) == $site . '|' . $path_file . '/'
1940 )
1941 )
1942 {
1943 if (CSearch::CheckPath($path_file . '/') !== false)
1944 {
1945 if (CSearch::RecurseIndex([$site, $path_file], $max_execution_time, $NS) === false)
1946 {
1947 return false;
1948 }
1949 }
1950 }
1951 else //all done
1952 {
1953 continue;
1954 }
1955 }
1956 else
1957 {
1958 //not the first step and we found last file from previous one
1959 if (
1961 && isset($NS['MODULE'])
1962 && $NS['MODULE'] <> ''
1963 && $NS['MODULE'] == 'main'
1964 && $NS['ID'] == $site . '|' . $path_file
1965 )
1966 {
1967 $NS['MODULE'] = '';
1968 }
1969 elseif (!isset($NS['MODULE']) || $NS['MODULE'] == '')
1970 {
1971 $ID = CSearch::ReindexFile([$site, $path_file], $NS['SESS_ID'] ?? '');
1972 if (intval($ID) > 0)
1973 {
1974 $NS['CNT'] = intval($NS['CNT']) + 1;
1975 }
1976
1977 if (
1979 && (microtime(true) - START_EXEC_TIME > $max_execution_time)
1980 )
1981 {
1982 $NS['MODULE'] = 'main';
1983 $NS['ID'] = $site . '|' . $path_file;
1984 return false;
1985 }
1986 }
1987 }
1988 }
1989
1990 return true;
1991 }
1992
1993 public static function RemovePHP($str)
1994 {
1995 $res = '';
1996 $a = preg_split('/(<' . '\\?|\\?' . '>|\\/\\' . '*|\\' . '*' . '\\/|\\/\\/|\'|"|\\n)/', $str, -1, PREG_SPLIT_DELIM_CAPTURE);
1997 $c = count($a);
1998 $i = 0;
1999 $bPHP = false;
2000 while ($i < $c)
2001 {
2002 if ($a[$i] == '\'' && $bPHP)
2003 {
2004 while ((++$i) < $c)
2005 {
2006 if ($a[$i] === '\'')
2007 {
2008 $m = [];
2009 if (preg_match('/(\\\\+)$/', $a[$i - 1], $m))
2010 {
2011 if ((mb_strlen($m[1]) % 2) == 0)
2012 {
2013 //non even slashes
2014 break;
2015 }
2016 }
2017 else
2018 {
2019 break;
2020 }
2021 }
2022 }
2023 }
2024 elseif ($a[$i] == '"' && $bPHP)
2025 {
2026 while ((++$i) < $c)
2027 {
2028 if ($a[$i] === '"')
2029 {
2030 if (preg_match('/(\\\\+)$/', $a[$i - 1], $m))
2031 {
2032 if ((mb_strlen($m[1]) % 2) == 0)
2033 {
2034 //non even slashes
2035 break;
2036 }
2037 }
2038 else
2039 {
2040 break;
2041 }
2042 }
2043 }
2044 }
2045 elseif ($a[$i] == '//' && $bPHP)
2046 {
2047 //single line comment
2048 while ((++$i) < $c)
2049 {
2050 if ($a[$i] === "\n" || $a[$i] === '?>')
2051 {
2052 break;
2053 }
2054 }
2055 continue;
2056 }
2057 elseif ($a[$i] === '/*' && $bPHP)
2058 {
2059 while ((++$i) < $c)
2060 {
2061 if ($a[$i] === '*/')
2062 {
2063 break;
2064 }
2065 }
2066 continue;
2067 }
2068 elseif ($a[$i] === '<?' && !$bPHP) //start of php
2069 {
2070 $bPHP = true;
2071 $i++;
2072 continue;
2073 }
2074 elseif ($a[$i] === '?>' && $bPHP) //end of php
2075 {
2076 $bPHP = false;
2077 $i++;
2078 continue;
2079 }
2080
2081 if (!$bPHP)
2082 {
2083 $res .= $a[$i];
2084 }
2085
2086 $i++;
2087 }
2088
2089 return $res;
2090 }
2091
2092 public static function KillTags($str)
2093 {
2095
2096 static $search = [
2097 "'<!--.*?-->'si", // Strip out javascript
2098 "'<script[^>]*?>.*?</script>'si", // Strip out javascript
2099 "'<style[^>]*?>.*?</style>'si", // Strip out styles
2100 "'<select[^>]*?>.*?</select>'si", // Strip out <select></select>
2101 "'<head[^>]*?>.*?</head>'si", // Strip out <head></head>
2102 "'<tr[^>]*?>'",
2103 "'<[^>]*?>'",
2104 "'([\\r\\n])[\\s]+'", // Strip out white space
2105 "'&(quot|#34);'i", // Replace html entities
2106 "'&(amp|#38);'i",
2107 "'&(lt|#60);'i",
2108 "'&(gt|#62);'i",
2109 "'&(nbsp|#160);'i",
2110 "'[ ]+ '",
2111 ];
2112
2113 static $replace = [
2114 '',
2115 '',
2116 '',
2117 '',
2118 '',
2119 "\r\n",
2120 "\r\n",
2121 "\\1",
2122 '"',
2123 '&',
2124 '<',
2125 '>',
2126 ' ',
2127 ' ',
2128 ];
2129
2130 $str = preg_replace($search, $replace, $str);
2131
2132 return $str;
2133 }
2134
2135 public static function OnChangeFile($path, $site)
2136 {
2138 }
2139
2140 public static function OnGroupDelete($ID)
2141 {
2142 $DB = CDatabase::GetModuleConnection('search');
2143 $DB->Query("
2144 DELETE FROM b_search_content_right
2145 WHERE GROUP_CODE = 'G" . intval($ID) . "'
2146 ");
2147 }
2148
2149 public static function __PrepareFilter($arFilter, &$bIncSites, $strSearchContentAlias = 'sc.')
2150 {
2151 $DB = CDatabase::GetModuleConnection('search');
2152 $arSql = [];
2153 $arNewFilter = [];
2154 static $arFilterEvents = false;
2155
2156 if (!is_array($arFilter))
2157 {
2158 $arFilter = [];
2159 }
2160
2161 foreach ($arFilter as $field => $val)
2162 {
2163 $field = mb_strtoupper($field);
2164 if (
2165 is_array($val)
2166 && count($val) == 1
2167 && $field !== 'URL'
2168 && $field !== 'PARAMS'
2169 )
2170 {
2171 $val = $val[0];
2172 }
2173 switch ($field)
2174 {
2175 case '=MODULE_ID':
2176 if ($val !== false && $val !== 'no')
2177 {
2178 $arNewFilter[$field] = $val;
2179 }
2180 break;
2181 case 'MODULE_ID':
2182 if ($val !== false && $val !== 'no')
2183 {
2184 $arNewFilter['=' . $field] = $val;
2185 }
2186 break;
2187 case 'ITEM_ID':
2188 case 'PARAM1':
2189 case 'PARAM2':
2190 if ($val !== false)
2191 {
2192 $arNewFilter['=' . $field] = $val;
2193 }
2194 break;
2195 case 'CHECK_DATES':
2196 if ($val == 'Y')
2197 {
2198 $time = ConvertTimeStamp(time() + CTimeZone::GetOffset(), 'FULL');
2199 $arNewFilter[] = [
2200 'LOGIC' => 'AND',
2201 [
2202 'LOGIC' => 'OR',
2203 '=DATE_FROM' => false,
2204 '<=DATE_FROM' => $time,
2205 ],
2206 [
2207 'LOGIC' => 'OR',
2208 '=DATE_TO' => false,
2209 '>=DATE_TO' => $time,
2210 ],
2211 ];
2212 }
2213 break;
2214 case 'DATE_CHANGE':
2215 if ($val <> '')
2216 {
2217 $arNewFilter['>=' . $field] = $val;
2218 }
2219 break;
2220 case 'SITE_ID':
2221 if ($val !== false)
2222 {
2223 $arNewFilter['=' . $field] = $val;
2224 }
2225 break;
2226 default:
2227 if (!is_array($arFilterEvents))
2228 {
2229 $arFilterEvents = [];
2230 foreach (GetModuleEvents('search', 'OnSearchPrepareFilter', true) as $arEvent)
2231 {
2232 $arFilterEvents[] = $arEvent;
2233 }
2234 }
2235 //Try to get someone to make the filter sql
2236 $sql = '';
2237 foreach ($arFilterEvents as $arEvent)
2238 {
2239 $sql = ExecuteModuleEventEx($arEvent, [$strSearchContentAlias, $field, $val]);
2240 if ($sql <> '')
2241 {
2242 $arSql[] = '(' . $sql . ')';
2243 break;
2244 }
2245 }
2246
2247 if (!$sql)
2248 {
2249 $arNewFilter[$field] = $val;
2250 }
2251 }
2252 }
2253
2254 $strSearchContentAlias = rtrim($strSearchContentAlias, '.');
2255 $obWhereHelp = new CSearchSQLHelper($strSearchContentAlias);
2256 $obQueryWhere = new CSQLWhere;
2257 $obQueryWhere->SetFields([
2258 'MODULE_ID' => [
2259 'TABLE_ALIAS' => $strSearchContentAlias,
2260 'FIELD_NAME' => $strSearchContentAlias . '.MODULE_ID',
2261 'MULTIPLE' => 'N',
2262 'FIELD_TYPE' => 'string',
2263 'JOIN' => false,
2264 ],
2265 'ITEM_ID' => [
2266 'TABLE_ALIAS' => $strSearchContentAlias,
2267 'FIELD_NAME' => $strSearchContentAlias . '.ITEM_ID',
2268 'MULTIPLE' => 'N',
2269 'FIELD_TYPE' => 'string',
2270 'JOIN' => false,
2271 ],
2272 'PARAM1' => [
2273 'TABLE_ALIAS' => $strSearchContentAlias,
2274 'FIELD_NAME' => $strSearchContentAlias . '.PARAM1',
2275 'MULTIPLE' => 'N',
2276 'FIELD_TYPE' => 'string',
2277 'JOIN' => false,
2278 ],
2279 'PARAM2' => [
2280 'TABLE_ALIAS' => $strSearchContentAlias,
2281 'FIELD_NAME' => $strSearchContentAlias . '.PARAM2',
2282 'MULTIPLE' => 'N',
2283 'FIELD_TYPE' => 'string',
2284 'JOIN' => false,
2285 ],
2286 'DATE_FROM' => [
2287 'TABLE_ALIAS' => $strSearchContentAlias,
2288 'FIELD_NAME' => $strSearchContentAlias . '.DATE_FROM',
2289 'MULTIPLE' => 'N',
2290 'FIELD_TYPE' => 'datetime',
2291 'JOIN' => false,
2292 ],
2293 'DATE_TO' => [
2294 'TABLE_ALIAS' => $strSearchContentAlias,
2295 'FIELD_NAME' => $strSearchContentAlias . '.DATE_TO',
2296 'MULTIPLE' => 'N',
2297 'FIELD_TYPE' => 'datetime',
2298 'JOIN' => false,
2299 ],
2300 'DATE_CHANGE' => [
2301 'TABLE_ALIAS' => $strSearchContentAlias,
2302 'FIELD_NAME' => $strSearchContentAlias . '.DATE_CHANGE',
2303 'MULTIPLE' => 'N',
2304 'FIELD_TYPE' => 'datetime',
2305 'JOIN' => false,
2306 ],
2307 'SITE_ID' => [
2308 'TABLE_ALIAS' => 'scsite',
2309 'FIELD_NAME' => 'scsite.SITE_ID',
2310 'MULTIPLE' => 'N',
2311 'FIELD_TYPE' => 'string',
2312 'JOIN' => true,
2313 ],
2314 'SITE_URL' => [
2315 'TABLE_ALIAS' => 'scsite',
2316 'FIELD_NAME' => 'scsite.URL',
2317 'MULTIPLE' => 'N',
2318 'FIELD_TYPE' => 'string',
2319 'JOIN' => true,
2320 ],
2321 'URL' => [
2322 'TABLE_ALIAS' => $strSearchContentAlias,
2323 'FIELD_NAME' => $strSearchContentAlias . '.URL',
2324 'MULTIPLE' => 'N',
2325 'FIELD_TYPE' => 'callback',
2326 'CALLBACK' => [$obWhereHelp, '_CallbackURL'],
2327 'JOIN' => true,
2328 ],
2329 'PARAMS' => [
2330 'TABLE_ALIAS' => $strSearchContentAlias,
2331 'FIELD_NAME' => $strSearchContentAlias . '.ID',
2332 'MULTIPLE' => 'N',
2333 'FIELD_TYPE' => 'callback',
2334 'CALLBACK' => [$obWhereHelp, '_CallbackPARAMS'],
2335 'JOIN' => false,
2336 ],
2337 ]);
2338
2339 $strWhere = $obQueryWhere->GetQuery($arNewFilter);
2340
2341 if (count($arSql) > 0)
2342 {
2343 if ($strWhere)
2344 {
2345 $strWhere .= "\nAND (" . implode(' AND ', $arSql) . ')';
2346 }
2347 else
2348 {
2349 $strWhere = implode("\nAND ", $arSql);
2350 }
2351 }
2352
2353 $bIncSites = $bIncSites || $obQueryWhere->GetJoins() <> '';
2354 return $strWhere;
2355 }
2356
2357 function __PrepareSort($aSort = [], $strSearchContentAlias = 'sc.', $bTagsCloud = false)
2358 {
2359 $DB = CDatabase::GetModuleConnection('search');
2360 $helper = $DB->getConnection()->getSqlHelper();
2361 $arOrder = [];
2362
2363 if (!is_array($aSort))
2364 {
2365 $aSort = [$aSort => 'ASC'];
2366 }
2367
2368 if ($bTagsCloud)
2369 {
2370 foreach ($aSort as $key => $ord)
2371 {
2372 $ord = mb_strtoupper($ord) <> 'ASC' ? 'DESC' : 'ASC';
2373 $key = mb_strtoupper($key);
2374 switch ($key)
2375 {
2376 case 'DATE_CHANGE':
2377 $arOrder[] = 'DC_TMP ' . $ord;
2378 break;
2379 case 'NAME':
2380 case 'CNT':
2381 $arOrder[] = $key . ' ' . $ord;
2382 break;
2383 }
2384 }
2385 if (count($arOrder) == 0)
2386 {
2387 $arOrder[] = 'NAME ASC';
2388 }
2389 }
2390 else
2391 {
2392 $this->flagsUseRatingSort = 0;
2393 foreach ($aSort as $key => $ord)
2394 {
2395 $ord = mb_strtoupper($ord) <> 'ASC' ? 'DESC' : 'ASC';
2396 $key = mb_strtoupper($key);
2397 switch ($key)
2398 {
2399 case 'DATE_CHANGE':
2400 if (!($this->flagsUseRatingSort & 0x01))
2401 {
2402 $this->flagsUseRatingSort = 0x02;
2403 }
2404 $arOrder[] = $strSearchContentAlias . $key . ' ' . $ord;
2405 break;
2406 case 'RANK':
2407 if (!($this->flagsUseRatingSort & 0x02))
2408 {
2409 $this->flagsUseRatingSort = 0x01;
2410 }
2411 $arOrder[] = $helper->quote('RANK') . ' ' . $ord;
2412 break;
2413 case 'TITLE_RANK':
2414 case 'CUSTOM_RANK':
2415 $arOrder[] = $key . ' ' . $ord;
2416 break;
2417 case 'ID':
2418 case 'MODULE_ID':
2419 case 'ITEM_ID':
2420 case 'TITLE':
2421 case 'PARAM1':
2422 case 'PARAM2':
2423 case 'UPD':
2424 case 'DATE_FROM':
2425 case 'DATE_TO':
2426 case 'URL':
2427 if (!($this->flagsUseRatingSort & 0x01))
2428 {
2429 $this->flagsUseRatingSort = 0x02;
2430 }
2431 $arOrder[] = $key . ' ' . $ord;
2432 break;
2433 }
2434 }
2435
2436 if (count($arOrder) == 0)
2437 {
2438 $arOrder[] = 'CUSTOM_RANK DESC';
2439 $arOrder[] = $helper->quote('RANK') . ' DESC';
2440 $arOrder[] = $strSearchContentAlias . 'DATE_CHANGE DESC';
2441 $this->flagsUseRatingSort = 0x01;
2442 }
2443 }
2444
2445 return ' ORDER BY ' . implode(', ', $arOrder);
2446 }
2447
2448 public static function Add($arFields)
2449 {
2450 $DB = CDatabase::GetModuleConnection('search');
2451 $helper = $DB->getConnection()->getSqlHelper();
2452
2453 if (array_key_exists('~DATE_CHANGE', $arFields))
2454 {
2455 $arFields['DATE_CHANGE'] = $arFields['~DATE_CHANGE'];
2456 unset($arFields['~DATE_CHANGE']);
2457 }
2458 elseif (array_key_exists('LAST_MODIFIED', $arFields))
2459 {
2460 $arFields['DATE_CHANGE'] = $arFields['LAST_MODIFIED'];
2461 unset($arFields['LAST_MODIFIED']);
2462 }
2463 elseif (array_key_exists('DATE_CHANGE', $arFields))
2464 {
2465 $arFields['DATE_CHANGE'] = $DB->FormatDate($arFields['DATE_CHANGE'], 'DD.MM.YYYY HH:MI:SS', CLang::GetDateFormat());
2466 }
2467
2468 $tableFields = [];
2469 foreach ($DB->GetTableFields('b_search_content') as $field)
2470 {
2471 $tableFields[$field['NAME']] = $field['TYPE'];
2472 }
2473
2474 foreach ($arFields as $fieldName => $_)
2475 {
2476 $search = str_ends_with($fieldName, '~') ? mb_substr($fieldName, 0, -1) : $fieldName;
2477 if (!isset($tableFields[$search]))
2478 {
2479 unset($arFields[$fieldName]);
2480 }
2481 }
2482
2483 if (isset($arFields['DATE_CHANGE']))
2484 {
2485 $arFields['DATE_CHANGE'] = new \Bitrix\Main\Type\DateTime($arFields['DATE_CHANGE']);
2486 }
2487
2488 if (isset($arFields['DATE_FROM']))
2489 {
2490 if ($arFields['DATE_FROM'])
2491 {
2492 $arFields['DATE_FROM'] = new \Bitrix\Main\Type\DateTime($arFields['DATE_FROM']);
2493 }
2494 }
2495
2496 if (isset($arFields['DATE_TO']))
2497 {
2498 if ($arFields['DATE_TO'])
2499 {
2500 $arFields['DATE_TO'] = new \Bitrix\Main\Type\DateTime($arFields['DATE_TO']);
2501 }
2502 }
2503
2504 $update = $arFields;
2505 unset($update['MODULE_ID']);
2506 unset($update['ITEM_ID']);
2507
2508 $merge = $helper->prepareMerge('b_search_content', ['MODULE_ID', 'ITEM_ID'], $arFields, $update);
2509 if ($merge && $merge[0])
2510 {
2511 $DB->Query($merge[0]);
2512 }
2513
2514 $ar = $DB->Query('SELECT ID FROM b_search_content WHERE MODULE_ID=\'' . $DB->ForSql($arFields['MODULE_ID']) . '\' AND ITEM_ID=\'' . $DB->ForSql($arFields['ITEM_ID']) . '\'')->Fetch();
2515
2516 return $ar ? $ar['ID'] : false;
2517 }
2518
2519 public static function OnChangeFilePermissions($path, $permission = [], $old_permission = [], $arGroups = false)
2520 {
2521
2522 global $APPLICATION;
2523 $DB = CDatabase::GetModuleConnection('search');
2524
2525 $site = false;
2526 CMain::InitPathVars($site, $path);
2527 $DOC_ROOT = CSite::GetSiteDocRoot($site);
2528 $path = rtrim($path, '/');
2529
2530 if (!is_array($arGroups))
2531 {
2533 //Check if anonymous permission was changed
2534 if (!array_key_exists(2, $permission) && array_key_exists('*', $permission))
2535 {
2536 $permission[2] = $permission['*'];
2537 }
2538 if (!is_array($old_permission))
2539 {
2540 $old_permission = [];
2541 }
2542 if (!array_key_exists(2, $old_permission) && array_key_exists('*', $old_permission))
2543 {
2544 $old_permission[2] = $old_permission['*'];
2545 }
2546 //And if not when will do nothing
2547 if (
2548 (array_key_exists(2, $permission)
2549 && $permission[2] >= 'R')
2550 && array_key_exists(2, $old_permission)
2551 && $old_permission[2] >= 'R'
2552 )
2553 {
2554 return;
2555 }
2556 }
2557
2558 if (file_exists($DOC_ROOT . $path))
2559 {
2560 @set_time_limit(300);
2561 if (is_dir($DOC_ROOT . $path))
2562 {
2563 $handle = @opendir($DOC_ROOT . $path);
2564 while (false !== ($file = @readdir($handle)))
2565 {
2566 if ($file == '.' || $file == '..')
2567 {
2568 continue;
2569 }
2570
2571 $full_file = $path . '/' . $file;
2572 if ($full_file == '/bitrix')
2573 {
2574 continue;
2575 }
2576
2577 if (is_dir($DOC_ROOT . $full_file) || CSearch::CheckPath($full_file))
2578 {
2579 CSearch::OnChangeFilePermissions([$site, $full_file], [], [], $arGroups);
2580 }
2581 }
2582 }
2583 else
2584 {
2585 $rs = $DB->Query("
2586 SELECT SC.ID
2587 FROM b_search_content SC
2588 WHERE MODULE_ID='main'
2589 AND ITEM_ID='" . $DB->ForSql($site . '|' . $path) . "'
2590 ");
2591 if ($ar = $rs->Fetch())
2592 {
2593 $arNewGroups = [];
2594 foreach ($arGroups as $group_id)
2595 {
2596 $p = $APPLICATION->GetFileAccessPermission([$site, $path], [$group_id]);
2597 if ($p >= 'R')
2598 {
2599 $arNewGroups[$group_id] = 'G' . $group_id;
2600 if ($group_id == 2)
2601 {
2602 break;
2603 }
2604 }
2605 }
2606 CSearch::SetContentItemGroups($ar['ID'], $arNewGroups);
2607 }
2608 }
2609 }
2610 }
2611
2612 public static function SetContentItemGroups($index_id, $arGroups)
2613 {
2614 $DB = CDatabase::GetModuleConnection('search');
2615 $index_id = intval($index_id);
2616
2617 $arToInsert = [];
2618 foreach ($arGroups as $group_code)
2619 {
2620 if ($group_code <> '')
2621 {
2622 $arToInsert[$group_code] = $group_code;
2623 }
2624 }
2625
2626 //Read database
2627 $rs = $DB->Query('
2628 SELECT * FROM b_search_content_right
2629 WHERE SEARCH_CONTENT_ID = ' . $index_id . '
2630 ');
2631 while ($ar = $rs->Fetch())
2632 {
2633 $group_code = $ar['GROUP_CODE'];
2634 if (isset($arToInsert[$group_code]))
2635 {
2636 unset($arToInsert[$group_code]); //This already in DB
2637 }
2638 else
2639 {
2640 $DB->Query('
2641 DELETE FROM b_search_content_right
2642 WHERE
2643 SEARCH_CONTENT_ID = ' . $index_id . "
2644 AND GROUP_CODE = '" . $DB->ForSQL($group_code) . "'
2645 "); //And this should be deleted
2646 }
2647 }
2648
2649 foreach ($arToInsert as $group_code)
2650 {
2651 $DB->Query('
2652 INSERT INTO b_search_content_right
2653 (SEARCH_CONTENT_ID, GROUP_CODE)
2654 VALUES
2655 (' . $index_id . ", '" . $DB->ForSQL($group_code, 100) . "')
2656 ", true);
2657 }
2658 }
2659
2660 public static function CheckPermissions($FIELD = 'sc.ID')
2661 {
2662 global $USER;
2663
2664 $arResult = [];
2665
2666 if ($USER->IsAdmin())
2667 {
2668 $arResult[] = '1=1';
2669 }
2670 else
2671 {
2672 if ($USER->GetID() > 0)
2673 {
2675 $arResult[] = '
2676 EXISTS (
2677 SELECT 1
2678 FROM b_search_content_right scg
2679 WHERE ' . $FIELD . ' = scg.SEARCH_CONTENT_ID
2680 AND scg.GROUP_CODE IN (
2681 SELECT GROUP_CODE FROM b_search_user_right
2682 WHERE USER_ID = ' . $USER->GetID() . '
2683 )
2684 )';
2685 }
2686 else
2687 {
2688 $arResult[] = '
2689 EXISTS (
2690 SELECT 1
2691 FROM b_search_content_right scg
2692 WHERE ' . $FIELD . " = scg.SEARCH_CONTENT_ID
2693 AND scg.GROUP_CODE = 'G2'
2694 )";
2695 }
2696 }
2697 return '((' . implode(') OR (', $arResult) . '))';
2698 }
2699
2700 public static function SetContentItemParams($index_id, $arParams)
2701 {
2702 $DB = CDatabase::GetModuleConnection('search');
2703 $index_id = intval($index_id);
2704
2705 $arToInsert = [];
2706
2707 if (is_array($arParams))
2708 {
2709 foreach ($arParams as $k1 => $v1)
2710 {
2711 $name = trim($k1);
2712 if ($name <> '')
2713 {
2714 $sql_name = "'" . $DB->ForSQL($name, 100) . "'";
2715
2716 if (!is_array($v1))
2717 {
2718 $v1 = [$v1];
2719 }
2720
2721 foreach ($v1 as $v2)
2722 {
2723 $value = trim($v2);
2724 if ($value <> '')
2725 {
2726 $sql_value = "'" . $DB->ForSQL($value, 100) . "'";
2727 $key = md5($sql_name) . md5($sql_value);
2728
2729 $arToInsert[$key] = '
2730 INSERT INTO b_search_content_param
2731 (SEARCH_CONTENT_ID, PARAM_NAME, PARAM_VALUE)
2732 VALUES
2733 (' . $index_id . ', ' . $sql_name . ', ' . $sql_value . ')
2734 ';
2735 }
2736 }
2737 }
2738 }
2739 }
2740
2741 if (empty($arToInsert))
2742 {
2743 $DB->Query('
2744 DELETE FROM b_search_content_param
2745 WHERE
2746 SEARCH_CONTENT_ID = ' . $index_id . '
2747 ');
2748 }
2749 else
2750 {
2751 $rs = $DB->Query('
2752 SELECT PARAM_NAME, PARAM_VALUE
2753 FROM b_search_content_param
2754 WHERE SEARCH_CONTENT_ID = ' . $index_id . '
2755 ');
2756 while ($ar = $rs->Fetch())
2757 {
2758 $sql_name = "'" . $DB->ForSQL($ar['PARAM_NAME'], 100) . "'";
2759 $sql_value = "'" . $DB->ForSQL($ar['PARAM_VALUE'], 100) . "'";
2760 $key = md5($sql_name) . md5($sql_value);
2761
2762 if (array_key_exists($key, $arToInsert))
2763 {
2764 unset($arToInsert[$key]);
2765 }
2766 else
2767 {
2768 $DB->Query($s = '
2769 DELETE FROM b_search_content_param
2770 WHERE
2771 SEARCH_CONTENT_ID = ' . $index_id . '
2772 AND PARAM_NAME = ' . $sql_name . '
2773 AND PARAM_VALUE = ' . $sql_value . '
2774 ');
2775 }
2776 }
2777 }
2778
2779 foreach ($arToInsert as $sql)
2780 {
2781 $DB->Query($sql);
2782 }
2783 }
2784
2785 public static function GetContentItemParams($index_id, $param_name = false)
2786 {
2787 $DB = CDatabase::GetModuleConnection('search');
2788 $index_id = intval($index_id);
2789
2790 if ($index_id <= 0)
2791 {
2792 return false;
2793 }
2794
2795 $arResult = [];
2796
2797 $rs = $DB->Query('
2798 SELECT PARAM_NAME, PARAM_VALUE
2799 FROM b_search_content_param
2800 WHERE SEARCH_CONTENT_ID = ' . $index_id . '
2801 ' . ($param_name && $param_name <> '' ? " AND PARAM_NAME = '" . $DB->ForSQL($param_name) . "'" : '') . '
2802 ');
2803 while ($ar = $rs->Fetch())
2804 {
2805 if (!isset($ar['PARAM_NAME'], $arResult))
2806 {
2807 $arResult[$ar['PARAM_NAME']] = [];
2808 }
2809 $arResult[$ar['PARAM_NAME']][] = $ar['PARAM_VALUE'];
2810 }
2811
2812 return $arResult;
2813 }
2814
2816 {
2817 $mean = array_sum($arValues) / count($arValues);
2818 $variance = 0.0;
2819 foreach ($arValues as $v)
2820 {
2821 $variance += pow($v - $mean, 2);
2822 }
2823 return sqrt($variance / count($arValues));
2824 }
2825
2826 function normdev($words_count)
2827 {
2828 $a = [];
2829 while ($words_count > 0)
2830 {
2831 $a[] = $words_count--;
2832 }
2833 return $this->stddev($a);
2834 }
2835
2836 public static function DeleteOld($SESS_ID, $MODULE_ID = '', $SITE_ID = '')
2837 {
2838 $DB = CDatabase::GetModuleConnection('search');
2839
2840 $strFilter = '';
2841 if ($MODULE_ID != '')
2842 {
2843 $strFilter .= " AND MODULE_ID = '" . $DB->ForSql($MODULE_ID) . "' ";
2844 }
2845
2846 $strJoin = '';
2847 if ($SITE_ID != '')
2848 {
2849 $strFilter .= " AND scsite.SITE_ID = '" . $DB->ForSql($SITE_ID) . "' ";
2850 $strJoin .= ' INNER JOIN b_search_content_site scsite ON sc.ID=scsite.SEARCH_CONTENT_ID ';
2851 }
2852
2853 if (!is_array($SESS_ID))
2854 {
2855 $SESS_ID = [$SESS_ID];
2856 }
2857
2858 foreach ($SESS_ID as $key => $value)
2859 {
2860 $SESS_ID[$key] = $DB->ForSql($value);
2861 }
2862
2863 $strSql = '
2864 SELECT ID
2865 FROM b_search_content sc
2866 ' . $strJoin . "
2867 WHERE (UPD not in ('" . implode("', '", $SESS_ID) . "') OR UPD IS NULL)
2868 " . $strFilter . '
2869 ';
2870
2871 $arEvents = GetModuleEvents('search', 'OnBeforeIndexDelete', true);
2872
2873 $rs = $DB->Query($strSql);
2874 while ($ar = $rs->Fetch())
2875 {
2876 foreach ($arEvents as $arEvent)
2877 {
2878 ExecuteModuleEventEx($arEvent, ['SEARCH_CONTENT_ID = ' . $ar['ID']]);
2879 }
2880
2881 $DB->Query('DELETE FROM b_search_content_param WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2882 $DB->Query('DELETE FROM b_search_content_right WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2883 $DB->Query('DELETE FROM b_search_content_site WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2884 $DB->Query('DELETE FROM b_search_content_title WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2885 $DB->Query('DELETE FROM b_search_tags WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2886 CSearchFullText::getInstance()->deleteById($ar['ID']);
2887 $DB->Query('DELETE FROM b_search_content WHERE ID = ' . $ar['ID']);
2888 }
2889
2891 }
2892
2893 public static function DeleteForReindex($MODULE_ID)
2894 {
2895 $DB = CDatabase::GetModuleConnection('search');
2896
2897 $MODULE_ID = $DB->ForSql($MODULE_ID);
2898 $strSql = "SELECT ID FROM b_search_content WHERE MODULE_ID = '" . $MODULE_ID . "'";
2899
2900 $arEvents = GetModuleEvents('search', 'OnBeforeIndexDelete', true);
2901
2902 $rs = $DB->Query($strSql);
2903 while ($ar = $rs->Fetch())
2904 {
2905 foreach ($arEvents as $arEvent)
2906 {
2907 ExecuteModuleEventEx($arEvent, ['SEARCH_CONTENT_ID = ' . $ar['ID']]);
2908 }
2909
2910 $DB->Query('DELETE FROM b_search_content_param WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2911 $DB->Query('DELETE FROM b_search_content_right WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2912 $DB->Query('DELETE FROM b_search_content_site WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2913 $DB->Query('DELETE FROM b_search_content_title WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2914 $DB->Query('DELETE FROM b_search_tags WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2915 CSearchFullText::getInstance()->deleteById($ar['ID']);
2916 $DB->Query('DELETE FROM b_search_content WHERE ID = ' . $ar['ID']);
2917 }
2918
2920 }
2921
2922 public static function DeleteIndex($MODULE_ID, $ITEM_ID = false, $PARAM1 = false, $PARAM2 = false, $SITE_ID = false)
2923 {
2924 $DB = CDatabase::GetModuleConnection('search');
2925 $bIncSites = false;
2926 $op = (mb_strpos($ITEM_ID, '%') !== false ? '%=' : '=');
2927
2928 if ($PARAM1 !== false && $PARAM2 !== false)
2929 {
2931 'MODULE_ID' => $MODULE_ID,
2932 $op . 'ITEM_ID' => $ITEM_ID,
2933 [
2934 '=PARAM1' => $PARAM1,
2935 'PARAM2' => $PARAM2,
2936 ],
2937 'SITE_ID' => $SITE_ID,
2938 ], $bIncSites);
2939 }
2940 else
2941 {
2943 'MODULE_ID' => $MODULE_ID,
2944 $op . 'ITEM_ID' => $ITEM_ID,
2945 'PARAM1' => $PARAM1,
2946 'PARAM2' => $PARAM2,
2947 'SITE_ID' => $SITE_ID,
2948 ], $bIncSites);
2949 }
2950
2951 $strSql = '
2952 SELECT sc.ID
2953 FROM b_search_content sc
2954 ' . ($bIncSites ? 'INNER JOIN b_search_content_site scsite ON sc.ID=scsite.SEARCH_CONTENT_ID' : '') . '
2955 WHERE
2956 ' . $strSqlWhere . '
2957 ';
2958
2959 $arEvents = GetModuleEvents('search', 'OnBeforeIndexDelete', true);
2960
2961 $rs = $DB->Query($strSql);
2962 while ($ar = $rs->Fetch())
2963 {
2964 foreach ($arEvents as $arEvent)
2965 {
2966 ExecuteModuleEventEx($arEvent, ['SEARCH_CONTENT_ID = ' . $ar['ID']]);
2967 }
2968
2969 $DB->Query('DELETE FROM b_search_content_param WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2970 $DB->Query('DELETE FROM b_search_content_right WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2971 $DB->Query('DELETE FROM b_search_content_site WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2972 $DB->Query('DELETE FROM b_search_content_title WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2973 $DB->Query('DELETE FROM b_search_tags WHERE SEARCH_CONTENT_ID = ' . $ar['ID']);
2974 CSearchFullText::getInstance()->deleteById($ar['ID']);
2975 $DB->Query('DELETE FROM b_search_content WHERE ID = ' . $ar['ID']);
2976 }
2977
2979 }
2980
2981 public static function Update($ID, $arFields)
2982 {
2983 $DB = CDatabase::GetModuleConnection('search');
2984 $bUpdate = false;
2985
2986 if (array_key_exists('~DATE_CHANGE', $arFields))
2987 {
2988 $arFields['DATE_CHANGE'] = $arFields['~DATE_CHANGE'];
2989 unset($arFields['~DATE_CHANGE']);
2990 }
2991 elseif (array_key_exists('LAST_MODIFIED', $arFields))
2992 {
2993 $arFields['DATE_CHANGE'] = $arFields['LAST_MODIFIED'];
2994 unset($arFields['LAST_MODIFIED']);
2995 }
2996 elseif (array_key_exists('DATE_CHANGE', $arFields))
2997 {
2998 $arFields['DATE_CHANGE'] = $DB->FormatDate($arFields['DATE_CHANGE'], 'DD.MM.YYYY HH:MI:SS', CLang::GetDateFormat());
2999 }
3000
3001 if (array_key_exists('SITE_ID', $arFields))
3002 {
3003 CSearch::UpdateSite($ID, $arFields['SITE_ID']);
3004 $bUpdate = true;
3005 }
3006
3007 if (array_key_exists('PERMISSIONS', $arFields))
3008 {
3009 $arNewGroups = [];
3010 foreach ($arFields['PERMISSIONS'] as $group_id)
3011 {
3012 if (is_numeric($group_id))
3013 {
3014 $arNewGroups[$group_id] = 'G' . intval($group_id);
3015 }
3016 else
3017 {
3018 $arNewGroups[$group_id] = $group_id;
3019 }
3020 }
3021 CSearch::SetContentItemGroups($ID, $arNewGroups);
3022 $bUpdate = true;
3023 }
3024
3025 if (array_key_exists('PARAMS', $arFields))
3026 {
3028 $bUpdate = true;
3029 }
3030
3031 $strUpdate = $DB->PrepareUpdate('b_search_content', $arFields);
3032 if ($strUpdate <> '')
3033 {
3034 $DB->Query('UPDATE b_search_content SET ' . $strUpdate . ' WHERE ID = ' . intval($ID));
3035 $bUpdate = true;
3036 }
3037
3038 if ($bUpdate)
3039 {
3041 }
3042 }
3043
3044 public static function ChangeIndex($MODULE_ID, $arFields, $ITEM_ID = false, $PARAM1 = false, $PARAM2 = false, $SITE_ID = false)
3045 {
3046 $DB = CDatabase::GetModuleConnection('search');
3047 $bIncSites = false;
3048
3050 'MODULE_ID' => $MODULE_ID,
3051 'ITEM_ID' => $ITEM_ID,
3052 'PARAM1' => $PARAM1,
3053 'PARAM2' => $PARAM2,
3054 'SITE_ID' => $SITE_ID,
3055 ], $bIncSites);
3056 $strSql = '
3057 SELECT sc.ID
3058 FROM b_search_content sc
3059 ' . ($bIncSites ? 'INNER JOIN b_search_content_site scsite ON sc.ID=scsite.SEARCH_CONTENT_ID' : '') . '
3060 ' . ($strSqlWhere <> '' ? 'WHERE ' . $strSqlWhere : '') . '
3061 ';
3062 $rs = $DB->Query($strSql);
3063 while ($ar = $rs->Fetch())
3064 {
3066 }
3067 }
3068
3069 public static function ChangeSite($MODULE_ID, $arSite, $ITEM_ID = false, $PARAM1 = false, $PARAM2 = false, $SITE_ID = false)
3070 {
3071 $DB = CDatabase::GetModuleConnection('search');
3072 $bIncSites = false;
3073
3075 'MODULE_ID' => $MODULE_ID,
3076 'ITEM_ID' => $ITEM_ID,
3077 'PARAM1' => $PARAM1,
3078 'PARAM2' => $PARAM2,
3079 'SITE_ID' => $SITE_ID,
3080 ], $bIncSites);
3081
3082 $strSql = '
3083 SELECT sc.ID
3084 FROM b_search_content sc
3085 ' . ($bIncSites ? 'INNER JOIN b_search_content_site scsite ON sc.ID=scsite.SEARCH_CONTENT_ID' : '') . '
3086 WHERE
3087 ' . $strSqlWhere . '
3088 ';
3089
3090 $r = $DB->Query($strSql);
3091 while ($arR = $r->Fetch())
3092 {
3093 CSearch::Update($arR['ID'], ['SITE_ID' => $arSite]);
3094 }
3095 }
3096
3097 public static function ChangePermission($MODULE_ID, $arGroups, $ITEM_ID = false, $PARAM1 = false, $PARAM2 = false, $SITE_ID = false, $PARAMS = false)
3098 {
3099 $DB = CDatabase::GetModuleConnection('search');
3100 $bIncSites = false;
3101
3103 'MODULE_ID' => $MODULE_ID,
3104 'ITEM_ID' => $ITEM_ID,
3105 'PARAM1' => $PARAM1,
3106 'PARAM2' => $PARAM2,
3107 'SITE_ID' => $SITE_ID,
3108 'PARAMS' => $PARAMS,
3109 ], $bIncSites);
3110
3111 if ($strSqlWhere)
3112 {
3113 $strSqlJoin1 = 'INNER JOIN b_search_content sc ON sc.ID = b_search_content_right.SEARCH_CONTENT_ID';
3114 $match = [];
3115 //Copy first exists into inner join in hopeless try to defeat MySQL optimizer
3116 if (preg_match('#^\\s*EXISTS (\\(SELECT \\* FROM b_search_content_param WHERE SEARCH_CONTENT_ID = sc.ID AND PARAM_NAME = \'[^\']+\' AND PARAM_VALUE = \'[^\']+\'\\))#', $strSqlWhere, $match))
3117 {
3118 $subTable = str_replace('SEARCH_CONTENT_ID = sc.ID AND', '', $match[1]);
3119 $strSqlJoin2 = 'INNER JOIN ' . $subTable . ' p1 ON p1.SEARCH_CONTENT_ID = sc.ID';
3120 }
3121 else
3122 {
3123 $strSqlJoin2 = '';
3124 }
3125 }
3126 else
3127 {
3128 $strSqlJoin1 = '';
3129 $strSqlJoin2 = '';
3130 }
3131
3132 $rs = $DB->Query('
3133 SELECT sc.ID
3134 FROM b_search_content sc
3135 ' . $strSqlJoin2 . '
3136 ' . ($strSqlWhere ?
3137 'WHERE ' . $strSqlWhere :
3138 ''
3139 ) . '
3140 ');
3141 while ($arR = $rs->fetch())
3142 {
3143 CSearch::Update($arR['ID'], ['PERMISSIONS' => $arGroups]);
3144 }
3145 }
3146}
3147
3149{
3150 var $bIncSites = false;
3152
3154 {
3155 $this->strSearchContentAlias = $strSearchContentAlias;
3156 }
3157
3158 function _CallbackURL($field_name, $operation, $field_value)
3159 {
3160 global $DB;
3161
3162 if (is_array($field_value))
3163 {
3164 $sql_values = array_map([$DB, 'ForSQL'], array_filter($field_value));
3165 }
3166 elseif ($field_value !== false)
3167 {
3168 $sql_values = [$DB->ForSQL($field_value)];
3169 }
3170 else
3171 {
3172 $sql_values = [];
3173 }
3174
3175 $strSql = '';
3176 if (!empty($sql_values))
3177 {
3178 switch ($operation)
3179 {
3180 case 'I':
3181 case 'E':
3182 case 'S':
3183 case 'M':
3184 foreach ($sql_values as $url_i)
3185 {
3186 $arSQL[] = $this->strSearchContentAlias . ".URL LIKE '" . $url_i . "'";
3187 $arSQL[] = "scsite.URL LIKE '" . $url_i . "'";
3188 }
3189 $strSql = '(' . implode(') OR (', $arSQL) . ')';
3190 $this->bIncSites = true;
3191 break;
3192 case 'NI':
3193 case 'N':
3194 case 'NS':
3195 case 'NM':
3196 $arSQL = [];
3197 foreach ($sql_values as $url_i)
3198 {
3199 $arSQL[] = $this->strSearchContentAlias . ".URL NOT LIKE '" . $url_i . "'";
3200 $arSQL[] = "scsite.URL NOT LIKE '" . $url_i . "'";
3201 }
3202 $strSql = '(' . implode(') AND (', $arSQL) . ')';
3203 $this->bIncSites = true;
3204 break;
3205 default:
3206 break;
3207 }
3208 }
3209
3210 if ($strSql)
3211 {
3212 return '(' . $strSql . ')';
3213 }
3214 else
3215 {
3216 return '';
3217 }
3218 }
3219
3220 function _CallbackPARAMS($field_name, $operation, $field_value)
3221 {
3222 global $DB;
3223
3224 $arSql = [];
3225 if (is_array($field_value))
3226 {
3227 foreach ($field_value as $key => $val)
3228 {
3229 if (is_array($val))
3230 {
3231 foreach ($val as $i => $val2)
3232 {
3233 $val[$i] = $DB->ForSQL($val2);
3234 }
3235 $where = " in ('" . implode("', '", $val) . "')";
3236 }
3237 else
3238 {
3239 $where = " = '" . $DB->ForSQL($val) . "'";
3240 }
3241 $arSql[] = 'EXISTS (SELECT * FROM b_search_content_param WHERE SEARCH_CONTENT_ID = ' . $field_name . " AND PARAM_NAME = '" . $DB->ForSQL($key) . "' AND PARAM_VALUE " . $where . ')';
3242 }
3243 }
3244
3245 switch ($operation)
3246 {
3247 case 'I':
3248 case 'E':
3249 case 'S':
3250 case 'M':
3251 if (count($arSql))
3252 {
3253 return implode(' AND ', $arSql);
3254 }
3255 }
3256 }
3257}
3258
3260{
3272 var $error = '';
3273 var $errorno = 0;
3274 var $bTagsSearch = false;
3276 var $bStemming = false;
3277 var $bText = false;
3278
3280 {
3281 $this->m_query = '';
3282 $this->m_stemmed_words = [];
3283 $this->m_tags_words = [];
3284 $this->m_fields = '';
3285 $this->default_query_type = $default_query_type;
3286 $this->rus_bool_lang = $rus_bool_lang;
3287 $this->m_casematch = $m_casematch;
3288 $this->m_kav = [];
3289 $this->error = '';
3290
3291 $db_site_tmp = CSite::GetByID($site_id);
3292 if ($ar_site_tmp = $db_site_tmp->Fetch())
3293 {
3294 $this->m_lang = $ar_site_tmp['LANGUAGE_ID'];
3295 }
3296 else
3297 {
3298 $this->m_lang = 'en';
3299 }
3300 }
3301
3302 function GetQueryString($fields, $query, $bTagsSearch = false, $bUseStemming = true, $bErrorOnEmptyStem = false)
3303 {
3304 $this->m_words = [];
3305 $this->m_fields = explode(',', $fields);
3306
3307 $this->bTagsSearch = $bTagsSearch;
3308 //In case there is no masks used we'll keep list
3309 //of all tags in this member
3310 //to perform optimization
3311 $this->m_tags_words = [];
3312
3313 $this->m_query = $query = $this->CutKav($query);
3314
3315 //Assume query does not have any word which can be stemmed
3316 $this->bStemming = false;
3317 if (!$this->bTagsSearch && $bUseStemming && COption::GetOptionString('search', 'use_stemming') == 'Y')
3318 {
3319 //In case when at least one word found: $this->bStemming = true
3320 $stem_query = $this->StemQuery($query, $this->m_lang);
3321 if ($this->bStemming === true || $bErrorOnEmptyStem)
3322 {
3323 $query = $stem_query;
3324 }
3325 }
3326 $this->m_parsed_query = $query = $this->ParseQ($query);
3327
3328 if ($query == '( )' || $query == '')
3329 {
3330 $this->error = GetMessage('SEARCH_ERROR3');
3331 $this->errorno = 3;
3332 return false;
3333 }
3334
3335 $query = $this->PrepareQuery($query);
3336
3337 return $query;
3338 }
3339
3340 function CutKav($query)
3341 {
3342 $arQuotes = [];
3343 if (preg_match_all("/([\"'])(.*?)(?<!\\\\)(\\1)/s", $query, $arQuotes))
3344 {
3345 foreach ($arQuotes[2] as $i => $quoted)
3346 {
3347 $quoted = trim($quoted);
3348 if ($quoted <> '')
3349 {
3350 $repl = $i . 'cut5';
3351 $this->m_kav[$repl] = str_replace('\\"', '"', $quoted);
3352 $query = str_replace($arQuotes[0][$i], ' ' . $repl . ' ', $query);
3353 }
3354 else
3355 {
3356 $query = str_replace($arQuotes[0][$i], ' ', $query);
3357 }
3358
3359 if ($i > 100)
3360 {
3361 break;
3362 }
3363 }
3364 }
3365 return $query;
3366 }
3367
3368 function ParseQ($q)
3369 {
3370 $q = trim($q);
3371 if ($q == '')
3372 {
3373 return '';
3374 }
3375
3376 $q = $this->ParseStr($q);
3377
3378 $q = str_replace(
3379 ['&', '|', '~', '(', ')'],
3380 [' && ', ' || ', ' ! ', ' ( ', ' ) '],
3381 $q
3382 );
3383 $q = '( ' . $q . ' )';
3384 $q = preg_replace('/\\s+/u', ' ', $q);
3385
3386 return $q;
3387 }
3388
3389 function ParseStr($qwe)
3390 {
3391 //Take alphabet into account
3392 $arStemInfo = stemming_init($this->m_lang);
3393 $letters = $arStemInfo['pcre_letters'] . '|+&~()';
3394
3395 //Erase delimiters from the query
3396 $qwe = trim(preg_replace('/[^' . $letters . ']+/u', ' ', $qwe));
3397
3398 // query language normalizer
3399 if (!$this->no_bool_lang)
3400 {
3401 $qwe = preg_replace('/(\\s+|^|[|&~])or(\\s+|$|[|&~])/isu', "\\1|\\2", $qwe);
3402 $qwe = preg_replace('/(\\s+|^|[|&~])and(\\s+|$|[|&~])/isu', "\\1&\\2", $qwe);
3403 $qwe = preg_replace('/(\\s+|^|[|&~])not(\\s+|$|[|&~])/isu', "\\1~\\2", $qwe);
3404 $qwe = preg_replace('/(\\s+|^|[|&~])without(\\s+|$|[|&~])/isu', "\\1~\\2", $qwe);
3405
3406 if ($this->rus_bool_lang == 'yes')
3407 {
3408 $qwe = preg_replace('/(\\s+|^|[|&~])' . GetMessage('SEARCH_TERM_OR') . '(\\s+|$|[|&~])/isu', "\\1|\\2", $qwe);
3409 $qwe = preg_replace('/(\\s+|^|[|&~])' . GetMessage('SEARCH_TERM_AND') . '(\\s+|$|[|&~])/isu', "\\1&\\2", $qwe);
3410 $qwe = preg_replace('/(\\s+|^|[|&~])' . GetMessage('SEARCH_TERM_NOT_1') . '(\\s+|$|[|&~])/isu', "\\1~\\2", $qwe);
3411 $qwe = preg_replace('/(\\s+|^|[|&~])' . GetMessage('SEARCH_TERM_NOT_2') . '(\\s+|$|[|&~])/isu', "\\1~\\2", $qwe);
3412 }
3413 }
3414
3415 $qwe = preg_replace('/(\\s*\\|+\\s*)/isu', '|', $qwe);
3416 $qwe = preg_replace('/(\\s*\\++\\s*|\\s*\\&\\s*)/isu', '&', $qwe);
3417 $qwe = preg_replace('/(\\s*\\~+\\s*)/isu', '~', $qwe);
3418
3419 $qwe = preg_replace('/\s*([()])\s*/su', "\\1", $qwe);
3420
3421 // default query type is and
3422 if (mb_strtolower($this->default_query_type) == 'or')
3423 {
3424 $default_op = '|';
3425 }
3426 else
3427 {
3428 $default_op = '&';
3429 }
3430
3431 $qwe = preg_replace('/(\s+|\&\|+|\|\&+)/su', $default_op, $qwe);
3432
3433 // remove unnesessary boolean operators
3434 $qwe = preg_replace('/\|+/', '|', $qwe);
3435 $qwe = preg_replace('/&+/', '&', $qwe);
3436 $qwe = preg_replace('/~+/', '~', $qwe);
3437 $qwe = preg_replace('/\|\&\|/', '&', $qwe);
3438 $qwe = preg_replace('/[\|\&\~]+$/', '', $qwe);
3439 $qwe = preg_replace('/^[\|\&]+/', '', $qwe);
3440
3441 // transform "w1 ~w2" -> "w1 default_op ~ w2"
3442 // ") ~w" -> ") default_op ~w"
3443 // "w ~ (" -> "w default_op ~("
3444 // ") w" -> ") default_op w"
3445 // "w (" -> "w default_op ("
3446 // ")(" -> ") default_op ("
3447
3448 $qwe = preg_replace('/([^\&\~\|\‍(\‍)]+)~([^\&\~\|\‍(\‍)]+)/su', "\\1" . $default_op . "~\\2", $qwe);
3449 $qwe = preg_replace('/\‍)~{1,}/su', ')' . $default_op . '~', $qwe);
3450 $qwe = preg_replace('/~{1,}\‍(/su', ($default_op == '|' ? '~|(' : '&~('), $qwe);
3451 $qwe = preg_replace('/\‍)([^\&\~\|\‍(\‍)]+)/su', ')' . $default_op . "\\1", $qwe);
3452 $qwe = preg_replace('/([^\&\~\|\‍(\‍)]+)\‍(/su', "\\1" . $default_op . '(', $qwe);
3453 $qwe = preg_replace('/\‍) *\‍(/su', ')' . $default_op . '(', $qwe);
3454
3455 // remove unnesessary boolean operators
3456 $qwe = preg_replace('/\|+/', '|', $qwe);
3457 $qwe = preg_replace('/&+/', '&', $qwe);
3458
3459 // remove errornous format of query - ie: '(&', '&)', '(|', '|)', '~&', '~|', '~)'
3460 $qwe = preg_replace('/\‍(\&{1,}/s', '(', $qwe);
3461 $qwe = preg_replace('/\&{1,}\‍)/s', ')', $qwe);
3462 $qwe = preg_replace('/\~{1,}\‍)/s', ')', $qwe);
3463 $qwe = preg_replace('/\‍(\|{1,}/s', '(', $qwe);
3464 $qwe = preg_replace('/\|{1,}\‍)/s', ')', $qwe);
3465 $qwe = preg_replace('/\~{1,}\&{1,}/s', '&', $qwe);
3466 $qwe = preg_replace('/\~{1,}\|{1,}/s', '|', $qwe);
3467
3468 $qwe = preg_replace('/\‍(\‍)/s', '', $qwe);
3469 $qwe = preg_replace('/^[\|\&]{1,}/s', '', $qwe);
3470 $qwe = preg_replace('/[\|\&\~]{1,}$/s', '', $qwe);
3471 $qwe = preg_replace('/\|\&/s', '&', $qwe);
3472 $qwe = preg_replace('/\&\|/s', '|', $qwe);
3473
3474 // remove unnesessary boolean operators one more time
3475 $qwe = preg_replace('/\|+/', '|', $qwe);
3476 $qwe = preg_replace('/&+/', '&', $qwe);
3477
3478 return $qwe;
3479 }
3480
3481 function StemWord($w)
3482 {
3483 static $preg_ru = false;
3484 if (is_array($w))
3485 {
3486 $w = $w[0];
3487 }
3488 $wu = mb_strtoupper($w);
3489
3490 if (!$this->no_bool_lang)
3491 {
3492 if (preg_match('/^(OR|AND|NOT|WITHOUT)$/', $wu))
3493 {
3494 return $w;
3495 }
3496 elseif ($this->rus_bool_lang == 'yes')
3497 {
3498 if ($preg_ru === false)
3499 {
3500 $preg_ru = '/^(' . mb_strtoupper(GetMessage('SEARCH_TERM_OR') . '|' . GetMessage('SEARCH_TERM_AND') . '|' . GetMessage('SEARCH_TERM_NOT_1') . '|' . GetMessage('SEARCH_TERM_NOT_2')) . ')$/u';
3501 }
3502 if (preg_match($preg_ru, $wu))
3503 {
3504 return $w;
3505 }
3506 }
3507 }
3508
3509 if (preg_match('/cut[56]/i', $w))
3510 {
3511 return $w;
3512 }
3513 $arrStem = array_keys(stemming($w, $this->m_lang));
3514 if (count($arrStem) < 1)
3515 {
3516 return ' ';
3517 }
3518 else
3519 {
3520 $this->bStemming = true;
3521 return '(' . implode('|', $arrStem) . ')';
3522 }
3523 }
3524
3525 function StemQuery($q, $lang = 'en')
3526 {
3527 $arStemInfo = stemming_init($lang);
3528 return preg_replace_callback('/([' . $arStemInfo['pcre_letters'] . ']+)/u', [$this, 'StemWord'], $q);
3529 }
3530
3531 function PrepareQuery($q)
3532 {
3533 $state = 0;
3534 $qu = [];
3535 $n = 0;
3536 $this->error = '';
3537
3538 foreach (preg_split('/ +/', $q) as $t)
3539 {
3540 if ($state === 0)
3541 {
3542 if (($t === '||') || ($t === '&&') || ($t === ')'))
3543 {
3544 $this->error = GetMessage('SEARCH_ERROR2') . ' ' . $t;
3545 $this->errorno = 2;
3546 break;
3547 }
3548 elseif ($t === '!')
3549 {
3550 $state = 0;
3551 $qu[] = ' NOT ';
3552 }
3553 elseif ($t == '(')
3554 {
3555 $n++;
3556 $state = 0;
3557 $qu[] = '(';
3558 }
3559 else
3560 {
3561 $state = 1;
3562 $where = $this->BuildWhereClause($t);
3563 $c = count($qu);
3564 if (
3565 $where === '1=1'
3566 && (
3567 ($c > 0 && $qu[$c - 1] === ' OR ')
3568 || ($c > 1 && $qu[$c - 1] === '(' && $qu[$c - 2] === ' OR ')
3569 )
3570 )
3571 {
3572 $where = '1<>1';
3573 }
3574 $qu[] = ' ' . $where . ' ';
3575 }
3576 }
3577 else
3578 {
3579 if (($t === '||') || ($t === '&&'))
3580 {
3581 $state = 0;
3582 if ($t === '||')
3583 {
3584 $qu[] = ' OR ';
3585 }
3586 else
3587 {
3588 $qu[] = ' AND ';
3589 }
3590 }
3591 elseif ($t === ')')
3592 {
3593 $n--;
3594 $state = 1;
3595 $qu[] = ')';
3596 }
3597 else
3598 {
3599 $this->error = GetMessage('SEARCH_ERROR2') . ' ' . $t;
3600 $this->errorno = 2;
3601 break;
3602 }
3603 }
3604 }
3605
3606 if (($this->error === '') && ($n !== 0))
3607 {
3608 $this->error = GetMessage('SEARCH_ERROR1');
3609 $this->errorno = 1;
3610 }
3611
3612 if ($this->error != '')
3613 {
3614 return 0;
3615 }
3616
3617 return implode($qu);
3618 }
3619}
3620
3622{
3623 var $MODULE = '';
3625 var $CNT = 0;
3626 var $SESS_ID = '';
3627
3629 {
3630 $ID = $arFields['ID'];
3631 if ($ID == '')
3632 {
3633 return true;
3634 }
3635 unset($arFields['ID']);
3636 CSearch::Index($this->MODULE, $ID, $arFields, false, $this->SESS_ID);
3637 $this->CNT = $this->CNT + 1;
3638 if ($this->max_execution_time > 0 && microtime(true) - START_EXEC_TIME > $this->max_execution_time)
3639 {
3640 return false;
3641 }
3642 else
3643 {
3644 return true;
3645 }
3646 }
3647}
$path
Определения access_edit.php:21
$type
Определения options.php:106
$arSites
Определения options.php:15
global $APPLICATION
Определения include.php:80
static convertEncoding($data, $charsetFrom, $charsetTo)
Определения encoding.php:17
static DeleteByTag($tagId)
Определения admin_notify.php:146
$result
Определения dbresult.php:16
$arResult
Определения dbresult.php:17
$bShowAll
Определения dbresult.php:21
$DB
Определения dbresult.php:40
static GetList($by='c_sort', $order='asc', $arFilter=[], $SHOW_USERS_AMOUNT="N")
Определения group.php:136
SetFields($arFields)
Определения sqlwhere.php:239
Определения search.php:10
$strTagsText
Определения search.php:14
static ChangeIndex($MODULE_ID, $arFields, $ITEM_ID=false, $PARAM1=false, $PARAM2=false, $SITE_ID=false)
Определения search.php:3044
$strSqlWhere
Определения search.php:15
SetOptions($arOptions)
Определения search.php:301
static CheckPath($path)
Определения search.php:855
static OnChangeFile($path, $site)
Определения search.php:2135
$strTags
Определения search.php:16
static ChangePermission($MODULE_ID, $arGroups, $ITEM_ID=false, $PARAM1=false, $PARAM2=false, $SITE_ID=false, $PARAMS=false)
Определения search.php:3097
$url_add_params
Определения search.php:20
$tf_hwm_site_id
Определения search.php:22
$bUseRatingSort
Определения search.php:27
Repl($strCond, $strType, $strWh)
Определения search.php:451
static RecurseIndex($path=[], $max_execution_time=0, &$NS)
Определения search.php:1894
static __PrepareFilter($arFilter, &$bIncSites, $strSearchContentAlias='sc.')
Определения search.php:2149
$Query
Определения search.php:11
static chr($a)
Определения search.php:331
$_opt_ERROR_ON_EMPTY_STEM
Определения search.php:23
static Add($arFields)
Определения search.php:2448
$tf_hwm
Определения search.php:21
normdev($words_count)
Определения search.php:2826
GetFreqStatistics($lang_id, $arStem, $site_id='')
Определения search.php:336
static KillEntities($str)
Определения search.php:1663
$arParams
Определения search.php:19
static DeleteIndex($MODULE_ID, $ITEM_ID=false, $PARAM1=false, $PARAM2=false, $SITE_ID=false)
Определения search.php:2922
static GetContentItemParams($index_id, $param_name=false)
Определения search.php:2785
static DeleteOld($SESS_ID, $MODULE_ID='', $SITE_ID='')
Определения search.php:2836
static ReindexModule($MODULE_ID, $bFull=false)
Определения search.php:1218
static ChangeSite($MODULE_ID, $arSite, $ITEM_ID=false, $PARAM1=false, $PARAM2=false, $SITE_ID=false)
Определения search.php:3069
Fetch()
Определения search.php:752
$Statistic
Определения search.php:12
static RemovePHP($str)
Определения search.php:1993
CSearch($strQuery=false, $LID=false, $MODULE_ID=false, $ITEM_ID=false, $PARAM1=false, $PARAM2=false, $aSort=[], $aParamsEx=[], $bTagsCloud=false)
Определения search.php:43
static CheckPermissions($FIELD='sc.ID')
Определения search.php:2660
static ReindexFile($path, $SEARCH_SESS_ID='')
Определения search.php:1735
stddev($arValues)
Определения search.php:2815
static QueryMnogoSearch(&$xml)
Определения search.php:965
static DeleteForReindex($MODULE_ID)
Определения search.php:2893
NavStart($nPageSize=0, $bShowAll=true, $iNumPage=false)
Определения search.php:738
static ReIndexAll($bFull=false, $max_execution_time=0, $NS=[], $clear_suggest=false)
Определения search.php:1008
static KillTags($str)
Определения search.php:2092
Search($arParams, $aSort=[], $aParamsEx=[], $bTagsCloud=false)
Определения search.php:62
GetFilterMD5()
Определения search.php:324
$errorno
Определения search.php:17
$_opt_NO_WORD_LOGIC
Определения search.php:24
$formatter
Определения search.php:30
$flagsUseRatingSort
Определения search.php:28
__construct($strQuery=false, $SITE_ID=false, $MODULE_ID=false, $ITEM_ID=false, $PARAM1=false, $PARAM2=false, $aSort=[], $aParamsEx=[], $bTagsCloud=false)
Определения search.php:32
SetOffset($offset)
Определения search.php:314
static OnGroupDelete($ID)
Определения search.php:2140
SetLimit($limit)
Определения search.php:319
static GetIndex($MODULE_ID, $ITEM_ID)
Определения search.php:1266
PrepareSearchResult($str)
Определения search.php:530
static GetGroupCached()
Определения search.php:944
static OnChangeFilePermissions($path, $permission=[], $old_permission=[], $arGroups=false)
Определения search.php:2519
__PrepareSort($aSort=[], $strSearchContentAlias='sc.', $bTagsCloud=false)
Определения search.php:2357
$limit
Определения search.php:26
static Update($ID, $arFields)
Определения search.php:2981
$error
Определения search.php:18
$offset
Определения search.php:25
static SetContentItemParams($index_id, $arParams)
Определения search.php:2700
static Index($MODULE_ID, $ITEM_ID, $arFields, $bOverWrite=false, $SEARCH_SESS_ID='')
Определения search.php:1302
static SetContentItemGroups($index_id, $arGroups)
Определения search.php:2612
$strQueryText
Определения search.php:13
Определения search.php:3260
__construct($default_query_type='and', $rus_bool_lang='yes', $m_casematch=0, $site_id='')
Определения search.php:3279
$m_query
Определения search.php:3261
$no_bool_lang
Определения search.php:3270
GetQueryString($fields, $query, $bTagsSearch=false, $bUseStemming=true, $bErrorOnEmptyStem=false)
Определения search.php:3302
$m_stemmed_words
Определения search.php:3264
$bStemming
Определения search.php:3276
$m_fields
Определения search.php:3266
CutKav($query)
Определения search.php:3340
StemWord($w)
Определения search.php:3481
$m_casematch
Определения search.php:3271
$rus_bool_lang
Определения search.php:3269
$m_parsed_query
Определения search.php:3262
$bTagsSearch
Определения search.php:3274
ParseQ($q)
Определения search.php:3368
$errorno
Определения search.php:3273
$m_kav
Определения search.php:3267
StemQuery($q, $lang='en')
Определения search.php:3525
PrepareQuery($q)
Определения search.php:3531
ParseStr($qwe)
Определения search.php:3389
$m_stemmed_words_id
Определения search.php:3265
$m_tags_words
Определения search.php:3275
$default_query_type
Определения search.php:3268
$error
Определения search.php:3272
$bText
Определения search.php:3277
$m_words
Определения search.php:3263
static GetInstance()
Определения virtual_io.php:60
Определения sqlwhere.php:1359
Определения search.php:3622
$SESS_ID
Определения search.php:3626
Index($arFields)
Определения search.php:3628
$CNT
Определения search.php:3625
$max_execution_time
Определения search.php:3624
$MODULE
Определения search.php:3623
static getInstance()
Определения full_text.php:15
static CleanFreqCache($ID)
Определения search.php:736
static TagsIndex($arLID, $ID, $sContent)
Определения search.php:1005
static IndexTitle($arLID, $ID, $sTitle)
Определения search.php:753
static UpdateSite($ID, $arSITE_ID)
Определения search.php:1057
static ReindexLock()
Определения search.php:714
Определения search.php:1120
Определения search.php:3149
_CallbackURL($field_name, $operation, $field_value)
Определения search.php:3158
$strSearchContentAlias
Определения search.php:3151
_CallbackPARAMS($field_name, $operation, $field_value)
Определения search.php:3220
__construct($strSearchContentAlias)
Определения search.php:3153
$bIncSites
Определения search.php:3150
Определения statistic.php:4
static CleanCache($arTags='', $content_id=false)
Определения tags.php:234
static CheckCurrentUserGroups()
Определения user.php:27
$str
Определения commerceml2.php:63
$content
Определения commerceml.php:144
$abs_path
Определения component_props2.php:76
$f
Определения component_props.php:52
$arValues
Определения component_props.php:25
$filesrc
Определения component_props.php:53
$arFields
Определения dblapprove.php:5
$DOC_ROOT
Определения file_edit.php:66
$arGroups
Определения options.php:1766
$rsSites
Определения options.php:477
$res
Определения filter_act.php:7
$handle
Определения include.php:55
$perm
Определения options.php:169
$query
Определения get_search.php:11
if($ajaxMode) $ID
Определения get_user.php:27
else $ch
Определения group_list_element_edit.php:27
$p
Определения group_list_element_edit.php:23
global $DB
Определения cron_frame.php:29
global $USER
Определения csv_new_run.php:40
$max_execution_time
Определения csv_new_run.php:85
$io
Определения csv_new_run.php:98
if(!defined('SITE_ID')) $lang
Определения include.php:91
$l
Определения options.php:783
$arOptions
Определения structure.php:223
const START_EXEC_TIME
Определения start.php:12
if(!defined('NOT_CHECK_PERMISSIONS')) $NS
Определения backup.php:24
ExecuteModuleEventEx($arEvent, $arParams=[])
Определения tools.php:5214
ParseFileContent($filesrc, $params=[])
Определения tools.php:4780
FormatDate($format="", $timestamp=false, $now=false, ?string $languageId=null)
Определения tools.php:871
htmlspecialcharsEx($str)
Определения tools.php:2685
GetModuleEvents($MODULE_ID, $MESSAGE_ID, $bReturnArray=false)
Определения tools.php:5177
IncludeModuleLangFile($filepath, $lang=false, $bReturnArray=false)
Определения tools.php:3778
is_set($a, $k=false)
Определения tools.php:2133
GetMessage($name, $aReplace=null)
Определения tools.php:3397
$name
Определения menu_edit.php:35
$time
Определения payment.php:61
$delta
Определения prolog_main_admin.php:363
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
$ar
Определения options.php:199
if(empty($signedUserToken)) $key
Определения quickway.php:257
$i
Определения factura.php:643
</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
else $a
Определения template.php:137
$title
Определения pdf.php:123
$val
Определения options.php:1793
$matches
Определения index.php:22
tags_prepare($sText, $site_id=false)
Определения tags.php:4
$site_id
Определения sonet_set_content_view.php:9
const SITE_ID
Определения sonet_set_content_view.php:12
stemming_init($sLang='ru')
Определения stemming.php:3
stemming_upper($sText, $sLang='ru')
Определения stemming.php:115
stemming($sText, $sLang='ru', $bIgnoreStopWords=false, $bReturnPositions=false)
Определения stemming.php:148
$rs
Определения action.php:82
$k
Определения template_pdf.php:567
$arLangDirs
Определения translate_tools.php:9
$n
Определения update_log.php:107
$arFilter
Определения user_search.php:106
$url
Определения iframe.php:7
$SITE_ID
Определения yandex_run.php:607
$site
Определения yandex_run.php:614
$fields
Определения yandex_run.php:501