24 private const FIELD_DELIMITER =
"\x7F";
25 private const ROW_DELIMITER =
"\n";
32 private $messageGroupFactory;
34 private $namespaceInfo;
36 private $messageIndex;
40 private $titleFormatter;
42 public function __construct(
43 WANObjectCache $cache,
46 NamespaceInfo $namespaceInfo,
48 TitleParser $titleParser,
49 TitleFormatter $titleFormatter
51 $this->cache = $cache;
52 $this->collation = $collation;
53 $this->messageGroupFactory = $messageGroupFactory;
54 $this->namespaceInfo = $namespaceInfo;
55 $this->messageIndex = $messageIndex;
56 $this->titleParser = $titleParser;
57 $this->titleFormatter = $titleFormatter;
60 public function searchStaticMessageGroups(
string $query,
int $maxResults ): array {
61 $cache = $this->cache;
64 $key = $cache->makeKey(
'Translate',
'EntitySearch',
'static-groups' );
65 $haystack = $cache->getWithSetCallback(
67 ExpirationAwareness::TTL_WEEK,
69 return $this->getStaticMessageGroupsHaystack();
73 'checkKeys' => [ $this->messageGroupFactory->getCacheKey() ],
75 'pcTTL' => ExpirationAwareness::TTL_PROC_LONG
84 $delimiter = self::FIELD_DELIMITER;
85 $anything =
"[^$delimiter\n]";
86 $query = preg_quote( $query,
'/' );
88 $pattern =
"/^($query$anything*)$delimiter($anything+)$/miu";
89 preg_match_all( $pattern, $haystack, $matches, PREG_SET_ORDER );
90 foreach ( $matches as [ , $label, $groupId ] ) {
92 $results[$groupId] = [
97 if ( count( $results ) >= $maxResults ) {
98 return array_values( $results );
103 $pattern =
"/^($anything*\b$query$anything*)$delimiter($anything+)$/miu";
104 preg_match_all( $pattern, $haystack, $matches, PREG_SET_ORDER );
105 foreach ( $matches as [ , $label, $groupId ] ) {
106 $results[$groupId] = [
111 if ( count( $results ) >= $maxResults ) {
112 return array_values( $results );
116 return array_values( $results );
119 public function searchMessages(
string $query,
int $maxResults ): array {
126 $cache = $this->cache;
127 $key = $cache->makeKey(
'Translate',
'EntitySearch',
'messages' );
128 $haystack = $cache->getWithSetCallback(
130 ExpirationAwareness::TTL_WEEK,
131 function ():
string {
135 return $this->getMessagesHaystack();
139 'checkKeys' => [ $this->messageIndex->getStatusCacheKey() ],
141 'pcTTL' => ExpirationAwareness::TTL_PROC_LONG
151 $rowDelimiter = self::ROW_DELIMITER;
152 $anything =
"[^$rowDelimiter]";
153 $query = preg_quote( $query,
'/' );
156 $pattern =
"/^($anything*\b$query)$anything*$/miu";
157 preg_match_all( $pattern, $haystack, $matches, PREG_SET_ORDER );
158 $previousPrefixMatch =
null;
159 foreach ( $matches as [ $full, $prefixMatch ] ) {
162 if ( count( $results ) >= $maxResults && $previousPrefixMatch !== $prefixMatch ) {
166 if ( $full === $prefixMatch ) {
167 $results[$full] = [ $full, 1,
true, $full ];
169 if ( !isset( $results[
"$prefixMatch*"] ) ) {
170 $results[
"$prefixMatch*"] = [
"$prefixMatch*", 0,
false, $full ];
172 $results[
"$prefixMatch*"][1]++;
174 $previousPrefixMatch = $prefixMatch;
178 foreach ( $results as $index => [ $label, $count, $isExact, $full ] ) {
179 if ( $count === 1 && !$isExact ) {
180 $results[$index][0] = $full;
185 foreach ( $results as &$value ) {
187 $title = $this->titleParser->parseTitle( $value[0] );
188 $label = $this->titleFormatter->getPrefixedText( $title );
189 }
catch ( MalformedTitleException $e ) {
198 return array_values( $results );
201 private function getStaticMessageGroupsHaystack():
string {
202 $groups = $this->messageGroupFactory->getGroups();
203 $data =
new SplMinHeap();
204 foreach ( $groups as $group ) {
205 $label = $group->getLabel();
207 $label = strtr( $label, [ self::FIELD_DELIMITER =>
'', self::ROW_DELIMITER =>
'' ] );
208 $sortKey = $this->collation->getSortKey( $label );
211 $data->insert( [ $sortKey, $label, $group->getId() ] );
215 foreach ( $data as [ , $label, $groupId ] ) {
216 $haystack .= $label . self::FIELD_DELIMITER . $groupId . self::ROW_DELIMITER;
222 private function getMessagesHaystack():
string {
224 $data =
new SplMinHeap();
225 $keys = $this->messageIndex->getKeys();
226 foreach ( $keys as $key ) {
228 $key = strtr( $key, [
'_' =>
' ' ] );
230 [ $namespaceId, $label ] = explode(
':', $key, 2 );
231 if ( !isset( $namespaceMap[$namespaceId] ) ) {
232 $namespaceMap[$namespaceId] = $this->namespaceInfo->getCanonicalName( (
int)$namespaceId );
234 $label = $namespaceMap[$namespaceId] .
":$label";
237 $label = strtr( $label, [ self::ROW_DELIMITER =>
'' ] );
238 $sortKey = $this->collation->getSortKey( $label );
239 $data->insert( [ $sortKey, $label ] );
243 foreach ( $data as [ , $label ] ) {
244 $haystack .= $label . self::ROW_DELIMITER;