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

heimrichhannot / contao-utils-bundle / 6004356604

28 Aug 2023 08:01PM UTC coverage: 22.181% (-0.4%) from 22.549%
6004356604

push

github

koertho
removed dependency on request bundle

93 of 93 new or added lines in 2 files covered. (100.0%)

1196 of 5392 relevant lines covered (22.18%)

1.57 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

160
                        $label = '';
×
161

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

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

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

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

180
                return $formatted;
×
181
            }
182
        }
183

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

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

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

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

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

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

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

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

221
        $reference = null;
×
222

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

227
        $rgxp = null;
×
228

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

319
        $requestCleaner = new RequestCleaner();
×
320

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

338
        return $value;
×
339
    }
340

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

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

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

373
        $result = [];
×
374

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

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

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

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

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

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

407
        return $result;
×
408
    }
409
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc