register_hook('TPL_CONTENT_DISPLAY', 'BEFORE', $this, 'handle_act_unknown'); } public function handle_act_unknown(\dokuwiki\Extension\Event $event, $param) { global $ACT, $QUERY; if ($ACT !== 'search') return; $event->stopPropagation(); $event->preventDefault(); $this->_search($QUERY, (int)($_REQUEST['start'] ?? 0)); } private function _search($query, $start) { $search = new SphinxSearch($this->getConf('host'), $this->getConf('port'), $this->getConf('index')); $maxResults = (int)$this->getConf('maxresults') ?: 10; $search->setSnippetSize((int)$this->getConf('snippetsize') ?: 512); $search->setTitlePriority((int)$this->getConf('weight_title') ?: 10); $search->setPagenamePriority((int)$this->getConf('weight_pagename') ?: 8); $search->setNamespacePriority((int)$this->getConf('weight_namespace') ?: 5); $search->setBodyPriority((int)$this->getConf('weight_body') ?: 1); $keywords = sphinx_getKeywords($query); $cats = sphinx_getCategories($query); if (empty($keywords)) { echo "No keywords."; return; } $searchData = sphinx_executeMultiPassSearch($search, $keywords, $cats, $start, $maxResults); $res = $searchData['res']; $total = $searchData['total']; $finalQuery = $searchData['finalQuery']; $passUsed = $searchData['passUsed']; $this->_sObj = $search; $rawPages = $search->getPages($keywords); $pages = is_array($rawPages) ? $rawPages : []; $docsCount = count($pages); if (!empty($pages)) { // Step 1: Sort all 59 pages instantly based on lightweight metadata $pages = sphinx_sortPagesByLevenshtein($pages, $keywords); // Step 2: Slice the list down to just the 10 results for the current page $pages = array_slice($pages, 0, $maxResults, true); // Step 3: Lazy-load rich media snippets ONLY for the 10 displayed results $cleanK = $search->removeStars($keywords); foreach ($pages as $id => $row) { $pages[$id]['bodyExcerpt'] = $search->generateSingleSnippet($row['page'], $cleanK); } } $this->_dom = new DOMDocument('1.0', 'UTF-8'); $root = $this->_dom->createElement('div'); $this->_dom->appendChild($root); if ((int)$this->getConf('search_debug') === 1) { $db = $this->_dom->createElement('div'); $db->setAttribute('style', 'background:#f4f4f4; border:1px solid #ccc; padding:8px; margin:10px 0; font:11px monospace; color:#444; line-height:1.4;'); $infoStr = sprintf( "Query: [%s] | Target: [%s] | Strategy: %s | Matches: %d | Docs Passed: %d%s", $keywords, $finalQuery, $passUsed, $total, $docsCount, $search->getError() ? " | Error: " . $search->getError() : "" ); $db->appendChild($this->_dom->createTextNode($infoStr)); $root->appendChild($db); } if ($search->getError()) { echo $this->_dom->saveHTML($root); return; } if (!$res || empty($pages) || $total == 0) { $root->appendChild($this->_dom->createElement('div', 'No results.')); echo $this->_dom->saveHTML($root); return; } $h1 = $this->_dom->createElement('h1', "Found $total matches for \"" . hsc($query) . "\""); $root->appendChild($h1); $container = $this->_dom->createElement('div'); $container->setAttribute('class', 'sphinx_search_container'); $root->appendChild($container); $sidebar = $this->_dom->createElement('div'); $sidebar->setAttribute('class', 'search_sidebar'); ob_start(); if (function_exists('printNamespacesNew')) printNamespacesNew($this->_getMatchingPagenames($keywords, $cats)); $this->_appendRawHTML($sidebar, ob_get_clean()); $container->appendChild($sidebar); $container->appendChild($this->_dom->createElement('h2', "Matching keywords")); $list = $this->_dom->createElement('div'); $list->setAttribute('class', 'search_result_list'); $container->appendChild($list); foreach ($pages as $row) { $list->appendChild($this->_createResultNode($row, $keywords)); } $this->_addNumberedPagination($container, $query, $start, $total, $maxResults); echo $this->_dom->saveHTML($root); } private function _createResultNode($row, $keywords) { $div = $this->_dom->createElement('div'); $div->setAttribute('class', 'search_result_row'); $a = $this->_dom->createElement('a', hsc($row['titleTextExcerpt'])); $a->setAttribute('class', 'title'); $a->setAttribute('href', wl($row['page'])); $div->appendChild($a); $snippet = $this->_dom->createElement('div'); $snippet->setAttribute('class', 'search_snippet'); $this->_appendRawHTML($snippet, $row['bodyExcerpt']); $div->appendChild($snippet); $nmsp = $this->_dom->createElement('span'); $nmsp->setAttribute('class', 'search_nmsp'); $nsLinks = getNsLinks($row['page'], $keywords, $this->_sObj); if (is_array($nsLinks)) { foreach ($nsLinks as $i => $n) { if ($i > 0) $nmsp->appendChild($this->_dom->createTextNode(' : ')); $na = $this->_dom->createElement('a', hsc($n['title'])); $na->setAttribute('href', wl($n['link'])); $nmsp->appendChild($na); } } $div->appendChild($nmsp); return $div; } private function _addNumberedPagination($parent, $query, $start, $total, $perPage) { $nav = $this->_dom->createElement('div'); $nav->setAttribute('class', 'sphinxsearch_pagination'); $totalPages = (int)ceil($total / $perPage); $currentPage = (int)floor($start / $perPage) + 1; $range = (int)$this->getConf('pagination_range') ?: 4; $pageStart = max(1, $currentPage - $range); $pageEnd = min($totalPages, $currentPage + $range); if ($currentPage > 1) $this->_addPageLink($nav, $query, ($currentPage - 2) * $perPage, '« Prev', 'page_box prev'); for ($i = $pageStart; $i <= $pageEnd; $i++) { $this->_addPageLink($nav, $query, ($i - 1) * $perPage, $i, ($i == $currentPage) ? 'page_box active' : 'page_box'); } if ($currentPage < $totalPages) $this->_addPageLink($nav, $query, $currentPage * $perPage, 'Next »', 'page_box next'); $parent->appendChild($nav); } private function _addPageLink($parent, $query, $startValue, $label, $class) { $a = $this->_dom->createElement('a', $label); $a->setAttribute('href', wl('', ['do'=>'search', 'id'=>$query, 'start'=>$startValue])); $a->setAttribute('class', $class); $parent->appendChild($a); } private function _appendRawHTML($node, $html) { if (empty($html)) return; $fragment = $this->_dom->createDocumentFragment(); if (@$fragment->appendXML(' ' . $html)) { if ($fragment->firstChild) $fragment->removeChild($fragment->firstChild); $node->appendChild($fragment); } else { $node->appendChild($this->_dom->createTextNode(strip_tags($html))); } } private function _getMatchingPagenames($kw, $cat) { $this->_sObj->setSearchOnlyPagename(); if (!empty($cat)) $this->_sObj->setSearchAllQueryWithCategoryFilter($kw, $cat); $sidebarLimit = (int)$this->getConf('sidebar_limit') ?: 10; if (!$this->_sObj->search(0, $sidebarLimit)) return []; $m = []; $ids = $this->_sObj->getPagesIds(); if (is_array($ids)) { foreach ($ids as $p) $m[$p['page']] = $p['hid']; } return $m; } }