1<?php 2 3namespace dokuwiki\plugin\struct\meta; 4 5use dokuwiki\File\PageResolver; 6 7/** 8 * Class SearchConfig 9 * 10 * The same as @see Search but can be initialized by a configuration array 11 * 12 * @package dokuwiki\plugin\struct\meta 13 */ 14class SearchConfig extends Search 15{ 16 /** @var int default aggregation caching (depends on last struct save) */ 17 public static $CACHE_DEFAULT = 1; 18 /** @var int caching depends on current user */ 19 public static $CACHE_USER = 2; 20 /** @var int caching depends on current date */ 21 public static $CACHE_DATE = 4; 22 23 /** 24 * @var array hold the configuration as parsed and extended by dynamic params 25 */ 26 protected $config; 27 28 /** 29 * @var SearchConfigParameters manages dynamic parameters 30 */ 31 protected $dynamicParameters; 32 33 /** 34 * @var int the cache flag to use (binary flags) 35 */ 36 protected $cacheFlag; 37 38 /** 39 * SearchConfig constructor. 40 * @param array $config The parsed configuration for this search 41 * @param bool $dynamic Should dynamic parameters be applied? 42 */ 43 public function __construct($config, $dynamic = true) 44 { 45 parent::__construct(); 46 47 // setup schemas and columns 48 if (!empty($config['schemas'])) foreach ($config['schemas'] as $schema) { 49 $this->addSchema($schema[0], $schema[1]); 50 } 51 if (!empty($config['cols'])) foreach ($config['cols'] as $col) { 52 $this->addColumn($col); 53 } 54 55 // cache flag setting 56 $this->cacheFlag = self::$CACHE_DEFAULT; 57 if (!empty($config['filters'])) $this->cacheFlag = $this->determineCacheFlag($config['filters']); 58 59 // configure search from configuration 60 if (!empty($config['filter'])) foreach ($config['filter'] as $filter) { 61 $this->addFilter($filter[0], $this->applyFilterVars($filter[2]), $filter[1], $filter[3]); 62 } 63 64 if (!empty($config['sort'])) foreach ($config['sort'] as $sort) { 65 $this->addSort($sort[0], $sort[1]); 66 } 67 68 if (!empty($config['limit'])) { 69 $this->setLimit($config['limit']); 70 } 71 72 if (!empty($config['offset'])) { 73 $this->setOffset($config['offset']); 74 } 75 76 // prepare dynamic parameters 77 $this->dynamicParameters = new SearchConfigParameters($this); 78 if ($dynamic) { 79 $this->dynamicParameters->apply(); 80 } 81 82 $this->config = $config; 83 } 84 85 /** 86 * Set the cache flag accordingly to the set filter placeholders 87 * 88 * @param array $filters 89 * @return int 90 */ 91 protected function determineCacheFlag($filters) 92 { 93 $flags = self::$CACHE_DEFAULT; 94 95 foreach ($filters as $filter) { 96 if (is_array($filter)) $filter = $filter[2]; // this is the format we get fro the config parser 97 98 if (str_contains($filter, '$USER$')) { 99 $flags |= self::$CACHE_USER; 100 } elseif (str_contains($filter, '$TODAY$')) { 101 $flags |= self::$CACHE_DATE; 102 } 103 } 104 105 return $flags; 106 } 107 108 /** 109 * Replaces placeholders in the given filter value by the proper value 110 * 111 * @param string $filter 112 * @return string|string[] Result may be an array when a multi column placeholder is used 113 */ 114 protected function applyFilterVars($filter) 115 { 116 global $INPUT; 117 global $INFO; 118 if (!isset($INFO['id'])) { 119 $INFO['id'] = ''; 120 } 121 $ns = getNS($INFO['id']); 122 123 // apply inexpensive filters first 124 $filter = str_replace( 125 [ 126 '$ID$', 127 '$NS$', 128 '$PAGE$', 129 '$USER$', 130 '$TODAY$' 131 ], 132 [ 133 $INFO['id'], 134 $ns, 135 noNS($INFO['id']), 136 $INPUT->server->str('REMOTE_USER'), 137 date('Y-m-d') 138 ], 139 $filter 140 ); 141 142 // apply namespace or id placeholder #712 143 // returns the namespace for start pages, otherwise the ID 144 if (preg_match('/\$NSORID\$/', $filter)) { 145 $resolver = new PageResolver(''); 146 147 $start = $resolver->resolveId($ns . ':'); 148 if ($start === $INFO['id']) { 149 // This is a start page, we return the namespace 150 $val = $ns; 151 } else { 152 // This is a normal page, we return the ID 153 $val = $INFO['id']; 154 } 155 $filter = str_replace('$NSORID$', $val, $filter); 156 } 157 158 // apply struct column placeholder (we support only one!) 159 // or apply date formula, given as strtotime 160 if (preg_match('/^(.*?)(?:\$STRUCT\.(.*?)\$)(.*?)$/', $filter, $match)) { 161 $filter = $this->applyFilterVarsStruct($match); 162 } elseif (preg_match('/^(.*?)(?:\$USER\.(.*?)\$)(.*?)$/', $filter, $match)) { 163 $filter = $this->applyFilterVarsUser($match); 164 } elseif (preg_match('/^(.*?)(?:\$DATE\((.*?)\)\$?)(.*?)$/', $filter, $match)) { 165 $toparse = $match[2]; 166 if ($toparse == '') { 167 $toparse = 'now'; 168 } 169 $timestamp = strtotime($toparse); 170 if ($timestamp === false) { 171 throw new StructException('datefilter', hsc($toparse)); 172 } else { 173 $filter = str_replace($filter, date('Y-m-d', $timestamp), $filter); 174 } 175 } 176 177 return $filter; 178 } 179 180 /** 181 * Replaces struct placeholders in the given filter value by the proper value 182 * 183 * @param string $match 184 * @return string|string[] Result may be an array when a multi column placeholder is used 185 */ 186 protected function applyFilterVarsStruct($match) 187 { 188 global $INFO; 189 190 $key = $match[2]; 191 192 // we try to resolve the key via the assigned schemas first, otherwise take it literally 193 $column = $this->findColumn($key, true); 194 if ($column) { 195 $label = $column->getLabel(); 196 $table = $column->getTable(); 197 } else { 198 [$table, $label] = sexplode('.', $key, 2, ''); 199 } 200 201 // get the data from the current page 202 if ($table && $label) { 203 $schemaData = AccessTable::getPageAccess($table, $INFO['id']); 204 $data = $schemaData->getData(); 205 if (!isset($data[$label])) { 206 throw new StructException("column not in table", $label, $table); 207 } 208 $value = $data[$label]->getCompareValue(); 209 210 if (is_array($value) && $value === []) { 211 $value = ''; 212 } 213 } else { 214 $value = ''; 215 } 216 217 // apply any pre and postfixes, even when multi value 218 if (is_array($value)) { 219 $filter = []; 220 foreach ($value as $item) { 221 $filter[] = $match[1] . $item . $match[3]; 222 } 223 } else { 224 $filter = $match[1] . $value . $match[3]; 225 } 226 227 return $filter; 228 } 229 230 /** 231 * Replaces user placeholders in the given filter value by the proper value 232 * 233 * @param string $match 234 * @return string|string[] String for name and mail, array for grps 235 */ 236 protected function applyFilterVarsUser($match) 237 { 238 global $INFO; 239 240 $key = strtolower($match[2]); 241 242 if (!in_array($key, ['name', 'mail', 'grps'])) { 243 throw new StructException('"%s" is not a valid USER key', $key); 244 } 245 246 if (empty($INFO['userinfo'])) { 247 $filter = ''; 248 } else { 249 $filter = $INFO['userinfo'][$key]; 250 } 251 252 return $filter; 253 } 254 255 /** 256 * @return int cacheflag for this search 257 */ 258 public function getCacheFlag() 259 { 260 return $this->cacheFlag; 261 } 262 263 /** 264 * Access the dynamic parameters of this search 265 * 266 * Note: This call returns a clone of the parameters as they were initialized 267 * 268 * @return SearchConfigParameters 269 */ 270 public function getDynamicParameters() 271 { 272 return clone $this->dynamicParameters; 273 } 274 275 /** 276 * Get the config this search was initialized with 277 * 278 * Note that the search may have been modified by dynamic parameters or additional member calls 279 * 280 * @return array 281 */ 282 public function getConf() 283 { 284 return $this->config; 285 } 286} 287