xref: /plugin/struct/types/AbstractBaseType.php (revision 4d220607ed4491cd4b59bb07ebea91787ab19050)
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        $name = hsc($name);
273        $value = hsc($value);
274        $html = "<input name=\"$name\" value=\"$value\" class=\"$class\" />";
275        return "$html";
276    }
277
278    /**
279     * Output the stored data
280     *
281     * @param string|int $value the value stored in the database
282     * @param \Doku_Renderer $R the renderer currently used to render the data
283     * @param string $mode The mode the output is rendered in (eg. XHTML)
284     * @return bool true if $mode could be satisfied
285     */
286    public function renderValue($value, \Doku_Renderer $R, $mode) {
287        $R->cdata($value);
288        return true;
289    }
290
291    /**
292     * format and return the data
293     *
294     * @param int[]|string[] $values the values stored in the database
295     * @param \Doku_Renderer $R the renderer currently used to render the data
296     * @param string $mode The mode the output is rendered in (eg. XHTML)
297     * @return bool true if $mode could be satisfied
298     */
299    public function renderMultiValue($values, \Doku_Renderer $R, $mode) {
300        $len = count($values);
301        for($i = 0; $i < $len; $i++) {
302            $this->renderValue($values[$i], $R, $mode);
303            if($i < $len - 1) {
304                $R->cdata(', ');
305            }
306        }
307        return true;
308    }
309
310    /**
311     * This function builds a where clause for this column, comparing
312     * the current value stored in $column with $value. Types can use it to do
313     * clever things with the comparison.
314     *
315     * This default implementation is probably good enough for most basic types
316     *
317     * @param string $column The column name to us in the SQL
318     * @param string $comp The comparator @see Search::$COMPARATORS
319     * @param string $value
320     * @return array Tuple with the SQL and parameter array
321     */
322    public function compare($column, $comp, $value) {
323        switch ($comp) {
324            case '~':
325                $sql = "$column LIKE ?";
326                $opt = array($value);
327                break;
328            case '!~':
329                $sql = "$column NOT LIKE ?";
330                $opt = array($value);
331                break;
332            default:
333                $sql = "$column $comp ?";
334                $opt = array($value);
335        }
336
337        return array($sql, $opt);
338    }
339
340    /**
341     * Validate a single value
342     *
343     * This function needs to throw a validation exception when validation fails.
344     * The exception message will be prefixed by the appropriate field on output
345     *
346     * @param string|int $value
347     * @throws ValidationException
348     */
349    public function validate($value) {
350        // nothing by default - we allow everything
351    }
352
353    /**
354     * Overwrite to handle Ajax requests
355     *
356     * A call to DOKU_BASE/lib/exe/ajax.php?call=plugin_struct&column=schema.name will
357     * be redirected to this function on a fully initialized type. The result is
358     * JSON encoded and returned to the caller. Access additional parameter via $INPUT
359     * as usual
360     *
361     * @throws StructException when something goes wrong
362     * @return mixed
363     */
364    public function handleAjax() {
365        throw new StructException('not implemented');
366    }
367
368    /**
369     * Convenience method to access plugin language strings
370     *
371     * @param string $string
372     * @return string
373     */
374    public function getLang($string) {
375        if(is_null($this->hlp)) $this->hlp = plugin_load('action', 'struct_autoloader');
376        return $this->hlp->getLang($string);
377    }
378}
379