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