1<?php 2 3use dokuwiki\Cache\Cache; 4 5class helper_plugin_tagfilter_syntax extends DokuWiki_Plugin 6{ 7 /** 8 * 9 * @param array $opt 10 * with amongst others: 11 * array 'tagfilterFlags' with: 12 * string[] 'excludeNs', 13 * string[] 'withTags' (optional), 14 * string[] 'excludeTags (optional) 15 * array 'tagFilters' with three arrays with same keys:: 16 * key=>string 'label' 17 * key=>string 'tagExpression' 18 * key=>array 'selectedTags' 19 * - string 'ns' 20 * @return array 21 * with 22 * - array $tagFilters with four arrays with same key for each tagExpression: 23 * key=>string 'label' 24 * key=>string 'tagExpression' 25 * key=>array 'selectedTags' 26 * key=>array 'pagesPerMatchedTag' with 27 * tag=>array of pageids of pages having the tag 28 * - array $pageids ids of related pages 29 * 30 */ 31 public function getTagPageRelations($opt) 32 { 33 /* @var helper_plugin_tagfilter $Htagfilter */ 34 $Htagfilter = $this->loadHelper('tagfilter'); 35 36 $flags = $opt['tagfilterFlags']; 37 38 $tagFilters = $opt['tagFilters']; 39 foreach ($tagFilters['tagExpression'] as $key => $tagExpression) { //build tag->pages relation 40 $tagFilters['pagesPerMatchedTags'][$key] = $Htagfilter->getPagesByMatchedTags($tagExpression, $opt['ns']); 41 } 42 43 //extract all pageids 44 $allPageids = []; 45 foreach ($tagFilters['pagesPerMatchedTags'] as $pagesPerMatchedTag) { 46 if (!is_array($pagesPerMatchedTag)) { 47 continue; 48 } 49 foreach ($pagesPerMatchedTag as $tag => $pageidsPerTag) { 50 if (!empty($flags['withTags']) && !in_array($tag, $flags['withTags'])) { 51 continue; 52 } 53 if (!empty($flags['excludeTags']) && in_array($tag, $flags['excludeTags'])) { 54 continue; 55 } 56 $allPageids = array_merge($allPageids, $pageidsPerTag); 57 } 58 } 59 60 $allPageids = array_filter($allPageids, function ($val) use ($opt) { 61 //Template nicht anzeigen 62 if (strpos($val, '_template') !== false) { 63 return false; 64 } 65 66 foreach ($opt['tagfilterFlags']['excludeNs'] as $excludeNs) { 67 if (strpos($val, $excludeNs) === 0) { 68 return false; 69 } 70 } 71 return true; 72 }); 73 74 $allPageids = array_unique($allPageids); //TODO cache this 75 76 //cache $pageids and $tagFilters for all users 77 return [ 78 $tagFilters, 79 $allPageids 80 ]; 81 } 82 83 /** 84 * Prepare array with data for each page suitable for displaying with the pagelist plugin 85 * 86 * @param array $pageids pages to list 87 * @param array $flags with 88 * - array 'tagcolumn' (optional) 89 * - string tagexpr 90 * - array 'tagimagecolumn' 91 * - string tagexpr 92 * - string namespace of images 93 * - bool 'rsort' whether reverse sort 94 * @return array[] with 95 * - array with 96 * - string 'title' 97 * - string 'id' page id 98 * - string 'tmp_id' 99 * - for each tagcolumn: string '<tagexpr as column key>' html of cell 100 * - for each tagimagecolumn: string '<tagexpr as column key>' html of cell 101 */ 102 public function prepareList($pageids, $flags) 103 { 104 global $ID; 105 global $INFO; 106 107 /* @var helper_plugin_tagfilter $Htagfilter */ 108 $Htagfilter = $this->loadHelper('tagfilter'); 109 110 if (!isset($flags['tagcolumn'])) { 111 $flags['tagcolumn'] = []; 112 } 113 114 115 $pages = []; 116 $_uniqueid = 0; 117 foreach ($pageids as $page) { 118 119 $depends = ['files' => [ 120 $INFO['filepath'], 121 wikiFN($page) 122 ]]; 123 $cache_key = implode('_', ['plugin_tagfilter', $ID, $page, $flags['sortbypageid']]); 124 $cache = new Cache($cache_key, '.tpcache'); 125 if (!$cache->useCache($depends)) { 126 $title = p_get_metadata($page, 'title', METADATA_DONT_RENDER); 127 128 $cache_page = [ 129 'title' => $title ?: $page, 130 'id' => $page, 131 'tmp_id' => $flags['sortbypageid'] 132 ? $page 133 : ($title ?: (noNS($page) ?: $page)), 134 ]; 135 136 foreach ($flags['tagcolumn'] as $tagcolumn) { 137 $cache_page[hsc($tagcolumn)] = $Htagfilter->td($page, hsc($tagcolumn)); 138 } 139 foreach ($flags['tagimagecolumn'] as $tagimagecolumn) { 140 $cache_page[hsc($tagimagecolumn[0]) . ' '] = $Htagfilter->getTagImageColumn($page, $tagimagecolumn[0], $tagimagecolumn[1]); 141 } 142 $cache->storeCache(serialize($cache_page)); 143 } else { 144 $cache_page = unserialize($cache->retrieveCache()); 145 } 146 147 //create unique key 148 $tmp_id = $cache_page['tmp_id']; 149 if (isset($pages[$tmp_id])) { 150 $tmp_id .= '_' . $_uniqueid++; 151 } 152 153 $pages[$tmp_id] = $cache_page; 154 } 155 156 157 if ($flags['rsort']) { 158 krsort($pages, SORT_NATURAL | SORT_FLAG_CASE); 159 } else { 160 ksort($pages, SORT_NATURAL | SORT_FLAG_CASE); 161 } 162 return $pages; 163 } 164 165 166 /** 167 * Generated list of the give page data 168 * 169 * @param array $pages for format @see prepareList() 170 * @param array $flags tagfilter flags with at least: 171 * - array 'tagcolumn' (optional) 172 * - string tagexpr 173 * - array 'tagimagecolumn' 174 * - string tagexpr 175 * - string namespace of images 176 * @param array $pagelistflags all flags set by user 177 * @return false|string 178 */ 179 public function renderList($pages, $flags, $pagelistflags) 180 { 181 if (!isset($flags['tagcolumn'])) { 182 $flags['tagcolumn'] = []; 183 } 184 185 186 // let Pagelist Plugin do the work for us 187 /* @var helper_plugin_pagelist $Hpagelist */ 188 if (plugin_isdisabled('pagelist') 189 || (!$Hpagelist = plugin_load('helper', 'pagelist'))) { 190 msg($this->getLang('missing_pagelistplugin'), -1); 191 return false; 192 } 193 194 foreach ($flags['tagcolumn'] as $tagcolumn) { 195 $Hpagelist->addColumn('tagfilter', hsc($tagcolumn)); 196 } 197 foreach ($flags['tagimagecolumn'] as $tagimagecolumn) { 198 $Hpagelist->addColumn('tagfilter', hsc($tagimagecolumn[0] . ' ')); 199 } 200 201 unset($flags['tagcolumn']); //TODO unset is not needed because pagelistflags are separate array? 202 $Hpagelist->setFlags($pagelistflags); 203 $Hpagelist->startList(); 204 205 foreach ($pages as $page) { 206 $Hpagelist->addPage($page); 207 } 208 209 return $Hpagelist->finishList(); 210 } 211 212 213 /** 214 * parseFlags checks for tagfilter flags and returns them as true/false 215 * 216 * @param array $flags array with (all optional): 217 * multi, chosen, tagimage, pagesearch, cacheage, nocache, rsort, nolabels, noneonclear, tagimagecolumn, 218 * tagcolumn, excludeNs, withTags, excludeTags, images, count, tagintersect, sortbypageid, include 219 * @return array tagfilter flags with: 220 * multi, chosen, tagimage, pagesearch, pagesearchlabel, cache, rsort, labels, noneonclear, tagimagecolumn, 221 * tagcolumn (optional), excludeNs, withTags, excludeTags, images, count, tagintersect, sortbypageid, include 222 */ 223 public function parseFlags($flags) 224 { 225 $conf = [ 226 'multi' => false, 227 'chosen' => false, 228 'tagimage' => false, 229 'pagesearch' => false, 230 'pagesearchlabel' => 'Seiten', 231 'cache' => false, 232 'rsort' => false, 233 'labels' => true, 234 'noneonclear' => false, 235 'tagimagecolumn' => [], 236 'excludeNs' => [], 237 'withTags' => [], 238 'excludeTags' => [], 239 'images' => false, 240 'count' => false, 241 'tagintersect' => false, 242 'sortbypageid' => false, 243 'include' => [], 244 ]; 245 if (!is_array($flags)) { 246 return $conf; 247 } 248 249 foreach ($flags as $flag) { 250 list($flag, $value) = array_pad(explode('=', $flag, 2), 2, ''); 251 $flag = trim($flag); 252 $value = trim($value); 253 switch ($flag) { 254 case 'multi': 255 $conf['multi'] = true; 256 break; 257 case 'chosen': 258 $conf['chosen'] = true; 259 break; 260 case 'tagimage': 261 $conf['tagimage'] = true; 262 break; 263 case 'pagesearch': 264 $conf['pagesearch'] = true; 265 if ($value != '') { 266 $conf['pagesearchlabel'] = hsc($value); 267 } 268 break; 269 case 'cacheage': 270 $conf['cache'] = intval($value); 271 break; 272 case 'nocache': 273 $conf['cache'] = null; 274 break; 275 case 'tagcolumn': 276 $conf['tagcolumn'][] = $value; 277 break; 278 case 'tagimagecolumn': 279 $conf['tagimagecolumn'][] = explode('=', $value, 2); 280 break; 281 case 'rsort': 282 $conf['rsort'] = true; 283 break; 284 case 'nolabels': 285 $conf['labels'] = false; 286 break; 287 case 'noneonclear': 288 $conf['noneonclear'] = true; 289 break; 290 case 'excludeNs': 291 $conf['excludeNs'] = explode(',', $value, 2); //TODO really maximum of two namespaces? 292 break; 293 case 'withTags': 294 $conf['withTags'] = explode(',', $value, 2); //TODO really maximum of two tags? 295 break; 296 case 'excludeTags': 297 $conf['excludeTags'] = explode(',', $value, 2); //TODO really maximum of two tags? 298 break; 299 case 'images': 300 $conf['images'] = true; 301 break; 302 case 'count': 303 $conf['count'] = true; 304 break; 305 case 'tagintersect': 306 $conf['tagintersect'] = true; 307 break; 308 case 'sortbypageid': 309 $conf['sortbypageid'] = true; 310 break; 311 case 'include': 312 $conf['include'] = explode(';', $value); 313 break; 314 } 315 } 316 317 return $conf; 318 } 319 320 321 /** 322 * This function just lists documents (for RSS namespace export) 323 * 324 * @param array $data Reference to the result data structure 325 * @param string $base Base usually $conf['datadir'] 326 * @param string $file current file or directory relative to $base 327 * @param string $type Type either 'd' for directory or 'f' for file 328 * @param int $lvl Current recursion depth 329 * @param array $opts option array as given to search() with: 330 * string[] 'excludeNs' 331 * @return bool if this directory should be traversed (true) or not (false) 332 * return value is ignored for files 333 * @author Andreas Gohr <andi@splitbrain.org> 334 */ 335 public function search_all_pages(&$data, $base, $file, $type, $lvl, $opts) 336 { 337 global $conf; 338 339 //we do nothing with directories 340 if ($type == 'd') { 341 return true; 342 } 343 344 //only search txt files 345 if (substr($file, -4) == '.txt') { 346 foreach ($opts['excludeNs'] as $excludeNs) { 347 if (strpos($file, str_replace(':', '/', $excludeNs)) === 0) { 348 return true; 349 } 350 } 351 352 //check ACL 353 $data[] = $conf['datadir'] . '/' . $file; 354 } 355 return false; 356 } 357} 358