Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
phraseindexsearch.php
1<?php
2
4
17
19{
20 public const SEARCH_METHOD_EXACT = 'exact';
21 public const SEARCH_METHOD_EQUAL = 'equal';
22 public const SEARCH_METHOD_CASE_SENSITIVE = 'case_sensitive';
23 public const SEARCH_METHOD_ENTRY_WORD = 'entry_word';
24 public const SEARCH_METHOD_START_WITH = 'start_with';
25 public const SEARCH_METHOD_END_WITH = 'end_with';
26
27 // Lang code by IETF BCP 47
28 private const NON_FTS = ['th', 'zh-cn', 'zh-tw', 'ja', 'ko'];
29
36 public static function query(array $params = []): Main\ORM\Query\Query
37 {
38 [, $runtime, ] = self::processParams($params);
39
40 $entity = self::getPathCodeEntity();
41 foreach ($runtime as $field)
42 {
43 $entity->addField($field);
44 }
45
46 return new Main\ORM\Query\Query($entity);
47 }
48
49
56 public static function getCount(array $filterIn): int
57 {
58 [, $runtime, $filter] = self::processParams(['filter' => $filterIn]);
59
60 $entity = self::getPathCodeEntity();
61 foreach ($runtime as $field)
62 {
63 $entity->addField($field);
64 }
65
66 $query = new Main\ORM\Query\Query($entity);
67
68 $query
69 ->addSelect(new ExpressionField('CNT', 'COUNT(1)'))
70 ->setFilter($filter);
71
72 $result = $query->exec()->fetch();
73
74 return (int)$result['CNT'];
75 }
76
77
84 public static function getList(array $params): Main\ORM\Query\Result
85 {
86 [$select, $runtime, $filter] = self::processParams($params);
87
88 $executeParams = [
89 'select' => \array_merge(
90 [
91 'PATH_ID' => 'PATH_ID',
92 'PHRASE_CODE' => 'CODE',
93 'FILE_PATH' => 'PATH.PATH',
94 'TITLE' => 'PATH.NAME',
95 ],
96 $select
97 ),
98 'runtime' => $runtime,
99 'filter' => $filter,
100 ];
101
102 if (isset($params['order']))
103 {
104 $executeParams['order'] = $params['order'];
105 }
106 if (isset($params['offset']))
107 {
108 $executeParams['offset'] = $params['offset'];
109 }
110 if (isset($params['limit']))
111 {
112 $executeParams['limit'] = $params['limit'];
113 }
114 if (isset($params['count_total']))
115 {
116 $executeParams['count_total'] = true;
117 }
118
119 $entityClass = self::getPathCodeEntityClass();
120
121 return $entityClass::getList($executeParams);
122 }
123
124
125
130 public static function getPathCodeEntityClass(): string
131 {
132 static $class;
133 if ($class === null)
134 {
135 $entity = self::getPathCodeEntity();
136 $class = $entity->getDataClass();
137 }
138
139 return $class;
140 }
141
146 public static function getPathCodeEntity(): Main\ORM\Entity
147 {
148 static $entity;
149 if ($entity === null)
150 {
151 $subQuery = (new Main\ORM\Query\Query(Index\Internals\PhraseIndexTable::getEntity()))
152 ->setSelect(['PATH_ID', 'CODE'])
153 ->setGroup(['PATH_ID', 'CODE']);
154
155 $entity = Main\ORM\Entity::compileEntity(
156 'PathPhraseIndexReference',
157 [
158 'PATH_ID' => ['data_type' => 'string'],
159 'CODE' => ['data_type' => 'string'],
160 ],
161 [
162 'table_name' => '('.$subQuery->getQuery().')',
163 'namespace' => __NAMESPACE__. '\\Internals',
164 ]
165 );
166 }
167
168 return $entity;
169 }
170
177 private static function processParams(array $params): array
178 {
179 $select = [];
180 $runtime = [];
181 $filterIn = [];
182 $filterOut = [];
183 $runtimeInx = [];
184
185 if (isset($params['filter']))
186 {
187 if (\is_object($params['filter']))
188 {
189 $filterIn = clone $params['filter'];
190 }
191 else
192 {
193 $filterIn = $params['filter'];
194 }
195 }
196
197 $enabledLanguages = Translate\Config::getEnabledLanguages();
198 $languageUpperKeys = \array_combine($enabledLanguages, \array_map('mb_strtoupper', $enabledLanguages));
199
200 foreach ($languageUpperKeys as $langId => $langUpper)
201 {
202 $tbl = "{$langUpper}_LNG";
203 $alias = "{$langUpper}_LANG";
204
205 if (
206 !empty($params['select']) && in_array($alias, $params['select'])
207 || isset($params['order'], $params['order'][$alias])
208 )
209 {
210 $i = count($runtime);
211 $runtimeInx[$tbl] = $i;
212 $runtime[$i] = new Main\ORM\Fields\Relations\Reference(
213 $tbl,
214 Index\Internals\PhraseFts::getFtsEntityClass($langId),
215 Main\ORM\Query\Join::on('ref.PATH_ID', '=', 'this.PATH_ID')
216 ->whereColumn('ref.CODE', '=', 'this.CODE'),
217 ['join_type' => 'LEFT']
218 );
219
220 $runtimeInx[$alias] = $i++;
221 $runtime[$i] = new ExpressionField($alias, '%s', "{$tbl}.PHRASE");
222 $select[] = $alias;
223 }
224 }
225
226 if (!isset($filterIn['PHRASE_ENTRY']))
227 {
228 $filterIn['PHRASE_ENTRY'] = [];
229 }
230 if (!isset($filterIn['CODE_ENTRY']))
231 {
232 $filterIn['CODE_ENTRY'] = [];
233 }
234
235 // top folder
236 if (!empty($filterIn['PATH']))
237 {
238 $topIndexPath = Index\PathIndex::loadByPath($filterIn['PATH']);
239 if ($topIndexPath instanceof Index\PathIndex)
240 {
241 $filterOut['=PATH.DESCENDANTS.PARENT_ID'] = $topIndexPath->getId();//ancestor
242 }
243 unset($filterIn['PATH']);
244 }
245
246 // search by code
247 if (!empty($filterIn['INCLUDE_PHRASE_CODES']))
248 {
249 $codes = \preg_split("/[\r\n\t,; ]+/".\BX_UTF_PCRE_MODIFIER, $filterIn['INCLUDE_PHRASE_CODES']);
250 $codes = \array_filter($codes);
251 if (\count($codes) > 0)
252 {
253 $useLike = false;
254 foreach ($codes as $code)
255 {
256 if (\mb_strpos($code, '%') !== false)
257 {
258 $useLike = true;
259 break;
260 }
261 }
262 if ($useLike)
263 {
264 $filterOut['=%CODE'] = $codes;
265 }
266 else
267 {
268 $filterOut['=CODE'] = $codes;
269 }
270 }
271 unset($filterIn['INCLUDE_PHRASE_CODES']);
272 }
273 if (!empty($filterIn['EXCLUDE_PHRASE_CODES']))
274 {
275 $codes = \preg_split("/[\r\n\t,; ]+/".\BX_UTF_PCRE_MODIFIER, $filterIn['EXCLUDE_PHRASE_CODES']);
276 $codes = \array_filter($codes);
277 if (\count($codes) > 0)
278 {
279 $useLike = false;
280 foreach ($codes as $code)
281 {
282 if (\mb_strpos($code, '%') !== false)
283 {
284 $useLike = true;
285 break;
286 }
287 }
288 if ($useLike)
289 {
290 $filterOut["!=%CODE"] = $codes;
291 }
292 else
293 {
294 $filterOut["!=CODE"] = $codes;
295 }
296 }
297 unset($filterIn['EXCLUDE_PHRASE_CODES']);
298 }
299
300 if (!empty($filterIn['PHRASE_CODE']))
301 {
302 if (\in_array(self::SEARCH_METHOD_CASE_SENSITIVE, $filterIn['CODE_ENTRY']))
303 {
304 if (\in_array(self::SEARCH_METHOD_EQUAL, $filterIn['CODE_ENTRY']))
305 {
306 $filterOut["=CODE"] = $filterIn['PHRASE_CODE'];
307 }
308 elseif (\in_array(self::SEARCH_METHOD_START_WITH, $filterIn['CODE_ENTRY']))
309 {
310 $filterOut["=%CODE"] = $filterIn['PHRASE_CODE'].'%';
311 }
312 elseif (\in_array(self::SEARCH_METHOD_END_WITH, $filterIn['CODE_ENTRY']))
313 {
314 $filterOut["=%CODE"] = '%'.$filterIn['PHRASE_CODE'];
315 }
316 else
317 {
318 $filterOut["=%CODE"] = '%'.$filterIn['PHRASE_CODE'].'%';
319 }
320 }
321 else
322 {
323 $runtime[] = new ExpressionField('CODE_UPPER', 'UPPER(CONVERT(%s USING latin1))', 'CODE');
324 if (\in_array(self::SEARCH_METHOD_EQUAL, $filterIn['CODE_ENTRY']))
325 {
326 $filterOut['=CODE_UPPER'] = \mb_strtoupper($filterIn['PHRASE_CODE']);
327 }
328 elseif (\in_array(self::SEARCH_METHOD_START_WITH, $filterIn['CODE_ENTRY']))
329 {
330 $filterOut['=%CODE_UPPER'] = \mb_strtoupper($filterIn['PHRASE_CODE']).'%';
331 }
332 elseif (\in_array(self::SEARCH_METHOD_END_WITH, $filterIn['CODE_ENTRY']))
333 {
334 $filterOut['=%CODE_UPPER'] = '%'.\mb_strtoupper($filterIn['PHRASE_CODE']);
335 }
336 else
337 {
338 $filterOut['=%CODE_UPPER'] = '%'.\mb_strtoupper($filterIn['PHRASE_CODE']).'%';
339 }
340 }
341 }
342 unset($filterIn['PHRASE_CODE'], $filterIn['CODE_ENTRY']);
343
344 $runtime[] = new Main\ORM\Fields\Relations\Reference(
345 'PATH',
346 Index\Internals\PathIndexTable::class,
347 Main\ORM\Query\Join::on('ref.ID', '=', 'this.PATH_ID'),
348 ['join_type' => 'INNER']
349 );
350
351 $filterOut['=PATH.IS_DIR'] = 'N';
352
353 $replaceLangId = function(&$val)
354 {
355 $val = Translate\IO\Path::replaceLangId($val, '#LANG_ID#');
356 };
357 $trimSlash = function(&$val)
358 {
359 if (\mb_strpos($val, '%') === false)
360 {
361 if (Translate\IO\Path::isPhpFile($val))
362 {
363 $val = '/'. \trim($val, '/');
364 }
365 else
366 {
367 $val = '/'. \trim($val, '/'). '/%';
368 }
369 }
370 };
371
372 if (!empty($filterIn['INCLUDE_PATHS']))
373 {
374 $pathIncludes = \preg_split("/[\r\n\t,; ]+/".\BX_UTF_PCRE_MODIFIER, $filterIn['INCLUDE_PATHS']);
375 $pathIncludes = \array_filter($pathIncludes);
376 if (\count($pathIncludes) > 0)
377 {
378 $pathPathIncludes = [];
379 $pathNameIncludes = [];
380 foreach ($pathIncludes as $testPath)
381 {
382 if (!empty($testPath) && \trim($testPath) !== '')
383 {
384 if (\mb_strpos($testPath, '/') === false)
385 {
386 $pathNameIncludes[] = $testPath;
387 }
388 else
389 {
390 $pathPathIncludes[] = $testPath;
391 }
392 }
393 }
394 if (\count($pathNameIncludes) > 0 && \count($pathPathIncludes) > 0)
395 {
396 \array_walk($pathNameIncludes, $replaceLangId);
397 \array_walk($pathPathIncludes, $replaceLangId);
398 \array_walk($pathPathIncludes, $trimSlash);
399 $filterOut[] = [
400 'LOGIC' => 'OR',
401 '%=PATH.NAME' => $pathNameIncludes,
402 '%=PATH.PATH' => $pathPathIncludes,
403 ];
404 }
405 elseif (\count($pathNameIncludes) > 0)
406 {
407 \array_walk($pathNameIncludes, $replaceLangId);
408 $filterOut[] = [
409 'LOGIC' => 'OR',
410 '%=PATH.NAME' => $pathNameIncludes,
411 '%=PATH.PATH' => $pathNameIncludes,
412 ];
413 }
414 elseif (\count($pathPathIncludes) > 0)
415 {
416 \array_walk($pathPathIncludes, $replaceLangId);
417 \array_walk($pathPathIncludes, $trimSlash);
418 $filterOut['%=PATH.PATH'] = $pathPathIncludes;
419 }
420 }
421 unset($testPath, $pathIncludes, $pathNameIncludes, $pathPathIncludes);
422 }
423 if (!empty($filterIn['EXCLUDE_PATHS']))
424 {
425 $pathExcludes = \preg_split("/[\r\n\t,; ]+/".\BX_UTF_PCRE_MODIFIER, $filterIn['EXCLUDE_PATHS']);
426 $pathExcludes = \array_filter($pathExcludes);
427 if (\count($pathExcludes) > 0)
428 {
429 $pathPathExcludes = [];
430 $pathNameExcludes = [];
431 foreach ($pathExcludes as $testPath)
432 {
433 if (!empty($testPath) && \trim($testPath) !== '')
434 {
435 if (\mb_strpos($testPath, '/') === false)
436 {
437 $pathNameExcludes[] = $testPath;
438 }
439 else
440 {
441 $pathPathExcludes[] = $testPath;
442 }
443 }
444 }
445 if (\count($pathNameExcludes) > 0 && \count($pathPathExcludes) > 0)
446 {
447 \array_walk($pathNameExcludes, $replaceLangId);
448 \array_walk($pathPathExcludes, $replaceLangId);
449 \array_walk($pathPathExcludes, $trimSlash);
450 $filterOut[] = [
451 'LOGIC' => 'AND',
452 '!=%PATH.NAME' => $pathNameExcludes,
453 '!=%PATH.PATH' => $pathPathExcludes,
454 ];
455 }
456 elseif (\count($pathNameExcludes) > 0)
457 {
458 \array_walk($pathNameExcludes, $replaceLangId);
459 $filterOut[] = [
460 'LOGIC' => 'AND',
461 '!=%PATH.NAME' => $pathNameExcludes,
462 '!=%PATH.PATH' => $pathNameExcludes,
463 ];
464 }
465 elseif (\count($pathPathExcludes) > 0)
466 {
467 \array_walk($pathPathExcludes, $replaceLangId);
468 \array_walk($pathPathExcludes, $trimSlash);
469 $filterOut["!=%PATH.PATH"] = $pathPathExcludes;
470 }
471 }
472 unset($testPath, $pathExcludes, $pathPathExcludes, $pathNameExcludes);
473 }
474 unset($filterIn['INCLUDE_PATHS'], $filterIn['EXCLUDE_PATHS']);
475
476 // search by phrase
477 if (!empty($filterIn['PHRASE_TEXT']))
478 {
479 $langId = !empty($filterIn['LANGUAGE_ID']) ? $filterIn['LANGUAGE_ID'] : Loc::getCurrentLang();
480
481 $langUpper = $languageUpperKeys[$langId];
482 $tbl = "{$langUpper}_LNG";
483 $alias = "{$langUpper}_LANG";
484 $fieldAlias = "{$tbl}.PHRASE";
485
486 /*
487 $runtime[] = new Main\ORM\Fields\Relations\Reference(
488 $tbl,
489 Index\Internals\PhraseIndexTable::class,
490 Main\ORM\Query\Join::on('ref.PATH_ID', '=', 'this.PATH_ID')
491 ->whereColumn('ref.CODE', '=', 'this.CODE')
492 ->where('ref.LANG_ID', '=', $langId),
493 ['join_type' => 'INNER']
494 );
495 */
496
497 $i = isset($runtimeInx[$tbl]) ? $runtimeInx[$tbl] : count($runtime);
498
499 $runtime[$i] = new Main\ORM\Fields\Relations\Reference(
500 $tbl,
501 Index\Internals\PhraseFts::getFtsEntityClass($langId),
502 Main\ORM\Query\Join::on('ref.PATH_ID', '=', 'this.PATH_ID')
503 ->whereColumn('ref.CODE', '=', 'this.CODE'),
504 ['join_type' => 'INNER']
505 );
506
507 if (!isset($runtimeInx[$alias]))
508 {
509 $select[$alias] = $fieldAlias;
510 }
511 $select["{$langUpper}_FILE_ID"] = "{$tbl}.FILE_ID";
512
513 $exact = \in_array(self::SEARCH_METHOD_EXACT, $filterIn['PHRASE_ENTRY']);
514 $entry = \in_array(self::SEARCH_METHOD_ENTRY_WORD, $filterIn['PHRASE_ENTRY']);
515 $case = \in_array(self::SEARCH_METHOD_CASE_SENSITIVE, $filterIn['PHRASE_ENTRY']);
516 $start = \in_array(self::SEARCH_METHOD_START_WITH, $filterIn['PHRASE_ENTRY']);
517 $end = \in_array(self::SEARCH_METHOD_END_WITH, $filterIn['PHRASE_ENTRY']);
518 $equal = \in_array(self::SEARCH_METHOD_EQUAL, $filterIn['PHRASE_ENTRY']);
519
520 if ($exact)
521 {
522 $phraseSearch = ["={$fieldAlias}" => $filterIn['PHRASE_TEXT']];
523 }
524 else
525 {
526 $sqlHelper = Main\Application::getConnection()->getSqlHelper();
527 $str = $sqlHelper->forSql($filterIn['PHRASE_TEXT']);
528
529 $phraseSearch = [
530 'LOGIC' => 'AND'
531 ];
532
533 // use fulltext index to help like operator
534 // todo: preg_replace has bug when replaces Thai unicode Non-spacing mark
535 if (!self::disallowFtsIndex($langId))
536 {
537 $minLengthFulltextWorld = self::getFullTextMinLength();
538 $fulltextIndexSearchStr = self::prepareTextForFulltextSearch($filterIn['PHRASE_TEXT']);
539 if (\mb_strlen($fulltextIndexSearchStr) > $minLengthFulltextWorld)
540 {
541 /*
542 if ($entry)
543 {
544 // identical full text match
545 // MATCH(PHRASE) AGAINST ('+smth' IN BOOLEAN MODE)
546 //$phraseSearch["*={$fieldAlias}"] = $fulltextIndexSearchStr;
547 $fulltextIndexSearchStr = "+" . \preg_replace("/\s+/i" . \BX_UTF_PCRE_MODIFIER, " +", $fulltextIndexSearchStr);
548 $runtime[] = (new ExpressionField(
549 'PHRASE_FTS',
550 "MATCH (%s) AGAINST ('({$fulltextIndexSearchStr})')",
551 "{$fieldAlias}"
552 ))->configureValueType(FloatField::class);
553 }
554 else
555 {
556 // use fulltext index to help like operator
557 // partial full text match
558 // MATCH(PHRASE) AGAINST ('+smth*' IN BOOLEAN MODE)
559 //$phraseSearch["*{$fieldAlias}"] = $fulltextIndexSearchStr;
560 $fulltextIndexSearchStr = "+" . \preg_replace("/\s+/i" . \BX_UTF_PCRE_MODIFIER, "* +", $fulltextIndexSearchStr) . "*";
561 $runtime[] = (new ExpressionField(
562 'PHRASE_FTS',
563 "MATCH (%s) AGAINST ('({$fulltextIndexSearchStr})')",
564 "{$fieldAlias}"
565 ))->configureValueType(FloatField::class);
566 }
567 $phraseSearch[">PHRASE_FTS"] = 0;
568 */
569
570 if ($entry)
571 {
572 // identical full text match
573 // MATCH(PHRASE) AGAINST ('+smth' IN BOOLEAN MODE)
574 $phraseSearch["*={$fieldAlias}"] = $fulltextIndexSearchStr;
575 }
576 else
577 {
578 // use fulltext index to help like operator
579 // partial full text match
580 // MATCH(PHRASE) AGAINST ('+smth*' IN BOOLEAN MODE)
581 $phraseSearch["*{$fieldAlias}"] = $fulltextIndexSearchStr;
582 }
583 }
584 }
585
586 if ($equal)
587 {
588 $likeStr = "{$str}";
589 }
590 elseif ($start)
591 {
592 $likeStr = "{$str}%%";
593 }
594 elseif ($end)
595 {
596 $likeStr = "%%{$str}";
597 }
598 elseif ($entry)
599 {
600 $likeStr = "%%{$str}%%";
601 }
602 elseif (self::disallowFtsIndex($langId))
603 {
604 //todo: preg_replace has bug when replaces Thai unicode Non-spacing mark
605 $likeStr = "%%" . \preg_replace("/\s+/i" . \BX_UTF_PCRE_MODIFIER, "%%", $str) . "%%";
606 }
607 else
608 {
609 $likeStr = "%%" . \preg_replace("/\W+/i" . \BX_UTF_PCRE_MODIFIER, "%%", $str) . "%%";
610 }
611
612 if (self::allowICURegularExpression())
613 {
614 $regStr = \preg_replace("/\s+/i" . \BX_UTF_PCRE_MODIFIER, '[[:blank:]]+', $str);
615 }
616 else
617 {
618 if ($case)
619 {
620 $regStr = \preg_replace("/\s+/i" . \BX_UTF_PCRE_MODIFIER, '[[:blank:]]+', $str);
621 }
622 else
623 {
624 $regStr = '';
625 $regChars = ['?', '*', '|', '[', ']', '(', ')', '-', '+', '.'];
626 for ($p = 0, $len = Translate\Text\StringHelper::getLength($str); $p < $len; $p++)
627 {
628 $c0 = Translate\Text\StringHelper::getSubstring($str, $p, 1);
629 if (\in_array($c0, $regChars))
630 {
631 $regStr .= "\\\\" . $c0;
632 continue;
633 }
634 $c1 = Translate\Text\StringHelper::changeCaseToLower($c0);
635 $c2 = Translate\Text\StringHelper::changeCaseToUpper($c0);
636 if ($c0 != $c1)
637 {
638 $regStr .= '(' . $c0 . '|' . $c1 . '){1}';
639 }
640 elseif ($c0 != $c2)
641 {
642 $regStr .= '(' . $c0 . '|' . $c2 . '){1}';
643 }
644 else
645 {
646 $regStr .= $c0;
647 }
648 }
649 $regStr = \preg_replace("/\s+/i" . \BX_UTF_PCRE_MODIFIER, '[[:blank:]]+', $regStr);
650 }
651 }
652
653 $regExpStart = '';
654 $regExpEnd = '';
655 if (\preg_match("/^[[:alnum:]]+/i" . \BX_UTF_PCRE_MODIFIER, $str))
656 {
657 if (self::allowICURegularExpression())
658 {
659 $regExpStart = '\\\\b';
660 }
661 else
662 {
663 $regExpStart = '[[:<:]]';
664 }
665 }
666 if (\preg_match("/[[:alnum:]]+$/i" . \BX_UTF_PCRE_MODIFIER, $str))
667 {
668 if (self::allowICURegularExpression())
669 {
670 $regExpEnd = '\\\\b';
671 }
672 else
673 {
674 $regExpEnd = '[[:>:]]';
675 }
676 }
677
678 // Exact word match
679 if ($equal)
680 {
681 $regStr = "[[:blank:]]*{$regExpStart}({$regStr}){$regExpEnd}[[:blank:]]*";
682 }
683 elseif ($start)
684 {
685 $regStr = "[[:blank:]]*{$regExpStart}({$regStr}){$regExpEnd}";
686 }
687 elseif ($end)
688 {
689 $regStr = "{$regExpStart}({$regStr}){$regExpEnd}[[:blank:]]*";
690 }
691 elseif ($entry)
692 {
693 $regStr = "[[:blank:]]*{$regExpStart}({$regStr}){$regExpEnd}[[:blank:]]*";
694 }
695
696 // regexp binary mode works not exactly we want using like binary to fix it
697 $binarySensitive = $case ? 'BINARY' : '';
698 $runtime[] = (new ExpressionField(
699 'PHRASE_LIKE',
700 "CASE WHEN %s LIKE {$binarySensitive} '{$likeStr}' THEN 1 ELSE 0 END",
701 "{$fieldAlias}"
702 ))->configureValueType(IntegerField::class);
703 $phraseSearch["=PHRASE_LIKE"] = 1;
704
705 if (self::allowICURegularExpression())
706 {
707 // c meaning case-sensitive matching
708 // i meaning case-insensitive matching
709 $regCaseSensitive = $case ? 'c' : 'i';
710 $runtime[] = (new ExpressionField(
711 'PHRASE_REGEXP',
712 "REGEXP_LIKE(%s, '{$regStr}', '{$regCaseSensitive}')",
713 "{$fieldAlias}"
714 ))->configureValueType(IntegerField::class);
715 }
716 else
717 {
718 $runtime[] = (new ExpressionField(
719 'PHRASE_REGEXP',
720 "CASE WHEN %s REGEXP '{$regStr}' THEN 1 ELSE 0 END",
721 "{$fieldAlias}"
722 ))->configureValueType(IntegerField::class);
723 }
724 $phraseSearch["=PHRASE_REGEXP"] = 1;
725 }
726
727 $filterOut[] = $phraseSearch;
728 }
729 unset($filterIn['PHRASE_ENTRY'], $filterIn['PHRASE_TEXT'], $filterIn['LANGUAGE_ID']);
730
731
732 if (!empty($filterIn['FILE_NAME']))
733 {
734 $filterOut["=%PATH.NAME"] = '%'. $filterIn['FILE_NAME']. '%';
735 unset($filterIn['FILE_NAME']);
736 }
737 if (!empty($filterIn['FOLDER_NAME']))
738 {
739 $filterOut['=%PATH.PATH'] = '%/'. $filterIn['FOLDER_NAME']. '/%';
740 unset($filterIn['FOLDER_NAME']);
741 }
742
743 foreach ($filterIn as $key => $value)
744 {
745 if (\in_array($key, ['tabId', 'FILTER_ID', 'PRESET_ID', 'FILTER_APPLIED', 'FIND']))
746 {
747 continue;
748 }
749 $filterOut[$key] = $value;
750 }
751
752 return [$select, $runtime, $filterOut];
753 }
754
761 public static function disallowFtsIndex(string $langId): bool
762 {
763 static $cache;
764 if (empty($cache))
765 {
766 $cache = [];
767
768 $iterator = Main\Localization\LanguageTable::getList([
769 'select' => ['ID', 'CODE'],
770 'filter' => ['=ACTIVE' => 'Y'],
771 ]);
772 while ($row = $iterator->fetch())
773 {
774 if (!empty($row['CODE']))
775 {
776 $cache[mb_strtolower($row['ID'])] = trim(mb_strtolower($row['CODE']));
777 }
778 }
779 }
780
781 return isset($cache[$langId]) && in_array($cache[$langId], self::NON_FTS);
782 }
783
790 protected static function allowICURegularExpression(): bool
791 {
792 static $allowICURE;
793 if ($allowICURE === null)
794 {
795 $majorVersion = \mb_substr(Application::getConnection()->getVersion()[0], 0, 1);
796 $allowICURE = (int)$majorVersion >= 8;
797 }
798
799 return $allowICURE;
800 }
801
809 public static function prepareTextForFulltextSearch(string $text): string
810 {
811 $minLengthFulltextWorld = self::getFullTextMinLength();
812
813 $text = \preg_replace("/\b\w{1,{$minLengthFulltextWorld}}\b/i".\BX_UTF_PCRE_MODIFIER, '', $text);
814
815 $stopWorlds = self::getFullTextStopWords();
816 foreach ($stopWorlds as $stopWorld)
817 {
818 $text = \preg_replace("/\b{$stopWorld}\b/i".\BX_UTF_PCRE_MODIFIER, '', $text);
819 }
820
821 $text = \preg_replace("/^\W+/i".\BX_UTF_PCRE_MODIFIER, '', $text);
822 $text = \preg_replace("/\W+$/i".\BX_UTF_PCRE_MODIFIER, '', $text);
823 $text = \preg_replace("/\W+/i".\BX_UTF_PCRE_MODIFIER, ' ', $text);
824
825 return $text;
826 }
827
832 protected static function isInnodbEngine(): bool
833 {
834 static $available;
835 if ($available === null)
836 {
837 $available = false;
838 $cache = Cache::createInstance();
839 if ($cache->initCache(3600, 'translate::isInnodbEngine'))
840 {
841 $available = (bool)$cache->getVars();
842 }
843 elseif ($cache->startDataCache())
844 {
845 try
846 {
847 $check = Application::getConnection()->query(
848 "SHOW TABLE STATUS WHERE Name = 'b_translate_phrase' AND Engine = 'InnoDB'"
849 );
850 if ($check->fetch())
851 {
852 $available = true;
853 }
854 }
855 catch (SqlQueryException $exception)
856 {}
857 $cache->endDataCache((int)$available);
858 }
859 }
860
861 return $available;
862 }
863
864
870 protected static function getFullTextStopWords(): array
871 {
872 static $worldList;
873 if ($worldList === null)
874 {
875 $minLengthFulltextWorld = self::getFullTextMinLength();
876 $worldList = [];
877 $cache = Cache::createInstance();
878 if ($cache->initCache(3600, 'translate::FullTextStopWords'))
879 {
880 $worldList = $cache->getVars();
881 }
882 elseif ($cache->startDataCache())
883 {
884 try
885 {
886 if (self::isInnodbEngine())
887 {
888 $res = Application::getConnection()->query(
889 "SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD"
890 );
891 while ($row = $res->fetch())
892 {
893 if (mb_strlen($row['value']) > $minLengthFulltextWorld)
894 {
895 $worldList[] = $row['value'];
896 }
897 }
898 }
899 }
900 catch (SqlQueryException $exception)
901 {}
902 $cache->endDataCache($worldList);
903 }
904 }
905
906 return $worldList;
907 }
908
916 public static function getFullTextMinLength(): int
917 {
918 static $fullTextMinLength;
919 if ($fullTextMinLength === null)
920 {
921 $fullTextMinLength = 4;
922 $cache = Cache::createInstance();
923 if ($cache->initCache(3600, 'translate::FullTextMinLength'))
924 {
925 $fullTextMinLength = $cache->getVars();
926 }
927 elseif ($cache->startDataCache())
928 {
929 if (self::isInnodbEngine())
930 {
931 $var = 'innodb_ft_min_token_size';
932 }
933 else
934 {
935 $var = 'ft_min_word_len';
936 }
937 try
938 {
939 $res = Application::getConnection()->query("SHOW VARIABLES LIKE '{$var}'");
940 if ($row = $res->fetch())
941 {
942 $fullTextMinLength = (int)$row['Value'];
943 }
944 }
945 catch (SqlQueryException $exception)
946 {}
947 $cache->endDataCache($fullTextMinLength);
948 }
949 }
950
951 return $fullTextMinLength;
952 }
953}
static getConnection($name="")