1<?php 2/** 3 * Action Plugin for SphinxSearch - Full DOM Generation Version 4 */ 5 6if(!defined('DOKU_INC')) die(); 7 8require_once(__DIR__ . '/sphinxapi.php'); 9require_once(__DIR__ . '/PageMapper.php'); 10require_once(__DIR__ . '/SphinxSearch.php'); 11require_once(__DIR__ . '/functions.php'); 12 13class action_plugin_sphinxsearchwas extends DokuWiki_Action_Plugin { 14 15 private $_sObj = null; 16 private $_dom = null; 17 18 public function register(\dokuwiki\Extension\EventHandler $controller) { 19 $controller->register_hook('TPL_CONTENT_DISPLAY', 'BEFORE', $this, 'handle_act_unknown'); 20 } 21 22 public function handle_act_unknown(\dokuwiki\Extension\Event $event, $param) { 23 global $ACT, $QUERY; 24 if ($ACT !== 'search') return; 25 $event->stopPropagation(); $event->preventDefault(); 26 $this->_search($QUERY, (int)($_REQUEST['start'] ?? 0)); 27 } 28 29 private function _search($query, $start) { 30 $search = new SphinxSearch($this->getConf('host'), $this->getConf('port'), $this->getConf('index')); 31 $maxResults = (int)$this->getConf('maxresults') ?: 10; 32 $search->setSnippetSize((int)$this->getConf('snippetsize') ?: 512); 33 34 $keywords = $this->_getKeywords($query); 35 $cats = $this->_getCategories($query); 36 if (empty($keywords)) { echo "No keywords."; return; } 37 38 if (empty($cats)) $search->setSearchAllQuery($keywords, ''); 39 else $search->setSearchAllQueryWithCategoryFilter($keywords, $cats); 40 41 $res = $search->search($start, $maxResults); 42 $this->_sObj = $search; 43 44 if ($search->getError()) { echo "Sphinx Error: " . hsc($search->getError()); return; } 45 46 $pages = $search->getPages($keywords); 47 $total = $search->getTotalFound(); 48 if (!$res || empty($pages) || $total == 0) { echo "No results."; return; } 49 50 $this->_dom = new DOMDocument('1.0', 'UTF-8'); 51 $root = $this->_dom->createElement('div'); 52 $this->_dom->appendChild($root); 53 54 $h1 = $this->_dom->createElement('h1', "Found $total matches for \"" . hsc($query) . "\""); 55 $root->appendChild($h1); 56 57 $container = $this->_dom->createElement('div'); 58 $container->setAttribute('class', 'sphinx_search_container'); 59 $root->appendChild($container); 60 61 $sidebar = $this->_dom->createElement('div'); 62 $sidebar->setAttribute('class', 'search_sidebar'); 63 ob_start(); 64 if (function_exists('printNamespacesNew')) printNamespacesNew($this->_getMatchingPagenames($keywords, $cats)); 65 $this->_appendRawHTML($sidebar, ob_get_clean()); 66 $container->appendChild($sidebar); 67 68 $h2 = $this->_dom->createElement('h2', "Matching keywords"); 69 $container->appendChild($h2); 70 71 $list = $this->_dom->createElement('div'); 72 $list->setAttribute('class', 'search_result_list'); 73 $container->appendChild($list); 74 75 foreach ($pages as $row) { 76 $list->appendChild($this->_createResultNode($row, $keywords)); 77 } 78 79 $this->_addNumberedPagination($container, $query, $start, $total, $maxResults); 80 echo $this->_dom->saveHTML($root); 81 } 82 83 private function _createResultNode($row, $keywords) { 84 $div = $this->_dom->createElement('div'); 85 $div->setAttribute('class', 'search_result_row'); 86 $a = $this->_dom->createElement('a', hsc($row['titleTextExcerpt'])); 87 $a->setAttribute('class', 'title'); 88 $a->setAttribute('href', wl($row['page'])); 89 $div->appendChild($a); 90 91 $snippet = $this->_dom->createElement('div'); 92 $snippet->setAttribute('class', 'search_snippet'); 93 $this->_appendRawHTML($snippet, $row['bodyExcerpt']); 94 $div->appendChild($snippet); 95 96 $nmsp = $this->_dom->createElement('span'); 97 $nmsp->setAttribute('class', 'search_nmsp'); 98 foreach (getNsLinks($row['page'], $keywords, $this->_sObj) as $i => $n) { 99 if ($i > 0) $nmsp->appendChild($this->_dom->createTextNode(' : ')); 100 $na = $this->_dom->createElement('a', hsc($n['title'])); 101 $na->setAttribute('href', wl($n['link'])); 102 $nmsp->appendChild($na); 103 } 104 $div->appendChild($nmsp); 105 return $div; 106 } 107 108 private function _addNumberedPagination($parent, $query, $start, $total, $perPage) { 109 $nav = $this->_dom->createElement('div'); 110 $nav->setAttribute('class', 'sphinxsearch_pagination'); 111 $totalPages = (int)ceil($total / $perPage); 112 $currentPage = (int)floor($start / $perPage) + 1; 113 $range = 4; 114 $pageStart = max(1, $currentPage - $range); 115 $pageEnd = min($totalPages, $currentPage + $range); 116 117 if ($currentPage > 1) $this->_addPageLink($nav, $query, ($currentPage - 2) * $perPage, '« Prev', 'page_box prev'); 118 for ($i = $pageStart; $i <= $pageEnd; $i++) { 119 $class = ($i == $currentPage) ? 'page_box active' : 'page_box'; 120 $this->_addPageLink($nav, $query, ($i - 1) * $perPage, $i, $class); 121 } 122 if ($currentPage < $totalPages) $this->_addPageLink($nav, $query, $currentPage * $perPage, 'Next »', 'page_box next'); 123 $parent->appendChild($nav); 124 } 125 126 private function _addPageLink($parent, $query, $startValue, $label, $class) { 127 $a = $this->_dom->createElement('a', $label); 128 $a->setAttribute('href', wl('', ['do'=>'search', 'id'=>$query, 'start'=>$startValue])); 129 $a->setAttribute('class', $class); 130 $parent->appendChild($a); 131 } 132 133 private function _appendRawHTML($node, $html) { 134 if (empty($html)) return; 135 $tmp = new DOMDocument(); 136 @$tmp->loadHTML('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $html); 137 $body = $tmp->getElementsByTagName('body')->item(0); 138 if ($body) { 139 foreach ($body->childNodes as $child) { 140 if ($child->nodeName === 'meta') continue; 141 $node->appendChild($this->_dom->importNode($child, true)); 142 } 143 } 144 } 145 146 private function _getCategories($q) { preg_match('/@ns\s+([^\s]+)/i', urldecode($q), $m); return $m[1] ?? ''; } 147 private function _getKeywords($q) { return trim(preg_replace('/@ns\s+[^\s]+/i', '', urldecode($q))); } 148 private function _getMatchingPagenames($kw, $cat) { 149 $this->_sObj->setSearchOnlyPagename(); 150 if (!$this->_sObj->search(0, 50)) return false; 151 $m = []; 152 foreach ($this->_sObj->getPagesIds() as $p) $m[$p['page']] = $p['hid']; 153 return $m; 154 } 155} 156