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( 171 static fn($val) => json_encode([$val, null], JSON_THROW_ON_ERROR), 172 $preparedValue 173 ); 174 } else { 175 $preparedValue = json_encode([$preparedValue, null], JSON_THROW_ON_ERROR); 176 } 177 } 178 179 $value = new Value($this->column, $preparedValue); 180 181 // no way to pass $israw parameter to constructor, so re-set the Lookup value 182 if ($this->column->getType() instanceof Lookup) { 183 $value->setValue($preparedValue, true); 184 } 185 186 return $value; 187 } 188 189 /** 190 * Create the input field 191 * 192 * @param Value $field 193 * @param String $name field's name 194 * @return string 195 */ 196 protected function makeField(Value $field, $name) 197 { 198 $trans = hsc($field->getColumn()->getTranslatedLabel()); 199 $hint = hsc($field->getColumn()->getTranslatedHint()); 200 $class = $hint ? 'hashint' : ''; 201 $lclass = $this->error ? 'bureaucracy_error' : ''; 202 $colname = $field->getColumn()->getFullQualifiedLabel(); 203 $required = empty($this->opt['optional']) ? ' <sup>*</sup>' : ''; 204 205 $id = uniqid('struct__', true); 206 $input = $field->getValueEditor($name, $id); 207 208 $html = '<div class="field">'; 209 $html .= "<label class=\"$lclass\" data-column=\"$colname\" for=\"$id\">"; 210 $html .= "<span class=\"label $class\" title=\"$hint\">$trans$required</span>"; 211 $html .= '</label>'; 212 $html .= "<span class=\"input\">$input</span>"; 213 $html .= '</div>'; 214 215 return $html; 216 } 217 218 /** 219 * Tries to find the correct column and schema 220 * 221 * @param string $colname 222 * @return Column 223 * @throws StructException 224 */ 225 protected function findColumn($colname) 226 { 227 [$table, $label] = explode('.', $colname, 2); 228 if (!$table || !$label) { 229 throw new StructException('Field \'%s\' not given in schema.field form', $colname); 230 } 231 $schema = new Schema($table); 232 return $schema->findColumn($label); 233 } 234 235 /** 236 * This ensures all language strings are still working 237 * 238 * @return string always 'bureaucracy' 239 */ 240 public function getPluginName() 241 { 242 return 'bureaucracy'; 243 } 244} 245