1<?php 2/** 3 * Syntax plugin part for displaying a tag search form with results. 4 * 5 * Usage: {{tagsearch[&flags]}} 6 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 7 * @author Michael Hamann <michael@content-space.de> 8 */ 9 10/** 11 * Tagsearch syntax, displays a tag search form with results similar to the topic syntax 12 */ 13class syntax_plugin_tag_searchtags extends DokuWiki_Syntax_Plugin { 14 /** 15 * @return string Syntax type 16 */ 17 function getType() { return 'substition'; } 18 19 /** 20 * @return string Paragraph type 21 */ 22 function getPType() { return 'block'; } 23 24 /** 25 * @return int Sort order 26 */ 27 function getSort() { return 295; } 28 29 /** 30 * @param string $mode Parser mode 31 */ 32 function connectTo($mode) { 33 $this->Lexer->addSpecialPattern('\{\{searchtags}}', $mode,'plugin_tag_searchtags'); 34 // make sure that flags really start with & and media files starting with "searchtags" still work 35 $this->Lexer->addSpecialPattern('\{\{searchtags&.*?\}\}',$mode,'plugin_tag_searchtags'); 36 } 37 38 /** 39 * Handle matches of the searchtags syntax 40 * 41 * @param string $match The match of the syntax 42 * @param int $state The state of the handler 43 * @param int $pos The position in the document 44 * @param Doku_Handler $handler The handler 45 * @return array Data for the renderer 46 */ 47 function handle($match, $state, $pos, Doku_Handler $handler) { 48 $flags = substr($match, 12, -2); // strip {{searchtags from start and }} from end 49 // remove empty flags by using array_filter (removes elements == false) 50 return array_filter(explode('&', $flags)); 51 } 52 53 /** 54 * Render xhtml output or metadata 55 * 56 * @param string $format Renderer mode (supported modes: xhtml and metadata) 57 * @param Doku_Renderer $renderer The renderer 58 * @param array $data The data from the handler function 59 * @return bool If rendering was successful. 60 */ 61 function render($format, Doku_Renderer $renderer, $data) { 62 global $lang; 63 $flags = $data; 64 65 if ($format == 'xhtml') { 66 /* @var Doku_Renderer_xhtml $renderer */ 67 68 // prevent caching to ensure content is always fresh 69 $renderer->nocache(); 70 71 /* @var helper_plugin_pagelist $pagelist */ 72 // let Pagelist Plugin do the work for us 73 if (!$pagelist = $this->loadHelper('pagelist')) { 74 return false; 75 } 76 77 // Prepare the flags for the pagelist plugin 78 $configflags = explode(',', str_replace(" ", "", $this->getConf('pagelist_flags'))); 79 $flags = array_merge($configflags, $flags); 80 foreach($flags as $key => $flag) { 81 if($flag == "") { 82 unset($flags[$key]); 83 } 84 } 85 86 // print the search form 87 $nonsform = in_array('nonsform', $flags); 88 $renderer->doc .= $this->getForm($nonsform); 89 90 // get the tag input data 91 $tags = $this->getTagSearchString(); 92 93 if ($tags != null) { 94 /* @var helper_plugin_tag $helper */ 95 if ($helper = $this->loadHelper('tag')) { 96 $pages = $helper->getTopic($this->getNS(), '', $tags); 97 } 98 99 // Display a message when no pages were found 100 if (!isset($pages) || !$pages) { 101 $renderer->p_open(); 102 $renderer->cdata($lang['nothingfound']); 103 $renderer->p_close(); 104 } else { 105 106 // display the actual search results 107 $pagelist->setFlags($flags); 108 $pagelist->startList(); 109 foreach ($pages as $page) { 110 $pagelist->addPage($page); 111 } 112 $renderer->doc .= $pagelist->finishList(); 113 } 114 } 115 116 return true; 117 } 118 return false; 119 } 120 121 /** 122 * Return the search form for the namespace and the tag selection 123 * 124 * @return string the HTML code of the search form 125 */ 126 private function getForm($nonsform=false) { 127 global $conf, $lang; 128 129 if (!$nonsform) { 130 // Get the list of all namespaces for the dropdown 131 $namespaces = []; 132 search($namespaces,$conf['datadir'],'search_namespaces', []); 133 134 // build the list in the form value => label from the namespace search result 135 $ns_select = ['' => '']; 136 foreach ($namespaces as $ns) { 137 // only display namespaces the user can access when sneaky index is on 138 if ($ns['perm'] > 0 || $conf['sneaky_index'] == 0) { 139 $ns_select[$ns['id']] = $ns['id']; 140 } 141 } 142 } 143 144 $form = new Doku_Form(array('action' => '', 'method' => 'post', 'class' => 'plugin__tag_search')); 145 146 // add a paragraph around the inputs in order to get some margin around the form elements 147 $form->addElement(form_makeOpenTag('p')); 148 // namespace select 149 if (!$nonsform) { 150 $form->addElement(form_makeMenuField('plugin__tag_search_namespace', $ns_select, $this->getNS(), $lang['namespaces'])); 151 } 152 153 // checkbox for AND 154 $attr = array(); 155 if ($this->useAnd()) { 156 $attr['checked'] = 'checked'; 157 } 158 $form->addElement(form_makeCheckboxField('plugin__tag_search_and', 1, $this->getLang('use_and'), '', '', $attr)); 159 $form->addElement(form_makeCloseTag('p')); 160 161 // load the tag list - only tags that actually have pages assigned that the current user can access are listed 162 /* @var helper_plugin_tag $my */ 163 if ($my = $this->loadHelper('tag')) { 164 $tags = $my->tagOccurrences(array(), NULL, true); 165 } 166 // sort tags by name ($tags is in the form $tag => $count) 167 ksort($tags); 168 169 // display error message when no tags were found 170 if (!isset($tags) || $tags == NULL) { 171 $form->addElement(form_makeOpenTag('p')); 172 $form->addElement($this->getLang('no_tags')); 173 $form->addElement(form_makeCloseTag('p')); 174 } else { 175 // the tags table 176 $form->addElement(form_makeOpenTag('div', array('class' => 'table'))); 177 $form->addElement(form_makeOpenTag('table', array('class' => 'inline'))); 178 // print table header 179 $form->addElement(form_makeOpenTag('tr')); 180 $form->addElement(form_makeOpenTag('th')); 181 $form->addElement($this->getLang('include')); 182 $form->addElement(form_makeCloseTag('th')); 183 $form->addElement(form_makeOpenTag('th')); 184 $form->addElement($this->getLang('exclude')); 185 $form->addElement(form_makeCloseTag('th')); 186 $form->addElement(form_makeOpenTag('th')); 187 $form->addElement($this->getLang('tags')); 188 $form->addElement(form_makeCloseTag('th')); 189 $form->addElement(form_makeCloseTag('tr')); 190 191 // print tag checkboxes 192 foreach ($tags as $tag => $count) { 193 $form->addElement(form_makeOpenTag('tr')); 194 $form->addElement(form_makeOpenTag('td')); 195 $attr = array(); 196 if ($this->isSelected($tag)) { 197 $attr['checked'] = 'checked'; 198 } 199 $form->addElement(form_makeCheckboxField('plugin__tag_search_tags[]', $tag, '+', '', 'plus', $attr)); 200 $form->addElement(form_makeCloseTag('td')); 201 $form->addElement(form_makeOpenTag('td')); 202 $attr = array(); 203 if ($this->isSelected('-'.$tag)) { 204 $attr['checked'] = 'checked'; 205 } 206 $form->addElement(form_makeCheckboxField('plugin__tag_search_tags[]', '-'.$tag, '-', '', 'minus', $attr)); 207 $form->addElement(form_makeCloseTag('td')); 208 $form->addElement(form_makeOpenTag('td')); 209 $form->addElement(hsc($tag).' ['.$count.']'); 210 $form->addElement(form_makeCloseTag('td')); 211 $form->addElement(form_makeCloseTag('tr')); 212 } 213 214 $form->addElement(form_makeCloseTag('table')); 215 $form->addElement(form_makeCloseTag('div')); 216 217 // submit button (doesn't use the button form element because it always submits an action which is not 218 // recognized for $preact in inc/actions.php and thus always causes a redirect) 219 $form->addElement(form_makeOpenTag('p')); 220 $form->addElement(form_makeTag('input', array('type' => 'submit', 'value' => $lang['btn_search']))); 221 $form->addElement(form_makeCloseTag('p')); 222 } 223 224 return $form->getForm(); 225 } 226 227 /** 228 * Returns the currently selected namespace 229 * @return string the cleaned namespace id 230 */ 231 private function getNS() { 232 global $INPUT; 233 if ($INPUT->post->has('plugin__tag_search_namespace')) { 234 return cleanID($INPUT->post->str('plugin__tag_search_namespace')); 235 } else { 236 return ''; 237 } 238 } 239 240 /** 241 * Returns the tag search string from the selected tags 242 * @return string|null the tag search or null when no tags were selected 243 */ 244 private function getTagSearchString() { 245 global $INPUT; 246 if ($INPUT->post->has('plugin__tag_search_tags') && is_array($INPUT->post->param('plugin__tag_search_tags'))) { 247 $tags = $INPUT->post->arr('plugin__tag_search_tags'); 248 // When 'and' is set, prepend "+" to each tag 249 $plus = $this->useAnd() ? '+' : ''; 250 $positive_tags = ''; 251 $negative_tags = ''; 252 foreach ($tags as $tag) { 253 $tag = (string)$tag; 254 if ($tag[0] == '-') { 255 $negative_tags .= $tag.' '; 256 } else { 257 if ($positive_tags === '') { 258 $positive_tags = $tag.' '; 259 } else { 260 $positive_tags .= $plus.$tag.' '; 261 } 262 } 263 } 264 return $positive_tags.$negative_tags; 265 } else { 266 return null; // return NULL when no tags were selected so no results will be displayed 267 } 268 } 269 270 /** 271 * Check if a tag was selected for search 272 * 273 * @param string $tag The tag to check 274 * @return bool if the tag was checked 275 */ 276 private function isSelected($tag) { 277 global $INPUT; 278 if ($INPUT->post->has('plugin__tag_search_tags')) { 279 return in_array($tag, $INPUT->post->arr('plugin__tag_search_tags'), true); 280 } else { 281 return false; // no tags in the post data - no tag selected 282 } 283 } 284 285 /** 286 * Check if the tag query should use AND (instead of OR) 287 * 288 * @return bool if the query should use AND 289 */ 290 private function useAnd() { 291 global $INPUT; 292 return $INPUT->post->has('plugin__tag_search_and'); 293 } 294} 295// vim:ts=4:sw=4:et: 296