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