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