xref: /plugin/struct/helper/field.php (revision 473a32c359f5248c755505d09942b8f27ab16083)
1ed3de3d6SAndreas Gohr<?php
2d6d97f60SAnna Dabrowska
3ba766201SAndreas Gohruse dokuwiki\plugin\struct\meta\Column;
4ba766201SAndreas Gohruse dokuwiki\plugin\struct\meta\Schema;
5ba766201SAndreas Gohruse dokuwiki\plugin\struct\meta\StructException;
6ba766201SAndreas Gohruse dokuwiki\plugin\struct\meta\Value;
793ca6f4fSAndreas Gohruse dokuwiki\plugin\struct\meta\ValueValidator;
8e46eaffdSSzymon Olewniczakuse dokuwiki\plugin\struct\types\Lookup;
9d1482d40SAnna Dabrowskause dokuwiki\plugin\struct\types\Page;
10b360e3b5SAnna Dabrowskause dokuwiki\plugin\struct\types\User;
11ed3de3d6SAndreas Gohr
12ed3de3d6SAndreas Gohr/**
133ad9c1eaSAndreas Gohr * Allows adding a single struct field as a bureaucracy field
143ad9c1eaSAndreas Gohr *
153ad9c1eaSAndreas Gohr * This class is used when a field of the type struct_field is encountered in the
163ad9c1eaSAndreas Gohr * bureaucracy syntax.
17ed3de3d6SAndreas Gohr */
18d6d97f60SAnna Dabrowskaclass helper_plugin_struct_field extends helper_plugin_bureaucracy_field
19d6d97f60SAnna Dabrowska{
203ad9c1eaSAndreas Gohr    /** @var  Column */
213ad9c1eaSAndreas Gohr    public $column;
22ed3de3d6SAndreas Gohr
233ad9c1eaSAndreas Gohr    /**
243ad9c1eaSAndreas Gohr     * Initialize the appropriate column
253ad9c1eaSAndreas Gohr     *
263ad9c1eaSAndreas Gohr     * @param array $args
273ad9c1eaSAndreas Gohr     */
28d6d97f60SAnna Dabrowska    public function initialize($args)
29d6d97f60SAnna Dabrowska    {
3089be912bSSzymon Olewniczak        $this->init($args);
31ed3de3d6SAndreas Gohr
323ad9c1eaSAndreas Gohr        // find the column
333ad9c1eaSAndreas Gohr        try {
343ad9c1eaSAndreas Gohr            $this->column = $this->findColumn($this->opt['label']);
353ad9c1eaSAndreas Gohr        } catch (StructException $e) {
363ad9c1eaSAndreas Gohr            msg(hsc($e->getMessage()), -1);
373ad9c1eaSAndreas Gohr        }
3889be912bSSzymon Olewniczak
3989be912bSSzymon Olewniczak        $this->standardArgs($args);
403ad9c1eaSAndreas Gohr    }
413ad9c1eaSAndreas Gohr
423ad9c1eaSAndreas Gohr    /**
436c71c031SAndreas Gohr     * Sets the value and validates it
443ad9c1eaSAndreas Gohr     *
456c71c031SAndreas Gohr     * @param mixed $value
466c71c031SAndreas Gohr     * @return bool value was set successfully validated
473ad9c1eaSAndreas Gohr     */
48d6d97f60SAnna Dabrowska    protected function setVal($value)
49d6d97f60SAnna Dabrowska    {
506c71c031SAndreas Gohr        if (!$this->column) {
516c71c031SAndreas Gohr            $value = '';
52131fd507SSzymon Olewniczak            //don't validate placeholders here
53131fd507SSzymon Olewniczak        } elseif ($this->replace($value) == $value) {
5493ca6f4fSAndreas Gohr            $validator = new ValueValidator();
556c71c031SAndreas Gohr            $this->error = !$validator->validateValue($this->column, $value);
566c71c031SAndreas Gohr            if ($this->error) {
576c71c031SAndreas Gohr                foreach ($validator->getErrors() as $error) {
586c71c031SAndreas Gohr                    msg(hsc($error), -1);
596c71c031SAndreas Gohr                }
606c71c031SAndreas Gohr            }
616c71c031SAndreas Gohr        }
626c71c031SAndreas Gohr
637234bfb1Ssplitbrain        if ($value === [] || $value === '') {
646c71c031SAndreas Gohr            if (!isset($this->opt['optional'])) {
656c71c031SAndreas Gohr                $this->error = true;
662e0e9347SSzymon Olewniczak                if ($this->column) {
672e0e9347SSzymon Olewniczak                    $label = $this->column->getTranslatedLabel();
682e0e9347SSzymon Olewniczak                } else {
692e0e9347SSzymon Olewniczak                    $label = $this->opt['label'];
702e0e9347SSzymon Olewniczak                }
712e0e9347SSzymon Olewniczak                msg(sprintf($this->getLang('e_required'), hsc($label)), -1);
726c71c031SAndreas Gohr            }
736c71c031SAndreas Gohr        }
746c71c031SAndreas Gohr
756c71c031SAndreas Gohr        $this->opt['value'] = $value;
766c71c031SAndreas Gohr        return !$this->error;
773ad9c1eaSAndreas Gohr    }
783ad9c1eaSAndreas Gohr
793ad9c1eaSAndreas Gohr    /**
803ad9c1eaSAndreas Gohr     * Creates the HTML for the field
813ad9c1eaSAndreas Gohr     *
823ad9c1eaSAndreas Gohr     * @param array $params
833ad9c1eaSAndreas Gohr     * @param Doku_Form $form
843ad9c1eaSAndreas Gohr     * @param int $formid
853ad9c1eaSAndreas Gohr     */
86d6d97f60SAnna Dabrowska    public function renderfield($params, Doku_Form $form, $formid)
87d6d97f60SAnna Dabrowska    {
883ad9c1eaSAndreas Gohr        if (!$this->column) return;
893ad9c1eaSAndreas Gohr
903ad9c1eaSAndreas Gohr        // this is what parent does
913ad9c1eaSAndreas Gohr        $this->_handlePreload();
923ad9c1eaSAndreas Gohr        if (!$form->_infieldset) {
933ad9c1eaSAndreas Gohr            $form->startFieldset('');
943ad9c1eaSAndreas Gohr        }
953ad9c1eaSAndreas Gohr        if ($this->error) {
963ad9c1eaSAndreas Gohr            $params['class'] = 'bureaucracy_error';
973ad9c1eaSAndreas Gohr        }
983ad9c1eaSAndreas Gohr
993ad9c1eaSAndreas Gohr        // output the field
100d1482d40SAnna Dabrowska        $value = $this->createValue();
10195838d50SAndreas Gohr        $field = $this->makeField($value, $params['name']);
1023ad9c1eaSAndreas Gohr        $form->addElement($field);
1033ad9c1eaSAndreas Gohr    }
1043ad9c1eaSAndreas Gohr
10595838d50SAndreas Gohr    /**
106b360e3b5SAnna Dabrowska     * Adds replacement for type user to the parent method
107b360e3b5SAnna Dabrowska     *
108b360e3b5SAnna Dabrowska     * @return array|mixed|string
109b360e3b5SAnna Dabrowska     */
11064480a7bSAnna Dabrowska    public function getReplacementValue()
11164480a7bSAnna Dabrowska    {
112b360e3b5SAnna Dabrowska        $value = $this->getParam('value');
113b360e3b5SAnna Dabrowska
114b360e3b5SAnna Dabrowska        if (is_array($value)) {
1157234bfb1Ssplitbrain            return [$this, 'replacementMultiValueCallback'];
116b360e3b5SAnna Dabrowska        }
117b360e3b5SAnna Dabrowska
118b360e3b5SAnna Dabrowska        if (!empty($value) && $this->column->getType() instanceof User) {
119b360e3b5SAnna Dabrowska            return userlink($value, true);
120b360e3b5SAnna Dabrowska        }
121b360e3b5SAnna Dabrowska
122b360e3b5SAnna Dabrowska        return parent::getReplacementValue();
123b360e3b5SAnna Dabrowska    }
124b360e3b5SAnna Dabrowska
125b360e3b5SAnna Dabrowska    /**
126b360e3b5SAnna Dabrowska     * Adds handling of type user to the parent method
127b360e3b5SAnna Dabrowska     *
128b360e3b5SAnna Dabrowska     * @param $matches
129b360e3b5SAnna Dabrowska     * @return string
130b360e3b5SAnna Dabrowska     */
13164480a7bSAnna Dabrowska    public function replacementMultiValueCallback($matches)
13264480a7bSAnna Dabrowska    {
133b360e3b5SAnna Dabrowska        $value = $this->opt['value'];
134b360e3b5SAnna Dabrowska
135b360e3b5SAnna Dabrowska        //default value
136b360e3b5SAnna Dabrowska        if (is_null($value) || $value === false) {
137b360e3b5SAnna Dabrowska            if (isset($matches['default']) && $matches['default'] != '') {
138b360e3b5SAnna Dabrowska                return $matches['default'];
139b360e3b5SAnna Dabrowska            }
140b360e3b5SAnna Dabrowska            return $matches[0];
141b360e3b5SAnna Dabrowska        }
142b360e3b5SAnna Dabrowska
143b360e3b5SAnna Dabrowska        if (!empty($value) && $this->column->getType() instanceof User) {
1445e29103aSannda            $value = array_map(static fn($user) => userlink($user, true), $value);
145b360e3b5SAnna Dabrowska        }
146b360e3b5SAnna Dabrowska
147b360e3b5SAnna Dabrowska        //check if matched string containts a pair of brackets
148b360e3b5SAnna Dabrowska        $delimiter = preg_match('/\(.*\)/s', $matches[0]) ? $matches['delimiter'] : ', ';
149b360e3b5SAnna Dabrowska
150b360e3b5SAnna Dabrowska        return implode($delimiter, $value);
151b360e3b5SAnna Dabrowska    }
152b360e3b5SAnna Dabrowska
153b360e3b5SAnna Dabrowska    /**
154d1482d40SAnna Dabrowska     * Returns a Value object for the current column.
155d1482d40SAnna Dabrowska     * Special handling for Page and Lookup literal form values.
156d1482d40SAnna Dabrowska     *
157d1482d40SAnna Dabrowska     * @return Value
158d1482d40SAnna Dabrowska     */
159d1482d40SAnna Dabrowska    protected function createValue()
160d1482d40SAnna Dabrowska    {
16130ad7b71SAnna Dabrowska        // input value or appropriately initialized empty value
16230ad7b71SAnna Dabrowska        $preparedValue = $this->opt['value'] ?? ($this->column->isMulti() ? [] : '');
163d1482d40SAnna Dabrowska
164d1482d40SAnna Dabrowska        // page fields might need to be JSON encoded depending on usetitles config
165d1482d40SAnna Dabrowska        if (
166d1482d40SAnna Dabrowska            $this->column->getType() instanceof Page
167d1482d40SAnna Dabrowska            && $this->column->getType()->getConfig()['usetitles']
168d1482d40SAnna Dabrowska        ) {
16930ad7b71SAnna Dabrowska            if ($this->column->isMulti()) {
170*473a32c3SAnna Dabrowska                $preparedValue = array_map(
171*473a32c3SAnna Dabrowska                    static fn($val) => json_encode([$val, null], JSON_THROW_ON_ERROR),
172*473a32c3SAnna Dabrowska                    $preparedValue
173*473a32c3SAnna Dabrowska                );
17430ad7b71SAnna Dabrowska            } else {
1755e29103aSannda                $preparedValue = json_encode([$preparedValue, null], JSON_THROW_ON_ERROR);
176d1482d40SAnna Dabrowska            }
17730ad7b71SAnna Dabrowska        }
178d1482d40SAnna Dabrowska
179d1482d40SAnna Dabrowska        $value = new Value($this->column, $preparedValue);
180d1482d40SAnna Dabrowska
181d1482d40SAnna Dabrowska        // no way to pass $israw parameter to constructor, so re-set the Lookup value
182d1482d40SAnna Dabrowska        if ($this->column->getType() instanceof Lookup) {
183d1482d40SAnna Dabrowska            $value->setValue($preparedValue, true);
184d1482d40SAnna Dabrowska        }
185d1482d40SAnna Dabrowska
186d1482d40SAnna Dabrowska        return $value;
187d1482d40SAnna Dabrowska    }
188d1482d40SAnna Dabrowska
189d1482d40SAnna Dabrowska    /**
19095838d50SAndreas Gohr     * Create the input field
19195838d50SAndreas Gohr     *
19295838d50SAndreas Gohr     * @param Value $field
19395838d50SAndreas Gohr     * @param String $name field's name
19495838d50SAndreas Gohr     * @return string
19595838d50SAndreas Gohr     */
196d6d97f60SAnna Dabrowska    protected function makeField(Value $field, $name)
197d6d97f60SAnna Dabrowska    {
19895838d50SAndreas Gohr        $trans = hsc($field->getColumn()->getTranslatedLabel());
19995838d50SAndreas Gohr        $hint = hsc($field->getColumn()->getTranslatedHint());
20095838d50SAndreas Gohr        $class = $hint ? 'hashint' : '';
20195838d50SAndreas Gohr        $lclass = $this->error ? 'bureaucracy_error' : '';
20295838d50SAndreas Gohr        $colname = $field->getColumn()->getFullQualifiedLabel();
2037234bfb1Ssplitbrain        $required = empty($this->opt['optional']) ? ' <sup>*</sup>' : '';
20495838d50SAndreas Gohr
2055275870bSAnna Dabrowska        $id = uniqid('struct__', true);
206ee983135SMichael Große        $input = $field->getValueEditor($name, $id);
20795838d50SAndreas Gohr
208ee983135SMichael Große        $html = '<div class="field">';
209ee983135SMichael Große        $html .= "<label class=\"$lclass\" data-column=\"$colname\" for=\"$id\">";
21095838d50SAndreas Gohr        $html .= "<span class=\"label $class\" title=\"$hint\">$trans$required</span>";
21195838d50SAndreas Gohr        $html .= '</label>';
212ee983135SMichael Große        $html .= "<span class=\"input\">$input</span>";
213ee983135SMichael Große        $html .= '</div>';
21495838d50SAndreas Gohr
21595838d50SAndreas Gohr        return $html;
21695838d50SAndreas Gohr    }
21795838d50SAndreas Gohr
2183ad9c1eaSAndreas Gohr    /**
2193ad9c1eaSAndreas Gohr     * Tries to find the correct column and schema
2203ad9c1eaSAndreas Gohr     *
2213ad9c1eaSAndreas Gohr     * @param string $colname
2227234bfb1Ssplitbrain     * @return Column
2230549dcc5SAndreas Gohr     * @throws StructException
2243ad9c1eaSAndreas Gohr     */
225d6d97f60SAnna Dabrowska    protected function findColumn($colname)
226d6d97f60SAnna Dabrowska    {
2277234bfb1Ssplitbrain        [$table, $label] = explode('.', $colname, 2);
2283ad9c1eaSAndreas Gohr        if (!$table || !$label) {
2293ad9c1eaSAndreas Gohr            throw new StructException('Field \'%s\' not given in schema.field form', $colname);
2303ad9c1eaSAndreas Gohr        }
2313ad9c1eaSAndreas Gohr        $schema = new Schema($table);
2323ad9c1eaSAndreas Gohr        return $schema->findColumn($label);
2333ad9c1eaSAndreas Gohr    }
2343ad9c1eaSAndreas Gohr
2353ad9c1eaSAndreas Gohr    /**
2363ad9c1eaSAndreas Gohr     * This ensures all language strings are still working
2373ad9c1eaSAndreas Gohr     *
2383ad9c1eaSAndreas Gohr     * @return string always 'bureaucracy'
2393ad9c1eaSAndreas Gohr     */
240d6d97f60SAnna Dabrowska    public function getPluginName()
241d6d97f60SAnna Dabrowska    {
2423ad9c1eaSAndreas Gohr        return 'bureaucracy';
2433ad9c1eaSAndreas Gohr    }
244ed3de3d6SAndreas Gohr}
245