1<?php 2namespace plugin\struct\types; 3use plugin\struct\meta\Column; 4use plugin\struct\meta\StructException; 5use plugin\struct\meta\ValidationException; 6 7/** 8 * Class AbstractBaseType 9 * 10 * This class represents a basic type that can be configured to be used in a Schema. It is the main 11 * part of a column definition as defined in meta\Column 12 * 13 * This defines also how the content of the coulmn will be entered and formatted. 14 * 15 * @package plugin\struct\types 16 * @see Column 17 */ 18abstract class AbstractBaseType { 19 20 /** 21 * @var array current config 22 */ 23 protected $config = array(); 24 25 /** 26 * @var array config keys that should not be cleaned despite not being in $config 27 */ 28 protected $keepconfig = array('label', 'hint'); 29 30 /** 31 * @var string label for the field 32 */ 33 protected $label = ''; 34 35 /** 36 * @var bool is this a multivalue field? 37 */ 38 protected $ismulti = false; 39 40 /** 41 * @var int the type ID 42 */ 43 protected $tid = 0; 44 45 /** 46 * @var null|Column the column context this type is part of 47 */ 48 protected $context = null; 49 50 /** 51 * @var \DokuWiki_Plugin 52 */ 53 protected $hlp = null; 54 55 /** 56 * AbstractBaseType constructor. 57 * @param array|null $config The configuration, might be null if nothing saved, yet 58 * @param string $label The label for this field (empty for new definitions= 59 * @param bool $ismulti Should this field accept multiple values? 60 * @param int $tid The id of this type if it has been saved, yet 61 */ 62 public function __construct($config = null, $label = '', $ismulti = false, $tid = 0) { 63 // initialize the configuration, ignoring all keys that are not supposed to be here 64 if(!is_null($config)) { 65 foreach($config as $key => $value) { 66 if(isset($this->config[$key]) || in_array($key, $this->keepconfig)) { 67 $this->config[$key] = $value; 68 } 69 } 70 } 71 72 $this->initTransConfig(); 73 $this->label = $label; 74 $this->ismulti = (bool) $ismulti; 75 $this->tid = $tid; 76 } 77 78 /** 79 * Add the translatable keys to the configuration 80 * 81 * This checks if a configuration for the translation plugin exists and if so 82 * adds all configured languages to the config array. This ensures all types 83 * can have translatable labels. 84 */ 85 protected function initTransConfig() { 86 global $conf; 87 $lang = $conf['lang']; 88 if(isset($conf['plugin']['translation']['translations'])) { 89 $lang .= ' ' . $conf['plugin']['translation']['translations']; 90 } 91 $langs = explode(' ', $lang); 92 $langs = array_map('trim', $langs); 93 $langs = array_filter($langs); 94 $langs = array_unique($langs); 95 96 if(!isset($this->config['label'])) $this->config['label'] = array(); 97 if(!isset($this->config['hint'])) $this->config['hint'] = array(); 98 // initialize missing keys 99 foreach($langs as $lang) { 100 if(!isset($this->config['label'][$lang])) $this->config['label'][$lang] = ''; 101 if(!isset($this->config['hint'][$lang])) $this->config['hint'][$lang] = ''; 102 } 103 // strip unknown languages 104 foreach(array_keys($this->config['label']) as $key) { 105 if(!in_array($key, $langs)) unset($this->config['label'][$key]); 106 } 107 foreach(array_keys($this->config['hint']) as $key) { 108 if(!in_array($key, $langs)) unset($this->config['hint'][$key]); 109 } 110 111 } 112 113 /** 114 * Returns data as associative array 115 * 116 * @return array 117 */ 118 public function getAsEntry() { 119 return array( 120 'config' => json_encode($this->config), 121 'label' => $this->label, 122 'ismulti' => $this->ismulti, 123 'class' => $this->getClass() 124 ); 125 } 126 127 /** 128 * The class name of this type (no namespace) 129 * @return string 130 */ 131 public function getClass() { 132 return substr(get_class($this), 20); 133 } 134 135 /** 136 * Return the current configuration for this type 137 * 138 * @return array 139 */ 140 public function getConfig() { 141 return $this->config; 142 } 143 144 /** 145 * @return boolean 146 */ 147 public function isMulti() { 148 return $this->ismulti; 149 } 150 151 /** 152 * @return string 153 */ 154 public function getLabel() { 155 return $this->label; 156 } 157 158 /** 159 * Returns the translated label for this type 160 * 161 * Uses the current language as determined by $conf['lang']. Falls back to english 162 * and then to the Schema label 163 * 164 * @return string 165 */ 166 public function getTranslatedLabel() { 167 global $conf; 168 $lang = $conf['lang']; 169 if(!blank($this->config['label'][$lang])) { 170 return $this->config['label'][$lang]; 171 } 172 if(!blank($this->config['label']['en'])) { 173 return $this->config['label']['en']; 174 } 175 return $this->label; 176 } 177 178 /** 179 * Returns the translated hint for this type 180 * 181 * Uses the current language as determined by $conf['lang']. Falls back to english. 182 * Returns empty string if no hint is configured 183 * 184 * @return string 185 */ 186 public function getTranslatedHint() { 187 global $conf; 188 $lang = $conf['lang']; 189 if(!blank($this->config['hint'][$lang])) { 190 return $this->config['hint'][$lang]; 191 } 192 if(!blank($this->config['hint']['en'])) { 193 return $this->config['hint']['en']; 194 } 195 return ''; 196 } 197 198 /** 199 * @return int 200 */ 201 public function getTid() { 202 return $this->tid; 203 } 204 205 /** 206 * @throws StructException 207 * @return Column 208 */ 209 public function getContext() { 210 if(is_null($this->context)) 211 throw new StructException('Empty column context requested. Type was probably initialized outside of Schema.'); 212 return $this->context; 213 } 214 215 /** 216 * @param Column $context 217 */ 218 public function setContext($context) { 219 $this->context = $context; 220 } 221 222 /** 223 * Split a single value into multiple values 224 * 225 * This function is called on saving data when only a single value instead of an array 226 * was submitted. 227 * 228 * Types implementing their own @see multiValueEditor() will probably want to override this 229 * 230 * @param string $value 231 * @return array 232 */ 233 public function splitValues($value) { 234 return array_map('trim', explode(',', $value)); 235 } 236 237 /** 238 * Return the editor to edit multiple values 239 * 240 * Types can override this to provide a better alternative than multiple entry fields 241 * 242 * @param string $name the form base name where this has to be stored 243 * @param string[] $values the current values 244 * @return string html 245 */ 246 public function multiValueEditor($name, $values) { 247 $html = ''; 248 foreach($values as $value) { 249 $html .= '<div class="multiwrap">'; 250 $html .= $this->valueEditor($name . '[]', $value); 251 $html .= '</div>'; 252 } 253 // empty field to add 254 $html .= '<div class="newtemplate">'; 255 $html .= '<div class="multiwrap">'; 256 $html .= $this->valueEditor($name . '[]', ''); 257 $html .= '</div>'; 258 $html .= '</div>'; 259 260 return $html; 261 } 262 263 /** 264 * Return the editor to edit a single value 265 * 266 * @param string $name the form name where this has to be stored 267 * @param string $value the current value 268 * @return string html 269 */ 270 public function valueEditor($name, $value) { 271 $class = 'struct_'.strtolower($this->getClass()); 272 273 // support the autocomplete configurations out of the box 274 if(isset($this->config['autocomplete']['maxresult']) && $this->config['autocomplete']['maxresult']) { 275 $class .= ' struct_autocomplete'; 276 } 277 278 $name = hsc($name); 279 $value = hsc($value); 280 $html = "<input name=\"$name\" value=\"$value\" class=\"$class\" />"; 281 return "$html"; 282 } 283 284 /** 285 * Output the stored data 286 * 287 * @param string|int $value the value stored in the database 288 * @param \Doku_Renderer $R the renderer currently used to render the data 289 * @param string $mode The mode the output is rendered in (eg. XHTML) 290 * @return bool true if $mode could be satisfied 291 */ 292 public function renderValue($value, \Doku_Renderer $R, $mode) { 293 $R->cdata($value); 294 return true; 295 } 296 297 /** 298 * format and return the data 299 * 300 * @param int[]|string[] $values the values stored in the database 301 * @param \Doku_Renderer $R the renderer currently used to render the data 302 * @param string $mode The mode the output is rendered in (eg. XHTML) 303 * @return bool true if $mode could be satisfied 304 */ 305 public function renderMultiValue($values, \Doku_Renderer $R, $mode) { 306 $len = count($values); 307 for($i = 0; $i < $len; $i++) { 308 $this->renderValue($values[$i], $R, $mode); 309 if($i < $len - 1) { 310 $R->cdata(', '); 311 } 312 } 313 return true; 314 } 315 316 /** 317 * This function builds a where clause for this column, comparing 318 * the current value stored in $column with $value. Types can use it to do 319 * clever things with the comparison. 320 * 321 * This default implementation is probably good enough for most basic types 322 * 323 * @param string $column The column name to us in the SQL 324 * @param string $comp The comparator @see Search::$COMPARATORS 325 * @param string $value 326 * @return array Tuple with the SQL and parameter array 327 */ 328 public function compare($column, $comp, $value) { 329 switch ($comp) { 330 case '~': 331 $sql = "$column LIKE ?"; 332 $opt = array($value); 333 break; 334 case '!~': 335 $sql = "$column NOT LIKE ?"; 336 $opt = array($value); 337 break; 338 default: 339 $sql = "$column $comp ?"; 340 $opt = array($value); 341 } 342 343 return array($sql, $opt); 344 } 345 346 /** 347 * Validate and optionally clean a single value 348 * 349 * This function needs to throw a validation exception when validation fails. 350 * The exception message will be prefixed by the appropriate field on output 351 * 352 * The function should return the value as it should be saved later on. 353 * 354 * @param string|int $value 355 * @return int|string the cleaned value 356 * @throws ValidationException 357 */ 358 public function validate($value) { 359 return trim($value); 360 } 361 362 /** 363 * Overwrite to handle Ajax requests 364 * 365 * A call to DOKU_BASE/lib/exe/ajax.php?call=plugin_struct&column=schema.name will 366 * be redirected to this function on a fully initialized type. The result is 367 * JSON encoded and returned to the caller. Access additional parameter via $INPUT 368 * as usual 369 * 370 * @throws StructException when something goes wrong 371 * @return mixed 372 */ 373 public function handleAjax() { 374 throw new StructException('not implemented'); 375 } 376 377 /** 378 * Convenience method to access plugin language strings 379 * 380 * @param string $string 381 * @return string 382 */ 383 public function getLang($string) { 384 if(is_null($this->hlp)) $this->hlp = plugin_load('action', 'struct_autoloader'); 385 return $this->hlp->getLang($string); 386 } 387} 388