1<?php 2 3namespace dokuwiki\plugin\struct\types; 4 5use dokuwiki\plugin\struct\meta\Column; 6use dokuwiki\plugin\struct\meta\PageColumn; 7use dokuwiki\plugin\struct\meta\QueryBuilder; 8use dokuwiki\plugin\struct\meta\QueryBuilderWhere; 9use dokuwiki\plugin\struct\meta\RevisionColumn; 10use dokuwiki\plugin\struct\meta\RowColumn; 11use dokuwiki\plugin\struct\meta\Schema; 12use dokuwiki\plugin\struct\meta\Search; 13use dokuwiki\plugin\struct\meta\SummaryColumn; 14use dokuwiki\plugin\struct\meta\UserColumn; 15use dokuwiki\plugin\struct\meta\Value; 16 17class Lookup extends Dropdown 18{ 19 protected $config = [ 20 'schema' => '', 21 'field' => '', 22 'combobox' => false, 23 ]; 24 25 /** @var Column caches the referenced column */ 26 protected $column; 27 28 /** 29 * Dropdown constructor. 30 * 31 * @param array|null $config 32 * @param string $label 33 * @param bool $ismulti 34 * @param int $tid 35 */ 36 public function __construct($config = null, $label = '', $ismulti = false, $tid = 0) 37 { 38 parent::__construct($config, $label, $ismulti, $tid); 39 $this->config['schema'] = Schema::cleanTableName($this->config['schema']); 40 } 41 42 /** 43 * Get the configured loojup column 44 * 45 * @return Column|false 46 */ 47 protected function getLookupColumn() 48 { 49 if ($this->column instanceof Column) return $this->column; 50 $this->column = $this->getColumn($this->config['schema'], $this->config['field']); 51 return $this->column; 52 } 53 54 /** 55 * Gets the given column, applies language place holder 56 * 57 * @param string $table 58 * @param string $infield 59 * @return Column|false 60 */ 61 protected function getColumn($table, $infield) 62 { 63 global $conf; 64 65 $table = new Schema($table); 66 if (!$table->getId()) { 67 // schema does not exist 68 msg(sprintf('Schema %s does not exist', $table), -1); 69 return false; 70 } 71 72 // apply language replacement 73 $field = str_replace('$LANG', $conf['lang'], $infield); 74 $column = $table->findColumn($field); 75 if (!$column) { 76 $field = str_replace('$LANG', 'en', $infield); // fallback to en 77 $column = $table->findColumn($field); 78 } 79 if (!$column) { 80 if ($infield == '%pageid%') { 81 $column = new PageColumn(0, new Page(), $table); 82 } 83 if ($infield == '%title%') { 84 $column = new PageColumn(0, new Page(['usetitles' => true]), $table); 85 } 86 if ($infield == '%lastupdate%') { 87 $column = new RevisionColumn(0, new DateTime(), $table); 88 } 89 if ($infield == '%lasteditor%') { 90 $column = new UserColumn(0, new User(), $table); 91 } 92 if ($infield == '%lastsummary%') { 93 return new SummaryColumn(0, new AutoSummary(), $table); 94 } 95 if ($infield == '%rowid%') { 96 $column = new RowColumn(0, new Decimal(), $table); 97 } 98 } 99 if (!$column) { 100 // field does not exist 101 msg(sprintf('Field %s.%s does not exist', $table, $infield), -1); 102 return false; 103 } 104 105 if ($column->isMulti()) { 106 // field is multi 107 msg(sprintf('Field %s.%s is a multi field - not allowed for lookup', $table, $field), -1); 108 return false; 109 } 110 111 return $column; 112 } 113 114 /** 115 * Creates the options array 116 * 117 * @return array 118 */ 119 protected function getOptions() 120 { 121 $schema = $this->config['schema']; 122 $column = $this->getLookupColumn(); 123 if (!$column) return []; 124 $field = $column->getLabel(); 125 126 $search = new Search(); 127 $search->addSchema($schema); 128 $search->addColumn($field); 129 $search->addSort($field); 130 131 $result = $search->getRows(); 132 $pids = $search->getPids(); 133 $rids = $search->getRids(); 134 $len = count($result); 135 136 $options = ['' => '']; 137 for ($i = 0; $i < $len; $i++) { 138 $val = json_encode([$pids[$i], (int)$rids[$i]], JSON_THROW_ON_ERROR); 139 $options[$val] = $result[$i][0]->getDisplayValue(); 140 } 141 return $options; 142 } 143 144 145 /** 146 * Render using linked field 147 * 148 * @param int|string $value 149 * @param \Doku_Renderer $R 150 * @param string $mode 151 * @return bool 152 */ 153 public function renderValue($value, \Doku_Renderer $R, $mode) 154 { 155 [, $value] = \helper_plugin_struct::decodeJson($value); 156 $column = $this->getLookupColumn(); 157 if (!$column) return false; 158 return $column->getType()->renderValue($value, $R, $mode); 159 } 160 161 /** 162 * Render using linked field 163 * 164 * @param \int[]|\string[] $values 165 * @param \Doku_Renderer $R 166 * @param string $mode 167 * @return bool 168 */ 169 public function renderMultiValue($values, \Doku_Renderer $R, $mode) 170 { 171 $values = array_map( 172 function ($val) { 173 [, $val] = \helper_plugin_struct::decodeJson($val); 174 return $val; 175 }, 176 $values 177 ); 178 $column = $this->getLookupColumn(); 179 if (!$column) return false; 180 return $column->getType()->renderMultiValue($values, $R, $mode); 181 } 182 183 /** 184 * @param string $value 185 * @return string 186 */ 187 public function rawValue($value) 188 { 189 [$value] = \helper_plugin_struct::decodeJson($value); 190 return $value; 191 } 192 193 /** 194 * @param string $value 195 * @return string 196 */ 197 public function displayValue($value) 198 { 199 [, $value] = \helper_plugin_struct::decodeJson($value); 200 $column = $this->getLookupColumn(); 201 if ($column) { 202 return $column->getType()->displayValue($value); 203 } else { 204 return ''; 205 } 206 } 207 208 /** 209 * This is the value to be used as argument to a filter for another column. 210 * 211 * In a sense this is the counterpart to the @param string $value 212 * 213 * @return string 214 * @see filter() function 215 * 216 */ 217 public function compareValue($value) 218 { 219 [, $value] = \helper_plugin_struct::decodeJson($value); 220 $column = $this->getLookupColumn(); 221 if ($column) { 222 return $column->getType()->rawValue($value); 223 } else { 224 return ''; 225 } 226 } 227 228 229 /** 230 * Merge with lookup table 231 * 232 * @param QueryBuilder $QB 233 * @param string $tablealias 234 * @param string $colname 235 * @param string $alias 236 */ 237 public function select(QueryBuilder $QB, $tablealias, $colname, $alias) 238 { 239 $schema = 'data_' . $this->config['schema']; 240 $column = $this->getLookupColumn(); 241 if (!$column) { 242 parent::select($QB, $tablealias, $colname, $alias); 243 return; 244 } 245 246 $field = $column->getColName(); 247 $rightalias = $QB->generateTableAlias(); 248 $QB->addLeftJoin( 249 $tablealias, 250 $schema, 251 $rightalias, 252 "STRUCT_LOOKUP($tablealias.$colname, 0) = $rightalias.pid " . 253 "AND STRUCT_LOOKUP($tablealias.$colname, 1) = $rightalias.rid " . 254 "AND $rightalias.latest = 1" 255 ); 256 $column->getType()->select($QB, $rightalias, $field, $alias); 257 $sql = $QB->getSelectStatement($alias); 258 $QB->addSelectStatement("STRUCT_JSON($tablealias.$colname, $sql)", $alias); 259 } 260 261 /** 262 * Compare against lookup table 263 * 264 * @param QueryBuilderWhere $add 265 * @param string $tablealias 266 * @param string $colname 267 * @param string $comp 268 * @param string|\string[] $value 269 * @param string $op 270 */ 271 public function filter(QueryBuilderWhere $add, $tablealias, $colname, $comp, $value, $op) 272 { 273 $schema = 'data_' . $this->config['schema']; 274 $column = $this->getLookupColumn(); 275 if (!$column) { 276 parent::filter($add, $tablealias, $colname, $comp, $value, $op); 277 return; 278 } 279 $field = $column->getColName(); 280 281 // compare against lookup field 282 $QB = $add->getQB(); 283 $rightalias = $QB->generateTableAlias(); 284 $QB->addLeftJoin( 285 $tablealias, 286 $schema, 287 $rightalias, 288 "$tablealias.$colname = STRUCT_JSON($rightalias.pid, CAST($rightalias.rid AS DECIMAL)) AND " . 289 "$rightalias.latest = 1" 290 ); 291 $column->getType()->filter($add, $rightalias, $field, $comp, $value, $op); 292 } 293 294 /** 295 * Sort by lookup table 296 * 297 * @param QueryBuilder $QB 298 * @param string $tablealias 299 * @param string $colname 300 * @param string $order 301 */ 302 public function sort(QueryBuilder $QB, $tablealias, $colname, $order) 303 { 304 $schema = 'data_' . $this->config['schema']; 305 $column = $this->getLookupColumn(); 306 if (!$column) { 307 parent::sort($QB, $tablealias, $colname, $order); 308 return; 309 } 310 $field = $column->getColName(); 311 312 $rightalias = $QB->generateTableAlias(); 313 $QB->addLeftJoin( 314 $tablealias, 315 $schema, 316 $rightalias, 317 "$tablealias.$colname = STRUCT_JSON($rightalias.pid, CAST($rightalias.rid AS DECIMAL)) AND " . 318 "$rightalias.latest = 1" 319 ); 320 $column->getType()->sort($QB, $rightalias, $field, $order); 321 } 322} 323