• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

heimrichhannot / contao-utils-bundle / 22301002903

23 Feb 2026 08:33AM UTC coverage: 26.12% (-0.02%) from 26.142%
22301002903

push

github

koertho
correclty evaluate boolean value in FormUtil

0 of 13 new or added lines in 1 file covered. (0.0%)

1568 of 6003 relevant lines covered (26.12%)

1.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Form/FormUtil.php
1
<?php
2

3
/*
4
 * Copyright (c) 2022 Heimrich & Hannot GmbH
5
 *
6
 * @license LGPL-3.0-or-later
7
 */
8

9
namespace HeimrichHannot\UtilsBundle\Form;
10

11
use Contao\Config;
12
use Contao\Controller;
13
use Contao\CoreBundle\Framework\ContaoFrameworkInterface;
14
use Contao\DataContainer;
15
use Contao\Date;
16
use Contao\Environment;
17
use Contao\StringUtil;
18
use Contao\System;
19
use Contao\Validator;
20
use Contao\Widget;
21
use HeimrichHannot\UtilsBundle\Model\CfgTagModel;
22
use HeimrichHannot\UtilsBundle\Request\RequestCleaner;
23
use Symfony\Component\DependencyInjection\ContainerInterface;
24
use function Symfony\Component\String\b;
25

26
/**
27
 * Class FormUtil.
28
 *
29
 * @see https://heimrichhannot.github.io/contao-utils-bundle/HeimrichHannot/UtilsBundle/Form/FormUtil.html
30
 */
31
class FormUtil
32
{
33
    /** @var ContaoFrameworkInterface */
34
    protected $framework;
35

36
    /** @var array */
37
    protected $optionsCache;
38
    /**
39
     * @var ContainerInterface
40
     */
41
    private $container;
42

43
    public function __construct(ContainerInterface $container, ContaoFrameworkInterface $framework)
44
    {
45
        $this->framework = $framework;
×
46
        $this->container = $container;
×
47
    }
48

49
    /**
50
     * Get a new widget instance based on given attributes from a Data Container array.
51
     *
52
     * @param string $name The field name in the form
53
     * @param array $data The field configuration array
54
     * @param mixed $value The field value
55
     * @param string $dbName The field name in the database
56
     * @param string $table The table name in the database
57
     * @param DataContainer|null $dc An optional DataContainer object
58
     * @param string $mode The contao mode, use FE or BE to get proper widget/form type
59
     *
60
     * @return Widget|null The new widget based on given attributes
61
     */
62
    public function getWidgetFromAttributes(string $name, array $data, $value = null, string $dbName = '', string $table = '', DataContainer $dc = null, string $mode = ''): ?Widget
63
    {
64
        if ('' === $mode) {
×
65
            $mode = System::getContainer()->get('huh.utils.container')->isFrontend() ? 'FE' : 'BE';
×
66
        }
67

68
        if ('hidden' === $data['inputType']) {
×
69
            $mode = 'FE';
×
70
        }
71

72
        $mode = strtoupper($mode);
×
73
        $mode = \in_array($mode, ['FE', 'BE']) ? $mode : 'FE';
×
74
        $class = 'FE' === $mode ? $GLOBALS['TL_FFL'][$data['inputType']] : $GLOBALS['BE_FFL'][$data['inputType']];
×
75
        /** @var $widget Widget */
76
        $widget = $this->framework->getAdapter(Widget::class);
×
77

78
        if (empty($class) || !class_exists($class)) {
×
79
            return null;
×
80
        }
81

82
        return new $class($widget->getAttributesFromDca($data, $name, $value, $dbName, $table, $dc));
×
83
    }
84

85
    /**
86
     * Prepares a special field's value. If an array is inserted, the function will call itself recursively.
87
     *
88
     * Possible config options:
89
     *
90
     * * preserveEmptyArrayValues -> preserves array values even if they're empty
91
     * * skipLocalization -> skips usage of "reference" array defined in the field's dca
92
     * * skipDcaLoading: boolean -> skip calling Controller::loadDataContainer on $dc->table
93
     * * skipOptionCaching -> skip caching options if $value is an array
94
     * * _dcaOverride: Array Set a custom dca from outside, which will be used instead of global dca value.
95
     *
96
     * @param $value
97
     *
98
     * @return string
99
     */
100
    public function prepareSpecialValueForOutput(string $field, $value, DataContainer $dc, array $config = [], bool $isRecursiveCall = false)
101
    {
102
        $value = StringUtil::deserialize($value);
×
103

104
        /** @var Controller $controller */
105
        $controller = $this->framework->getAdapter(Controller::class);
×
106

107
        /** @var System $system */
108
        $system = $this->framework->getAdapter(System::class);
×
109

110
        /** @var CfgTagModel $cfgTagModel */
111
        $cfgTagModel = $this->framework->getAdapter(CfgTagModel::class);
×
112

113
        $system->loadLanguageFile('default');
×
114

115
        // prepare data
116
        $table = $dc->table;
×
117

118
        if (!isset($config['skipDcaLoading']) || !$config['skipDcaLoading']) {
×
119
            $controller->loadDataContainer($table);
×
120
            $system->loadLanguageFile($table);
×
121
        }
122

123
        $arraySeparator = $config['arraySeparator'] ?? ', ';
×
124
        $skipReplaceInsertTags = $config['skipReplaceInsertTags'] ?? false;
×
125

126
        // dca can be overridden from outside
127
        if (isset($config['_dcaOverride']) && \is_array($config['_dcaOverride'])) {
×
128
            $data = $config['_dcaOverride'];
×
129
        } elseif (!isset($GLOBALS['TL_DCA'][$table]['fields'][$field]) || !\is_array($GLOBALS['TL_DCA'][$table]['fields'][$field])) {
×
130
            return $value;
×
131
        } else {
132
            $data = $GLOBALS['TL_DCA'][$table]['fields'][$field];
×
133
        }
134

135
        $inputType = $data['inputType'] ?? null;
×
136

137
        // multicolumneditor
138
        $mceFieldSeparator = $config['mceFieldSeparator'] ?? "\t";
×
139
        $mceRowSeparator = $config['mceRowSeparator'] ?? "\t\n";
×
140
        $skipMceFieldLabels = $config['skipMceFieldLabels'] ?? false;
×
141
        $skipMceFieldLabelFormatting = $config['skipMceFieldLabelFormatting'] ?? false;
×
142
        $skipMceFields = isset($config['skipMceFields']) && \is_array($config['skipMceFields']) ? $config['skipMceFields'] : [];
×
143
        $mceFields = isset($config['mceFields']) && \is_array($config['mceFields']) ? $config['mceFields'] : [];
×
144

145
        if ('multiColumnEditor' == $inputType
×
146
            && $this->container->get('huh.utils.container')->isBundleActive('HeimrichHannot\MultiColumnEditorBundle\HeimrichHannotContaoMultiColumnEditorBundle')) {
×
147
            if (\is_array($value)) {
×
148
                $formatted = '';
×
149

150
                foreach ($value as $row) {
×
151
                    // new line - add "\t\n" after each line and not only "\n" to prevent outlook line break remover
152
                    $formatted .= $mceRowSeparator;
×
153

154
                    foreach ($row as $fieldName => $fieldValue) {
×
155
                        if (\in_array($fieldName, $skipMceFields) || (!empty($mceFields) && !\in_array($fieldName, $mceFields))) {
×
156
                            continue;
×
157
                        }
158

159
                        $dca = $data['eval']['multiColumnEditor']['fields'][$fieldName];
×
160

161
                        $label = '';
×
162

163
                        if (!$skipMceFieldLabels) {
×
NEW
164
                            $label = ($dca['label'][0] ?: $fieldName) . ': ';
×
165

166
                            if ($skipMceFieldLabelFormatting) {
×
NEW
167
                                $label = $fieldName . ': ';
×
168
                            }
169
                        }
170

171
                        // indent new line
NEW
172
                        $formatted .= $mceFieldSeparator . $label . $this->prepareSpecialValueForOutput($fieldName, $fieldValue, $dc, array_merge($config, [
×
173
                                '_dcaOverride' => $dca,
×
174
                            ]));
×
175
                    }
176
                }
177

178
                // new line - add "\t\n" after each line and not only "\n" to prevent outlook line break remover
179
                $formatted .= $mceRowSeparator;
×
180

181
                return $formatted;
×
182
            }
183
        }
184

185
        // inputUnit
186
        if ('inputUnit' == $inputType) {
×
187
            $data = StringUtil::deserialize($value, true);
×
188

189
            if (!isset($data['value'])) {
×
190
                $data['value'] = '';
×
191
            }
192

193
            if (!isset($data['unit'])) {
×
194
                $data['unit'] = '';
×
195
            }
196

NEW
197
            return $data['value'] . $arraySeparator . $data['unit'];
×
198
        }
199

200
        // Recursively apply logic to array
201
        if (\is_array($value)) {
×
202
            foreach ($value as $k => $v) {
×
203
                $result = $this->prepareSpecialValueForOutput($field, $v, $dc, $config, true);
×
204

205
                if (isset($config['preserveEmptyArrayValues']) && $config['preserveEmptyArrayValues']) {
×
206
                    $value[$k] = $result;
×
207
                } else {
208
                    if (null !== $result && !empty($result)) {
×
209
                        $value[$k] = $result;
×
210
                    } else {
211
                        unset($value[$k]);
×
212
                    }
213
                }
214
            }
215

216
            // reset caches
217
            $this->optionsCache = null;
×
218

219
            return implode($arraySeparator, $value);
×
220
        }
221

222
        $reference = null;
×
223

224
        if (isset($data['reference']) && (!isset($config['skipLocalization']) || !$config['skipLocalization'])) {
×
225
            $reference = $data['reference'];
×
226
        }
227

228
        $rgxp = null;
×
229

230
        if (isset($data['eval']['rgxp'])) {
×
231
            $rgxp = $data['eval']['rgxp'];
×
232
        }
233

234
        if ((!isset($config['skipOptionCaching']) || !$config['skipOptionCaching']) && null !== $this->optionsCache) {
×
235
            $options = $this->optionsCache;
×
236
        } else {
237
            try {
238
                $options = $this->container->get('huh.utils.dca')->getConfigByArrayOrCallbackOrFunction($data, 'options', [$dc]);
×
239
            } catch (\ErrorException $e) {
×
240
                $options = [];
×
241
            }
242

243
            $this->optionsCache = !\is_array($options) ? [] : $options;
×
244
        }
245

246
        // foreignKey
247
        if (isset($data['foreignKey'])) {
×
248
            [$foreignTable, $foreignField] = explode('.', $data['foreignKey']);
×
249

250
            if (null !== ($instance = $this->container->get('huh.utils.model')->findModelInstanceByPk($foreignTable, $value))) {
×
251
                $value = $instance->{$foreignField};
×
252
            }
253
        }
254

255
        if ('explanation' == $inputType) {
×
256
            if (isset($data['eval']['text'])) {
×
257
                return $data['eval']['text'];
×
258
            }
259
        } elseif ('cfgTags' == $inputType) {
×
260
            $collection = $cfgTagModel->findBy(['source=?', 'id = ?'], [$data['eval']['tagsManager'], $value]);
×
261
            $value = null;
×
262

263
            if (null !== $collection) {
×
264
                $result = $collection->fetchEach('name');
×
265
                $value = implode($arraySeparator, $result);
×
266
            }
267
        } elseif ('date' == $rgxp) {
×
268
            $value = Date::parse(Config::get('dateFormat'), $value);
×
269
        } elseif ('time' == $rgxp) {
×
270
            $value = Date::parse(Config::get('timeFormat'), $value);
×
271
        } elseif ('datim' == $rgxp) {
×
272
            $value = Date::parse(Config::get('datimFormat'), $value);
×
273
        } elseif (Validator::isBinaryUuid($value)) {
×
274
            $strPath = $this->container->get('huh.utils.file')->getPathFromUuid($value);
×
NEW
275
            $value = $strPath ? Environment::get('url') . '/' . $strPath : StringUtil::binToUuid($value);
×
276
        } // Replace boolean checkbox value with "yes" and "no"
277
        else {
278
            if ((isset($data['eval']['isBoolean']) && $data['eval']['isBoolean']) || ('checkbox' == $inputType && !($data['eval']['multiple'] ?? false))) {
×
NEW
279
                $value = $this->evaluateBoolean($value) ? $GLOBALS['TL_LANG']['MSC']['yes'] : $GLOBALS['TL_LANG']['MSC']['no'];
×
280
            } elseif (\is_array($options) && array_is_assoc($options)) {
×
281
                $value = isset($options[$value]) ? $options[$value] : $value;
×
282
            }
283
        }
284

285
        if (\is_array($reference)) {
×
286
            $value = isset($reference[$value]) ? ((\is_array($reference[$value])) ? $reference[$value][0] : $reference[$value]) : $value;
×
287
        }
288

289
        if (isset($data['eval']['encrypt']) && $data['eval']['encrypt']) {
×
290
            [$encrypted, $iv] = explode('.', $value);
×
291

292
            $value = $this->container->get('huh.utils.encryption')->decrypt($encrypted, $iv);
×
293
        }
294

295
        // reset caches
296
        if (!$isRecursiveCall) {
×
297
            $this->optionsCache = null;
×
298
        }
299

300
        if (!$skipReplaceInsertTags) {
×
301
            $value = Controller::replaceInsertTags($value);
×
302
        }
303

304
        // Convert special characters (see #1890)
305
        return StringUtil::specialchars($value);
×
306
    }
307

308
    public function escapeAllHtmlEntities($table, $field, $value)
309
    {
310
        if (!$value) {
×
311
            return $value;
×
312
        }
313

314
        Controller::loadDataContainer($table);
×
315

316
        $data = $GLOBALS['TL_DCA'][$table]['fields'][$field];
×
317

318
        $preservedTags = isset($data['eval']['allowedTags']) ? $data['eval']['allowedTags'] : Config::get('allowedTags');
×
319

320
        $requestCleaner = new RequestCleaner();
×
321

322
        if (
323
            isset($data['eval'])
×
324
            && (
325
                ($data['eval']['allowHtml'] ?? false)
×
326
                || \strlen($data['eval']['rte'] ?? '')
×
327
                || ($data['eval']['preserveTags'] ?? false)
×
328
            )
329
        ) {
330
            // always decode entities if HTML is allowed
331
            $value = $requestCleaner->cleanHtml($value, true, true, $preservedTags);
×
332
        } elseif (\is_array($data['options'] ?? false) || isset($data['options_callback']) || isset($data['foreignKey'])) {
×
333
            // options should not be strict cleaned, as they might contain html tags like <strong>
334
            $value = $requestCleaner->cleanHtml($value, true, true, $preservedTags);
×
335
        } else {
336
            $value = $requestCleaner->clean($value, $data['eval']['decodeEntities'] ?? false, true);
×
337
        }
338

339
        return $value;
×
340
    }
341

342
    /**
343
     * Get an instance of Widget by passing fieldname and dca data.
344
     *
345
     * @param string $fieldName The field name
346
     * @param array $dca The DCA
347
     * @param array|null $value
348
     * @param string $dbField The database field name
349
     * @param string $table The table
350
     * @param null $dataContainer object The data container
351
     *
352
     * @return Widget|null
353
     */
354
    public function getBackendFormField(string $fieldName, array $dca, $value = null, $dbField = '', $table = '', $dataContainer = null)
355
    {
356
        if (!($strClass = $GLOBALS['BE_FFL'][$dca['inputType']])) {
×
357
            return null;
×
358
        }
359

360
        return new $strClass(Widget::getAttributesFromDca($dca, $fieldName, $value, $dbField, $table, $dataContainer));
×
361
    }
362

363
    public function getModelDataAsNotificationTokens(array $data, string $prefix, DataContainer $dc, array $config = [])
364
    {
365
        $prefix = $prefix ?? 'form_';
×
366
        $skipRawValues = $config['skipRawValues'] ?? false;
×
367
        $rawValuePrefix = $config['rawValuePrefix'] ?? 'raw_';
×
368
        $skipFormattedValues = $config['skipFormattedValues'] ?? false;
×
369
        $formattedValuePrefix = $config['formattedValuePrefix'] ?? '';
×
370
        $skipFields = $config['skipFields'] ?? [];
×
371
        $restrictFields = $config['restrictFields'] ?? [];
×
372
        $formatOptions = $config['formatOptions'] ?? [];
×
373

374
        $result = [];
×
375

376
        // raw values
377
        if (!$skipRawValues) {
×
378
            foreach ($data as $field => $value) {
×
379
                if (empty($restrictFields) && \in_array($field, $skipFields)) {
×
380
                    continue;
×
381
                }
382

383
                if (!empty($restrictFields) && !\in_array($field, $restrictFields)) {
×
384
                    continue;
×
385
                }
386

NEW
387
                $result[$prefix . $rawValuePrefix . $field] = $value;
×
388
            }
389
        }
390

391
        // formatted values
392
        if (!$skipFormattedValues) {
×
393
            foreach ($data as $field => $value) {
×
394
                if (empty($restrictFields) && \in_array($field, $skipFields)) {
×
395
                    continue;
×
396
                }
397

398
                if (!empty($restrictFields) && !\in_array($field, $restrictFields)) {
×
399
                    continue;
×
400
                }
401

NEW
402
                $result[$prefix . $formattedValuePrefix . $field] = $this->prepareSpecialValueForOutput(
×
403
                    $field, $value, $dc, $formatOptions
×
404
                );
×
405
            }
406
        }
407

408
        return $result;
×
409
    }
410

411
    public function evaluateBoolean(mixed $value): bool
412
    {
NEW
413
        if (is_int($value)) {
×
NEW
414
            return $value > 0;
×
415
        }
NEW
416
        if (is_string($value)) {
×
NEW
417
            return !empty($value);
×
418
        }
NEW
419
        return (bool)$value;
×
420
    }
421
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc