AbstractTable::setConfiguration()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
5
 * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
6
 */
7
8
namespace Spryker\Zed\Gui\Communication\Table;
9
10
use DateTime;
11
use Generated\Shared\Transfer\DataTablesColumnTransfer;
12
use Generated\Shared\Transfer\NumberFormatFilterTransfer;
13
use Generated\Shared\Transfer\NumberFormatFloatRequestTransfer;
14
use Generated\Shared\Transfer\NumberFormatIntRequestTransfer;
15
use Laminas\Filter\FilterChain;
16
use Laminas\Filter\StringToLower;
17
use Laminas\Filter\Word\CamelCaseToDash;
18
use LogicException;
19
use PDO;
20
use Propel\Runtime\ActiveQuery\ModelCriteria;
21
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
22
use Propel\Runtime\Formatter\OnDemandFormatter;
23
use Propel\Runtime\Map\TableMap;
24
use Propel\Runtime\Propel;
25
use ReflectionClass;
26
use Spryker\Service\UtilNumber\UtilNumberServiceInterface;
27
use Spryker\Service\UtilSanitize\UtilSanitizeService;
28
use Spryker\Service\UtilText\Model\Url\Url;
29
use Spryker\Shared\Kernel\Container\GlobalContainer;
30
use Spryker\Shared\Kernel\Container\GlobalContainerInterface;
31
use Spryker\Zed\Gui\Communication\Exception\TableException;
32
use Spryker\Zed\Gui\Communication\Form\DeleteForm;
33
use Spryker\Zed\PropelOrm\Business\Runtime\ActiveQuery\Criteria;
34
use Symfony\Component\Form\FormInterface;
35
use Symfony\Component\HttpFoundation\File\UploadedFile;
36
use Symfony\Component\HttpFoundation\Response;
37
use Symfony\Component\HttpFoundation\StreamedResponse;
38
use Symfony\Contracts\Translation\TranslatorInterface;
39
use Twig\Environment;
40
use Twig\Loader\FilesystemLoader;
41
42
abstract class AbstractTable
43
{
44
    /**
45
     * @var string
46
     */
47
    public const TABLE_CLASS = 'gui-table-data';
48
49
    /**
50
     * @var string
51
     */
52
    public const TABLE_CLASS_NO_SEARCH_SUFFIX = '-no-search';
53
54
    /**
55
     * @var string
56
     */
57
    public const BUTTON_CLASS = 'class';
58
59
    /**
60
     * @var string
61
     */
62
    public const BUTTON_HREF = 'href';
63
64
    /**
65
     * @var string
66
     */
67
    public const BUTTON_DEFAULT_CLASS = 'btn-default';
68
69
    /**
70
     * @var string
71
     */
72
    public const BUTTON_ICON = 'icon';
73
74
    /**
75
     * @var string
76
     */
77
    public const PARAMETER_VALUE = 'value';
78
79
    /**
80
     * @var string
81
     */
82
    public const SORT_BY_COLUMN = 'column';
83
84
    /**
85
     * @var string
86
     */
87
    public const SORT_BY_DIRECTION = 'dir';
88
89
    /**
90
     * @var string
91
     */
92
    public const URL_ANCHOR = '#';
93
94
    /**
95
     * @uses \Spryker\Zed\Twig\Communication\Plugin\Application\TwigApplicationPlugin::SERVICE_TWIG
96
     *
97
     * @var string
98
     */
99
    public const SERVICE_TWIG = 'twig';
100
101
    /**
102
     * @uses \Spryker\Zed\Translator\Communication\Plugin\Application\TranslatorApplicationPlugin::SERVICE_TRANSLATOR
103
     *
104
     * @var string
105
     */
106
    public const SERVICE_TRANSLATOR = 'translator';
107
108
    /**
109
     * @uses \Spryker\Zed\Form\Communication\Plugin\Application\FormApplicationPlugin::SERVICE_FORM_FACTORY
110
     *
111
     * @var string
112
     */
113
    public const SERVICE_FORM_FACTORY = 'form.factory';
114
115
    /**
116
     * @uses \Spryker\Zed\UtilNumber\Communication\Plugin\Application\NumberFormatterApplicationPlugin::SERVICE_UTIL_NUMBER
117
     *
118
     * @var string
119
     */
120
    public const SERVICE_UTIL_NUMBER = 'SERVICE_UTIL_NUMBER';
121
122
    /**
123
     * @uses \Spryker\Zed\Locale\Communication\Plugin\Application\LocaleApplicationPlugin::SERVICE_LOCALE
124
     *
125
     * @var string
126
     */
127
    public const SERVICE_LOCALE = 'locale';
128
129
    /**
130
     * Defines delete form name suffix allowing to avoid non-unique attributes (e.g. form name or id) for delete forms on one page.
131
     * It is recommended to fill parameter $options in AbstractTable:generateRemoveButton() to avoid non-unique id warning in browser console.
132
     *
133
     * $options parameter example:
134
     * [
135
     *    'name_suffix' => $id,
136
     * ]
137
     *
138
     * @var string
139
     */
140
    protected const DELETE_FORM_NAME_SUFFIX = 'name_suffix';
141
142
    /**
143
     * @var string
144
     */
145
    protected const DELETE_FORM_NAME = 'delete_form';
146
147
    /**
148
     * @var string
149
     */
150
    protected const DATE_TIME_FORMAT_FOR_CSV_FILENAME = 'Y-m-d-h-i-s';
151
152
    /**
153
     * @uses \Spryker\Zed\Propel\PropelConfig::DB_ENGINE_PGSQL
154
     *
155
     * @var string
156
     */
157
    protected const DRIVER_NAME_PGSQL = 'pgsql';
158
159
    /**
160
     * @var \Symfony\Component\HttpFoundation\Request
161
     */
162
    protected $request;
163
164
    /**
165
     * @var array
166
     */
167
    protected $data;
168
169
    /**
170
     * @var \Spryker\Zed\Gui\Communication\Table\TableConfiguration
171
     */
172
    protected $config;
173
174
    /**
175
     * @var int
176
     */
177
    protected $total = 0;
178
179
    /**
180
     * @var int
181
     */
182
    protected $limit = 0;
183
184
    /**
185
     * @var int
186
     */
187
    protected $filtered = 0;
188
189
    /**
190
     * @var int
191
     */
192
    protected $defaultLimit = 10;
193
194
    /**
195
     * @var string
196
     */
197
    protected $baseUrl;
198
199
    /**
200
     * @var string
201
     */
202
    protected $defaultUrl = 'table';
203
204
    /**
205
     * @var string
206
     */
207
    protected $tableClass = self::TABLE_CLASS;
208
209
    /**
210
     * @var bool
211
     */
212
    protected $initialized = false;
213
214
    /**
215
     * @var string|null
216
     */
217
    protected $tableIdentifier;
218
219
    /**
220
     * @var \Generated\Shared\Transfer\DataTablesTransfer
221
     */
222
    protected $dataTablesTransfer;
223
224
    /**
225
     * @var \Twig\Environment
226
     */
227
    protected $twig;
228
229
    /**
230
     * @var string
231
     */
232
    protected const SEARCH_PATTERN_FOR_STRICT_SEARCH_MYSQL = '%s%s = BINARY %s';
233
234
    /**
235
     * @var string
236
     */
237
    protected const SEARCH_PATTERN_FOR_STRICT_SEARCH_POSTGRESQL = '%s%s = %s';
238
239
    /**
240
     * @var string
241
     */
242
    protected const SEARCH_PATTERN_FOR_FUZZY_SEARCH = 'LOWER(%s%s) LIKE %s';
243
244
    /**
245
     * @var string
246
     */
247
    protected const SEARCH = 'search';
248
249
    /**
250
     * @var string
251
     */
252
    protected const VALUE = 'value';
253
254
    /**
255
     * @var string
256
     */
257
    protected const COLUMNS = 'columns';
258
259
    /**
260
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
261
     *
262
     * @return \Spryker\Zed\Gui\Communication\Table\TableConfiguration
263
     */
264
    abstract protected function configure(TableConfiguration $config);
265
266
    /**
267
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
268
     *
269
     * @return array
270
     */
271
    abstract protected function prepareData(TableConfiguration $config);
272
273
    /**
274
     * @return \Generated\Shared\Transfer\DataTablesTransfer
275
     */
276
    public function getDataTablesTransfer()
277
    {
278
        return $this->dataTablesTransfer;
279
    }
280
281
    /**
282
     * @param \Generated\Shared\Transfer\DataTablesTransfer $dataTablesTransfer
283
     *
284
     * @return void
285
     */
286
    public function setDataTablesTransfer($dataTablesTransfer)
287
    {
288
        $this->dataTablesTransfer = $dataTablesTransfer;
289
    }
290
291
    /**
292
     * @return \Symfony\Component\HttpFoundation\StreamedResponse
293
     */
294
    public function streamDownload(): StreamedResponse
295
    {
296
        $streamedResponse = new StreamedResponse();
297
        $streamedResponse->setCallback($this->getStreamCallback());
298
        $streamedResponse->setStatusCode(Response::HTTP_OK);
299
        $streamedResponse->headers->set('Content-Type', 'text/csv; charset=utf-8');
300
        $streamedResponse->headers->set('Content-Disposition', sprintf('attachment; filename="%s"', $this->getCsvFileName()));
301
302
        return $streamedResponse;
303
    }
304
305
    /**
306
     * @return callable
307
     */
308
    protected function getStreamCallback(): callable
309
    {
310
        $csvHeaders = $this->getCsvHeaders();
311
312
        return function () use ($csvHeaders) {
313
            /** @var resource $csvHandle */
314
            $csvHandle = fopen('php://output', 'w+');
315
            $translatedHeaders = $this->translateCsvHeaders($csvHeaders);
316
317
            fputcsv($csvHandle, $translatedHeaders);
318
319
            foreach ($this->executeDownloadQuery() as $entity) {
320
                $formattedRow = $this->formatCsvRow($entity);
321
                $intersection = array_intersect_key($formattedRow, $csvHeaders);
322
                $orderedCsvData = array_replace($csvHeaders, $intersection);
323
324
                fputcsv($csvHandle, $orderedCsvData);
325
            }
326
327
            fclose($csvHandle);
328
        };
329
    }
330
331
    /**
332
     * @throws \Spryker\Zed\Gui\Communication\Exception\TableException
333
     *
334
     * @return array
335
     */
336
    protected function getCsvHeaders(): array
337
    {
338
        throw new TableException(sprintf('You need to implement `%s()` in your `%s`.', __METHOD__, static::class));
339
    }
340
341
    /**
342
     * @param array $csvHeaders
343
     *
344
     * @return array
345
     */
346
    protected function translateCsvHeaders(array $csvHeaders): array
347
    {
348
        $translator = $this->getTranslator();
349
350
        if (!$translator) {
351
            return $csvHeaders;
352
        }
353
354
        foreach ($csvHeaders as $key => $value) {
355
            $csvHeaders[$key] = $translator->trans($value);
356
        }
357
358
        return $csvHeaders;
359
    }
360
361
    /**
362
     * @return \Symfony\Contracts\Translation\TranslatorInterface|null
363
     */
364
    protected function getTranslator(): ?TranslatorInterface
365
    {
366
        $container = $this->getApplicationContainer();
367
368
        if (!$container->has(static::SERVICE_TRANSLATOR)) {
369
            return null;
370
        }
371
372
        return $container->get(static::SERVICE_TRANSLATOR);
373
    }
374
375
    /**
376
     * @return iterable
377
     */
378
    protected function executeDownloadQuery(): iterable
379
    {
380
        return $this->getDownloadQuery()
381
            ->setFormatter(OnDemandFormatter::class)
382
            ->find();
383
    }
384
385
    /**
386
     * @throws \Spryker\Zed\Gui\Communication\Exception\TableException
387
     *
388
     * @return \Propel\Runtime\ActiveQuery\ModelCriteria
389
     */
390
    protected function getDownloadQuery(): ModelCriteria
391
    {
392
        throw new TableException(sprintf('You need to implement `%s()` in your `%s`.', __METHOD__, static::class));
393
    }
394
395
    /**
396
     * @param \Propel\Runtime\ActiveRecord\ActiveRecordInterface $entity
397
     *
398
     * @throws \Spryker\Zed\Gui\Communication\Exception\TableException
399
     *
400
     * @return array
401
     */
402
    protected function formatCsvRow(ActiveRecordInterface $entity): array
403
    {
404
        if (!method_exists($entity, 'toArray')) {
405
            throw new TableException(sprintf('Missing method `%s::toArray()`.', get_class($entity)));
406
        }
407
408
        return $entity->toArray();
409
    }
410
411
    /**
412
     * @return string
413
     */
414
    protected function getCsvFileName(): string
415
    {
416
        return sprintf('%s-%s.csv', $this->getClassNameShort(), $this->getDatetimeString());
417
    }
418
419
    /**
420
     * @return string
421
     */
422
    protected function getClassNameShort(): string
423
    {
424
        $reflectionClass = new ReflectionClass($this);
425
        $classNameShort = $reflectionClass->getShortName();
426
427
        $filter = new FilterChain();
428
        $filter
429
            ->attach(new CamelCaseToDash())
430
            ->attach(new StringToLower());
431
432
        return $filter->filter($classNameShort);
433
    }
434
435
    /**
436
     * @return string
437
     */
438
    protected function getDatetimeString(): string
439
    {
440
        $dateTime = new DateTime('NOW');
441
442
        return $dateTime->format(static::DATE_TIME_FORMAT_FOR_CSV_FILENAME);
443
    }
444
445
    /**
446
     * @return $this
447
     */
448
    protected function init()
449
    {
450
        if (!$this->initialized) {
451
            $this->initialized = true;
452
            $this->request = $this->getRequest();
453
            $config = $this->newTableConfiguration();
454
            $config->setPageLength($this->getLimit());
455
            $config = $this->configure($config);
456
            $this->setConfiguration($config);
457
            $this->twig = $this->getTwig();
458
459
            if ($this->tableIdentifier === null) {
460
                $this->generateTableIdentifier();
461
            }
462
        }
463
464
        return $this;
465
    }
466
467
    /**
468
     * @return \Symfony\Component\HttpFoundation\Request
469
     */
470
    protected function getRequest()
471
    {
472
        $container = $this->getApplicationContainer();
473
474
        if ($container->has('request')) {
475
            return $container->get('request');
476
        }
477
478
        return $container->get('request_stack')->getCurrentRequest();
479
    }
480
481
    /**
482
     * @return void
483
     */
484
    public function disableSearch()
485
    {
486
        $this->tableClass .= static::TABLE_CLASS_NO_SEARCH_SUFFIX;
487
    }
488
489
    /**
490
     * @deprecated this method should not be needed.
491
     *
492
     * @param string $name
493
     *
494
     * @return string
495
     */
496
    public function buildAlias($name)
497
    {
498
        return str_replace(
499
            ['.', '(', ')'],
500
            '',
501
            $name,
502
        );
503
    }
504
505
    /**
506
     * @return \Spryker\Zed\Gui\Communication\Table\TableConfiguration
507
     */
508
    protected function newTableConfiguration()
509
    {
510
        return new TableConfiguration();
511
    }
512
513
    /**
514
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
515
     *
516
     * @return void
517
     */
518
    public function setConfiguration(TableConfiguration $config)
519
    {
520
        $this->config = $config;
521
    }
522
523
    /**
524
     * @param array<string, mixed> $data
525
     *
526
     * @return void
527
     */
528
    public function loadData(array $data)
529
    {
530
        $tableData = [];
531
532
        /** @var array|null $headers */
533
        $headers = $this->config->getHeader();
534
        $safeColumns = $this->config->getRawColumns();
535
        $extraColumns = $this->config->getExtraColumns();
536
537
        $isArray = is_array($headers);
538
        foreach ($data as $row) {
539
            $originalRow = $row;
540
            if ($isArray) {
541
                $row = array_intersect_key($row, $headers);
542
543
                $row = $this->reOrderByHeaders($headers, $row);
544
            }
545
546
            $row = $this->escapeColumns($row, $safeColumns);
547
            $row = array_values($row);
548
549
            if ($isArray) {
550
                $row = $this->addExtraColumns($row, $originalRow, $extraColumns);
551
            }
552
553
            $tableData[] = $row;
554
        }
555
556
        $this->setData($tableData);
557
    }
558
559
    /**
560
     * @param array $row
561
     * @param array $safeColumns
562
     *
563
     * @return mixed
564
     */
565
    protected function escapeColumns(array $row, array $safeColumns)
566
    {
567
        $callback = function (&$value, $key) use ($safeColumns) {
568
            if (!in_array($key, $safeColumns)) {
569
                $value = twig_escape_filter(new Environment(new FilesystemLoader()), $value);
0 ignored issues
show
Bug introduced by
The function twig_escape_filter was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

569
                $value = /** @scrutinizer ignore-call */ twig_escape_filter(new Environment(new FilesystemLoader()), $value);
Loading history...
570
            }
571
572
            return $value;
573
        };
574
575
        array_walk($row, $callback);
576
577
        return $row;
578
    }
579
580
    /**
581
     * @param array $headers
582
     * @param array $row
583
     *
584
     * @return array
585
     */
586
    protected function reOrderByHeaders(array $headers, array $row)
587
    {
588
        $result = [];
589
590
        foreach ($headers as $key => $value) {
591
            if (!array_key_exists($key, $row)) {
592
                continue;
593
            }
594
            $result[$key] = $row[$key];
595
        }
596
597
        return $result;
598
    }
599
600
    /**
601
     * @param array<mixed> $row
602
     * @param array<mixed> $originalRow
603
     * @param array<string> $extraColumns
604
     *
605
     * @return array
606
     */
607
    protected function addExtraColumns(array $row, array $originalRow, array $extraColumns)
608
    {
609
        foreach ($extraColumns as $extraColumnName) {
610
            if (array_key_exists($extraColumnName, $row)) {
611
                continue;
612
            }
613
            $row[$extraColumnName] = $originalRow[$extraColumnName];
614
        }
615
616
        return $row;
617
    }
618
619
    /**
620
     * @param array<mixed> $data
621
     *
622
     * @return void
623
     */
624
    public function setData(array $data)
625
    {
626
        $this->data = $data;
627
    }
628
629
    /**
630
     * @return array<mixed>
631
     */
632
    public function getData()
633
    {
634
        return $this->data;
635
    }
636
637
    /**
638
     * @return \Spryker\Zed\Gui\Communication\Table\TableConfiguration
639
     */
640
    public function getConfiguration()
641
    {
642
        return $this->config;
643
    }
644
645
    /**
646
     * @return string
647
     */
648
    public function getTableIdentifier()
649
    {
650
        if ($this->tableIdentifier === null) {
651
            $this->generateTableIdentifier();
652
        }
653
654
        return $this->tableIdentifier;
655
    }
656
657
    /**
658
     * @param string $prefix
659
     *
660
     * @return $this
661
     */
662
    protected function generateTableIdentifier($prefix = 'table-')
663
    {
664
        $this->tableIdentifier = $prefix . md5(static::class);
665
666
        return $this;
667
    }
668
669
    /**
670
     * @param string|null $tableIdentifier
671
     *
672
     * @return void
673
     */
674
    public function setTableIdentifier($tableIdentifier)
675
    {
676
        $this->tableIdentifier = $tableIdentifier;
677
    }
678
679
    /**
680
     * @throws \LogicException
681
     *
682
     * @return \Twig\Environment
683
     */
684
    private function getTwig()
685
    {
686
        /** @var \Twig\Environment|null $twig */
687
        $twig = $this->getApplicationContainer()->get(static::SERVICE_TWIG);
688
689
        if ($twig === null) {
690
            throw new LogicException('Twig environment not set up.');
691
        }
692
693
        /** @var \Twig\Loader\ChainLoader $loaderChain */
694
        $loaderChain = $twig->getLoader();
695
        $loaderChain->addLoader(new FilesystemLoader(
696
            $this->getTwigPaths(),
697
            $this->getTwigRootPath(),
698
        ));
699
700
        return $twig;
701
    }
702
703
    /**
704
     * @return \Spryker\Shared\Kernel\Container\GlobalContainerInterface
705
     */
706
    protected function getApplicationContainer(): GlobalContainerInterface
707
    {
708
        return new GlobalContainer();
709
    }
710
711
    /**
712
     * @return array<string>
713
     */
714
    protected function getTwigPaths()
715
    {
716
        return [
717
            __DIR__ . '/../../Presentation/Table/',
718
        ];
719
    }
720
721
    /**
722
     * @return string|null
723
     */
724
    protected function getTwigRootPath()
725
    {
726
        return null;
727
    }
728
729
    /**
730
     * @return int
731
     */
732
    public function getOffset()
733
    {
734
        return $this->request->query->getInt('start', 0);
735
    }
736
737
    /**
738
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
739
     *
740
     * @return array
741
     */
742
    public function getOrders(TableConfiguration $config)
743
    {
744
        $defaultSorting = [$this->getDefaultSorting($config)];
745
746
        $orderParameter = $this->getOrderParameter();
747
748
        if (!is_array($orderParameter)) {
749
            return $defaultSorting;
750
        }
751
752
        $sorting = $this->createSortingParameters($orderParameter);
753
754
        if (!$sorting) {
755
            return $defaultSorting;
756
        }
757
758
        return $sorting;
759
    }
760
761
    /**
762
     * @return array|null
763
     */
764
    protected function getOrderParameter(): ?array
765
    {
766
        return $this->request->query->all()['order'] ?? null;
767
    }
768
769
    /**
770
     * Retrieving non-string values using InputBag::get() was deprecated in symfony/http-foundation:5.1
771
     * Using the InputBag::all() method with an argument was introduced in symfony/http-foundation:5.0
772
     *
773
     * The method UploadedFile::getClientSize() was removed in symfony/http-foundation:5.0.
774
     *
775
     * To find which way to use we check for the existence of the UploadedFile::getClientSize(), when the method does
776
     * not exist we have symfony/http-foundation:5.0 or higher installed.
777
     *
778
     * @return bool
779
     */
780
    protected function isSymfonyHttpFoundationVersion5OrHigher(): bool
781
    {
782
        return !method_exists(UploadedFile::class, 'getClientSize');
783
    }
784
785
    /**
786
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
787
     *
788
     * @return array
789
     */
790
    protected function getDefaultSorting(TableConfiguration $config)
791
    {
792
        $sort = [
793
            static::SORT_BY_COLUMN => $config->getDefaultSortColumnIndex(),
794
            static::SORT_BY_DIRECTION => $config->getDefaultSortDirection(),
795
        ];
796
797
        $defaultSortField = $config->getDefaultSortField();
798
        if (!$defaultSortField) {
799
            return $sort;
800
        }
801
802
        $field = key($defaultSortField);
803
        $direction = $defaultSortField[$field];
804
805
        $availableFields = array_keys($config->getHeader());
806
        $index = array_search($field, $availableFields, true);
807
        if ($index === false) {
808
            return $sort;
809
        }
810
811
        $sort = [
812
            static::SORT_BY_COLUMN => $index,
813
            static::SORT_BY_DIRECTION => $direction,
814
        ];
815
816
        return $sort;
817
    }
818
819
    /**
820
     * @param array $orderParameter
821
     *
822
     * @return array
823
     */
824
    protected function createSortingParameters(array $orderParameter)
825
    {
826
        $sorting = [];
827
        foreach ($orderParameter as $sortingRules) {
828
            if (!is_array($sortingRules)) {
829
                continue;
830
            }
831
            $sorting[] = [
832
                static::SORT_BY_COLUMN => $this->getParameter($sortingRules, static::SORT_BY_COLUMN, '0'),
833
                static::SORT_BY_DIRECTION => $this->getParameter($sortingRules, static::SORT_BY_DIRECTION, 'asc'),
834
            ];
835
        }
836
837
        return $sorting;
838
    }
839
840
    /**
841
     * @param array<string, mixed> $dataArray
842
     * @param string $key
843
     * @param string $defaultValue
844
     *
845
     * @return string
846
     */
847
    protected function getParameter(array $dataArray, $key, $defaultValue)
848
    {
849
        if (array_key_exists($key, $dataArray)) {
850
            return $dataArray[$key];
851
        }
852
853
        return $defaultValue;
854
    }
855
856
    /**
857
     * @return mixed|null
858
     */
859
    public function getSearchTerm()
860
    {
861
        if (!$this->request->query->has('search')) {
862
            return null;
863
        }
864
865
        return $this->getSearchParameter();
866
    }
867
868
    /**
869
     * @return array
870
     */
871
    protected function getSearchParameter(): array
872
    {
873
        if ($this->isSymfonyHttpFoundationVersion5OrHigher()) {
874
            return $this->request->query->all('search');
875
        }
876
877
        return (array)$this->request->query->get('search');
878
    }
879
880
    /**
881
     * @return int
882
     */
883
    public function getLimit()
884
    {
885
        if (!$this->limit) {
886
            $this->limit = $this->request->query->getInt('length', $this->defaultLimit);
887
        }
888
889
        return $this->limit;
890
    }
891
892
    /**
893
     * @param int $limit
894
     *
895
     * @return $this
896
     */
897
    public function setLimit($limit)
898
    {
899
        $this->limit = (int)$limit;
900
901
        return $this;
902
    }
903
904
    /**
905
     * @return string
906
     */
907
    public function render()
908
    {
909
        $this->init();
910
911
        $twigVars = [
912
            'config' => $this->prepareConfig(),
913
        ];
914
915
        return $this->twig
916
            ->render('index.twig', $twigVars);
917
    }
918
919
    /**
920
     * @return array
921
     */
922
    public function prepareConfig()
923
    {
924
        $configArray = [
925
            'tableId' => $this->getTableIdentifier(),
926
            'class' => $this->tableClass,
927
            'url' => $this->defaultUrl,
928
            'baseUrl' => $this->baseUrl,
929
            'header' => [],
930
        ];
931
932
        if ($this->getConfiguration() instanceof TableConfiguration) {
0 ignored issues
show
introduced by
$this->getConfiguration() is always a sub-type of Spryker\Zed\Gui\Communic...able\TableConfiguration.
Loading history...
933
            $configTableArray = [
934
                'url' => ($this->config->getUrl() === null) ? $this->defaultUrl : $this->config->getUrl(),
935
                'header' => $this->config->getHeader(),
936
                'footer' => $this->config->getFooter(),
937
                'order' => $this->getOrders($this->config),
938
                'searchable' => $this->config->getSearchable(),
939
                'searchableColumns' => $this->config->getSearchableColumns(),
940
                'sortable' => $this->config->getSortable(),
941
                'pageLength' => $this->config->getPageLength(),
942
                'processing' => $this->config->isProcessing(),
943
                'serverSide' => $this->config->isServerSide(),
944
                'stateSave' => $this->config->isStateSave(),
945
                'paging' => $this->config->isPaging(),
946
                'ordering' => $this->config->isOrdering(),
947
            ];
948
949
            $configArray = array_merge($configArray, $configTableArray);
950
        }
951
952
        return $configArray;
953
    }
954
955
    /**
956
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $criteria
957
     *
958
     * @return string
959
     */
960
    protected function getFirstAvailableColumnInQuery(ModelCriteria $criteria)
961
    {
962
        $tableMap = $criteria->getTableMap();
963
        $columns = array_keys($tableMap->getColumns());
964
965
        $firstColumnName = $tableMap->getColumn($columns[0])->getName();
966
967
        return $tableMap->getName() . '.' . $firstColumnName;
968
    }
969
970
    /**
971
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
972
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
973
     * @param array $order
974
     *
975
     * @return string
976
     */
977
    protected function getOrderByColumn(ModelCriteria $query, TableConfiguration $config, array $order)
978
    {
979
        $columns = $this->getColumnsList($query, $config);
980
981
        if (isset($order[0]) && isset($order[0][static::SORT_BY_COLUMN]) && isset($columns[$order[0][static::SORT_BY_COLUMN]])) {
982
            $selectedColumn = $columns[$order[0][static::SORT_BY_COLUMN]];
983
984
            if (in_array($selectedColumn, $config->getSortable(), true)) {
985
                return $selectedColumn;
986
            }
987
        }
988
989
        return $this->getFirstAvailableColumnInQuery($query);
990
    }
991
992
    /**
993
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
994
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
995
     *
996
     * @return array
997
     */
998
    protected function getColumnsList(ModelCriteria $query, TableConfiguration $config)
999
    {
1000
        if ($config->getHeader()) {
1001
            return array_keys($config->getHeader());
1002
        }
1003
1004
        return array_keys($query->getTableMap()->getColumns());
1005
    }
1006
1007
    /**
1008
     * @todo CD-412 refactor this class to allow unspecified header columns and to add flexibility
1009
     *
1010
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1011
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
1012
     * @param bool $returnRawResults
1013
     *
1014
     * @return \Propel\Runtime\Collection\ObjectCollection|array
1015
     */
1016
    protected function runQuery(ModelCriteria $query, TableConfiguration $config, $returnRawResults = false)
1017
    {
1018
        $this->total = $this->filtered = $this->countTotal($query);
1019
        $limit = $this->getLimit();
1020
        $offset = $this->getOffset();
1021
        $order = $this->getOrders($config);
1022
        $orderColumn = $this->getOrderByColumn($query, $config, $order);
1023
1024
        $query->orderBy($orderColumn, $order[0][static::SORT_BY_DIRECTION]);
1025
1026
        $searchTerm = $this->getSearchTerm();
1027
        $searchValue = $searchTerm[static::PARAMETER_VALUE] ?? '';
1028
1029
        if (mb_strlen($searchValue) > 0 || $this->isStrictSearch($query, $config) === true) {
1030
            $query->setIdentifierQuoting(true);
1031
1032
            $conditions = $this->resolveConditions($query, $config, $searchValue);
1033
1034
            if ($conditions !== []) {
1035
                $query = $this->applyConditions($query, $config, $conditions);
1036
            }
1037
1038
            $this->filtered = $query->count();
1039
        }
1040
1041
        if ($this->dataTablesTransfer !== null) {
1042
            $searchColumns = $config->getSearchable();
1043
1044
            $this->addFilteringConditions($query, $searchColumns);
1045
        }
1046
1047
        $data = $query->offset($offset)
1048
            ->limit($limit)
1049
            ->find();
1050
1051
        if ($returnRawResults === true) {
1052
            return $data;
1053
        }
1054
1055
        return $data->toArray(null, false, TableMap::TYPE_COLNAME);
1056
    }
1057
1058
    /**
1059
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1060
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
1061
     *
1062
     * @return array<string>
1063
     */
1064
    protected function getStrictConditionParameters(ModelCriteria $query, TableConfiguration $config): array
1065
    {
1066
        $conditionParameters = [];
1067
        $searchTerms = $this->getSearchColumns();
1068
1069
        foreach ($this->getColumnsList($query, $config) as $index => $colName) {
1070
            if ($searchTerms[$index][static::SEARCH][static::VALUE] !== '') {
1071
                $conditionParameters[$config->getSearchableColumns()[$colName]] = $searchTerms[$index][static::SEARCH][static::VALUE];
1072
            }
1073
        }
1074
1075
        return $conditionParameters;
1076
    }
1077
1078
    /**
1079
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1080
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
1081
     *
1082
     * @return bool
1083
     */
1084
    protected function isStrictSearch(ModelCriteria $query, TableConfiguration $config): bool
1085
    {
1086
        $searchTerms = $this->getSearchColumns();
1087
1088
        foreach ($this->getColumnsList($query, $config) as $index => $colName) {
1089
            if (isset($searchTerms[$index]) && $searchTerms[$index][static::SEARCH][static::VALUE] !== '') {
1090
                return true;
1091
            }
1092
        }
1093
1094
        return false;
1095
    }
1096
1097
    /**
1098
     * @return array<mixed>
1099
     */
1100
    protected function getSearchColumns(): array
1101
    {
1102
        return $this->request->query->all()[static::COLUMNS] ?? [];
1103
    }
1104
1105
    /**
1106
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1107
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
1108
     * @param string $searchValue
1109
     *
1110
     * @return array<string>
1111
     */
1112
    protected function resolveConditions(
1113
        ModelCriteria $query,
1114
        TableConfiguration $config,
1115
        string $searchValue
1116
    ): array {
1117
        $conditions = [];
1118
        $connection = Propel::getConnection();
1119
        $driverName = $connection->getAttribute(PDO::ATTR_DRIVER_NAME);
1120
        $filter = $driverName === static::DRIVER_NAME_PGSQL ? '::TEXT' : '';
1121
        $searchPattern = $this->getSearchPattern($config, $driverName, $query);
1122
1123
        if ($this->isStrictSearch($query, $config) === true) {
1124
            $strictConditionParameters = $this->getStrictConditionParameters($query, $config);
1125
            foreach ($strictConditionParameters as $value => $conditionParameter) {
1126
                $conditions[] = $this->buildCondition($searchPattern, $value, $filter, $connection->quote($conditionParameter));
1127
            }
1128
1129
            return $conditions;
1130
        }
1131
1132
        $conditionParameter = $connection->quote('%' . mb_strtolower($searchValue) . '%');
1133
        foreach ($config->getSearchable() as $value) {
1134
            $conditions[] = $this->buildCondition($searchPattern, $value, $filter, $conditionParameter);
1135
        }
1136
1137
        return $conditions;
1138
    }
1139
1140
    /**
1141
     * @param string $searchPattern
1142
     * @param string $value
1143
     * @param string $filter
1144
     * @param string $conditionParameter
1145
     *
1146
     * @return string
1147
     */
1148
    protected function buildCondition(
1149
        string $searchPattern,
1150
        string $value,
1151
        string $filter,
1152
        string $conditionParameter
1153
    ): string {
1154
        return sprintf($searchPattern, $value, $filter, $conditionParameter);
1155
    }
1156
1157
    /**
1158
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1159
     *
1160
     * @return int
1161
     */
1162
    protected function countTotal(ModelCriteria $query): int
1163
    {
1164
        return $query->count();
1165
    }
1166
1167
    /**
1168
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1169
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
1170
     * @param array<string> $conditions
1171
     *
1172
     * @return \Propel\Runtime\ActiveQuery\ModelCriteria
1173
     */
1174
    protected function applyConditions(ModelCriteria $query, TableConfiguration $config, array $conditions): ModelCriteria
1175
    {
1176
        $gluedCondition = implode(
1177
            sprintf(' %s ', $this->isStrictSearch($query, $config) === true ? Criteria::LOGICAL_AND : Criteria::LOGICAL_OR),
1178
            $conditions,
1179
        );
1180
1181
        /** @var literal-string $gluedCondition */
1182
        $gluedCondition = '(' . $gluedCondition . ')';
1183
1184
        if ($config->getHasSearchableFieldsWithAggregateFunctions()) {
1185
            return $query->having($gluedCondition);
1186
        }
1187
1188
        return $query->where($gluedCondition);
1189
    }
1190
1191
    /**
1192
     * @param string $value
1193
     *
1194
     * @return string
1195
     */
1196
    protected function filterSearchValue($value)
1197
    {
1198
        $value = str_replace(['^', '$'], '', $value);
1199
        $value = stripslashes($value);
1200
1201
        return $value;
1202
    }
1203
1204
    /**
1205
     * @return array
1206
     */
1207
    public function fetchData()
1208
    {
1209
        $this->init();
1210
1211
        $data = $this->prepareData($this->config);
1212
        $this->loadData($data);
1213
        $wrapperArray = [
1214
            'draw' => $this->request->query->getInt('draw', 1),
1215
            'recordsTotal' => $this->total,
1216
            'recordsFiltered' => $this->filtered,
1217
            'data' => $this->data,
1218
        ];
1219
1220
        return $wrapperArray;
1221
    }
1222
1223
    /**
1224
     * Drop table name from key
1225
     *
1226
     * @param string $key
1227
     *
1228
     * @return string
1229
     */
1230
    public function cutTablePrefix($key)
1231
    {
1232
        $position = mb_strpos($key, '.');
1233
1234
        return ($position !== false) ? mb_substr($key, $position + 1) : $key;
1235
    }
1236
1237
    /**
1238
     * @param string $str
1239
     *
1240
     * @return string
1241
     */
1242
    public function camelize($str)
1243
    {
1244
        return str_replace(' ', '', ucwords(mb_strtolower(str_replace('_', ' ', $str))));
1245
    }
1246
1247
    /**
1248
     * @param int $total
1249
     *
1250
     * @return void
1251
     */
1252
    protected function setTotal($total)
1253
    {
1254
        $this->total = $total;
1255
    }
1256
1257
    /**
1258
     * @param int $filtered
1259
     *
1260
     * @return void
1261
     */
1262
    protected function setFiltered($filtered)
1263
    {
1264
        $this->filtered = $filtered;
1265
    }
1266
1267
    /**
1268
     * @param string $url
1269
     * @param string $title
1270
     * @param array<string, mixed> $options
1271
     *
1272
     * @return string
1273
     */
1274
    protected function generateCreateButton($url, $title, array $options = [])
1275
    {
1276
        $defaultOptions = [
1277
            'class' => 'btn-create',
1278
            'icon' => 'fa-plus',
1279
        ];
1280
1281
        return $this->generateButton($url, $title, $defaultOptions, $options);
1282
    }
1283
1284
    /**
1285
     * @param string $url
1286
     * @param string $title
1287
     * @param array<string, mixed> $options
1288
     *
1289
     * @return string
1290
     */
1291
    protected function generateEditButton($url, $title, array $options = [])
1292
    {
1293
        $defaultOptions = [
1294
            'class' => 'btn-edit',
1295
            'icon' => 'fa-edit',
1296
        ];
1297
1298
        return $this->generateButton($url, $title, $defaultOptions, $options);
1299
    }
1300
1301
    /**
1302
     * @param string $url
1303
     * @param string $title
1304
     * @param array<string, mixed> $options
1305
     *
1306
     * @return string
1307
     */
1308
    protected function generateViewButton($url, $title, array $options = [])
1309
    {
1310
        $defaultOptions = [
1311
            'class' => 'btn-view',
1312
            'icon' => 'fa-eye',
1313
        ];
1314
1315
        return $this->generateButton($url, $title, $defaultOptions, $options);
1316
    }
1317
1318
    /**
1319
     * @param string $title
1320
     * @param string $url
1321
     * @param bool $separated
1322
     * @param array<string, mixed> $options
1323
     *
1324
     * @return array
1325
     */
1326
    protected function createButtonGroupItem($title, $url, $separated = false, array $options = [])
1327
    {
1328
        return [
1329
            'title' => $title,
1330
            'url' => $url,
1331
            'separated' => $separated,
1332
            'options' => $options,
1333
        ];
1334
    }
1335
1336
    /**
1337
     * @param array $buttonGroupItems
1338
     * @param string $title
1339
     * @param array<string, mixed> $options
1340
     *
1341
     * @return string
1342
     */
1343
    protected function generateButtonGroup(array $buttonGroupItems, $title, array $options = [])
1344
    {
1345
        $defaultOptions = [
1346
            'class' => 'btn-view',
1347
            'icon' => 'fa-eye',
1348
        ];
1349
1350
        return $this->generateButtonGroupHtml($buttonGroupItems, $title, $defaultOptions, $options);
1351
    }
1352
1353
    /**
1354
     * @param string $url
1355
     * @param string $title
1356
     * @param array<string, mixed> $options
1357
     * @param string $formClassName
1358
     *
1359
     * @return string
1360
     */
1361
    protected function generateRemoveButton($url, $title, array $options = [], string $formClassName = DeleteForm::class)
1362
    {
1363
        $name = isset($options[static::DELETE_FORM_NAME_SUFFIX]) ? static::DELETE_FORM_NAME . $options[static::DELETE_FORM_NAME_SUFFIX] : '';
1364
1365
        $options = [
1366
            'fields' => $options,
1367
            'action' => (string)$url,
1368
        ];
1369
1370
        $form = $this->createForm($formClassName, $name, $options);
1371
        $options['form'] = $form->createView();
1372
        $options['title'] = $title;
1373
1374
        return $this->twig->render('delete-form.twig', $options);
1375
    }
1376
1377
    /**
1378
     * @deprecated Use {@link \Spryker\Zed\Gui\Communication\Table\AbstractTable::createForm()} instead.
1379
     *
1380
     * @param array<string, mixed> $options
1381
     * @param string $name
1382
     *
1383
     * @return \Symfony\Component\Form\FormInterface
1384
     */
1385
    protected function createDeleteForm(array $options, string $name = ''): FormInterface
1386
    {
1387
        if (!$name) {
1388
            return $this->getFormFactory()->create(DeleteForm::class, [], $options);
1389
        }
1390
1391
        return $this->getFormFactory()->createNamed($name, DeleteForm::class, [], $options);
1392
    }
1393
1394
    /**
1395
     * @return \Symfony\Component\Form\FormFactoryInterface
1396
     */
1397
    protected function getFormFactory()
1398
    {
1399
        return $this->getApplicationContainer()->get(static::SERVICE_FORM_FACTORY);
1400
    }
1401
1402
    /**
1403
     * @param \Spryker\Service\UtilText\Model\Url\Url|string $url
1404
     * @param string $title
1405
     * @param array $defaultOptions
1406
     * @param array $customOptions
1407
     *
1408
     * @return string
1409
     */
1410
    protected function generateButton($url, $title, array $defaultOptions, array $customOptions = [])
1411
    {
1412
        $buttonOptions = $this->generateButtonOptions($defaultOptions, $customOptions);
1413
1414
        $class = $this->getButtonClass($defaultOptions, $customOptions);
1415
        $parameters = $this->getButtonParameters($buttonOptions);
1416
        $icon = '';
1417
1418
        if (array_key_exists(static::BUTTON_ICON, $buttonOptions) === true && $buttonOptions[static::BUTTON_ICON] !== null) {
1419
            $icon = '<i class="fa ' . $buttonOptions[static::BUTTON_ICON] . '"></i> ';
1420
        }
1421
1422
        return $this->getTwig()->render('button.twig', [
1423
            'url' => $this->buildUrl($url),
1424
            'class' => $class,
1425
            'title' => $title,
1426
            'icon' => $icon,
1427
            'parameters' => $parameters,
1428
        ]);
1429
    }
1430
1431
    /**
1432
     * @param \Spryker\Service\UtilText\Model\Url\Url|string $url
1433
     * @param string $title
1434
     * @param string $formClassName
1435
     * @param array $buttonOptions
1436
     * @param array $formOptions
1437
     *
1438
     * @return string
1439
     */
1440
    protected function generateFormButton($url, string $title, string $formClassName, array $buttonOptions = [], array $formOptions = [])
1441
    {
1442
        $buttonOptions = $this->generateButtonOptions([
1443
            'class' => 'btn-view',
1444
            'icon' => 'fa-caret-right',
1445
        ], $buttonOptions);
1446
1447
        $buttonClass = $this->getButtonClass($buttonOptions);
1448
        $buttonParameters = $this->getButtonParameters($buttonOptions);
1449
1450
        $formOptions = array_merge($formOptions, [
1451
            'action' => $this->buildUrl($url),
1452
            'attr' => ['class' => 'form-inline'],
1453
        ]);
1454
1455
        $form = $this->createForm($formClassName, null, $formOptions);
1456
1457
        return $this->getTwig()->render('button-form.twig', [
1458
            'class' => $buttonClass,
1459
            'title' => $title,
1460
            'icon' => $this->generateButtonIcon($buttonOptions),
1461
            'parameters' => $buttonParameters,
1462
            'form' => $form->createView(),
1463
        ]);
1464
    }
1465
1466
    /**
1467
     * @param string $formClassName
1468
     * @param string|null $formName
1469
     * @param array $formOptions
1470
     * @param array<string, mixed> $data
1471
     *
1472
     * @return \Symfony\Component\Form\FormInterface
1473
     */
1474
    protected function createForm(
1475
        string $formClassName,
1476
        ?string $formName = null,
1477
        array $formOptions = [],
1478
        array $data = []
1479
    ): FormInterface {
1480
        if (!$formName) {
1481
            return $this->getFormFactory()->create($formClassName, $data, $formOptions);
1482
        }
1483
1484
        return $this->getFormFactory()->createNamed($formName, $formClassName, $data, $formOptions);
1485
    }
1486
1487
    /**
1488
     * @param array $buttonOptions
1489
     *
1490
     * @return string
1491
     */
1492
    protected function generateButtonIcon(array $buttonOptions): string
1493
    {
1494
        if (array_key_exists(static::BUTTON_ICON, $buttonOptions) === true && $buttonOptions[static::BUTTON_ICON] !== null) {
1495
            return '<i class="fa ' . $buttonOptions[static::BUTTON_ICON] . '"></i> ';
1496
        }
1497
1498
        return '';
1499
    }
1500
1501
    /**
1502
     * @param \Spryker\Service\UtilText\Model\Url\Url|string $url
1503
     *
1504
     * @return string
1505
     */
1506
    protected function buildUrl($url): string
1507
    {
1508
        if ($url === static::URL_ANCHOR) {
1509
            return static::URL_ANCHOR;
1510
        }
1511
1512
        if (is_string($url)) {
1513
            $url = Url::parse($url);
1514
        }
1515
1516
        return $url->build();
1517
    }
1518
1519
    /**
1520
     * @param string $title
1521
     * @param string|null $class
1522
     *
1523
     * @return string
1524
     */
1525
    protected function generateLabel(string $title, ?string $class): string
1526
    {
1527
        return $this->getTwig()->render('label.twig', [
1528
            'title' => $title,
1529
            'class' => $class,
1530
        ]);
1531
    }
1532
1533
    /**
1534
     * @param array $buttons
1535
     * @param string $title
1536
     * @param array $defaultOptions
1537
     * @param array $customOptions
1538
     *
1539
     * @return string
1540
     */
1541
    protected function generateButtonGroupHtml(array $buttons, $title, array $defaultOptions, array $customOptions = [])
1542
    {
1543
        $buttonOptions = $this->generateButtonOptions($defaultOptions, $customOptions);
1544
        $class = $this->getButtonClass($defaultOptions, $customOptions);
1545
        $parameters = $this->getButtonParameters($buttonOptions);
1546
1547
        $icon = '';
1548
        if (array_key_exists(static::BUTTON_ICON, $buttonOptions) === true && $buttonOptions[static::BUTTON_ICON] !== null) {
1549
            $icon .= '<i class="fa ' . $buttonOptions[static::BUTTON_ICON] . '"></i> ';
1550
        }
1551
1552
        return $this->generateButtonDropdownHtml($buttons, $title, $icon, $class, $parameters);
1553
    }
1554
1555
    /**
1556
     * @param array $buttons
1557
     * @param string $title
1558
     * @param string $icon
1559
     * @param string $class
1560
     * @param string $parameters
1561
     *
1562
     * @return string
1563
     */
1564
    protected function generateButtonDropdownHtml(array $buttons, $title, $icon, $class, $parameters)
1565
    {
1566
        $nestedButtons = [];
1567
1568
        foreach ($buttons as $button) {
1569
            if (is_string($button['url'])) {
1570
                $utilSanitizeService = new UtilSanitizeService();
1571
                $url = $utilSanitizeService->escapeHtml($button['url']);
1572
            } else {
1573
                /** @var \Spryker\Service\UtilText\Model\Url\Url $buttonUrl */
1574
                $buttonUrl = $button['url'];
1575
                $url = $buttonUrl->buildEscaped();
1576
            }
1577
1578
            $buttonParameters = '';
1579
            if (isset($button['options'])) {
1580
                $buttonParameters = $this->getButtonParameters($button['options']);
1581
            }
1582
1583
            $nestedButtons[] = [
1584
                'needDivider' => !empty($button['separated']),
1585
                'url' => $url,
1586
                'params' => $buttonParameters,
1587
                'title' => $button['title'],
1588
            ];
1589
        }
1590
1591
        return $this->getTwig()->render('button-dropdown.twig', [
1592
            'class' => $class,
1593
            'parameters' => $parameters,
1594
            'icon' => $icon,
1595
            'title' => $title,
1596
            'nestedButtons' => $nestedButtons,
1597
        ]);
1598
    }
1599
1600
    /**
1601
     * @param array $defaultOptions
1602
     * @param array<string, mixed> $options
1603
     *
1604
     * @return string
1605
     */
1606
    protected function getButtonClass(array $defaultOptions, array $options = [])
1607
    {
1608
        $class = '';
1609
1610
        if (isset($defaultOptions[static::BUTTON_CLASS])) {
1611
            $class .= ' ' . $defaultOptions[static::BUTTON_CLASS];
1612
        }
1613
        if (isset($options[static::BUTTON_CLASS])) {
1614
            $class .= ' ' . $options[static::BUTTON_CLASS];
1615
        }
1616
1617
        if (!$class) {
1618
            return static::BUTTON_DEFAULT_CLASS;
1619
        }
1620
1621
        return $class;
1622
    }
1623
1624
    /**
1625
     * @param array $buttonOptions
1626
     *
1627
     * @return string
1628
     */
1629
    protected function getButtonParameters(array $buttonOptions)
1630
    {
1631
        $parameters = '';
1632
        foreach ($buttonOptions as $argument => $value) {
1633
            if (in_array($argument, [static::BUTTON_CLASS, static::BUTTON_HREF, static::BUTTON_ICON])) {
1634
                continue;
1635
            }
1636
            $parameters .= sprintf(' %s=\'%s\'', $argument, $value);
1637
        }
1638
1639
        return $parameters;
1640
    }
1641
1642
    /**
1643
     * @param array $defaultOptions
1644
     * @param array<string, mixed> $options
1645
     *
1646
     * @return array
1647
     */
1648
    protected function generateButtonOptions(array $defaultOptions, array $options = [])
1649
    {
1650
        $buttonOptions = $defaultOptions;
1651
        if (is_array($options)) {
0 ignored issues
show
introduced by
The condition is_array($options) is always true.
Loading history...
1652
            $buttonOptions = array_merge($defaultOptions, $options);
1653
        }
1654
1655
        return $buttonOptions;
1656
    }
1657
1658
    /**
1659
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1660
     * @param array $searchColumns
1661
     * @param \Generated\Shared\Transfer\DataTablesColumnTransfer $column
1662
     *
1663
     * @return void
1664
     */
1665
    protected function addQueryCondition(ModelCriteria $query, array $searchColumns, DataTablesColumnTransfer $column)
1666
    {
1667
        $search = $column->getSearch();
1668
        if (preg_match('/created_at|updated_at/', $searchColumns[$column->getData()])) {
1669
            /** @var literal-string $where */
1670
            $where = sprintf(
1671
                '(%s >= %s AND %s <= %s)',
1672
                $searchColumns[$column->getData()],
1673
                Propel::getConnection()->quote($this->filterSearchValue($search[static::PARAMETER_VALUE]) . ' 00:00:00'),
1674
                $searchColumns[$column->getData()],
1675
                Propel::getConnection()->quote($this->filterSearchValue($search[static::PARAMETER_VALUE]) . ' 23:59:59'),
1676
            );
1677
            $query->where($where);
1678
1679
            return;
1680
        }
1681
1682
        $value = $this->filterSearchValue($search[static::PARAMETER_VALUE]);
1683
        if ($value === 'null') {
1684
            return;
1685
        }
1686
1687
        /** @var literal-string $where */
1688
        $where = sprintf(
1689
            '%s = %s',
1690
            $searchColumns[$column->getData()],
1691
            Propel::getConnection()->quote($value),
1692
        );
1693
        $query->where($where);
1694
    }
1695
1696
    /**
1697
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1698
     * @param array $searchColumns
1699
     *
1700
     * @return void
1701
     */
1702
    protected function addFilteringConditions(ModelCriteria $query, array $searchColumns)
1703
    {
1704
        foreach ($this->dataTablesTransfer->getColumns() as $column) {
1705
            $search = $column->getSearch();
1706
            if (empty($search[static::PARAMETER_VALUE])) {
1707
                continue;
1708
            }
1709
1710
            $this->addQueryCondition($query, $searchColumns, $column);
1711
        }
1712
    }
1713
1714
    /**
1715
     * @return \Spryker\Service\UtilNumber\UtilNumberServiceInterface|null
1716
     */
1717
    protected function getUtilNumberService(): ?UtilNumberServiceInterface
1718
    {
1719
        $container = $this->getApplicationContainer();
1720
        if (!$container->has(static::SERVICE_UTIL_NUMBER)) {
1721
            return null;
1722
        }
1723
1724
        return $this->getApplicationContainer()->get(static::SERVICE_UTIL_NUMBER);
1725
    }
1726
1727
    /**
1728
     * @return string|null
1729
     */
1730
    protected function getCurrentLocaleName(): ?string
1731
    {
1732
        $container = $this->getApplicationContainer();
1733
        if (!$container->has(static::SERVICE_LOCALE)) {
1734
            return null;
1735
        }
1736
1737
        return $container->get(static::SERVICE_LOCALE);
1738
    }
1739
1740
    /**
1741
     * @param int $value
1742
     *
1743
     * @return string
1744
     */
1745
    protected function formatInt(int $value): string
1746
    {
1747
        $utilNumberService = $this->getUtilNumberService();
1748
        if (!$utilNumberService) {
1749
            return (string)$value;
1750
        }
1751
1752
        $currentLocaleName = $this->getCurrentLocaleName();
1753
        if (!$currentLocaleName) {
1754
            return (string)$value;
1755
        }
1756
1757
        $numberFormatIntRequestTransfer = (new NumberFormatIntRequestTransfer())
1758
            ->setNumber($value)
1759
            ->setNumberFormatFilter(
1760
                (new NumberFormatFilterTransfer())->setLocale($currentLocaleName),
1761
            );
1762
1763
        return $utilNumberService->formatInt($numberFormatIntRequestTransfer);
1764
    }
1765
1766
    /**
1767
     * @param float $value
1768
     *
1769
     * @return string
1770
     */
1771
    protected function formatFloat(float $value): string
1772
    {
1773
        $utilNumberService = $this->getUtilNumberService();
1774
        if (!$utilNumberService) {
1775
            return (string)$value;
1776
        }
1777
1778
        $currentLocaleName = $this->getCurrentLocaleName();
1779
        if (!$currentLocaleName) {
1780
            return (string)$value;
1781
        }
1782
1783
        $numberFormatFloatRequestTransfer = (new NumberFormatFloatRequestTransfer())
1784
            ->setNumber($value)
1785
            ->setNumberFormatFilter(
1786
                (new NumberFormatFilterTransfer())->setLocale($currentLocaleName),
1787
            );
1788
1789
        return $utilNumberService->formatFloat($numberFormatFloatRequestTransfer);
1790
    }
1791
1792
    /**
1793
     * @param string $formClassName
1794
     * @param string $fieldName
1795
     * @param array<string, mixed> $options
1796
     * @param array<string, mixed> $data
1797
     *
1798
     * @return string
1799
     */
1800
    protected function generateFormField(string $formClassName, string $fieldName, array $options = [], array $data = []): string
1801
    {
1802
        $formView = $this->createForm($formClassName, null, $options, $data)->createView();
1803
        if (!$formView->offsetExists($fieldName)) {
1804
            return '';
1805
        }
1806
1807
        $options['field'] = $formView->offsetGet($fieldName);
1808
1809
        return $this->twig->render('form-field.twig', $options);
1810
    }
1811
1812
    /**
1813
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
1814
     * @param string $driverName
1815
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1816
     *
1817
     * @return string
1818
     */
1819
    protected function getSearchPattern(TableConfiguration $config, string $driverName, ModelCriteria $query): string
1820
    {
1821
        if ($this->isStrictSearch($query, $config) === true) {
1822
            return $this->getStrictSearchPatternByDriverName($driverName);
1823
        }
1824
1825
        return static::SEARCH_PATTERN_FOR_FUZZY_SEARCH;
1826
    }
1827
1828
    /**
1829
     * @param string $driverName
1830
     *
1831
     * @return string
1832
     */
1833
    protected function getStrictSearchPatternByDriverName(string $driverName): string
1834
    {
1835
        if ($driverName === static::DRIVER_NAME_PGSQL) {
1836
            return static::SEARCH_PATTERN_FOR_STRICT_SEARCH_POSTGRESQL;
1837
        }
1838
1839
        return static::SEARCH_PATTERN_FOR_STRICT_SEARCH_MYSQL;
1840
    }
1841
}
1842