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 foreach($langs as $lang) { 93 if(!isset($this->config['label'][$lang])) $this->config['label'][$lang] = ''; 94 if(!isset($this->config['hint'][$lang])) $this->config['hint'][$lang] = ''; 95 } 96 } 97 98 /** 99 * Returns data as associative array 100 * 101 * @return array 102 */ 103 public function getAsEntry() { 104 return array( 105 'config' => json_encode($this->config), 106 'label' => $this->label, 107 'ismulti' => $this->ismulti, 108 'class' => $this->getClass() 109 ); 110 } 111 112 /** 113 * The class name of this type (no namespace) 114 * @return string 115 */ 116 public function getClass() { 117 return substr(get_class($this), 20); 118 } 119 120 /** 121 * Return the current configuration for this type 122 * 123 * @return array 124 */ 125 public function getConfig() { 126 return $this->config; 127 } 128 129 /** 130 * @return boolean 131 */ 132 public function isMulti() { 133 return $this->ismulti; 134 } 135 136 /** 137 * @return string 138 */ 139 public function getLabel() { 140 return $this->label; 141 } 142 143 /** 144 * Returns the translated label for this type 145 * 146 * Uses the current language as determined by $conf['lang']. Falls back to english 147 * and then to the Schema label 148 * 149 * @return string 150 */ 151 public function getTranslatedLabel() { 152 global $conf; 153 $lang = $conf['lang']; 154 if(!blank($this->config['label'][$lang])) { 155 return $this->config['label'][$lang]; 156 } 157 if(!blank($this->config['label']['en'])) { 158 return $this->config['label']['en']; 159 } 160 return $this->label; 161 } 162 163 /** 164 * Returns the translated hint for this type 165 * 166 * Uses the current language as determined by $conf['lang']. Falls back to english. 167 * Returns empty string if no hint is configured 168 * 169 * @return string 170 */ 171 public function getTranslatedHint() { 172 global $conf; 173 $lang = $conf['lang']; 174 if(!blank($this->config['hint'][$lang])) { 175 return $this->config['hint'][$lang]; 176 } 177 if(!blank($this->config['hint']['en'])) { 178 return $this->config['hint']['en']; 179 } 180 return ''; 181 } 182 183 /** 184 * @return int 185 */ 186 public function getTid() { 187 return $this->tid; 188 } 189 190 /** 191 * Split a single value into multiple values 192 * 193 * This function is called on saving data when only a single value instead of an array 194 * was submitted. 195 * 196 * Types implementing their own @see multiValueEditor() will probably want to override this 197 * 198 * @param string $value 199 * @return array 200 */ 201 public function splitValues($value) { 202 return array_map('trim', explode(',', $value)); 203 } 204 205 /** 206 * Return the editor to edit multiple values 207 * 208 * Types can override this to provide a better alternative than multiple entry fields 209 * 210 * @param string $name the form base name where this has to be stored 211 * @param string[] $values the current values 212 * @return string html 213 */ 214 public function multiValueEditor($name, $values) { 215 $html = ''; 216 foreach($values as $value) { 217 $html .= '<div class="multiwrap">'; 218 $html .= $this->valueEditor($name . '[]', $value); 219 $html .= '</div>'; 220 } 221 // empty field to add 222 $html .= '<div class="newtemplate">'; 223 $html .= '<div class="multiwrap">'; 224 $html .= $this->valueEditor($name . '[]', ''); 225 $html .= '</div>'; 226 $html .= '</div>'; 227 228 return $html; 229 } 230 231 /** 232 * Return the editor to edit a single value 233 * 234 * @param string $name the form name where this has to be stored 235 * @param string $value the current value 236 * @return string html 237 */ 238 public function valueEditor($name, $value) { 239 $class = 'struct_'.strtolower($this->getClass()); 240 $name = hsc($name); 241 $value = hsc($value); 242 $html = "<input name=\"$name\" value=\"$value\" class=\"$class\" />"; 243 return "$html"; 244 } 245 246 /** 247 * Output the stored data 248 * 249 * @param string|int $value the value stored in the database 250 * @param \Doku_Renderer $R the renderer currently used to render the data 251 * @param string $mode The mode the output is rendered in (eg. XHTML) 252 * @return bool true if $mode could be satisfied 253 */ 254 public function renderValue($value, \Doku_Renderer $R, $mode) { 255 $R->cdata($value); 256 return true; 257 } 258 259 /** 260 * format and return the data 261 * 262 * @param int[]|string[] $values the values stored in the database 263 * @param \Doku_Renderer $R the renderer currently used to render the data 264 * @param string $mode The mode the output is rendered in (eg. XHTML) 265 * @return bool true if $mode could be satisfied 266 */ 267 public function renderMultiValue($values, \Doku_Renderer $R, $mode) { 268 $len = count($values); 269 for($i = 0; $i < $len; $i++) { 270 $this->renderValue($values[$i], $R, $mode); 271 if($i < $len - 1) { 272 $R->cdata(', '); 273 } 274 } 275 return true; 276 } 277 278 /** 279 * This function builds a where clause for this column, comparing 280 * the current value stored in $column with $value. Types can use it to do 281 * clever things with the comparison. 282 * 283 * This default implementation is probably good enough for most basic types 284 * 285 * @param string $column The column name to us in the SQL 286 * @param string $comp The comparator @see Search::$COMPARATORS 287 * @param string $value 288 * @return array Tuple with the SQL and parameter array 289 */ 290 public function compare($column, $comp, $value) { 291 switch ($comp) { 292 case '~': 293 $sql = "$column LIKE ?"; 294 $opt = array($value); 295 break; 296 case '!~': 297 $sql = "$column NOT LIKE ?"; 298 $opt = array($value); 299 break; 300 default: 301 $sql = "$column $comp ?"; 302 $opt = array($value); 303 } 304 305 return array($sql, $opt); 306 } 307 308 /** 309 * Validate a single value 310 * 311 * This function needs to throw a validation exception when validation fails. 312 * The exception message will be prefixed by the appropriate field on output 313 * 314 * @param string|int $value 315 * @throws ValidationException 316 */ 317 public function validate($value) { 318 // nothing by default - we allow everything 319 } 320 321 /** 322 * Overwrite to handle Ajax requests 323 * 324 * A call to DOKU_BASE/lib/exe/ajax.php?call=plugin_struct&column=schema.name will 325 * be redirected to this function on a fully initialized type. The result is 326 * JSON encoded and returned to the caller. Access additional parameter via $INPUT 327 * as usual 328 * 329 * @throws StructException when something goes wrong 330 * @return mixed 331 */ 332 public function handleAjax() { 333 throw new StructException('not implemented'); 334 } 335 336 /** 337 * Convenience method to access plugin language strings 338 * 339 * @param string $string 340 * @return string 341 */ 342 public function getLang($string) { 343 if(is_null($this->hlp)) $this->hlp = plugin_load('action', 'struct_autoloader'); 344 return $this->hlp->getLang($string); 345 } 346} 347