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