AbstractTable::getSearchParameter()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 7
rs 10
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
            'tableAttributes' => [],
931
            'headerAttributes' => [],
932
        ];
933
934
        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...
935
            $configTableArray = [
936
                'url' => ($this->config->getUrl() === null) ? $this->defaultUrl : $this->config->getUrl(),
937
                'header' => $this->config->getHeader(),
938
                'footer' => $this->config->getFooter(),
939
                'order' => $this->getOrders($this->config),
940
                'searchable' => $this->config->getSearchable(),
941
                'searchableColumns' => $this->config->getSearchableColumns(),
942
                'sortable' => $this->config->getSortable(),
943
                'pageLength' => $this->config->getPageLength(),
944
                'processing' => $this->config->isProcessing(),
945
                'serverSide' => $this->config->isServerSide(),
946
                'stateSave' => $this->config->isStateSave(),
947
                'paging' => $this->config->isPaging(),
948
                'ordering' => $this->config->isOrdering(),
949
                'tableAttributes' => $this->config->getTableAttributes(),
950
                'headerAttributes' => $this->config->getHeaderAttributes(),
951
            ];
952
953
            $configArray = array_merge($configArray, $configTableArray);
954
        }
955
956
        return $configArray;
957
    }
958
959
    /**
960
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $criteria
961
     *
962
     * @return string
963
     */
964
    protected function getFirstAvailableColumnInQuery(ModelCriteria $criteria)
965
    {
966
        $tableMap = $criteria->getTableMap();
967
        $columns = array_keys($tableMap->getColumns());
968
969
        $firstColumnName = $tableMap->getColumn($columns[0])->getName();
970
971
        return $tableMap->getName() . '.' . $firstColumnName;
972
    }
973
974
    /**
975
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
976
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
977
     * @param array $order
978
     *
979
     * @return string
980
     */
981
    protected function getOrderByColumn(ModelCriteria $query, TableConfiguration $config, array $order)
982
    {
983
        $columns = $this->getColumnsList($query, $config);
984
985
        if (isset($order[0]) && isset($order[0][static::SORT_BY_COLUMN]) && isset($columns[$order[0][static::SORT_BY_COLUMN]])) {
986
            $selectedColumn = $columns[$order[0][static::SORT_BY_COLUMN]];
987
988
            if (in_array($selectedColumn, $config->getSortable(), true)) {
989
                return $selectedColumn;
990
            }
991
        }
992
993
        return $this->getFirstAvailableColumnInQuery($query);
994
    }
995
996
    /**
997
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
998
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
999
     *
1000
     * @return array
1001
     */
1002
    protected function getColumnsList(ModelCriteria $query, TableConfiguration $config)
1003
    {
1004
        if ($config->getHeader()) {
1005
            return array_keys($config->getHeader());
1006
        }
1007
1008
        return array_keys($query->getTableMap()->getColumns());
1009
    }
1010
1011
    /**
1012
     * @todo CD-412 refactor this class to allow unspecified header columns and to add flexibility
1013
     *
1014
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1015
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
1016
     * @param bool $returnRawResults
1017
     *
1018
     * @return \Propel\Runtime\Collection\ObjectCollection|array
1019
     */
1020
    protected function runQuery(ModelCriteria $query, TableConfiguration $config, $returnRawResults = false)
1021
    {
1022
        $this->total = $this->filtered = $this->countTotal($query);
1023
        $limit = $this->getLimit();
1024
        $offset = $this->getOffset();
1025
        $order = $this->getOrders($config);
1026
        $orderColumn = $this->getOrderByColumn($query, $config, $order);
1027
1028
        $query->orderBy($orderColumn, $order[0][static::SORT_BY_DIRECTION]);
1029
1030
        $searchTerm = $this->getSearchTerm();
1031
        $searchValue = $searchTerm[static::PARAMETER_VALUE] ?? '';
1032
1033
        if (mb_strlen($searchValue) > 0 || $this->isStrictSearch($query, $config) === true) {
1034
            $query->setIdentifierQuoting(true);
1035
1036
            $conditions = $this->resolveConditions($query, $config, $searchValue);
1037
1038
            if ($conditions !== []) {
1039
                $query = $this->applyConditions($query, $config, $conditions);
1040
            }
1041
1042
            $this->filtered = $query->count();
1043
        }
1044
1045
        if ($this->dataTablesTransfer !== null) {
1046
            $searchColumns = $config->getSearchable();
1047
1048
            $this->addFilteringConditions($query, $searchColumns);
1049
        }
1050
1051
        $data = $query->offset($offset)
1052
            ->limit($limit)
1053
            ->find();
1054
1055
        if ($returnRawResults === true) {
1056
            return $data;
1057
        }
1058
1059
        return $data->toArray(null, false, TableMap::TYPE_COLNAME);
1060
    }
1061
1062
    /**
1063
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1064
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
1065
     *
1066
     * @return array<string>
1067
     */
1068
    protected function getStrictConditionParameters(ModelCriteria $query, TableConfiguration $config): array
1069
    {
1070
        $conditionParameters = [];
1071
        $searchTerms = $this->getSearchColumns();
1072
1073
        foreach ($this->getColumnsList($query, $config) as $index => $colName) {
1074
            if ($searchTerms[$index][static::SEARCH][static::VALUE] !== '') {
1075
                $conditionParameters[$config->getSearchableColumns()[$colName]] = $searchTerms[$index][static::SEARCH][static::VALUE];
1076
            }
1077
        }
1078
1079
        return $conditionParameters;
1080
    }
1081
1082
    /**
1083
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1084
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
1085
     *
1086
     * @return bool
1087
     */
1088
    protected function isStrictSearch(ModelCriteria $query, TableConfiguration $config): bool
1089
    {
1090
        $searchTerms = $this->getSearchColumns();
1091
1092
        foreach ($this->getColumnsList($query, $config) as $index => $colName) {
1093
            if (isset($searchTerms[$index]) && $searchTerms[$index][static::SEARCH][static::VALUE] !== '') {
1094
                return true;
1095
            }
1096
        }
1097
1098
        return false;
1099
    }
1100
1101
    /**
1102
     * @return array<mixed>
1103
     */
1104
    protected function getSearchColumns(): array
1105
    {
1106
        return $this->request->query->all()[static::COLUMNS] ?? [];
1107
    }
1108
1109
    /**
1110
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1111
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
1112
     * @param string $searchValue
1113
     *
1114
     * @return array<string>
1115
     */
1116
    protected function resolveConditions(
1117
        ModelCriteria $query,
1118
        TableConfiguration $config,
1119
        string $searchValue
1120
    ): array {
1121
        $conditions = [];
1122
        $connection = Propel::getConnection();
1123
        $driverName = $connection->getAttribute(PDO::ATTR_DRIVER_NAME);
1124
        $filter = $driverName === static::DRIVER_NAME_PGSQL ? '::TEXT' : '';
1125
        $searchPattern = $this->getSearchPattern($config, $driverName, $query);
1126
1127
        if ($this->isStrictSearch($query, $config) === true) {
1128
            $strictConditionParameters = $this->getStrictConditionParameters($query, $config);
1129
            foreach ($strictConditionParameters as $value => $conditionParameter) {
1130
                $conditions[] = $this->buildCondition($searchPattern, $value, $filter, $connection->quote($conditionParameter));
1131
            }
1132
1133
            return $conditions;
1134
        }
1135
1136
        $conditionParameter = $connection->quote('%' . mb_strtolower($searchValue) . '%');
1137
        foreach ($config->getSearchable() as $value) {
1138
            $conditions[] = $this->buildCondition($searchPattern, $value, $filter, $conditionParameter);
1139
        }
1140
1141
        return $conditions;
1142
    }
1143
1144
    /**
1145
     * @param string $searchPattern
1146
     * @param string $value
1147
     * @param string $filter
1148
     * @param string $conditionParameter
1149
     *
1150
     * @return string
1151
     */
1152
    protected function buildCondition(
1153
        string $searchPattern,
1154
        string $value,
1155
        string $filter,
1156
        string $conditionParameter
1157
    ): string {
1158
        return sprintf($searchPattern, $value, $filter, $conditionParameter);
1159
    }
1160
1161
    /**
1162
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1163
     *
1164
     * @return int
1165
     */
1166
    protected function countTotal(ModelCriteria $query): int
1167
    {
1168
        return $query->count();
1169
    }
1170
1171
    /**
1172
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1173
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
1174
     * @param array<string> $conditions
1175
     *
1176
     * @return \Propel\Runtime\ActiveQuery\ModelCriteria
1177
     */
1178
    protected function applyConditions(ModelCriteria $query, TableConfiguration $config, array $conditions): ModelCriteria
1179
    {
1180
        $gluedCondition = implode(
1181
            sprintf(' %s ', $this->isStrictSearch($query, $config) === true ? Criteria::LOGICAL_AND : Criteria::LOGICAL_OR),
1182
            $conditions,
1183
        );
1184
1185
        /** @var literal-string $gluedCondition */
1186
        $gluedCondition = '(' . $gluedCondition . ')';
1187
1188
        if ($config->getHasSearchableFieldsWithAggregateFunctions()) {
1189
            $tablePrimaryKey = array_key_first($query->getTableMap()->getPrimaryKeys());
1190
1191
            return $query
1192
                ->having($gluedCondition)
1193
                ->groupBy($tablePrimaryKey);
1194
        }
1195
1196
        return $query->where($gluedCondition);
1197
    }
1198
1199
    /**
1200
     * @param string $value
1201
     *
1202
     * @return string
1203
     */
1204
    protected function filterSearchValue($value)
1205
    {
1206
        $value = str_replace(['^', '$'], '', $value);
1207
        $value = stripslashes($value);
1208
1209
        return $value;
1210
    }
1211
1212
    /**
1213
     * @return array
1214
     */
1215
    public function fetchData()
1216
    {
1217
        $this->init();
1218
1219
        $data = $this->prepareData($this->config);
1220
        $this->loadData($data);
1221
        $wrapperArray = [
1222
            'draw' => $this->request->query->getInt('draw', 1),
1223
            'recordsTotal' => $this->total,
1224
            'recordsFiltered' => $this->filtered,
1225
            'data' => $this->data,
1226
        ];
1227
1228
        return $wrapperArray;
1229
    }
1230
1231
    /**
1232
     * Drop table name from key
1233
     *
1234
     * @param string $key
1235
     *
1236
     * @return string
1237
     */
1238
    public function cutTablePrefix($key)
1239
    {
1240
        $position = mb_strpos($key, '.');
1241
1242
        return ($position !== false) ? mb_substr($key, $position + 1) : $key;
1243
    }
1244
1245
    /**
1246
     * @param string $str
1247
     *
1248
     * @return string
1249
     */
1250
    public function camelize($str)
1251
    {
1252
        return str_replace(' ', '', ucwords(mb_strtolower(str_replace('_', ' ', $str))));
1253
    }
1254
1255
    /**
1256
     * @param int $total
1257
     *
1258
     * @return void
1259
     */
1260
    protected function setTotal($total)
1261
    {
1262
        $this->total = $total;
1263
    }
1264
1265
    /**
1266
     * @param int $filtered
1267
     *
1268
     * @return void
1269
     */
1270
    protected function setFiltered($filtered)
1271
    {
1272
        $this->filtered = $filtered;
1273
    }
1274
1275
    /**
1276
     * @param string $url
1277
     * @param string $title
1278
     * @param array<string, mixed> $options
1279
     *
1280
     * @return string
1281
     */
1282
    protected function generateCreateButton($url, $title, array $options = [])
1283
    {
1284
        $defaultOptions = [
1285
            'class' => 'btn-create',
1286
            'icon' => 'fa-plus',
1287
        ];
1288
1289
        return $this->generateButton($url, $title, $defaultOptions, $options);
1290
    }
1291
1292
    /**
1293
     * @param string $url
1294
     * @param string $title
1295
     * @param array<string, mixed> $options
1296
     *
1297
     * @return string
1298
     */
1299
    protected function generateEditButton($url, $title, array $options = [])
1300
    {
1301
        $defaultOptions = [
1302
            'class' => 'btn-edit',
1303
            'icon' => 'fa-edit',
1304
        ];
1305
1306
        return $this->generateButton($url, $title, $defaultOptions, $options);
1307
    }
1308
1309
    /**
1310
     * @param string $url
1311
     * @param string $title
1312
     * @param array<string, mixed> $options
1313
     *
1314
     * @return string
1315
     */
1316
    protected function generateViewButton($url, $title, array $options = [])
1317
    {
1318
        $defaultOptions = [
1319
            'class' => 'btn-view',
1320
            'icon' => 'fa-eye',
1321
        ];
1322
1323
        return $this->generateButton($url, $title, $defaultOptions, $options);
1324
    }
1325
1326
    /**
1327
     * @param string $title
1328
     * @param string $url
1329
     * @param bool $separated
1330
     * @param array<string, mixed> $options
1331
     *
1332
     * @return array
1333
     */
1334
    protected function createButtonGroupItem($title, $url, $separated = false, array $options = [])
1335
    {
1336
        return [
1337
            'title' => $title,
1338
            'url' => $url,
1339
            'separated' => $separated,
1340
            'options' => $options,
1341
        ];
1342
    }
1343
1344
    /**
1345
     * @param array $buttonGroupItems
1346
     * @param string $title
1347
     * @param array<string, mixed> $options
1348
     *
1349
     * @return string
1350
     */
1351
    protected function generateButtonGroup(array $buttonGroupItems, $title, array $options = [])
1352
    {
1353
        $defaultOptions = [
1354
            'class' => 'btn-view',
1355
            'icon' => 'fa-eye',
1356
        ];
1357
1358
        return $this->generateButtonGroupHtml($buttonGroupItems, $title, $defaultOptions, $options);
1359
    }
1360
1361
    /**
1362
     * @param string $url
1363
     * @param string $title
1364
     * @param array<string, mixed> $options
1365
     * @param string $formClassName
1366
     *
1367
     * @return string
1368
     */
1369
    protected function generateRemoveButton($url, $title, array $options = [], string $formClassName = DeleteForm::class)
1370
    {
1371
        $name = isset($options[static::DELETE_FORM_NAME_SUFFIX]) ? static::DELETE_FORM_NAME . $options[static::DELETE_FORM_NAME_SUFFIX] : '';
1372
1373
        $options = [
1374
            'fields' => $options,
1375
            'action' => (string)$url,
1376
        ];
1377
1378
        $form = $this->createForm($formClassName, $name, $options);
1379
        $options['form'] = $form->createView();
1380
        $options['title'] = $title;
1381
1382
        return $this->twig->render('delete-form.twig', $options);
1383
    }
1384
1385
    /**
1386
     * @deprecated Use {@link \Spryker\Zed\Gui\Communication\Table\AbstractTable::createForm()} instead.
1387
     *
1388
     * @param array<string, mixed> $options
1389
     * @param string $name
1390
     *
1391
     * @return \Symfony\Component\Form\FormInterface
1392
     */
1393
    protected function createDeleteForm(array $options, string $name = ''): FormInterface
1394
    {
1395
        if (!$name) {
1396
            return $this->getFormFactory()->create(DeleteForm::class, [], $options);
1397
        }
1398
1399
        return $this->getFormFactory()->createNamed($name, DeleteForm::class, [], $options);
1400
    }
1401
1402
    /**
1403
     * @return \Symfony\Component\Form\FormFactoryInterface
1404
     */
1405
    protected function getFormFactory()
1406
    {
1407
        return $this->getApplicationContainer()->get(static::SERVICE_FORM_FACTORY);
1408
    }
1409
1410
    /**
1411
     * @param \Spryker\Service\UtilText\Model\Url\Url|string $url
1412
     * @param string $title
1413
     * @param array $defaultOptions
1414
     * @param array $customOptions
1415
     *
1416
     * @return string
1417
     */
1418
    protected function generateButton($url, $title, array $defaultOptions, array $customOptions = [])
1419
    {
1420
        $buttonOptions = $this->generateButtonOptions($defaultOptions, $customOptions);
1421
1422
        $class = $this->getButtonClass($defaultOptions, $customOptions);
1423
        $parameters = $this->getButtonParameters($buttonOptions);
1424
        $icon = '';
1425
1426
        if (array_key_exists(static::BUTTON_ICON, $buttonOptions) === true && $buttonOptions[static::BUTTON_ICON] !== null) {
1427
            $icon = '<i class="fa ' . $buttonOptions[static::BUTTON_ICON] . '"></i> ';
1428
        }
1429
1430
        return $this->getTwig()->render('button.twig', [
1431
            'url' => $this->buildUrl($url),
1432
            'class' => $class,
1433
            'title' => $title,
1434
            'icon' => $icon,
1435
            'parameters' => $parameters,
1436
        ]);
1437
    }
1438
1439
    /**
1440
     * @param \Spryker\Service\UtilText\Model\Url\Url|string $url
1441
     * @param string $title
1442
     * @param string $formClassName
1443
     * @param array $buttonOptions
1444
     * @param array $formOptions
1445
     *
1446
     * @return string
1447
     */
1448
    protected function generateFormButton($url, string $title, string $formClassName, array $buttonOptions = [], array $formOptions = [])
1449
    {
1450
        $buttonOptions = $this->generateButtonOptions([
1451
            'class' => 'btn-view',
1452
            'icon' => 'fa-caret-right',
1453
        ], $buttonOptions);
1454
1455
        $buttonClass = $this->getButtonClass($buttonOptions);
1456
        $buttonParameters = $this->getButtonParameters($buttonOptions);
1457
1458
        $formOptions = array_merge($formOptions, [
1459
            'action' => $this->buildUrl($url),
1460
            'attr' => ['class' => 'form-inline'],
1461
        ]);
1462
1463
        $form = $this->createForm($formClassName, null, $formOptions);
1464
1465
        return $this->getTwig()->render('button-form.twig', [
1466
            'class' => $buttonClass,
1467
            'title' => $title,
1468
            'icon' => $this->generateButtonIcon($buttonOptions),
1469
            'parameters' => $buttonParameters,
1470
            'form' => $form->createView(),
1471
        ]);
1472
    }
1473
1474
    /**
1475
     * @param string $formClassName
1476
     * @param string|null $formName
1477
     * @param array $formOptions
1478
     * @param array<string, mixed> $data
1479
     *
1480
     * @return \Symfony\Component\Form\FormInterface
1481
     */
1482
    protected function createForm(
1483
        string $formClassName,
1484
        ?string $formName = null,
1485
        array $formOptions = [],
1486
        array $data = []
1487
    ): FormInterface {
1488
        if (!$formName) {
1489
            return $this->getFormFactory()->create($formClassName, $data, $formOptions);
1490
        }
1491
1492
        return $this->getFormFactory()->createNamed($formName, $formClassName, $data, $formOptions);
1493
    }
1494
1495
    /**
1496
     * @param array $buttonOptions
1497
     *
1498
     * @return string
1499
     */
1500
    protected function generateButtonIcon(array $buttonOptions): string
1501
    {
1502
        if (array_key_exists(static::BUTTON_ICON, $buttonOptions) === true && $buttonOptions[static::BUTTON_ICON] !== null) {
1503
            return '<i class="fa ' . $buttonOptions[static::BUTTON_ICON] . '"></i> ';
1504
        }
1505
1506
        return '';
1507
    }
1508
1509
    /**
1510
     * @param \Spryker\Service\UtilText\Model\Url\Url|string $url
1511
     *
1512
     * @return string
1513
     */
1514
    protected function buildUrl($url): string
1515
    {
1516
        if ($url === static::URL_ANCHOR) {
1517
            return static::URL_ANCHOR;
1518
        }
1519
1520
        if (is_string($url)) {
1521
            $url = Url::parse($url);
1522
        }
1523
1524
        return $url->build();
1525
    }
1526
1527
    /**
1528
     * @param string $title
1529
     * @param string|null $class
1530
     *
1531
     * @return string
1532
     */
1533
    protected function generateLabel(string $title, ?string $class): string
1534
    {
1535
        return $this->getTwig()->render('label.twig', [
1536
            'title' => $title,
1537
            'class' => $class,
1538
        ]);
1539
    }
1540
1541
    /**
1542
     * @param array $buttons
1543
     * @param string $title
1544
     * @param array $defaultOptions
1545
     * @param array $customOptions
1546
     *
1547
     * @return string
1548
     */
1549
    protected function generateButtonGroupHtml(array $buttons, $title, array $defaultOptions, array $customOptions = [])
1550
    {
1551
        $buttonOptions = $this->generateButtonOptions($defaultOptions, $customOptions);
1552
        $class = $this->getButtonClass($defaultOptions, $customOptions);
1553
        $parameters = $this->getButtonParameters($buttonOptions);
1554
1555
        $icon = '';
1556
        if (array_key_exists(static::BUTTON_ICON, $buttonOptions) === true && $buttonOptions[static::BUTTON_ICON] !== null) {
1557
            $icon .= '<i class="fa ' . $buttonOptions[static::BUTTON_ICON] . '"></i> ';
1558
        }
1559
1560
        return $this->generateButtonDropdownHtml($buttons, $title, $icon, $class, $parameters);
1561
    }
1562
1563
    /**
1564
     * @param array $buttons
1565
     * @param string $title
1566
     * @param string $icon
1567
     * @param string $class
1568
     * @param string $parameters
1569
     *
1570
     * @return string
1571
     */
1572
    protected function generateButtonDropdownHtml(array $buttons, $title, $icon, $class, $parameters)
1573
    {
1574
        $nestedButtons = [];
1575
1576
        foreach ($buttons as $button) {
1577
            if (is_string($button['url'])) {
1578
                $utilSanitizeService = new UtilSanitizeService();
1579
                $url = $utilSanitizeService->escapeHtml($button['url']);
1580
            } else {
1581
                /** @var \Spryker\Service\UtilText\Model\Url\Url $buttonUrl */
1582
                $buttonUrl = $button['url'];
1583
                $url = $buttonUrl->buildEscaped();
1584
            }
1585
1586
            $buttonParameters = '';
1587
            if (isset($button['options'])) {
1588
                $buttonParameters = $this->getButtonParameters($button['options']);
1589
            }
1590
1591
            $nestedButtons[] = [
1592
                'needDivider' => !empty($button['separated']),
1593
                'url' => $url,
1594
                'params' => $buttonParameters,
1595
                'title' => $button['title'],
1596
            ];
1597
        }
1598
1599
        return $this->getTwig()->render('button-dropdown.twig', [
1600
            'class' => $class,
1601
            'parameters' => $parameters,
1602
            'icon' => $icon,
1603
            'title' => $title,
1604
            'nestedButtons' => $nestedButtons,
1605
        ]);
1606
    }
1607
1608
    /**
1609
     * @param array $defaultOptions
1610
     * @param array<string, mixed> $options
1611
     *
1612
     * @return string
1613
     */
1614
    protected function getButtonClass(array $defaultOptions, array $options = [])
1615
    {
1616
        $class = '';
1617
1618
        if (isset($defaultOptions[static::BUTTON_CLASS])) {
1619
            $class .= ' ' . $defaultOptions[static::BUTTON_CLASS];
1620
        }
1621
        if (isset($options[static::BUTTON_CLASS])) {
1622
            $class .= ' ' . $options[static::BUTTON_CLASS];
1623
        }
1624
1625
        if (!$class) {
1626
            return static::BUTTON_DEFAULT_CLASS;
1627
        }
1628
1629
        return $class;
1630
    }
1631
1632
    /**
1633
     * @param array $buttonOptions
1634
     *
1635
     * @return string
1636
     */
1637
    protected function getButtonParameters(array $buttonOptions)
1638
    {
1639
        $parameters = '';
1640
        foreach ($buttonOptions as $argument => $value) {
1641
            if (in_array($argument, [static::BUTTON_CLASS, static::BUTTON_HREF, static::BUTTON_ICON])) {
1642
                continue;
1643
            }
1644
            $parameters .= sprintf(' %s=\'%s\'', $argument, $value);
1645
        }
1646
1647
        return $parameters;
1648
    }
1649
1650
    /**
1651
     * @param array $defaultOptions
1652
     * @param array<string, mixed> $options
1653
     *
1654
     * @return array
1655
     */
1656
    protected function generateButtonOptions(array $defaultOptions, array $options = [])
1657
    {
1658
        $buttonOptions = $defaultOptions;
1659
        if (is_array($options)) {
0 ignored issues
show
introduced by
The condition is_array($options) is always true.
Loading history...
1660
            $buttonOptions = array_merge($defaultOptions, $options);
1661
        }
1662
1663
        return $buttonOptions;
1664
    }
1665
1666
    /**
1667
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1668
     * @param array $searchColumns
1669
     * @param \Generated\Shared\Transfer\DataTablesColumnTransfer $column
1670
     *
1671
     * @return void
1672
     */
1673
    protected function addQueryCondition(ModelCriteria $query, array $searchColumns, DataTablesColumnTransfer $column)
1674
    {
1675
        $search = $column->getSearch();
1676
        if (preg_match('/created_at|updated_at/', $searchColumns[$column->getData()])) {
1677
            /** @var literal-string $where */
1678
            $where = sprintf(
1679
                '(%s >= %s AND %s <= %s)',
1680
                $searchColumns[$column->getData()],
1681
                Propel::getConnection()->quote($this->filterSearchValue($search[static::PARAMETER_VALUE]) . ' 00:00:00'),
1682
                $searchColumns[$column->getData()],
1683
                Propel::getConnection()->quote($this->filterSearchValue($search[static::PARAMETER_VALUE]) . ' 23:59:59'),
1684
            );
1685
            $query->where($where);
1686
1687
            return;
1688
        }
1689
1690
        $value = $this->filterSearchValue($search[static::PARAMETER_VALUE]);
1691
        if ($value === 'null') {
1692
            return;
1693
        }
1694
1695
        /** @var literal-string $where */
1696
        $where = sprintf(
1697
            '%s = %s',
1698
            $searchColumns[$column->getData()],
1699
            Propel::getConnection()->quote($value),
1700
        );
1701
        $query->where($where);
1702
    }
1703
1704
    /**
1705
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1706
     * @param array $searchColumns
1707
     *
1708
     * @return void
1709
     */
1710
    protected function addFilteringConditions(ModelCriteria $query, array $searchColumns)
1711
    {
1712
        foreach ($this->dataTablesTransfer->getColumns() as $column) {
1713
            $search = $column->getSearch();
1714
            if (empty($search[static::PARAMETER_VALUE])) {
1715
                continue;
1716
            }
1717
1718
            $this->addQueryCondition($query, $searchColumns, $column);
1719
        }
1720
    }
1721
1722
    /**
1723
     * @return \Spryker\Service\UtilNumber\UtilNumberServiceInterface|null
1724
     */
1725
    protected function getUtilNumberService(): ?UtilNumberServiceInterface
1726
    {
1727
        $container = $this->getApplicationContainer();
1728
        if (!$container->has(static::SERVICE_UTIL_NUMBER)) {
1729
            return null;
1730
        }
1731
1732
        return $this->getApplicationContainer()->get(static::SERVICE_UTIL_NUMBER);
1733
    }
1734
1735
    /**
1736
     * @return string|null
1737
     */
1738
    protected function getCurrentLocaleName(): ?string
1739
    {
1740
        $container = $this->getApplicationContainer();
1741
        if (!$container->has(static::SERVICE_LOCALE)) {
1742
            return null;
1743
        }
1744
1745
        return $container->get(static::SERVICE_LOCALE);
1746
    }
1747
1748
    /**
1749
     * @param int $value
1750
     *
1751
     * @return string
1752
     */
1753
    protected function formatInt(int $value): string
1754
    {
1755
        $utilNumberService = $this->getUtilNumberService();
1756
        if (!$utilNumberService) {
1757
            return (string)$value;
1758
        }
1759
1760
        $currentLocaleName = $this->getCurrentLocaleName();
1761
        if (!$currentLocaleName) {
1762
            return (string)$value;
1763
        }
1764
1765
        $numberFormatIntRequestTransfer = (new NumberFormatIntRequestTransfer())
1766
            ->setNumber($value)
1767
            ->setNumberFormatFilter(
1768
                (new NumberFormatFilterTransfer())->setLocale($currentLocaleName),
1769
            );
1770
1771
        return $utilNumberService->formatInt($numberFormatIntRequestTransfer);
1772
    }
1773
1774
    /**
1775
     * @param float $value
1776
     *
1777
     * @return string
1778
     */
1779
    protected function formatFloat(float $value): string
1780
    {
1781
        $utilNumberService = $this->getUtilNumberService();
1782
        if (!$utilNumberService) {
1783
            return (string)$value;
1784
        }
1785
1786
        $currentLocaleName = $this->getCurrentLocaleName();
1787
        if (!$currentLocaleName) {
1788
            return (string)$value;
1789
        }
1790
1791
        $numberFormatFloatRequestTransfer = (new NumberFormatFloatRequestTransfer())
1792
            ->setNumber($value)
1793
            ->setNumberFormatFilter(
1794
                (new NumberFormatFilterTransfer())->setLocale($currentLocaleName),
1795
            );
1796
1797
        return $utilNumberService->formatFloat($numberFormatFloatRequestTransfer);
1798
    }
1799
1800
    /**
1801
     * @param string $formClassName
1802
     * @param string $fieldName
1803
     * @param array<string, mixed> $options
1804
     * @param array<string, mixed> $data
1805
     *
1806
     * @return string
1807
     */
1808
    protected function generateFormField(string $formClassName, string $fieldName, array $options = [], array $data = []): string
1809
    {
1810
        $formView = $this->createForm($formClassName, null, $options, $data)->createView();
1811
        if (!$formView->offsetExists($fieldName)) {
1812
            return '';
1813
        }
1814
1815
        $options['field'] = $formView->offsetGet($fieldName);
1816
1817
        return $this->twig->render('form-field.twig', $options);
1818
    }
1819
1820
    /**
1821
     * @param \Spryker\Zed\Gui\Communication\Table\TableConfiguration $config
1822
     * @param string $driverName
1823
     * @param \Propel\Runtime\ActiveQuery\ModelCriteria $query
1824
     *
1825
     * @return string
1826
     */
1827
    protected function getSearchPattern(TableConfiguration $config, string $driverName, ModelCriteria $query): string
1828
    {
1829
        if ($this->isStrictSearch($query, $config) === true) {
1830
            return $this->getStrictSearchPatternByDriverName($driverName);
1831
        }
1832
1833
        return static::SEARCH_PATTERN_FOR_FUZZY_SEARCH;
1834
    }
1835
1836
    /**
1837
     * @param string $driverName
1838
     *
1839
     * @return string
1840
     */
1841
    protected function getStrictSearchPatternByDriverName(string $driverName): string
1842
    {
1843
        if ($driverName === static::DRIVER_NAME_PGSQL) {
1844
            return static::SEARCH_PATTERN_FOR_STRICT_SEARCH_POSTGRESQL;
1845
        }
1846
1847
        return static::SEARCH_PATTERN_FOR_STRICT_SEARCH_MYSQL;
1848
    }
1849
}
1850