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