_host = $host; $this->_port = $port; $this->_index = $index; $this->_sphinx = new SphinxClient(); $this->_sphinx->SetServer($this->_host, $this->_port); $this->_sphinx->SetMatchMode(SPH_MATCH_EXTENDED2); } public function setSnippetSize(int $size): void { if ($size > 0) $this->_snippetSize = $size; } public function setIndexTags(string $tags): void { } public function search(int $start, int $resultsPerPage = 10): bool { $this->_resultsPerPage = $resultsPerPage; $this->_sphinx->SetFieldWeights([ 'namespace' => $this->_namespacePriority, 'pagename' => $this->_pagenamePriority, 'title' => $this->_titlePriority, 'body' => $this->_bodyPriority ]); $this->_sphinx->SetLimits($start, 1000); $res = $this->_sphinx->Query($this->_query, $this->_index); $this->_result = $res ?: []; return !empty($this->_result['matches']); } /** * Optimized: Returns raw page metadata instantly without parsing any text snippets */ public function getPages(string $keywords): array|false { if (empty($this->_result['matches'])) return false; $ids = $this->getPagesIds(); $results = []; foreach ($ids as $id => $data) { $this->_offset++; if (auth_quickaclcheck($data['page']) >= AUTH_READ) { $results[$id] = [ 'page' => $data['page'], 'titleTextExcerpt' => !empty($data['title_text']) ? $data['title_text'] : $data['page'], 'hid' => $data['hid'] ?? '', 'bodyExcerpt' => '' // Left empty intentionally for Phase 2 lazy-loading ]; } } return $results; } /** * Lazy Loader: Public access point to generate a single rich media snippet on demand */ public function generateSingleSnippet(string $pageId, string $query): string { $rawText = rawWiki($pageId); if (empty($rawText)) return ''; $words = preg_split('/\s+/', $query, -1, PREG_SPLIT_NO_EMPTY); $highlightTargets = []; foreach ($words as $word) { $highlightTargets[] = $word; $collapsed = preg_replace('/(.)\1/u', '$1', $word); if ($collapsed !== $word) { $highlightTargets[] = $collapsed; } } $highlightTargets = array_unique($highlightTargets); $pos = false; foreach ($highlightTargets as $word) { if (($pos = mb_stripos($rawText, $word)) !== false) { break; } } if ($pos !== false) { $start = max(0, $pos - 150); $windowLength = 400; $snippetWiki = mb_substr($rawText, $start, $windowLength); $openBraces = mb_substr_count($snippetWiki, '{{'); $closeBraces = mb_substr_count($snippetWiki, '}}'); if ($openBraces > $closeBraces) { $tail = mb_substr($rawText, $start + $windowLength, 120); if (($bracePos = mb_strpos($tail, '}}')) !== false) { $snippetWiki .= mb_substr($tail, 0, $bracePos + 2); } } $openCode = mb_substr_count($snippetWiki, ''); if ($openCode > $closeCode) { $tail = mb_substr($rawText, $start + $windowLength, 500); if (($codePos = mb_strpos($tail, '')) !== false) { $snippetWiki .= mb_substr($tail, 0, $codePos + 7); } } $info = []; $rendered = '... ' . p_render('xhtml', p_get_instructions($snippetWiki), $info) . ' ...'; } else { $snippetWiki = mb_substr($rawText, 0, 300); $info = []; $rendered = '... ' . p_render('xhtml', p_get_instructions($snippetWiki), $info) . ' ...'; } $rendered = preg_replace('/^(\s*

\s*\.\.\.)/i', '...', $rendered); foreach ($highlightTargets as $word) { if (mb_strlen($word) < 2) continue; $q = preg_quote(hsc($word), '/'); $rendered = preg_replace("/(?![^<]*>)$q/iu", '$0', $rendered); } return $rendered; } public function getExcerpt(array $data, string $query): array { $words = preg_split('/\s+/', $this->removeStars($query), -1, PREG_SPLIT_NO_EMPTY); $highlightTargets = []; foreach ($words as $word) { $highlightTargets[] = $word; $collapsed = preg_replace('/(.)\1/u', '$1', $word); if ($collapsed !== $word) { $highlightTargets[] = $collapsed; } } $highlightTargets = array_unique($highlightTargets); $res = []; foreach ($data as $text) { $out = hsc($text); foreach ($highlightTargets as $word) { if (mb_strlen($word) < 2) continue; $q = preg_quote(hsc($word), '/'); $out = preg_replace("/($q)/iu", '$1', $out); } $res[] = $out; } return $res; } public function getPagesIds(): array { return (new PageMapper())->getByCrc(array_keys($this->_result['matches'] ?? [])); } public function removeStars(string $query): string { return trim(str_replace('*', '', $query)); } public function starQuery(string $query): string { $words = preg_split('/\s+/', $this->removeStars($query), -1, PREG_SPLIT_NO_EMPTY); $starred = []; foreach ($words as $w) $starred[] = (str_starts_with($w, '-') || mb_strlen($w) < 3 || str_contains($w, '"')) ? $w : "*$w*"; return implode(" ", $starred); } public function getOffset(): int { return $this->_offset; } public function getError(): string { return $this->_sphinx->GetLastError(); } public function getTotalFound(): int { return (int)($this->_result['total_found'] ?? 0); } public function setNamespacePriority(int $p): void { $this->_namespacePriority = $p; } public function setPagenamePriority(int $p): void { $this->_pagenamePriority = $p; } public function setSearchOnlyPagename(): void { $this->_titlePriority = 0; $this->_bodyPriority = 0; } public function setTitlePriority(int $p): void { $this->_titlePriority = $p; } public function setBodyPriority(int $p): void { $this->_bodyPriority = $p; } public function setSearchAllQuery(string $k, string $c): void { $this->_query = $k; } public function setSearchAllQueryWithCategoryFilter(string $k, string $c): void { $cat = $this->_sphinx->EscapeString($c); $f = str_starts_with($c, "-") ? '-"' . substr($cat, 1) . '"' : '"' . $cat . '"'; $this->_query = "(@(namespace,pagename) $f) & ($k)"; } }