xref: /plugin/struct/types/AbstractBaseType.php (revision 881e940c5447b444534e39519dc2fdb881587526)
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