xref: /plugin/struct/helper/field.php (revision 30ad7b71bd28146309c181b77f1207cf670a913f)
1<?php
2
3use dokuwiki\plugin\struct\meta\Column;
4use dokuwiki\plugin\struct\meta\Schema;
5use dokuwiki\plugin\struct\meta\StructException;
6use dokuwiki\plugin\struct\meta\Value;
7use dokuwiki\plugin\struct\meta\ValueValidator;
8use dokuwiki\plugin\struct\types\Lookup;
9use dokuwiki\plugin\struct\types\Page;
10use dokuwiki\plugin\struct\types\User;
11
12/**
13 * Allows adding a single struct field as a bureaucracy field
14 *
15 * This class is used when a field of the type struct_field is encountered in the
16 * bureaucracy syntax.
17 */
18class helper_plugin_struct_field extends helper_plugin_bureaucracy_field
19{
20    /** @var  Column */
21    public $column;
22
23    /**
24     * Initialize the appropriate column
25     *
26     * @param array $args
27     */
28    public function initialize($args)
29    {
30        $this->init($args);
31
32        // find the column
33        try {
34            $this->column = $this->findColumn($this->opt['label']);
35        } catch (StructException $e) {
36            msg(hsc($e->getMessage()), -1);
37        }
38
39        $this->standardArgs($args);
40    }
41
42    /**
43     * Sets the value and validates it
44     *
45     * @param mixed $value
46     * @return bool value was set successfully validated
47     */
48    protected function setVal($value)
49    {
50        if (!$this->column) {
51            $value = '';
52            //don't validate placeholders here
53        } elseif ($this->replace($value) == $value) {
54            $validator = new ValueValidator();
55            $this->error = !$validator->validateValue($this->column, $value);
56            if ($this->error) {
57                foreach ($validator->getErrors() as $error) {
58                    msg(hsc($error), -1);
59                }
60            }
61        }
62
63        if ($value === [] || $value === '') {
64            if (!isset($this->opt['optional'])) {
65                $this->error = true;
66                if ($this->column) {
67                    $label = $this->column->getTranslatedLabel();
68                } else {
69                    $label = $this->opt['label'];
70                }
71                msg(sprintf($this->getLang('e_required'), hsc($label)), -1);
72            }
73        }
74
75        $this->opt['value'] = $value;
76        return !$this->error;
77    }
78
79    /**
80     * Creates the HTML for the field
81     *
82     * @param array $params
83     * @param Doku_Form $form
84     * @param int $formid
85     */
86    public function renderfield($params, Doku_Form $form, $formid)
87    {
88        if (!$this->column) return;
89
90        // this is what parent does
91        $this->_handlePreload();
92        if (!$form->_infieldset) {
93            $form->startFieldset('');
94        }
95        if ($this->error) {
96            $params['class'] = 'bureaucracy_error';
97        }
98
99        // output the field
100        $value = $this->createValue();
101        $field = $this->makeField($value, $params['name']);
102        $form->addElement($field);
103    }
104
105    /**
106     * Adds replacement for type user to the parent method
107     *
108     * @return array|mixed|string
109     */
110    public function getReplacementValue()
111    {
112        $value = $this->getParam('value');
113
114        if (is_array($value)) {
115            return [$this, 'replacementMultiValueCallback'];
116        }
117
118        if (!empty($value) && $this->column->getType() instanceof User) {
119            return userlink($value, true);
120        }
121
122        return parent::getReplacementValue();
123    }
124
125    /**
126     * Adds handling of type user to the parent method
127     *
128     * @param $matches
129     * @return string
130     */
131    public function replacementMultiValueCallback($matches)
132    {
133        $value = $this->opt['value'];
134
135        //default value
136        if (is_null($value) || $value === false) {
137            if (isset($matches['default']) && $matches['default'] != '') {
138                return $matches['default'];
139            }
140            return $matches[0];
141        }
142
143        if (!empty($value) && $this->column->getType() instanceof User) {
144            $value = array_map(static fn($user) => userlink($user, true), $value);
145        }
146
147        //check if matched string containts a pair of brackets
148        $delimiter = preg_match('/\(.*\)/s', $matches[0]) ? $matches['delimiter'] : ', ';
149
150        return implode($delimiter, $value);
151    }
152
153    /**
154     * Returns a Value object for the current column.
155     * Special handling for Page and Lookup literal form values.
156     *
157     * @return Value
158     */
159    protected function createValue()
160    {
161        // input value or appropriately initialized empty value
162        $preparedValue = $this->opt['value'] ?? ($this->column->isMulti() ? [] : '');
163
164        // page fields might need to be JSON encoded depending on usetitles config
165        if (
166            $this->column->getType() instanceof Page
167            && $this->column->getType()->getConfig()['usetitles']
168        ) {
169            if ($this->column->isMulti()) {
170                $preparedValue = array_map(static fn($val) => json_encode([$val, null], JSON_THROW_ON_ERROR), $preparedValue);
171            } else {
172                $preparedValue = json_encode([$preparedValue, null], JSON_THROW_ON_ERROR);
173            }
174        }
175
176        $value = new Value($this->column, $preparedValue);
177
178        // no way to pass $israw parameter to constructor, so re-set the Lookup value
179        if ($this->column->getType() instanceof Lookup) {
180            $value->setValue($preparedValue, true);
181        }
182
183        return $value;
184    }
185
186    /**
187     * Create the input field
188     *
189     * @param Value $field
190     * @param String $name field's name
191     * @return string
192     */
193    protected function makeField(Value $field, $name)
194    {
195        $trans = hsc($field->getColumn()->getTranslatedLabel());
196        $hint = hsc($field->getColumn()->getTranslatedHint());
197        $class = $hint ? 'hashint' : '';
198        $lclass = $this->error ? 'bureaucracy_error' : '';
199        $colname = $field->getColumn()->getFullQualifiedLabel();
200        $required = empty($this->opt['optional']) ? ' <sup>*</sup>' : '';
201
202        $id = uniqid('struct__', true);
203        $input = $field->getValueEditor($name, $id);
204
205        $html = '<div class="field">';
206        $html .= "<label class=\"$lclass\" data-column=\"$colname\" for=\"$id\">";
207        $html .= "<span class=\"label $class\" title=\"$hint\">$trans$required</span>";
208        $html .= '</label>';
209        $html .= "<span class=\"input\">$input</span>";
210        $html .= '</div>';
211
212        return $html;
213    }
214
215    /**
216     * Tries to find the correct column and schema
217     *
218     * @param string $colname
219     * @return Column
220     * @throws StructException
221     */
222    protected function findColumn($colname)
223    {
224        [$table, $label] = explode('.', $colname, 2);
225        if (!$table || !$label) {
226            throw new StructException('Field \'%s\' not given in schema.field form', $colname);
227        }
228        $schema = new Schema($table);
229        return $schema->findColumn($label);
230    }
231
232    /**
233     * This ensures all language strings are still working
234     *
235     * @return string always 'bureaucracy'
236     */
237    public function getPluginName()
238    {
239        return 'bureaucracy';
240    }
241}
242