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