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, 10, -2); // strip {{searchtags from start and }} from end 49 // remove empty flags by using array_filter (removes elements == false) 50 $flags = array_filter(explode('&', $flags)); 51 52 return $flags; 53 } 54 55 /** 56 * Render xhtml output or metadata 57 * 58 * @param string $mode Renderer mode (supported modes: xhtml and metadata) 59 * @param Doku_Renderer $renderer The renderer 60 * @param array $data The data from the handler function 61 * @return bool If rendering was successful. 62 */ 63 function render($mode, Doku_Renderer $renderer, $data) { 64 global $lang; 65 $flags = $data; 66 67 if ($mode == 'xhtml') { 68 /* @var Doku_Renderer_xhtml $renderer */ 69 70 // prevent caching to ensure content is always fresh 71 $renderer->nocache(); 72 73 /* @var helper_plugin_pagelist $pagelist */ 74 // let Pagelist Plugin do the work for us 75 if ((!$pagelist = $this->loadHelper('pagelist'))) { 76 return false; 77 } 78 79 // Prepare the flags for the pagelist plugin 80 $configflags = explode(',', str_replace(" ", "", $this->getConf('pagelist_flags'))); 81 $flags = array_merge($configflags, $flags); 82 foreach($flags as $key => $flag) { 83 if($flag == "") unset($flags[$key]); 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 $my */ 95 if ($my = $this->loadHelper('tag')) $pages = $my->getTopic($this->getNS(), '', $tags); 96 97 // Display a message when no pages were found 98 if (!isset($pages) || !$pages) { 99 $renderer->p_open(); 100 $renderer->cdata($lang['nothingfound']); 101 $renderer->p_close(); 102 } else { 103 104 // display the actual search results 105 $pagelist->setFlags($flags); 106 $pagelist->startList(); 107 foreach ($pages as $page) { 108 $pagelist->addPage($page); 109 } 110 $renderer->doc .= $pagelist->finishList(); 111 } 112 } 113 114 return true; 115 } 116 return false; 117 } 118 119 /** 120 * Return the search form for the namespace and the tag selection 121 * 122 * @return string the HTML code of the search form 123 */ 124 private function getForm($nonsform=false) { 125 global $conf, $lang; 126 127 if (!$nonsform) { 128 // Get the list of all namespaces for the dropdown 129 $namespaces = array(); 130 search($namespaces,$conf['datadir'],'search_namespaces',array()); 131 132 // build the list in the form value => label from the namespace search result 133 $ns_select = array('' => ''); 134 foreach ($namespaces as $ns) { 135 // only display namespaces the user can access when sneaky index is on 136 if ($ns['perm'] > 0 || $conf['sneaky_index'] == 0) { 137 $ns_select[$ns['id']] = $ns['id']; 138 } 139 } 140 } 141 142 $form = new Doku_Form(array('action' => '', 'method' => 'post', 'class' => 'plugin__tag_search')); 143 144 // add a paragraph around the inputs in order to get some margin around the form elements 145 $form->addElement(form_makeOpenTag('p')); 146 // namespace select 147 if (!$nonsform) { 148 $form->addElement(form_makeMenuField('plugin__tag_search_namespace', $ns_select, $this->getNS(), $lang['namespaces'])); 149 } 150 151 // checkbox for AND 152 $attr = array(); 153 if ($this->useAnd()) $attr['checked'] = 'checked'; 154 $form->addElement(form_makeCheckboxField('plugin__tag_search_and', 1, $this->getLang('use_and'), '', '', $attr)); 155 $form->addElement(form_makeCloseTag('p')); 156 157 // load the tag list - only tags that actually have pages assigned that the current user can access are listed 158 /* @var helper_plugin_tag $my */ 159 if ($my = $this->loadHelper('tag')) $tags = $my->tagOccurrences(array(), NULL, true); 160 // sort tags by name ($tags is in the form $tag => $count) 161 ksort($tags); 162 163 // display error message when no tags were found 164 if (!isset($tags) || $tags == NULL) { 165 $form->addElement(form_makeOpenTag('p')); 166 $form->addElement($this->getLang('no_tags')); 167 $form->addElement(form_makeCloseTag('p')); 168 } else { 169 // the tags table 170 $form->addElement(form_makeOpenTag('div', array('class' => 'table'))); 171 $form->addElement(form_makeOpenTag('table', array('class' => 'inline'))); 172 // print table header 173 $form->addElement(form_makeOpenTag('tr')); 174 $form->addElement(form_makeOpenTag('th')); 175 $form->addElement($this->getLang('include')); 176 $form->addElement(form_makeCloseTag('th')); 177 $form->addElement(form_makeOpenTag('th')); 178 $form->addElement($this->getLang('exclude')); 179 $form->addElement(form_makeCloseTag('th')); 180 $form->addElement(form_makeOpenTag('th')); 181 $form->addElement($this->getLang('tags')); 182 $form->addElement(form_makeCloseTag('th')); 183 $form->addElement(form_makeCloseTag('tr')); 184 185 // print tag checkboxes 186 foreach ($tags as $tag => $count) { 187 $form->addElement(form_makeOpenTag('tr')); 188 $form->addElement(form_makeOpenTag('td')); 189 $attr = array(); 190 if ($this->isSelected($tag)) $attr['checked'] = 'checked'; 191 $form->addElement(form_makeCheckboxField('plugin__tag_search_tags[]', $tag, '+', '', 'plus', $attr)); 192 $form->addElement(form_makeCloseTag('td')); 193 $form->addElement(form_makeOpenTag('td')); 194 $attr = array(); 195 if ($this->isSelected('-'.$tag)) $attr['checked'] = 'checked'; 196 $form->addElement(form_makeCheckboxField('plugin__tag_search_tags[]', '-'.$tag, '-', '', 'minus', $attr)); 197 $form->addElement(form_makeCloseTag('td')); 198 $form->addElement(form_makeOpenTag('td')); 199 $form->addElement(hsc($tag).' ['.$count.']'); 200 $form->addElement(form_makeCloseTag('td')); 201 $form->addElement(form_makeCloseTag('tr')); 202 } 203 204 $form->addElement(form_makeCloseTag('table')); 205 $form->addElement(form_makeCloseTag('div')); 206 207 // submit button (doesn't use the button form element because it always submits an action which is not 208 // recognized for $preact in inc/actions.php and thus always causes a redirect) 209 $form->addElement(form_makeOpenTag('p')); 210 $form->addElement(form_makeTag('input', array('type' => 'submit', 'value' => $lang['btn_search']))); 211 $form->addElement(form_makeCloseTag('p')); 212 } 213 214 return $form->getForm(); 215 } 216 217 /** 218 * Returns the currently selected namespace 219 * @return string the cleaned namespace id 220 */ 221 private function getNS() { 222 if (isset($_POST['plugin__tag_search_namespace'])) { 223 return cleanID($_POST['plugin__tag_search_namespace']); 224 } else { 225 return ''; 226 } 227 } 228 229 /** 230 * Returns the tag search string from the selected tags 231 * @return string|NULL the tag search or NULL when no tags were selected 232 */ 233 private function getTagSearchString() { 234 if (isset($_POST['plugin__tag_search_tags']) && is_array($_POST['plugin__tag_search_tags'])) { 235 $tags = $_POST['plugin__tag_search_tags']; 236 // wWhen and is set, prepend "+" to each tag 237 $plus = (isset($_POST['plugin__tag_search_and']) ? '+' : ''); 238 $positive_tags = ''; 239 $negative_tags = ''; 240 foreach ($tags as $tag) { 241 $tag = (string)$tag; 242 if ($tag[0] == '-') { 243 $negative_tags .= $tag.' '; 244 } else { 245 if ($positive_tags === '') { 246 $positive_tags = $tag.' '; 247 } else { 248 $positive_tags .= $plus.$tag.' '; 249 } 250 } 251 } 252 return $positive_tags.$negative_tags; 253 } else { 254 return NULL; // return NULL when no tags were selected so no results will be displayed 255 } 256 } 257 258 /** 259 * Check if a tag was selected for search 260 * 261 * @param string $tag The tag to check 262 * @return bool if the tag was checked 263 */ 264 private function isSelected($tag) { 265 if (isset($_POST['plugin__tag_search_tags']) && is_array($_POST['plugin__tag_search_tags'])) { 266 return in_array($tag, $_POST['plugin__tag_search_tags'], true); 267 } else { 268 return false; // no tags in the post data - no tag selected 269 } 270 } 271 272 /** 273 * Check if the tag query should use AND (instead of OR) 274 * 275 * @return bool if the query should use AND 276 */ 277 private function useAnd() { 278 return isset($_POST['plugin__tag_search_and']); 279 } 280} 281// vim:ts=4:sw=4:et: 282