1<?php 2namespace plugin\struct\types; 3 4use plugin\struct\meta\Column; 5use plugin\struct\meta\StructException; 6use 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 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 return substr(get_class($this), 20); 143 } 144 145 /** 146 * Return the current configuration for this type 147 * 148 * @return array 149 */ 150 public function getConfig() { 151 return $this->config; 152 } 153 154 /** 155 * @return boolean 156 */ 157 public function isMulti() { 158 return $this->ismulti; 159 } 160 161 /** 162 * @return string 163 */ 164 public function getLabel() { 165 return $this->label; 166 } 167 168 /** 169 * Returns the translated label for this type 170 * 171 * Uses the current language as determined by $conf['lang']. Falls back to english 172 * and then to the Schema label 173 * 174 * @return string 175 */ 176 public function getTranslatedLabel() { 177 global $conf; 178 $lang = $conf['lang']; 179 if(!blank($this->config['label'][$lang])) { 180 return $this->config['label'][$lang]; 181 } 182 if(!blank($this->config['label']['en'])) { 183 return $this->config['label']['en']; 184 } 185 return $this->label; 186 } 187 188 /** 189 * Returns the translated hint for this type 190 * 191 * Uses the current language as determined by $conf['lang']. Falls back to english. 192 * Returns empty string if no hint is configured 193 * 194 * @return string 195 */ 196 public function getTranslatedHint() { 197 global $conf; 198 $lang = $conf['lang']; 199 if(!blank($this->config['hint'][$lang])) { 200 return $this->config['hint'][$lang]; 201 } 202 if(!blank($this->config['hint']['en'])) { 203 return $this->config['hint']['en']; 204 } 205 return ''; 206 } 207 208 /** 209 * @return int 210 */ 211 public function getTid() { 212 return $this->tid; 213 } 214 215 /** 216 * @throws StructException 217 * @return Column 218 */ 219 public function getContext() { 220 if(is_null($this->context)) 221 throw new StructException('Empty column context requested. Type was probably initialized outside of Schema.'); 222 return $this->context; 223 } 224 225 /** 226 * @param Column $context 227 */ 228 public function setContext($context) { 229 $this->context = $context; 230 } 231 232 /** 233 * @return bool 234 */ 235 public function isVisibleInEditor() { 236 return $this->config['visibility']['ineditor']; 237 } 238 239 /** 240 * @return bool 241 */ 242 public function isVisibleInPage() { 243 return $this->config['visibility']['inpage']; 244 } 245 246 /** 247 * Split a single value into multiple values 248 * 249 * This function is called on saving data when only a single value instead of an array 250 * was submitted. 251 * 252 * Types implementing their own @see multiValueEditor() will probably want to override this 253 * 254 * @param string $value 255 * @return array 256 */ 257 public function splitValues($value) { 258 return array_map('trim', explode(',', $value)); 259 } 260 261 /** 262 * Return the editor to edit multiple values 263 * 264 * Types can override this to provide a better alternative than multiple entry fields 265 * 266 * @param string $name the form base name where this has to be stored 267 * @param string[] $values the current values 268 * @return string html 269 */ 270 public function multiValueEditor($name, $values) { 271 $html = ''; 272 foreach($values as $value) { 273 $html .= '<div class="multiwrap">'; 274 $html .= $this->valueEditor($name . '[]', $value); 275 $html .= '</div>'; 276 } 277 // empty field to add 278 $html .= '<div class="newtemplate">'; 279 $html .= '<div class="multiwrap">'; 280 $html .= $this->valueEditor($name . '[]', ''); 281 $html .= '</div>'; 282 $html .= '</div>'; 283 284 return $html; 285 } 286 287 /** 288 * Return the editor to edit a single value 289 * 290 * @param string $name the form name where this has to be stored 291 * @param string $value the current value 292 * @return string html 293 */ 294 public function valueEditor($name, $value) { 295 $class = 'struct_' . strtolower($this->getClass()); 296 297 // support the autocomplete configurations out of the box 298 if(isset($this->config['autocomplete']['maxresult']) && $this->config['autocomplete']['maxresult']) { 299 $class .= ' struct_autocomplete'; 300 } 301 302 $name = hsc($name); 303 $value = hsc($value); 304 $html = "<input name=\"$name\" value=\"$value\" class=\"$class\" />"; 305 return "$html"; 306 } 307 308 /** 309 * Output the stored data 310 * 311 * @param string|int $value the value stored in the database 312 * @param \Doku_Renderer $R the renderer currently used to render the data 313 * @param string $mode The mode the output is rendered in (eg. XHTML) 314 * @return bool true if $mode could be satisfied 315 */ 316 public function renderValue($value, \Doku_Renderer $R, $mode) { 317 $R->cdata($value); 318 return true; 319 } 320 321 /** 322 * format and return the data 323 * 324 * @param int[]|string[] $values the values stored in the database 325 * @param \Doku_Renderer $R the renderer currently used to render the data 326 * @param string $mode The mode the output is rendered in (eg. XHTML) 327 * @return bool true if $mode could be satisfied 328 */ 329 public function renderMultiValue($values, \Doku_Renderer $R, $mode) { 330 $len = count($values); 331 for($i = 0; $i < $len; $i++) { 332 $this->renderValue($values[$i], $R, $mode); 333 if($i < $len - 1) { 334 $R->cdata(', '); 335 } 336 } 337 return true; 338 } 339 340 /** 341 * This function builds a where clause for this column, comparing 342 * the current value stored in $column with $value. Types can use it to do 343 * clever things with the comparison. 344 * 345 * This default implementation is probably good enough for most basic types 346 * 347 * @param string $column The column name to us in the SQL 348 * @param string $comp The comparator @see Search::$COMPARATORS 349 * @param string $value 350 * @return array Tuple with the SQL and parameter array 351 */ 352 public function compare($column, $comp, $value) { 353 switch($comp) { 354 case '~': 355 $sql = "$column LIKE ?"; 356 $opt = array($value); 357 break; 358 case '!~': 359 $sql = "$column NOT LIKE ?"; 360 $opt = array($value); 361 break; 362 default: 363 $sql = "$column $comp ?"; 364 $opt = array($value); 365 } 366 367 return array($sql, $opt); 368 } 369 370 /** 371 * Validate and optionally clean a single value 372 * 373 * This function needs to throw a validation exception when validation fails. 374 * The exception message will be prefixed by the appropriate field on output 375 * 376 * The function should return the value as it should be saved later on. 377 * 378 * @param string|int $value 379 * @return int|string the cleaned value 380 * @throws ValidationException 381 */ 382 public function validate($value) { 383 return trim($value); 384 } 385 386 /** 387 * Overwrite to handle Ajax requests 388 * 389 * A call to DOKU_BASE/lib/exe/ajax.php?call=plugin_struct&column=schema.name will 390 * be redirected to this function on a fully initialized type. The result is 391 * JSON encoded and returned to the caller. Access additional parameter via $INPUT 392 * as usual 393 * 394 * @throws StructException when something goes wrong 395 * @return mixed 396 */ 397 public function handleAjax() { 398 throw new StructException('not implemented'); 399 } 400 401 /** 402 * Convenience method to access plugin language strings 403 * 404 * @param string $string 405 * @return string 406 */ 407 public function getLang($string) { 408 if(is_null($this->hlp)) $this->hlp = plugin_load('action', 'struct_autoloader'); 409 return $this->hlp->getLang($string); 410 } 411} 412