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

contributte / datagrid / 8685602316

13 Mar 2024 12:24PM UTC coverage: 34.102%. Remained the same
8685602316

push

github

web-flow
[7.x] Next (#1060)

* PHP 8.0

* Translator: switch from ITranslator to Translator (#973)

* [7.x] Bootstrap 5 + PHP 8 + vanilla javascript (#1021)

* Bootstrap 5

* Bootstrap 5 (docs)

* Bootstrap 5

* form-control -> form-select

* Bump bootstrap-select for Bootstrap 5 support

* Removed `input-sm` from Bootstrap 3

See https://getbootstrap.com/docs/4.0/migration/#forms-1

* Bootstrap 5: When selectpicker, replace form-select classes with form-control and refresh it

* Hide `underline` also for `dropdown-item`. And merged into one CSS rule.

* Update the filterMultiSelect initialization

* Text-align: left -> start

Co-authored-by: Radim Vaculík <radim.vaculik@gmail.com>
Co-authored-by: Jaroslav Líbal <jaroslav.libal@neatous.cz>

* [7.x] phpstan-deprecation-rules (#1061)

* Fix sort

* Add method for setting custom Action href, v6.x (#853)

* [7.x] Nextras ORM 4 support, closes #984

* Fix ElasticsearchDataSource.php data source (#1041)

* Error: Typed property Ublaboo\DataGrid\InlineEdit\InlineEdit::$itemID must not be accessed before initialization

* composer: allow-plugins: php-http/discovery

* ItemDetailForm: $httpPost: Typed property must not be accessed...

* phpstan: revert  --memory-limit=4G

* NetteDatabaseSelectionHelper: Context -> Explorer, getSupplementalDriver -> getDriver

* Update README.md

* Templates: fix variables

* data-bs-toggle attribute for multiaction button (#1072)

* dependabot.yml (#1078)

* Allow nette/utils:4.0 (#1077)

* Add onColumnShow and onColumnHide event methods (#1076)

* Add onColumnShow and onColumnHide event methods

* Add native type array

* Removed duplicity

* Return value of BackedEnum when reading Doctrine entity property. (#1081)

* Return value of BackedEnum when reading array value (#1083)

* Added method to check if filter is on default values; Closes #1082 (#1084)

* ublaboo -> contributte (#1067) (#1075)

* Delete depe... (continued)

117 of 435 new or added lines in 54 files covered. (26.9%)

1455 existing lines in 67 files now uncovered.

1124 of 3296 relevant lines covered (34.1%)

0.34 hits per line

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

33.91
/src/Datagrid.php
1
<?php declare(strict_types = 1);
2

3
namespace Contributte\Datagrid;
4

5
use Contributte\Datagrid\AggregationFunction\TDatagridAggregationFunction;
6
use Contributte\Datagrid\Column\Action;
7
use Contributte\Datagrid\Column\ActionCallback;
8
use Contributte\Datagrid\Column\Column;
9
use Contributte\Datagrid\Column\ColumnDateTime;
10
use Contributte\Datagrid\Column\ColumnLink;
11
use Contributte\Datagrid\Column\ColumnNumber;
12
use Contributte\Datagrid\Column\ColumnStatus;
13
use Contributte\Datagrid\Column\ColumnText;
14
use Contributte\Datagrid\Column\ItemDetail;
15
use Contributte\Datagrid\Column\MultiAction;
16
use Contributte\Datagrid\Components\DatagridPaginator\DatagridPaginator;
17
use Contributte\Datagrid\DataSource\IDataSource;
18
use Contributte\Datagrid\Exception\DatagridColumnNotFoundException;
19
use Contributte\Datagrid\Exception\DatagridException;
20
use Contributte\Datagrid\Exception\DatagridFilterNotFoundException;
21
use Contributte\Datagrid\Exception\DatagridHasToBeAttachedToPresenterComponentException;
22
use Contributte\Datagrid\Export\Export;
23
use Contributte\Datagrid\Export\ExportCsv;
24
use Contributte\Datagrid\Filter\Filter;
25
use Contributte\Datagrid\Filter\FilterDate;
26
use Contributte\Datagrid\Filter\FilterDateRange;
27
use Contributte\Datagrid\Filter\FilterMultiSelect;
28
use Contributte\Datagrid\Filter\FilterRange;
29
use Contributte\Datagrid\Filter\FilterSelect;
30
use Contributte\Datagrid\Filter\FilterText;
31
use Contributte\Datagrid\Filter\IFilterDate;
32
use Contributte\Datagrid\Filter\SubmitButton;
33
use Contributte\Datagrid\GroupAction\GroupAction;
34
use Contributte\Datagrid\GroupAction\GroupActionCollection;
35
use Contributte\Datagrid\GroupAction\GroupButtonAction;
36
use Contributte\Datagrid\InlineEdit\InlineAdd;
37
use Contributte\Datagrid\InlineEdit\InlineEdit;
38
use Contributte\Datagrid\Localization\SimpleTranslator;
39
use Contributte\Datagrid\Toolbar\ToolbarButton;
40
use Contributte\Datagrid\Utils\ArraysHelper;
41
use Contributte\Datagrid\Utils\ItemDetailForm;
42
use Contributte\Datagrid\Utils\Sorting;
43
use DateTime;
44
use InvalidArgumentException;
45
use Nette\Application\ForbiddenRequestException;
46
use Nette\Application\IPresenter;
47
use Nette\Application\Request;
48
use Nette\Application\UI\Component;
49
use Nette\Application\UI\Control;
50
use Nette\Application\UI\Form;
51
use Nette\Application\UI\Link;
52
use Nette\Application\UI\Presenter;
53
use Nette\Bridges\ApplicationLatte\Template;
54
use Nette\ComponentModel\IContainer;
55
use Nette\Forms\Container;
56
use Nette\Forms\Control as FormControl;
57
use Nette\Forms\Controls\SubmitButton as FormsSubmitButton;
58
use Nette\Forms\Form as NetteForm;
59
use Nette\Http\SessionSection;
60
use Nette\Localization\Translator;
61
use Nette\Utils\ArrayHash;
62
use UnexpectedValueException;
63

64
/**
65
 * @method onRedraw()
66
 * @method onRender(Datagrid $dataGrid)
67
 * @method onColumnAdd(string $key, Column $column)
68
 * @method onColumnShow(string $key)
69
 * @method onColumnHide(string $key)
70
 * @method onShowDefaultColumns()
71
 * @method onShowAllColumns()
72
 * @method onExport(Datagrid $dataGrid)
73
 * @method onFiltersAssembled(Filter[] $filters)
74
 */
75
class Datagrid extends Control
76
{
77

78
        use TDatagridAggregationFunction;
79

80
        private const HIDEABLE_COLUMNS_SESSION_KEYS = [
81
                '_grid_hidden_columns',
82
                '_grid_hidden_columns_manipulated',
83
        ];
84

85
        public static string $iconPrefix = 'fa fa-';
86

87
        public static string $btnSecondaryClass = 'btn-default btn-secondary';
88

89
        /**
90
         * Default form method
91
         */
92
        public static string $formMethod = 'post';
93

94
        /** @var array|callable[] */
95
        public array $onRedraw = [];
96

97
        /** @var array|callable[] */
98
        public array $onRender = [];
99

100
        /** @var array|callable[] */
101
        public array $onExport = [];
102

103
        /** @var array|callable[] */
104
        public array $onColumnAdd = [];
105

106
        /** @var array|callable[] */
107
        public array $onColumnShow = [];
108

109
        /** @var array|callable[] */
110
        public array $onColumnHide = [];
111

112
        /** @var array|callable[] */
113
        public array $onShowDefaultColumns = [];
114

115
        /** @var array|callable[] */
116
        public array $onShowAllColumns = [];
117

118
        /** @var array|callable[] */
119
        public array $onFiltersAssembled = [];
120

121
        /**
122
         * When set to TRUE, datagrid throws an exception
123
         *  when tring to get related entity within join and entity does not exist
124
         */
125
        public bool $strictEntityProperty = false;
126

127
        /**
128
         * When set to TRUE, datagrid throws an exception
129
         *  when tring to set filter value, that does not exist (select, multiselect, etc)
130
         */
131
        public bool $strictSessionFilterValues = true;
132

133
        /** @persistent */
134
        public int $page = 1;
135

136
        /** @persistent */
137
        public string|int|null $perPage = null;
138

139
        /** @persistent */
140
        public array $sort = [];
141

142
        public array $defaultSort = [];
143

144
        public array $defaultFilter = [];
145

146
        public bool $defaultFilterUseOnReset = true;
147

148
        public bool $defaultSortUseOnReset = true;
149

150
        /** @persistent */
151
        public array $filter = [];
152

153
        /** @var callable|null */
154
        protected $sortCallback = null;
155

156
        protected bool $useHappyComponents = true;
157

158
        /** @var callable */
159
        protected $rowCallback;
160

161
        protected array $itemsPerPageList = [10, 20, 50, 'all'];
162

163
        protected ?int $defaultPerPage = null;
164

165
        protected ?string $templateFile = null;
166

167
        /** @var array<Column> */
168
        protected array $columns = [];
169

170
        /** @var array<Action>|array<MultiAction> */
171
        protected array $actions = [];
172

173
        protected ?GroupActionCollection $groupActionCollection = null;
174

175
        /** @var array<Filter> */
176
        protected array $filters = [];
177

178
        /** @var array<Export> */
179
        protected array $exports = [];
180

181
        /** @var array<ToolbarButton> */
182
        protected array $toolbarButtons = [];
183

184
        protected ?DataModel $dataModel = null;
185

186
        protected string $primaryKey = 'id';
187

188
        protected bool $doPaginate = true;
189

190
        protected bool $csvExport = true;
191

192
        protected bool $csvExportFiltered = true;
193

194
        protected bool $sortable = false;
195

196
        protected bool $multiSort = false;
197

198
        protected string $sortableHandler = 'sort!';
199

200
        protected ?string $originalTemplate = null;
201

202
        protected array $redrawItem = [];
203

204
        protected ?Translator $translator = null;
205

206
        protected bool $forceFilterActive = false;
207

208
        /** @var callable|null */
209
        protected $treeViewChildrenCallback = null;
210

211
        /** @var callable|null */
212
        protected $treeViewHasChildrenCallback = null;
213

214
        protected ?string $treeViewHasChildrenColumn = null;
215

216
        protected bool $outerFilterRendering = false;
217

218
        protected int $outerFilterColumnsCount = 2;
219

220
        protected bool $collapsibleOuterFilters = true;
221

222
        /** @var array|string[] */
223
        protected array $columnsExportOrder = [];
224

225
        protected bool $rememberState = true;
226

227
        protected bool $rememberHideableColumnsState = true;
228

229
        protected bool $refreshURL = true;
230

231
        protected SessionSection $gridSession;
232

233
        protected ?ItemDetail $itemsDetail = null;
234

235
        protected array $rowConditions = [
236
                'group_action' => false,
237
                'action' => [],
238
        ];
239

240
        protected array $columnCallbacks = [];
241

242
        protected bool $canHideColumns = false;
243

244
        protected array $columnsVisibility = [];
245

246
        protected ?InlineEdit $inlineEdit = null;
247

248
        protected ?InlineAdd $inlineAdd = null;
249

250
        protected bool $snippetsSet = false;
251

252
        protected bool $someColumnDefaultHide = false;
253

254
        protected ?ColumnsSummary $columnsSummary = null;
255

256
        protected bool $autoSubmit = true;
257

258
        protected ?SubmitButton $filterSubmitButton = null;
259

260
        protected bool $hasColumnReset = true;
261

262
        protected bool $showSelectedRowsCount = true;
263

264
        private ?string $customPaginatorTemplate = null;
265

266
        private ?string $componentFullName = null;
267

268
        public function __construct(?IContainer $parent = null, ?string $name = null)
1✔
269
        {
270
                if ($parent !== null) {
1✔
271
                        $parent->addComponent($this, $name);
1✔
272
                }
273

274
                /**
275
                 * Try to find previous filters, pagination, perPage and other values in session
276
                 */
277
                $this->onRender[] = [$this, 'findSessionValues'];
1✔
278
                $this->onExport[] = [$this, 'findSessionValues'];
1✔
279

280
                /**
281
                 * Find default filter values
282
                 */
283
                $this->onRender[] = [$this, 'findDefaultFilter'];
1✔
284
                $this->onExport[] = [$this, 'findDefaultFilter'];
1✔
285

286
                /**
287
                 * Find default sort
288
                 */
289
                $this->onRender[] = [$this, 'findDefaultSort'];
1✔
290
                $this->onExport[] = [$this, 'findDefaultSort'];
1✔
291

292
                /**
293
                 * Find default items per page
294
                 */
295
                $this->onRender[] = [$this, 'findDefaultPerPage'];
1✔
296

297
                /**
298
                 * Notify about that json js extension
299
                 */
300
                $this->onFiltersAssembled[] = [$this, 'sendNonEmptyFiltersInPayload'];
1✔
301

302
                $this->monitor(
1✔
303
                        Presenter::class,
1✔
304
                        function (Presenter $presenter): void {
1✔
305
                                /**
306
                                 * Get session
307
                                 */
308
                                if ($this->rememberState || $this->canHideColumns()) {
1✔
309
                                        $this->gridSession = $presenter->getSession($this->getSessionSectionName());
1✔
310
                                }
311

312
                                $this->componentFullName = $this->lookupPath();
1✔
313
                        }
1✔
314
                );
315
        }
1✔
316

317
        /********************************************************************************
318
         *                                  RENDERING *
319
         ********************************************************************************/
320
        public function render(): void
321
        {
322
                /**
323
                 * Check whether datagrid has set some columns, initiated data source, etc
324
                 */
UNCOV
325
                if (!($this->dataModel instanceof DataModel)) {
×
NEW
326
                        throw new DatagridException('You have to set a data source first.');
×
327
                }
328

UNCOV
329
                if ($this->columns === []) {
×
NEW
330
                        throw new DatagridException('You have to add at least one column.');
×
331
                }
332

UNCOV
333
                $template = $this->getTemplate();
×
334

UNCOV
335
                if (!$template instanceof Template) {
×
NEW
336
                        throw new UnexpectedValueException();
×
337
                }
338

UNCOV
339
                $template->setTranslator($this->getTranslator());
×
340

341
                /**
342
                 * Invoke possible events
343
                 */
UNCOV
344
                $this->onRender($this);
×
345

346
                /**
347
                 * Prepare data for rendering (datagrid may render just one item)
348
                 */
UNCOV
349
                $rows = [];
×
350

NEW
351
                $items = $this->redrawItem !== [] ? $this->dataModel->filterRow($this->redrawItem) : $this->dataModel->filterData(
×
NEW
352
                        $this->getPaginator(),
×
NEW
353
                        $this->createSorting($this->sort, $this->sortCallback),
×
NEW
354
                        $this->assembleFilters()
×
355
                );
356

357
                $hasGroupActionOnRows = false;
×
358

359
                foreach ($items as $item) {
×
UNCOV
360
                        $rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
×
361

UNCOV
362
                        if (!$hasGroupActionOnRows && $row->hasGroupAction()) {
×
UNCOV
363
                                $hasGroupActionOnRows = true;
×
364
                        }
365

UNCOV
366
                        if ($this->rowCallback !== null) {
×
367
                                ($this->rowCallback)($item, $row->getControl());
×
368
                        }
369

370
                        /**
371
                         * Walkaround for item snippet - snippet is the <tr> element and its class has to be also updated
372
                         */
UNCOV
373
                        if ($this->redrawItem !== []) {
×
UNCOV
374
                                $this->getPresenterInstance()->payload->_datagrid_redrawItem_class = $row->getControlClass();
×
UNCOV
375
                                $this->getPresenterInstance()->payload->_datagrid_redrawItem_id = $row->getId();
×
376
                        }
377
                }
378

UNCOV
379
                if ($hasGroupActionOnRows) {
×
380
                        $hasGroupActionOnRows = $this->hasGroupActions();
×
381
                }
382

UNCOV
383
                if ($this->isTreeView()) {
×
384
                        $template->add('treeViewHasChildrenColumn', $this->treeViewHasChildrenColumn);
×
385
                }
386

UNCOV
387
                $template->rows = $rows;
×
388

UNCOV
389
                $template->columns = $this->getColumns();
×
UNCOV
390
                $template->actions = $this->actions;
×
UNCOV
391
                $template->exports = $this->exports;
×
UNCOV
392
                $template->filters = $this->filters;
×
UNCOV
393
                $template->toolbarButtons = $this->toolbarButtons;
×
UNCOV
394
                $template->aggregationFunctions = $this->getAggregationFunctions();
×
UNCOV
395
                $template->multipleAggregationFunction = $this->getMultipleAggregationFunction();
×
396

UNCOV
397
                $template->filter_active = $this->isFilterActive();
×
UNCOV
398
                $template->originalTemplate = $this->getOriginalTemplateFile();
×
UNCOV
399
                $template->iconPrefix = static::$iconPrefix;
×
400
                $template->btnSecondaryClass = static::$btnSecondaryClass;
×
401
                $template->itemsDetail = $this->itemsDetail;
×
UNCOV
402
                $template->columnsVisibility = $this->getColumnsVisibility();
×
UNCOV
403
                $template->columnsSummary = $this->columnsSummary;
×
404

UNCOV
405
                $template->inlineEdit = $this->inlineEdit;
×
406
                $template->inlineAdd = $this->inlineAdd;
×
407

UNCOV
408
                $template->hasGroupActions = $this->hasGroupActions();
×
UNCOV
409
                $template->hasGroupActionOnRows = $hasGroupActionOnRows;
×
410

411
                /**
412
                 * Walkaround for Latte (does not know $form in snippet in {form} etc)
413
                 */
UNCOV
414
                $template->filter = $this['filter'];
×
415

416
                /**
417
                 * Set template file and render it
418
                 */
419
                $template->setFile($this->getTemplateFile());
×
UNCOV
420
                $template->render();
×
421
        }
422

423

424
        /********************************************************************************
425
         *                                 ROW CALLBACK *
426
         ********************************************************************************/
427

428
        /**
429
         * Each row can be modified with user defined callback
430
         *
431
         * @return static
432
         */
433
        public function setRowCallback(callable $callback): self
434
        {
UNCOV
435
                $this->rowCallback = $callback;
×
436

UNCOV
437
                return $this;
×
438
        }
439

440

441
        /********************************************************************************
442
         *                                 DATA SOURCE *
443
         ********************************************************************************/
444

445
        /**
446
         * @return static
447
         */
448
        public function setPrimaryKey(string $primaryKey): self
449
        {
UNCOV
450
                if ($this->dataModel instanceof DataModel) {
×
NEW
451
                        throw new DatagridException('Please set datagrid primary key before setting datasource.');
×
452
                }
453

UNCOV
454
                $this->primaryKey = $primaryKey;
×
455

UNCOV
456
                return $this;
×
457
        }
458

459
        /**
460
         * @return static
461
         * @throws InvalidArgumentException
462
         */
463
        public function setDataSource(mixed $source): self
1✔
464
        {
465
                $this->dataModel = new DataModel($source, $this->primaryKey);
1✔
466

467
                $this->dataModel->onBeforeFilter[] = [$this, 'beforeDataModelFilter'];
1✔
468
                $this->dataModel->onAfterFilter[] = [$this, 'afterDataModelFilter'];
1✔
469
                $this->dataModel->onAfterPaginated[] = [$this, 'afterDataModelPaginated'];
1✔
470

471
                return $this;
1✔
472
        }
473

474
        public function getDataSource(): IDataSource|array|null
475
        {
476
                return isset($this->dataModel)
×
UNCOV
477
                        ? $this->dataModel->getDataSource()
×
UNCOV
478
                        : null;
×
479
        }
480

481

482
        /********************************************************************************
483
         *                                  TEMPLATING *
484
         ********************************************************************************/
485

486
        /**
487
         * @return static
488
         */
489
        public function setTemplateFile(string $templateFile): self
490
        {
UNCOV
491
                $this->templateFile = $templateFile;
×
492

UNCOV
493
                return $this;
×
494
        }
495

496
        public function getTemplateFile(): string
497
        {
498
                return $this->templateFile ?? $this->getOriginalTemplateFile();
×
499
        }
500

501
        public function getOriginalTemplateFile(): string
502
        {
UNCOV
503
                return __DIR__ . '/templates/datagrid.latte';
×
504
        }
505

506
        /**
507
         * @return static
508
         */
509
        public function useHappyComponents(bool $useHappyComponents): self
510
        {
UNCOV
511
                $this->useHappyComponents = $useHappyComponents;
×
512

UNCOV
513
                return $this;
×
514
        }
515

516
        public function shouldUseHappyComponents(): bool
517
        {
UNCOV
518
                return $this->useHappyComponents;
×
519
        }
520

521

522
        /********************************************************************************
523
         *                                   SORTING *
524
         ********************************************************************************/
525

526
 /**
527
  * @return static
528
  */
529
        public function setDefaultSort(string|array $sort, bool $useOnReset = true): self
530
        {
UNCOV
531
                $sort = is_string($sort)
×
UNCOV
532
                        ? [$sort => 'ASC']
×
UNCOV
533
                        : $sort;
×
534

UNCOV
535
                $this->defaultSort = $sort;
×
UNCOV
536
                $this->defaultSortUseOnReset = $useOnReset;
×
537

UNCOV
538
                return $this;
×
539
        }
540

541
        /**
542
         * Return default sort for column, if specified
543
         */
544
        public function getColumnDefaultSort(string $columnKey): ?string
545
        {
UNCOV
546
                if (isset($this->defaultSort[$columnKey])) {
×
UNCOV
547
                        return $this->defaultSort[$columnKey];
×
548
                }
549

UNCOV
550
                return null;
×
551
        }
552

553
        /**
554
         * User may set default sorting, apply it
555
         */
556
        public function findDefaultSort(): void
557
        {
558
                if ($this->sort !== []) {
1✔
UNCOV
559
                        return;
×
560
                }
561

562
                if ((bool) $this->getSessionData('_grid_has_sorted')) {
1✔
UNCOV
563
                        return;
×
564
                }
565

566
                if ($this->defaultSort !== []) {
1✔
UNCOV
567
                        $this->sort = $this->defaultSort;
×
568
                }
569

570
                $this->saveSessionData('_grid_sort', $this->sort);
1✔
571
        }
1✔
572

573
        /**
574
         * @return static
575
         * @throws DatagridException
576
         */
577
        public function setSortable(bool $sortable = true): self
578
        {
UNCOV
579
                if ($this->getItemsDetail() !== null) {
×
NEW
580
                        throw new DatagridException('You can not use both sortable datagrid and items detail.');
×
581
                }
582

UNCOV
583
                $this->sortable = $sortable;
×
584

UNCOV
585
                return $this;
×
586
        }
587

588
        public function isSortable(): bool
589
        {
UNCOV
590
                return $this->sortable;
×
591
        }
592

593
        /**
594
         * @return static
595
         */
596
        public function setMultiSortEnabled(bool $multiSort = true): self
597
        {
UNCOV
598
                $this->multiSort = $multiSort;
×
599

UNCOV
600
                return $this;
×
601
        }
602

603
        public function isMultiSortEnabled(): bool
604
        {
UNCOV
605
                return $this->multiSort;
×
606
        }
607

608
        /**
609
         * @return static
610
         */
611
        public function setSortableHandler(string $handler = 'sort!'): self
612
        {
UNCOV
613
                $this->sortableHandler = $handler;
×
614

UNCOV
615
                return $this;
×
616
        }
617

618
        public function getSortableHandler(): string
619
        {
UNCOV
620
                return $this->sortableHandler;
×
621
        }
622

623
        public function getSortNext(Column $column): array
624
        {
UNCOV
625
                $sort = $column->getSortNext();
×
626

UNCOV
627
                if ($this->isMultiSortEnabled()) {
×
628
                        $sort = array_merge($this->sort, $sort);
×
629
                }
630

UNCOV
631
                return array_filter($sort);
×
632
        }
633

634
        /********************************************************************************
635
         *                                  TREE VIEW *
636
         ********************************************************************************/
637
        public function isTreeView(): bool
638
        {
UNCOV
639
                return $this->treeViewChildrenCallback !== null;
×
640
        }
641

642
        /**
643
         * @return static
644
         */
645
        public function setTreeView(
646
                callable $getChildrenCallback,
647
                string|callable $treeViewHasChildrenColumn = 'has_children'
648
        ): self
649
        {
UNCOV
650
                if (is_callable($treeViewHasChildrenColumn)) {
×
UNCOV
651
                        $this->treeViewHasChildrenCallback = $treeViewHasChildrenColumn;
×
652
                        $treeViewHasChildrenColumn = null;
×
653
                }
654

UNCOV
655
                $this->treeViewChildrenCallback = $getChildrenCallback;
×
UNCOV
656
                $this->treeViewHasChildrenColumn = $treeViewHasChildrenColumn;
×
657

658
                /**
659
                 * Torn off pagination
660
                 */
UNCOV
661
                $this->setPagination(false);
×
662

663
                /**
664
                 * Set tree view template file
665
                 */
UNCOV
666
                if ($this->templateFile === null) {
×
UNCOV
667
                        $this->setTemplateFile(__DIR__ . '/templates/datagrid_tree.latte');
×
668
                }
669

UNCOV
670
                return $this;
×
671
        }
672

673
        public function hasTreeViewChildrenCallback(): bool
674
        {
UNCOV
675
                return is_callable($this->treeViewHasChildrenCallback);
×
676
        }
677

678
        public function treeViewChildrenCallback(mixed $item): bool
679
        {
680
                if ($this->treeViewHasChildrenCallback === null) {
×
NEW
681
                        throw new UnexpectedValueException();
×
682
                }
683

684
                return (bool) call_user_func($this->treeViewHasChildrenCallback, $item);
×
685
        }
686

687
        /********************************************************************************
688
         *                                    COLUMNS *
689
         ********************************************************************************/
690
        public function addColumnText(
1✔
691
                string $key,
692
                string $name,
693
                ?string $column = null
694
        ): ColumnText
695
        {
696
                $column ??= $key;
1✔
697

698
                $columnText = new ColumnText($this, $key, $column, $name);
1✔
699
                $this->addColumn($key, $columnText);
1✔
700

701
                return $columnText;
1✔
702
        }
703

704
        public function addColumnLink(
1✔
705
                string $key,
706
                string $name,
707
                ?string $href = null,
708
                ?string $column = null,
709
                ?array $params = null
710
        ): ColumnLink
711
        {
712
                $column ??= $key;
1✔
713
                $href ??= $key;
1✔
714

715
                if ($params === null) {
1✔
716
                        $params = [$this->primaryKey];
1✔
717
                }
718

719
                $columnLink = new ColumnLink($this, $key, $column, $name, $href, $params);
1✔
720
                $this->addColumn($key, $columnLink);
1✔
721

722
                return $columnLink;
1✔
723
        }
724

725
        public function addColumnNumber(
1✔
726
                string $key,
727
                string $name,
728
                ?string $column = null
729
        ): ColumnNumber
730
        {
731
                $column ??= $key;
1✔
732

733
                $columnNumber = new ColumnNumber($this, $key, $column, $name);
1✔
734
                $this->addColumn($key, $columnNumber);
1✔
735

736
                return $columnNumber;
1✔
737
        }
738

739
        public function addColumnDateTime(
1✔
740
                string $key,
741
                string $name,
742
                ?string $column = null
743
        ): ColumnDateTime
744
        {
745
                $column ??= $key;
1✔
746

747
                $columnDateTime = new ColumnDateTime($this, $key, $column, $name);
1✔
748
                $this->addColumn($key, $columnDateTime);
1✔
749

750
                return $columnDateTime;
1✔
751
        }
752

753
        public function addColumnStatus(
1✔
754
                string $key,
755
                string $name,
756
                ?string $column = null
757
        ): ColumnStatus
758
        {
759
                $column ??= $key;
1✔
760

761
                $columnStatus = new ColumnStatus($this, $key, $column, $name);
1✔
762
                $this->addColumn($key, $columnStatus);
1✔
763

764
                return $columnStatus;
1✔
765
        }
766

767
        /**
768
         * @throws DatagridColumnNotFoundException
769
         */
770
        public function getColumn(string $key): Column
1✔
771
        {
772
                if (!isset($this->columns[$key])) {
1✔
773
                        throw new DatagridColumnNotFoundException(
1✔
774
                                sprintf('There is no column at key [%s] defined.', $key)
1✔
775
                        );
776
                }
777

778
                return $this->columns[$key];
1✔
779
        }
780

781
        /**
782
         * @return static
783
         */
784
        public function removeColumn(string $key): self
1✔
785
        {
786
                unset($this->columnsVisibility[$key], $this->columns[$key]);
1✔
787

788
                return $this;
1✔
789
        }
790

791
        /********************************************************************************
792
         *                                    ACTIONS *
793
         ********************************************************************************/
794
        public function addAction(
1✔
795
                string $key,
796
                string $name,
797
                ?string $href = null,
798
                ?array $params = null
799
        ): Action
800
        {
801
                $this->addActionCheck($key);
1✔
802

803
                $href ??= $key;
1✔
804

805
                if ($params === null) {
1✔
806
                        $params = [$this->primaryKey];
1✔
807
                }
808

809
                return $this->actions[$key] = new Action($this, $key, $href, $name, $params);
1✔
810
        }
811

812
        public function addActionCallback(
813
                string $key,
814
                string $name,
815
                ?callable $callback = null
816
        ): ActionCallback
817
        {
UNCOV
818
                $this->addActionCheck($key);
×
819

UNCOV
820
                $params = ['__id' => $this->primaryKey];
×
821

UNCOV
822
                $this->actions[$key] = $action = new ActionCallback($this, $key, $key, $name, $params);
×
823

UNCOV
824
                if ($callback !== null) {
×
UNCOV
825
                        $action->onClick[] = $callback;
×
826
                }
827

UNCOV
828
                return $action;
×
829
        }
830

831
        public function addMultiAction(string $key, string $name): MultiAction
832
        {
UNCOV
833
                $this->addActionCheck($key);
×
834

UNCOV
835
                $action = new MultiAction($this, $key, $name);
×
836

UNCOV
837
                $this->actions[$key] = $action;
×
838

UNCOV
839
                return $action;
×
840
        }
841

842
        /**
843
         * @throws DatagridException
844
         */
845
        public function getAction(string $key): Action|MultiAction
846
        {
UNCOV
847
                if (!isset($this->actions[$key])) {
×
NEW
848
                        throw new DatagridException(sprintf('There is no action at key [%s] defined.', $key));
×
849
                }
850

851
                return $this->actions[$key];
×
852
        }
853

854
        /**
855
         * @return static
856
         */
857
        public function removeAction(string $key): self
858
        {
UNCOV
859
                unset($this->actions[$key]);
×
860

UNCOV
861
                return $this;
×
862
        }
863

864
        public function addFilterText(
1✔
865
                string $key,
866
                string $name,
867
                array|string|null $columns = null
868
        ): FilterText
869
        {
870
                $columns = $columns === null ? [$key] : (is_string($columns) ? [$columns] : $columns);
1✔
871

872
                $this->addFilterCheck($key);
1✔
873

874
                return $this->filters[$key] = new FilterText($this, $key, $name, $columns);
1✔
875
        }
876

877
        public function addFilterSelect(
878
                string $key,
879
                string $name,
880
                array $options,
881
                ?string $column = null
882
        ): FilterSelect
883
        {
NEW
884
                $column ??= $key;
×
885

886
                $this->addFilterCheck($key);
×
887

UNCOV
888
                return $this->filters[$key] = new FilterSelect($this, $key, $name, $options, $column);
×
889
        }
890

891
        public function addFilterMultiSelect(
892
                string $key,
893
                string $name,
894
                array $options,
895
                ?string $column = null
896
        ): FilterMultiSelect
897
        {
NEW
898
                $column ??= $key;
×
899

UNCOV
900
                $this->addFilterCheck($key);
×
901

UNCOV
902
                return $this->filters[$key] = new FilterMultiSelect($this, $key, $name, $options, $column);
×
903
        }
904

905
        public function addFilterDate(string $key, string $name, ?string $column = null): FilterDate
906
        {
NEW
907
                $column ??= $key;
×
908

UNCOV
909
                $this->addFilterCheck($key);
×
910

UNCOV
911
                return $this->filters[$key] = new FilterDate($this, $key, $name, $column);
×
912
        }
913

914
        public function addFilterRange(
915
                string $key,
916
                string $name,
917
                ?string $column = null,
918
                string $nameSecond = '-'
919
        ): FilterRange
920
        {
NEW
921
                $column ??= $key;
×
922

UNCOV
923
                $this->addFilterCheck($key);
×
924

UNCOV
925
                return $this->filters[$key] = new FilterRange($this, $key, $name, $column, $nameSecond);
×
926
        }
927

928
        /**
929
         * @throws DatagridException
930
         */
931
        public function addFilterDateRange(
932
                string $key,
933
                string $name,
934
                ?string $column = null,
935
                string $nameSecond = '-'
936
        ): FilterDateRange
937
        {
NEW
938
                $column ??= $key;
×
939

UNCOV
940
                $this->addFilterCheck($key);
×
941

UNCOV
942
                return $this->filters[$key] = new FilterDateRange($this, $key, $name, $column, $nameSecond);
×
943
        }
944

945
        /**
946
         * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
947
         * Fill array of Column\Column[] with values from $this->sort persistent parameter
948
         *
949
         * @return array<Filter>
950
         */
951
        public function assembleFilters(): array
952
        {
953
                foreach ($this->filter as $key => $value) {
1✔
UNCOV
954
                        if (!isset($this->filters[$key])) {
×
UNCOV
955
                                $this->deleteSessionData($key);
×
956

UNCOV
957
                                continue;
×
958
                        }
959

NEW
960
                        if (is_iterable($value)) {
×
961
                                if (!ArraysHelper::testEmpty($value)) {
×
UNCOV
962
                                        $this->filters[$key]->setValue($value);
×
963
                                }
964
                        } else {
UNCOV
965
                                if ($value !== '' && $value !== null) {
×
UNCOV
966
                                        $this->filters[$key]->setValue($value);
×
967
                                }
968
                        }
969
                }
970

971
                foreach ($this->columns as $key => $column) {
1✔
UNCOV
972
                        if (isset($this->sort[$key])) {
×
UNCOV
973
                                $column->setSort($this->sort[$key]);
×
974
                        }
975
                }
976

977
                $this->onFiltersAssembled($this->filters);
1✔
978

979
                return $this->filters;
1✔
980
        }
981

982
        /**
983
         * @return static
984
         */
985
        public function removeFilter(string $key): self
986
        {
UNCOV
987
                unset($this->filters[$key]);
×
988

UNCOV
989
                return $this;
×
990
        }
991

992
        public function getFilter(string $key): Filter
1✔
993
        {
994
                if (!isset($this->filters[$key])) {
1✔
NEW
995
                        throw new DatagridException(sprintf('Filter [%s] is not defined', $key));
×
996
                }
997

998
                return $this->filters[$key];
1✔
999
        }
1000

1001
        /**
1002
         * @return static
1003
         */
1004
        public function setStrictSessionFilterValues(bool $strictSessionFilterValues = true): self
1005
        {
UNCOV
1006
                $this->strictSessionFilterValues = $strictSessionFilterValues;
×
1007

1008
                return $this;
×
1009
        }
1010

1011
        /********************************************************************************
1012
         *                                  FILTERING *
1013
         ********************************************************************************/
1014
        public function isFilterActive(): bool
1015
        {
1016
                $is_filter = ArraysHelper::testTruthy($this->filter);
×
1017

1018
                return $is_filter || $this->forceFilterActive;
×
1019
        }
1020

1021
        public function isFilterDefault(): bool
1022
        {
1023
                return $this->filter === $this->defaultFilter;
1✔
1024
        }
1025

1026
        /**
1027
         * Tell that filter is active from whatever reasons
1028
         *
1029
         * @return static
1030
         */
1031
        public function setFilterActive(): self
1032
        {
UNCOV
1033
                $this->forceFilterActive = true;
×
1034

UNCOV
1035
                return $this;
×
1036
        }
1037

1038
        /**
1039
         * Set filter values (force - overwrite user data)
1040
         *
1041
         * @return static
1042
         */
1043
        public function setFilter(array $filter): self
1✔
1044
        {
1045
                $this->filter = $filter;
1✔
1046

1047
                $this->saveSessionData('_grid_has_filtered', 1);
1✔
1048

1049
                return $this;
1✔
1050
        }
1051

1052
        /**
1053
         * If we want to sent some initial filter
1054
         *
1055
         * @return static
1056
         * @throws DatagridException
1057
         */
1058
        public function setDefaultFilter(array $defaultFilter, bool $useOnReset = true): self
1✔
1059
        {
1060
                foreach ($defaultFilter as $key => $value) {
1✔
1061
                        /** @var Filter|null $filter */
1062
                        $filter = $this->getFilter($key);
1✔
1063

1064
                        if ($filter === null) {
1✔
NEW
1065
                                throw new DatagridException(
×
1066
                                        sprintf('Can not set default value to nonexisting filter [%s]', $key)
×
1067
                                );
1068
                        }
1069

1070
                        if ($filter instanceof FilterMultiSelect && !is_array($value)) {
1✔
NEW
1071
                                throw new DatagridException(
×
1072
                                        sprintf('Default value of filter [%s] - MultiSelect has to be an array', $key)
×
1073
                                );
1074
                        }
1075

1076
                        if ($filter instanceof FilterRange || $filter instanceof FilterDateRange) {
1✔
1077
                                if (!is_array($value)) {
×
NEW
1078
                                        throw new DatagridException(
×
1079
                                                sprintf('Default value of filter [%s] - Range/DateRange has to be an array [from/to => ...]', $key)
×
1080
                                        );
1081
                                }
1082

UNCOV
1083
                                $temp = $value;
×
UNCOV
1084
                                unset($temp['from'], $temp['to']);
×
1085

1086
                                if (count($temp) > 0) {
×
NEW
1087
                                        throw new DatagridException(
×
1088
                                                sprintf(
×
UNCOV
1089
                                                        'Default value of filter [%s] - Range/DateRange can contain only [from/to => ...] values',
×
1090
                                                        $key
1091
                                                )
1092
                                        );
1093
                                }
1094
                        }
1095
                }
1096

1097
                $this->defaultFilter = $defaultFilter;
1✔
1098
                $this->defaultFilterUseOnReset = $useOnReset;
1✔
1099

1100
                return $this;
1✔
1101
        }
1102

1103
        public function findDefaultFilter(): void
1104
        {
1105
                if ($this->filter !== []) {
1✔
1106
                        return;
×
1107
                }
1108

1109
                if ((bool) $this->getSessionData('_grid_has_filtered')) {
1✔
1110
                        return;
×
1111
                }
1112

1113
                if ($this->defaultFilter !== []) {
1✔
UNCOV
1114
                        $this->filter = $this->defaultFilter;
×
1115
                }
1116

1117
                foreach ($this->filter as $key => $value) {
1✔
1118
                        $this->saveSessionData($key, $value);
×
1119
                }
1120
        }
1✔
1121

1122
        public function createComponentFilter(): Form
1123
        {
1124
                $form = new Form($this, 'filter');
1✔
1125

1126
                $form->setMethod(static::$formMethod);
1✔
1127

1128
                $form->setTranslator($this->getTranslator());
1✔
1129

1130
                /**
1131
                 * InlineEdit part
1132
                 */
1133
                $inline_edit_container = $form->addContainer('inline_edit');
1✔
1134

1135
                if ($this->inlineEdit instanceof InlineEdit) {
1✔
NEW
1136
                        $inline_edit_container->addSubmit('submit', 'contributte_datagrid.save')
×
UNCOV
1137
                                ->setValidationScope([$inline_edit_container]);
×
NEW
1138
                        $inline_edit_container->addSubmit('cancel', 'contributte_datagrid.cancel')
×
UNCOV
1139
                                ->setValidationScope(null);
×
1140

1141
                        $this->inlineEdit->onControlAdd($inline_edit_container);
×
UNCOV
1142
                        $this->inlineEdit->onControlAfterAdd($inline_edit_container);
×
1143
                }
1144

1145
                /**
1146
                 * InlineAdd part
1147
                 */
1148
                $inlineAddContainer = $form->addContainer('inline_add');
1✔
1149

1150
                if ($this->inlineAdd instanceof InlineAdd) {
1✔
NEW
1151
                        $inlineAddContainer->addSubmit('submit', 'contributte_datagrid.save')
×
UNCOV
1152
                                ->setValidationScope([$inlineAddContainer]);
×
NEW
1153
                        $inlineAddContainer->addSubmit('cancel', 'contributte_datagrid.cancel')
×
1154
                                ->setValidationScope(null)
×
NEW
1155
                                ->setHtmlAttribute('data-datagrid-cancel-inline-add', true);
×
1156

UNCOV
1157
                        $this->inlineAdd->onControlAdd($inlineAddContainer);
×
1158
                        $this->inlineAdd->onControlAfterAdd($inlineAddContainer);
×
1159
                }
1160

1161
                /**
1162
                 * ItemDetail form part
1163
                 */
1164
                $itemsDetailForm = $this->getItemDetailForm();
1✔
1165

1166
                if ($itemsDetailForm instanceof Container) {
1✔
1167
                        $form['items_detail_form'] = $itemsDetailForm;
×
1168
                }
1169

1170
                /**
1171
                 * Filter part
1172
                 */
1173
                $filterContainer = $form->addContainer('filter');
1✔
1174

1175
                foreach ($this->filters as $filter) {
1✔
UNCOV
1176
                        $filter->addToFormContainer($filterContainer);
×
1177
                }
1178

1179
                if (!$this->hasAutoSubmit()) {
1✔
UNCOV
1180
                        $filterContainer['submit'] = $this->getFilterSubmitButton();
×
1181
                }
1182

1183
                /**
1184
                 * Group action part
1185
                 */
1186
                $groupActionContainer = $form->addContainer('group_action');
1✔
1187

1188
                if ($this->hasGroupActions()) {
1✔
UNCOV
1189
                        $this->getGroupActionCollection()->addToFormContainer($groupActionContainer);
×
1190
                }
1191

1192
                if ($form->isSubmitted() === false) {
1✔
1193
                        $this->setFilterContainerDefaults($filterContainer, $this->filter);
1✔
1194
                }
1195

1196
                /**
1197
                 * Per page part
1198
                 */
1199
                if ($this->isPaginated()) {
1✔
1200
                        $select = $form->addSelect('perPage', '', $this->getItemsPerPageList())
1✔
1201
                                ->setTranslator(null);
1✔
1202

1203
                        if ($form->isSubmitted() === false) {
1✔
1204
                                $select->setValue($this->getPerPage());
1✔
1205
                        }
1206

1207
                        $form->addSubmit('perPage_submit', 'contributte_datagrid.per_page_submit')
1✔
1208
                                ->setValidationScope([$select]);
1✔
1209
                }
1210

1211
                $form->onSubmit[] = function (NetteForm $form): void {
1✔
1212
                        $this->filterSucceeded($form);
1213
                };
1214

1215
                return $form;
1✔
1216
        }
1217

1218
        public function setFilterContainerDefaults(Container $container, array $values, ?string $parentKey = null): void
1✔
1219
        {
1220
                foreach ($container->getComponents() as $key => $control) {
1✔
1221
                        if (!isset($values[$key])) {
×
UNCOV
1222
                                continue;
×
1223
                        }
1224

UNCOV
1225
                        if ($control instanceof Container) {
×
UNCOV
1226
                                $this->setFilterContainerDefaults($control, (array) $values[$key], (string) $key);
×
1227

1228
                                continue;
×
1229
                        }
1230

UNCOV
1231
                        $value = $values[$key];
×
1232

NEW
1233
                        if ($value instanceof DateTime) {
×
NEW
1234
                                $filter = $parentKey !== null ? $this->getFilter($parentKey) : $this->getFilter((string) $key);
×
1235

UNCOV
1236
                                if ($filter instanceof IFilterDate) {
×
UNCOV
1237
                                        $value = $value->format($filter->getPhpFormat());
×
1238
                                }
1239
                        }
1240

1241
                        try {
NEW
1242
                                if (!$control instanceof FormControl) {
×
NEW
1243
                                        throw new UnexpectedValueException();
×
1244
                                }
1245

UNCOV
1246
                                $control->setValue($value);
×
1247

UNCOV
1248
                        } catch (InvalidArgumentException $e) {
×
UNCOV
1249
                                if ($this->strictSessionFilterValues) {
×
UNCOV
1250
                                        throw $e;
×
1251
                                }
1252
                        }
1253
                }
1254
        }
1✔
1255

1256
        /**
1257
         * Set $this->filter values after filter form submitted
1258
         */
1259
        public function filterSucceeded(NetteForm $form): void
1✔
1260
        {
1261
                if ($this->snippetsSet) {
1✔
UNCOV
1262
                        return;
×
1263
                }
1264

1265
                $values = (array) $form->getUntrustedValues(null);
1✔
1266

1267
                if ($this->getPresenterInstance()->isAjax()) {
1✔
UNCOV
1268
                        if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
×
UNCOV
1269
                                return;
×
1270
                        }
1271
                }
1272

1273
                /**
1274
                 * Per page
1275
                 */
1276
                if (isset($values['perPage'])) {
1✔
1277
                        $this->saveSessionData('_grid_perPage', $values['perPage']);
1✔
1278
                        $this->perPage = $values['perPage'];
1✔
1279
                }
1280

1281
                /**
1282
                 * Inline edit
1283
                 */
1284
                if (
1285
                        isset($form['inline_edit'])
1✔
1286
                        && isset($form['inline_edit']['submit'])
1✔
1287
                        && isset($form['inline_edit']['cancel'])
1✔
1288
                        && $this->inlineEdit !== null
1✔
1289
                ) {
1290
                        $edit = $form['inline_edit'];
×
1291

1292
                        if (
UNCOV
1293
                                !$edit instanceof Container
×
1294
                                || !$edit['submit'] instanceof FormsSubmitButton
×
1295
                                || !$edit['cancel'] instanceof FormsSubmitButton
×
1296
                        ) {
NEW
1297
                                throw new UnexpectedValueException();
×
1298
                        }
1299

UNCOV
1300
                        if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
×
NEW
1301
                                $id = $form->getHttpData(Form::DataLine, 'inline_edit[_id]');
×
NEW
1302
                                $primaryWhereColumn = $form->getHttpData(Form::DataLine, 'inline_edit[_primary_where_column]');
×
1303

UNCOV
1304
                                if ($edit['submit']->isSubmittedBy() && $edit->getErrors() === []) {
×
UNCOV
1305
                                        $this->inlineEdit->onSubmit($id, $values['inline_edit']);
×
UNCOV
1306
                                        $this->getPresenterInstance()->payload->_datagrid_inline_edited = $id;
×
UNCOV
1307
                                        $this->getPresenterInstance()->payload->_datagrid_name = $this->getFullName();
×
1308
                                } else {
UNCOV
1309
                                        $this->getPresenterInstance()->payload->_datagrid_inline_edit_cancel = $id;
×
UNCOV
1310
                                        $this->getPresenterInstance()->payload->_datagrid_name = $this->getFullName();
×
1311
                                }
1312

UNCOV
1313
                                if ($edit['submit']->isSubmittedBy() && $this->inlineEdit->onCustomRedraw !== []) {
×
UNCOV
1314
                                        $this->inlineEdit->onCustomRedraw('submit');
×
UNCOV
1315
                                } elseif ($edit['cancel']->isSubmittedBy() && $this->inlineEdit->onCustomRedraw !== []) {
×
UNCOV
1316
                                        $this->inlineEdit->onCustomRedraw('cancel');
×
1317
                                } else {
UNCOV
1318
                                        $this->redrawItem($id, $primaryWhereColumn);
×
UNCOV
1319
                                        $this->redrawControl('summary');
×
1320
                                }
1321

UNCOV
1322
                                return;
×
1323
                        }
1324
                }
1325

1326
                /**
1327
                 * Inline add
1328
                 */
1329
                if (
1330
                        isset($form['inline_add'])
1✔
1331
                        && isset($form['inline_add']['submit'])
1✔
1332
                        && isset($form['inline_add']['cancel'])
1✔
1333
                        && $this->inlineAdd !== null
1✔
1334
                ) {
UNCOV
1335
                        $add = $form['inline_add'];
×
1336

1337
                        if (
UNCOV
1338
                                !$add instanceof Container
×
UNCOV
1339
                                || !$add['submit'] instanceof FormsSubmitButton
×
UNCOV
1340
                                || !$add['cancel'] instanceof FormsSubmitButton
×
1341
                        ) {
NEW
1342
                                throw new UnexpectedValueException();
×
1343
                        }
1344

UNCOV
1345
                        if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
×
UNCOV
1346
                                if ($add['submit']->isSubmittedBy() && $add->getErrors() === []) {
×
UNCOV
1347
                                        $this->inlineAdd->onSubmit($values['inline_add']);
×
1348
                                }
1349

1350
                                $this->redrawControl('tbody');
×
1351

1352
                                $this->onRedraw();
×
1353

UNCOV
1354
                                return;
×
1355
                        }
1356
                }
1357

1358
                /**
1359
                 * Filter itself
1360
                 */
1361
                $values = $values['filter'];
1✔
1362

1363
                if (!$values instanceof ArrayHash) {
1✔
NEW
1364
                        throw new UnexpectedValueException();
×
1365
                }
1366

1367
                foreach ($values as $key => $value) {
1✔
1368
                        /**
1369
                         * Session stuff
1370
                         */
1371
                        if ($this->rememberState && $this->getSessionData((string) $key) !== $value) {
×
1372
                                /**
1373
                                 * Has been filter changed?
1374
                                 */
UNCOV
1375
                                $this->page = 1;
×
1376
                                $this->saveSessionData('_grid_page', 1);
×
1377
                        }
1378

UNCOV
1379
                        $this->saveSessionData((string) $key, $value);
×
1380

1381
                        /**
1382
                         * Other stuff
1383
                         */
1384
                        $this->filter[$key] = $value;
×
1385
                }
1386

1387
                if ($values->count() > 0) {
1✔
UNCOV
1388
                        $this->saveSessionData('_grid_has_filtered', 1);
×
1389
                }
1390

1391
                if ($this->getPresenterInstance()->isAjax()) {
1✔
UNCOV
1392
                        $this->getPresenterInstance()->payload->_datagrid_sort = [];
×
1393

UNCOV
1394
                        foreach ($this->columns as $key => $column) {
×
UNCOV
1395
                                if ($column->isSortable()) {
×
UNCOV
1396
                                        $this->getPresenterInstance()->payload->_datagrid_sort[$key] = $this->link('sort!', [
×
UNCOV
1397
                                                'sort' => $column->getSortNext(),
×
1398
                                        ]);
1399
                                }
1400
                        }
1401
                }
1402

1403
                $this->reload();
1✔
1404
        }
1405

1406
        /**
1407
         * @return static
1408
         */
1409
        public function setOuterFilterRendering(bool $outerFilterRendering = true): self
1410
        {
UNCOV
1411
                $this->outerFilterRendering = $outerFilterRendering;
×
1412

1413
                return $this;
×
1414
        }
1415

1416
        public function hasOuterFilterRendering(): bool
1417
        {
UNCOV
1418
                return $this->outerFilterRendering;
×
1419
        }
1420

1421
        /**
1422
         * @return static
1423
         * @throws InvalidArgumentException
1424
         */
1425
        public function setOuterFilterColumnsCount(int $count): self
1426
        {
UNCOV
1427
                $columnsCounts = [1, 2, 3, 4, 6, 12];
×
1428

UNCOV
1429
                if (!in_array($count, $columnsCounts, true)) {
×
UNCOV
1430
                        throw new InvalidArgumentException(sprintf(
×
1431
                                'Columns count must be one of following values: %s. Value %s given.',
×
UNCOV
1432
                                implode(', ', $columnsCounts),
×
1433
                                $count
1434
                        ));
1435
                }
1436

1437
                $this->outerFilterColumnsCount = $count;
×
1438

UNCOV
1439
                return $this;
×
1440
        }
1441

1442
        public function getOuterFilterColumnsCount(): int
1443
        {
UNCOV
1444
                return $this->outerFilterColumnsCount;
×
1445
        }
1446

1447
        /**
1448
         * @return static
1449
         */
1450
        public function setCollapsibleOuterFilters(bool $collapsibleOuterFilters = true): self
1451
        {
UNCOV
1452
                $this->collapsibleOuterFilters = $collapsibleOuterFilters;
×
1453

UNCOV
1454
                return $this;
×
1455
        }
1456

1457
        public function hasCollapsibleOuterFilters(): bool
1458
        {
UNCOV
1459
                return $this->collapsibleOuterFilters;
×
1460
        }
1461

1462
        /**
1463
         * Try to restore session stuff
1464
         *
1465
         * @throws DatagridFilterNotFoundException
1466
         */
1467
        public function findSessionValues(): void
1468
        {
1469
                if (!ArraysHelper::testEmpty($this->filter) || ($this->page !== 1) || $this->sort !== []) {
1✔
UNCOV
1470
                        return;
×
1471
                }
1472

1473
                if (!$this->rememberState) {
1✔
UNCOV
1474
                        return;
×
1475
                }
1476

1477
                $page = $this->getSessionData('_grid_page');
1✔
1478

1479
                if ($page !== null) {
1✔
1480
                        $this->page = (int) $page;
×
1481
                }
1482

1483
                $perPage = $this->getSessionData('_grid_perPage');
1✔
1484

1485
                if ($perPage !== null) {
1✔
UNCOV
1486
                        $this->perPage = $perPage;
×
1487
                }
1488

1489
                $sort = $this->getSessionData('_grid_sort');
1✔
1490

1491
                if (is_array($sort) && $sort !== []) {
1✔
UNCOV
1492
                        $this->sort = $sort;
×
1493
                }
1494

1495
                foreach ($this->getSessionData() as $key => $value) {
1✔
1496
                        $other_session_keys = [
1✔
1497
                                '_grid_perPage',
1498
                                '_grid_sort',
1499
                                '_grid_page',
1500
                                '_grid_has_sorted',
1501
                                '_grid_has_filtered',
1502
                                '_grid_hidden_columns',
1503
                                '_grid_hidden_columns_manipulated',
1504
                        ];
1505

1506
                        if (!in_array($key, $other_session_keys, true)) {
1✔
1507
                                try {
UNCOV
1508
                                        $this->getFilter($key);
×
1509

UNCOV
1510
                                        $this->filter[$key] = $value;
×
1511

NEW
1512
                                } catch (DatagridException) {
×
UNCOV
1513
                                        if ($this->strictSessionFilterValues) {
×
NEW
1514
                                                throw new DatagridFilterNotFoundException(
×
UNCOV
1515
                                                        sprintf('Session filter: Filter [%s] not found', $key)
×
1516
                                                );
1517
                                        }
1518
                                }
1519
                        }
1520
                }
1521

1522
                /**
1523
                 * When column is sorted via custom callback, apply it
1524
                 */
1525
                if ($this->sortCallback === null && $this->sort !== []) {
1✔
1526
                        foreach (array_keys($this->sort) as $key) {
×
1527
                                try {
UNCOV
1528
                                        $column = $this->getColumn((string) $key);
×
1529

NEW
1530
                                } catch (DatagridColumnNotFoundException) {
×
UNCOV
1531
                                        $this->deleteSessionData('_grid_sort');
×
UNCOV
1532
                                        $this->sort = [];
×
1533

UNCOV
1534
                                        return;
×
1535
                                }
1536

1537
                                if ($column->isSortable() && is_callable($column->getSortableCallback())) {
×
1538
                                        $this->sortCallback = $column->getSortableCallback();
×
1539
                                }
1540
                        }
1541
                }
1542
        }
1✔
1543

1544
        /********************************************************************************
1545
         *                                    EXPORTS *
1546
         ********************************************************************************/
1547
        public function addExportCallback(
1✔
1548
                string $text,
1549
                callable $callback,
1550
                bool $filtered = false
1551
        ): Export
1552
        {
1553
                return $this->addToExports(new Export($this, $text, $callback, $filtered));
1✔
1554
        }
1555

1556
        public function addExportCsvFiltered(
1557
                string $text,
1558
                string $csvFileName,
1559
                string $outputEncoding = 'utf-8',
1560
                string $delimiter = ';',
1561
                bool $includeBom = false
1562
        ): ExportCsv
1563
        {
UNCOV
1564
                return $this->addExportCsv($text, $csvFileName, $outputEncoding, $delimiter, $includeBom, true);
×
1565
        }
1566

1567
        public function addExportCsv(
1✔
1568
                string $text,
1569
                string $csvFileName,
1570
                string $outputEncoding = 'utf-8',
1571
                string $delimiter = ';',
1572
                bool $includeBom = false,
1573
                bool $filtered = false
1574
        ): ExportCsv
1575
        {
1576
                $exportCsv = new ExportCsv($this, $text, $csvFileName, $filtered, $outputEncoding, $delimiter, $includeBom);
1✔
1577

1578
                $this->addToExports($exportCsv);
1✔
1579

1580
                return $exportCsv;
1✔
1581
        }
1582

1583
        public function resetExportsLinks(): void
1584
        {
UNCOV
1585
                foreach ($this->exports as $id => $export) {
×
UNCOV
1586
                        $link = new Link($this, 'export!', ['id' => $id]);
×
1587

UNCOV
1588
                        $export->setLink($link);
×
1589
                }
1590
        }
1591

1592

1593
        /********************************************************************************
1594
         *                                TOOLBAR BUTTONS *
1595
         ********************************************************************************/
1596

1597
        /**
1598
         * @throws DatagridException
1599
         */
1600
        public function addToolbarButton(
1601
                string $href,
1602
                string $text = '',
1603
                array $params = []
1604
        ): ToolbarButton
1605
        {
UNCOV
1606
                if (isset($this->toolbarButtons[$href])) {
×
NEW
1607
                        throw new DatagridException(
×
UNCOV
1608
                                sprintf('There is already toolbar button at key [%s] defined.', $href)
×
1609
                        );
1610
                }
1611

UNCOV
1612
                return $this->toolbarButtons[$href] = new ToolbarButton($this, $href, $text, $params);
×
1613
        }
1614

1615
        /**
1616
         * @throws DatagridException
1617
         */
1618
        public function getToolbarButton(string $key): ToolbarButton
1619
        {
UNCOV
1620
                if (!isset($this->toolbarButtons[$key])) {
×
NEW
1621
                        throw new DatagridException(
×
UNCOV
1622
                                sprintf('There is no toolbar button at key [%s] defined.', $key)
×
1623
                        );
1624
                }
1625

UNCOV
1626
                return $this->toolbarButtons[$key];
×
1627
        }
1628

1629
        /**
1630
         * @return static
1631
         */
1632
        public function removeToolbarButton(string $key): self
1633
        {
1634
                unset($this->toolbarButtons[$key]);
×
1635

UNCOV
1636
                return $this;
×
1637
        }
1638

1639
        /********************************************************************************
1640
         *                                 GROUP ACTIONS *
1641
         ********************************************************************************/
1642
        public function addGroupAction(string $title, array $options = []): GroupAction
1643
        {
UNCOV
1644
                return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
×
1645
        }
1646

1647
        public function addGroupButtonAction(string $title, ?string $class = null): GroupButtonAction
1648
        {
UNCOV
1649
                return $this->getGroupActionCollection()->addGroupButtonAction($title, $class);
×
1650
        }
1651

1652
        public function addGroupSelectAction(string $title, array $options = []): GroupAction
1653
        {
UNCOV
1654
                return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
×
1655
        }
1656

1657
        public function addGroupMultiSelectAction(string $title, array $options = []): GroupAction
1658
        {
1659
                return $this->getGroupActionCollection()->addGroupMultiSelectAction($title, $options);
×
1660
        }
1661

1662
        public function addGroupTextAction(string $title): GroupAction
1663
        {
1664
                return $this->getGroupActionCollection()->addGroupTextAction($title);
×
1665
        }
1666

1667
        public function addGroupTextareaAction(string $title): GroupAction
1668
        {
1669
                return $this->getGroupActionCollection()->addGroupTextareaAction($title);
×
1670
        }
1671

1672
        public function getGroupActionCollection(): GroupActionCollection
1673
        {
1674
                if ($this->groupActionCollection === null) {
×
1675
                        $this->groupActionCollection = new GroupActionCollection($this);
×
1676
                }
1677

1678
                return $this->groupActionCollection;
×
1679
        }
1680

1681
        public function hasGroupActions(): bool
1682
        {
1683
                return $this->groupActionCollection instanceof GroupActionCollection;
1✔
1684
        }
1685

1686
        public function shouldShowSelectedRowsCount(): bool
1687
        {
1688
                return $this->showSelectedRowsCount;
×
1689
        }
1690

1691
        /**
1692
         * @return static
1693
         */
1694
        public function setShowSelectedRowsCount(bool $show = true): self
1695
        {
UNCOV
1696
                $this->showSelectedRowsCount = $show;
×
1697

UNCOV
1698
                return $this;
×
1699
        }
1700

1701
        /********************************************************************************
1702
         *                                   HANDLERS *
1703
         ********************************************************************************/
1704
        public function handlePage(int $page): void
1705
        {
UNCOV
1706
                $this->page = $page;
×
UNCOV
1707
                $this->saveSessionData('_grid_page', $page);
×
1708

UNCOV
1709
                $this->reload(['table']);
×
1710
        }
1711

1712
        /**
1713
         * @throws DatagridColumnNotFoundException
1714
         */
1715
        public function handleSort(array $sort): void
1716
        {
1717
                if (count($sort) === 0) {
×
UNCOV
1718
                        $sort = $this->defaultSort;
×
1719
                }
1720

UNCOV
1721
                foreach (array_keys($sort) as $key) {
×
1722
                        try {
UNCOV
1723
                                $column = $this->getColumn($key);
×
1724

NEW
1725
                        } catch (DatagridColumnNotFoundException) {
×
UNCOV
1726
                                unset($sort[$key]);
×
1727

1728
                                continue;
×
1729
                        }
1730

1731
                        if ($column->sortableResetPagination()) {
×
UNCOV
1732
                                $this->saveSessionData('_grid_page', $this->page = 1);
×
1733
                        }
1734

UNCOV
1735
                        if ($column->getSortableCallback() !== null) {
×
UNCOV
1736
                                $this->sortCallback = $column->getSortableCallback();
×
1737
                        }
1738
                }
1739

UNCOV
1740
                $this->saveSessionData('_grid_has_sorted', 1);
×
1741
                $this->saveSessionData('_grid_sort', $this->sort = $sort);
×
1742

UNCOV
1743
                $this->reloadTheWholeGrid();
×
1744
        }
1745

1746
        public function handleResetFilter(): void
1747
        {
1748
                /**
1749
                 * Session stuff
1750
                 */
1751
                $this->deleteSessionData('_grid_page');
1✔
1752

1753
                if ($this->defaultFilterUseOnReset) {
1✔
1754
                        $this->deleteSessionData('_grid_has_filtered');
1✔
1755
                }
1756

1757
                if ($this->defaultSortUseOnReset) {
1✔
1758
                        $this->deleteSessionData('_grid_has_sorted');
1✔
1759
                }
1760

1761
                $sessionData = is_array($this->getSessionData())
1✔
1762
                        ? $this->getSessionData()
1✔
1763
                        : iterator_to_array($this->getSessionData());
1✔
1764

1765
                foreach (array_keys($sessionData) as $key) {
1✔
NEW
1766
                        if (!in_array($key, ['_grid_perPage', '_grid_sort', '_grid_page', '_grid_has_filtered', '_grid_has_sorted', '_grid_hidden_columns', '_grid_hidden_columns_manipulated'], true)) {
×
1767
                                $this->deleteSessionData((string) $key);
×
1768
                        }
1769
                }
1770

1771
                $this->filter = [];
1✔
1772

1773
                $this->reloadTheWholeGrid();
1✔
1774
        }
1775

1776
        public function handleResetColumnFilter(string $key): void
1777
        {
UNCOV
1778
                $this->deleteSessionData($key);
×
1779
                unset($this->filter[$key]);
×
1780

UNCOV
1781
                $this->reloadTheWholeGrid();
×
1782
        }
1783

1784
        /**
1785
         * @return static
1786
         */
1787
        public function setColumnReset(bool $reset = true): self
1788
        {
UNCOV
1789
                $this->hasColumnReset = $reset;
×
1790

UNCOV
1791
                return $this;
×
1792
        }
1793

1794
        public function hasColumnReset(): bool
1795
        {
1796
                return $this->hasColumnReset;
1✔
1797
        }
1798

1799
        /**
1800
         * @param array<Filter> $filters
1801
         */
1802
        public function sendNonEmptyFiltersInPayload(array $filters): void
1✔
1803
        {
1804
                if (!$this->hasColumnReset()) {
1✔
UNCOV
1805
                        return;
×
1806
                }
1807

1808
                $non_empty_filters = [];
1✔
1809

1810
                foreach ($filters as $filter) {
1✔
1811
                        if ($filter->isValueSet()) {
1✔
1812
                                $non_empty_filters[] = $filter->getKey();
×
1813
                        }
1814
                }
1815

1816
                $this->getPresenterInstance()->payload->non_empty_filters = $non_empty_filters;
1✔
1817
        }
1✔
1818

1819
        public function handleExport(mixed $id): void
1✔
1820
        {
1821
                if (!isset($this->exports[$id])) {
1✔
NEW
1822
                        throw new ForbiddenRequestException();
×
1823
                }
1824

1825
                if ($this->columnsExportOrder !== []) {
1✔
UNCOV
1826
                        $this->setColumnsOrder($this->columnsExportOrder);
×
1827
                }
1828

1829
                $export = $this->exports[$id];
1✔
1830

1831
                /**
1832
                 * Invoke possible events
1833
                 */
1834
                $this->onExport($this);
1✔
1835

1836
                if ($export->isFiltered()) {
1✔
1837
                        $sort = $this->sort;
1✔
1838
                        $filter = $this->assembleFilters();
1✔
1839
                } else {
1840
                        $sort = [$this->primaryKey => 'ASC'];
1✔
1841
                        $filter = [];
1✔
1842
                }
1843

1844
                if ($this->dataModel === null) {
1✔
1845
                        throw new DatagridException('You have to set a data source first.');
1✔
1846
                }
1847

1848
                $rows = [];
1✔
1849

1850
                $items = $this->dataModel->filterData(
1✔
1851
                        null,
1✔
1852
                        $this->createSorting($sort, $this->sortCallback),
1✔
1853
                        $filter
1854
                );
1855

1856
                foreach ($items as $item) {
1✔
1857
                        $rows[] = new Row($this, $item, $this->getPrimaryKey());
1✔
1858
                }
1859

1860
                if ($export instanceof ExportCsv) {
1✔
1861
                        $export->invoke($rows);
1✔
1862
                } else {
1863
                        $export->invoke($items);
1✔
1864
                }
1865

1866
                if ($export->isAjax()) {
1✔
UNCOV
1867
                        $this->reload();
×
1868
                }
1869
        }
1✔
1870

1871
        public function handleGetChildren(mixed $parent): void
1872
        {
1873
                if (!is_callable($this->treeViewChildrenCallback)) {
×
NEW
1874
                        throw new UnexpectedValueException();
×
1875
                }
1876

UNCOV
1877
                $this->setDataSource(call_user_func($this->treeViewChildrenCallback, $parent));
×
1878

UNCOV
1879
                if ($this->getPresenterInstance()->isAjax()) {
×
1880
                        $this->getPresenterInstance()->payload->_datagrid_url = $this->refreshURL;
×
UNCOV
1881
                        $this->getPresenterInstance()->payload->_datagrid_tree = $parent;
×
1882

1883
                        $this->redrawControl('items');
×
1884

UNCOV
1885
                        $this->onRedraw();
×
1886
                } else {
1887
                        $this->getPresenterInstance()->redirect('this');
×
1888
                }
1889
        }
1890

1891
        public function handleGetItemDetail(mixed $id): void
1892
        {
UNCOV
1893
                $template = $this->getTemplate();
×
1894

UNCOV
1895
                if (!$template instanceof Template) {
×
NEW
1896
                        throw new UnexpectedValueException();
×
1897
                }
1898

UNCOV
1899
                $template->add('toggle_detail', $id);
×
1900

UNCOV
1901
                if ($this->itemsDetail === null) {
×
NEW
1902
                        throw new UnexpectedValueException();
×
1903
                }
1904

UNCOV
1905
                $this->redrawItem = [$this->itemsDetail->getPrimaryWhereColumn() => $id];
×
1906

UNCOV
1907
                if ($this->getPresenterInstance()->isAjax()) {
×
UNCOV
1908
                        $this->getPresenterInstance()->payload->_datagrid_toggle_detail = $id;
×
UNCOV
1909
                        $this->getPresenterInstance()->payload->_datagrid_name = $this->getFullName();
×
UNCOV
1910
                        $this->redrawControl('items');
×
1911

1912
                        /**
1913
                         * Only for nette 2.4
1914
                         */
UNCOV
1915
                        if (method_exists($template->getLatte(), 'addProvider')) {
×
UNCOV
1916
                                $this->redrawControl('gridSnippets');
×
1917
                        }
1918

UNCOV
1919
                        $this->onRedraw();
×
1920
                } else {
UNCOV
1921
                        $this->getPresenterInstance()->redirect('this');
×
1922
                }
1923
        }
1924

1925
        public function handleEdit(mixed $id, mixed $key): void
1926
        {
UNCOV
1927
                $column = $this->getColumn($key);
×
UNCOV
1928
                $request = $this->getPresenterInstance()->getRequest();
×
1929

UNCOV
1930
                if (!$request instanceof Request) {
×
NEW
1931
                        throw new UnexpectedValueException();
×
1932
                }
1933

UNCOV
1934
                $value = $request->getPost('value');
×
1935

1936
                // Could be null of course
UNCOV
1937
                if ($column->getEditableCallback() === null) {
×
NEW
1938
                        throw new UnexpectedValueException();
×
1939
                }
1940

UNCOV
1941
                $newValue = $column->getEditableCallback()($id, $value);
×
1942

UNCOV
1943
                $this->getPresenterInstance()->payload->_datagrid_editable_new_value = $newValue;
×
UNCOV
1944
                $this->getPresenterInstance()->payload->postGet = true;
×
UNCOV
1945
                $this->getPresenterInstance()->payload->url = $this->link('this');
×
1946

UNCOV
1947
                if (!$this->getPresenterInstance()->isControlInvalid(null)) {
×
UNCOV
1948
                        $this->getPresenterInstance()->sendPayload();
×
1949
                }
1950
        }
1951

1952
        /**
1953
         * @param array|string[] $snippets
1954
         */
1955
        public function reload(array $snippets = []): void
1✔
1956
        {
1957
                if ($this->getPresenterInstance()->isAjax()) {
1✔
UNCOV
1958
                        $this->redrawControl('tbody');
×
UNCOV
1959
                        $this->redrawControl('pagination');
×
UNCOV
1960
                        $this->redrawControl('summary');
×
UNCOV
1961
                        $this->redrawControl('thead-group-action');
×
1962

1963
                        /**
1964
                         * manualy reset exports links...
1965
                         */
UNCOV
1966
                        $this->resetExportsLinks();
×
UNCOV
1967
                        $this->redrawControl('exports');
×
1968

UNCOV
1969
                        foreach ($snippets as $snippet) {
×
UNCOV
1970
                                $this->redrawControl($snippet);
×
1971
                        }
1972

UNCOV
1973
                        $this->getPresenterInstance()->payload->_datagrid_url = $this->refreshURL;
×
UNCOV
1974
                        $this->getPresenterInstance()->payload->_datagrid_name = $this->getFullName();
×
1975

UNCOV
1976
                        $this->onRedraw();
×
1977
                } else {
1978
                        $this->getPresenterInstance()->redirect('this');
1✔
1979
                }
1980
        }
1981

1982
        public function reloadTheWholeGrid(): void
1983
        {
1984
                if ($this->getPresenterInstance()->isAjax()) {
1✔
1985
                        $this->redrawControl('grid');
×
1986

UNCOV
1987
                        $this->getPresenterInstance()->payload->_datagrid_url = $this->refreshURL;
×
1988
                        $this->getPresenterInstance()->payload->_datagrid_name = $this->getFullName();
×
1989

UNCOV
1990
                        $this->onRedraw();
×
1991
                } else {
1992
                        $this->getPresenterInstance()->redirect('this');
1✔
1993
                }
1994
        }
1995

1996
        public function handleChangeStatus(string $id, string $key, string $value): void
1997
        {
UNCOV
1998
                if (!isset($this->columns[$key])) {
×
NEW
1999
                        throw new DatagridException(sprintf('ColumnStatus[%s] does not exist', $key));
×
2000
                }
2001

UNCOV
2002
                if (!$this->columns[$key] instanceof ColumnStatus) {
×
NEW
2003
                        throw new UnexpectedValueException();
×
2004
                }
2005

UNCOV
2006
                $this->columns[$key]->onChange($id, $value);
×
2007
        }
2008

2009
        public function redrawItem(string|int $id, mixed $primaryWhereColumn = null): void
2010
        {
UNCOV
2011
                $this->snippetsSet = true;
×
2012

UNCOV
2013
                $this->redrawItem = [($primaryWhereColumn ?? $this->primaryKey) => $id];
×
2014

UNCOV
2015
                $this->redrawControl('items');
×
2016

UNCOV
2017
                $this->getPresenterInstance()->payload->_datagrid_url = $this->refreshURL;
×
2018

UNCOV
2019
                $this->onRedraw();
×
2020
        }
2021

2022
        public function handleShowAllColumns(): void
2023
        {
UNCOV
2024
                $this->deleteSessionData('_grid_hidden_columns');
×
UNCOV
2025
                $this->saveSessionData('_grid_hidden_columns_manipulated', true);
×
2026

UNCOV
2027
                $this->redrawControl();
×
2028

NEW
2029
                $this->onShowAllColumns();
×
UNCOV
2030
                $this->onRedraw();
×
2031
        }
2032

2033
        public function handleShowDefaultColumns(): void
2034
        {
UNCOV
2035
                $this->deleteSessionData('_grid_hidden_columns');
×
UNCOV
2036
                $this->saveSessionData('_grid_hidden_columns_manipulated', false);
×
2037

UNCOV
2038
                $this->redrawControl();
×
2039

NEW
2040
                $this->onShowDefaultColumns();
×
UNCOV
2041
                $this->onRedraw();
×
2042
        }
2043

2044
        public function handleShowColumn(string $column): void
2045
        {
UNCOV
2046
                $columns = $this->getSessionData('_grid_hidden_columns');
×
2047

UNCOV
2048
                if ($columns !== []) {
×
UNCOV
2049
                        $pos = array_search($column, $columns, true);
×
2050

UNCOV
2051
                        if ($pos !== false) {
×
2052
                                unset($columns[$pos]);
×
2053
                        }
2054
                }
2055

UNCOV
2056
                $this->saveSessionData('_grid_hidden_columns', $columns);
×
UNCOV
2057
                $this->saveSessionData('_grid_hidden_columns_manipulated', true);
×
2058

UNCOV
2059
                $this->redrawControl();
×
2060

NEW
2061
                $this->onColumnShow($column);
×
UNCOV
2062
                $this->onRedraw();
×
2063
        }
2064

2065
        public function handleHideColumn(string $column): void
2066
        {
2067
                /**
2068
                 * Store info about hiding a column to session
2069
                 */
UNCOV
2070
                $columns = $this->getSessionData('_grid_hidden_columns');
×
2071

2072
                if ($columns === [] || $columns === null) {
×
UNCOV
2073
                        $columns = [$column];
×
UNCOV
2074
                } elseif (!in_array($column, $columns, true)) {
×
UNCOV
2075
                        array_push($columns, $column);
×
2076
                }
2077

UNCOV
2078
                $this->saveSessionData('_grid_hidden_columns', $columns);
×
UNCOV
2079
                $this->saveSessionData('_grid_hidden_columns_manipulated', true);
×
2080

2081
                $this->redrawControl();
×
2082

NEW
2083
                $this->onColumnHide($column);
×
2084
                $this->onRedraw();
×
2085
        }
2086

2087
        public function handleActionCallback(mixed $__key, mixed $__id): void
2088
        {
2089
                $action = $this->getAction($__key);
×
2090

UNCOV
2091
                if (!($action instanceof ActionCallback)) {
×
NEW
2092
                        throw new DatagridException(
×
2093
                                sprintf('Action [%s] does not exist or is not an callback aciton.', $__key)
×
2094
                        );
2095
                }
2096

UNCOV
2097
                $action->onClick($__id);
×
2098
        }
2099

2100

2101
        /********************************************************************************
2102
         *                                  PAGINATION *
2103
         ********************************************************************************/
2104

2105
        /**
2106
         * @param array|array|int[]|array|string[] $itemsPerPageList
2107
         * @return static
2108
         */
2109
        public function setItemsPerPageList(array $itemsPerPageList, bool $includeAll = true): self
1✔
2110
        {
2111
                if ($itemsPerPageList === []) {
1✔
NEW
2112
                        throw new InvalidArgumentException('$itemsPerPageList can not be an empty array');
×
2113
                }
2114

2115
                $this->itemsPerPageList = $itemsPerPageList;
1✔
2116

2117
                if ($includeAll) {
1✔
2118
                        $this->itemsPerPageList[] = 'all';
1✔
2119
                }
2120

2121
                return $this;
1✔
2122
        }
2123

2124
        /**
2125
         * @return static
2126
         */
2127
        public function setDefaultPerPage(int $count): self
2128
        {
UNCOV
2129
                $this->defaultPerPage = $count;
×
2130

UNCOV
2131
                return $this;
×
2132
        }
2133

2134
        /**
2135
         * User may set default "items per page" value, apply it
2136
         */
2137
        public function findDefaultPerPage(): void
2138
        {
UNCOV
2139
                if ($this->perPage !== null) {
×
UNCOV
2140
                        return;
×
2141
                }
2142

2143
                if ($this->defaultPerPage !== null) {
×
UNCOV
2144
                        $this->perPage = $this->defaultPerPage;
×
2145
                }
2146

UNCOV
2147
                $this->saveSessionData('_grid_perPage', $this->perPage);
×
2148
        }
2149

2150
        public function createComponentPaginator(): DatagridPaginator
2151
        {
NEW
2152
                $component = new DatagridPaginator(
×
UNCOV
2153
                        $this->getTranslator(),
×
UNCOV
2154
                        static::$iconPrefix,
×
UNCOV
2155
                        static::$btnSecondaryClass
×
2156
                );
UNCOV
2157
                $paginator = $component->getPaginator();
×
2158

2159
                $paginator->setPage($this->page);
×
2160

2161
                if (is_int($this->getPerPage())) {
×
UNCOV
2162
                        $paginator->setItemsPerPage($this->getPerPage());
×
2163
                }
2164

2165
                if ($this->customPaginatorTemplate !== null) {
×
2166
                        $component->setTemplateFile($this->customPaginatorTemplate);
×
2167
                }
2168

UNCOV
2169
                return $component;
×
2170
        }
2171

2172
        public function getPerPage(): int|string
2173
        {
2174
                $itemsPerPageList = array_keys($this->getItemsPerPageList());
1✔
2175

2176
                $perPage = $this->perPage ?? reset($itemsPerPageList);
1✔
2177

2178
                if (($perPage !== 'all' && !in_array((int) $this->perPage, $itemsPerPageList, true))
1✔
2179
                        || ($perPage === 'all' && !in_array($this->perPage, $itemsPerPageList, true))) {
1✔
2180
                        $perPage = reset($itemsPerPageList);
1✔
2181
                }
2182

2183
                return $perPage === 'all'
1✔
2184
                        ? 'all'
1✔
2185
                        : (int) $perPage;
1✔
2186
        }
2187

2188
        /**
2189
         * @return array|array|int[]|array|string[]|\Stringable[]
2190
         */
2191
        public function getItemsPerPageList(): array
2192
        {
2193
                $list = array_flip($this->itemsPerPageList);
1✔
2194

2195
                foreach (array_keys($list) as $key) {
1✔
2196
                        $list[$key] = $key;
1✔
2197
                }
2198

2199
                if (array_key_exists('all', $list)) {
1✔
2200
                        $list['all'] = $this->getTranslator()->translate('contributte_datagrid.all');
1✔
2201
                }
2202

2203
                return $list;
1✔
2204
        }
2205

2206
        /**
2207
         * @return static
2208
         */
2209
        public function setPagination(bool $doPaginate): self
2210
        {
UNCOV
2211
                $this->doPaginate = $doPaginate;
×
2212

2213
                return $this;
×
2214
        }
2215

2216
        public function isPaginated(): bool
2217
        {
2218
                return $this->doPaginate;
1✔
2219
        }
2220

2221
        public function getPaginator(): ?DatagridPaginator
2222
        {
2223
                if ($this->isPaginated() && $this->perPage !== 'all') {
×
UNCOV
2224
                        return $this['paginator'];
×
2225
                }
2226

UNCOV
2227
                return null;
×
2228
        }
2229

2230

2231
        /********************************************************************************
2232
         *                                     I18N *
2233
         ********************************************************************************/
2234

2235
        /**
2236
         * @return static
2237
         */
2238
        public function setTranslator(Translator $translator): self
1✔
2239
        {
2240
                $this->translator = $translator;
1✔
2241

2242
                return $this;
1✔
2243
        }
2244

2245
        public function getTranslator(): Translator
2246
        {
2247
                if ($this->translator === null) {
1✔
2248
                        $this->translator = new SimpleTranslator();
1✔
2249
                }
2250

2251
                return $this->translator;
1✔
2252
        }
2253

2254

2255
        /********************************************************************************
2256
         *                                 COLUMNS ORDER *
2257
         ********************************************************************************/
2258

2259
        /**
2260
         * Set order of datagrid columns
2261
         *
2262
         * @param array|string[] $order
2263
         * @return static
2264
         */
2265
        public function setColumnsOrder(array $order): self
2266
        {
UNCOV
2267
                $new_order = [];
×
2268

UNCOV
2269
                foreach ($order as $key) {
×
2270
                        if (isset($this->columns[$key])) {
×
UNCOV
2271
                                $new_order[$key] = $this->columns[$key];
×
2272
                        }
2273
                }
2274

NEW
2275
                if (count($new_order) === count($this->columns)) {
×
UNCOV
2276
                        $this->columns = $new_order;
×
2277
                } else {
NEW
2278
                        throw new DatagridException('When changing columns order, you have to specify all columns');
×
2279
                }
2280

UNCOV
2281
                return $this;
×
2282
        }
2283

2284
        /**
2285
         * Columns order may be different for export and normal grid
2286
         *
2287
         * @param array|string[] $order
2288
         * @return static
2289
         */
2290
        public function setColumnsExportOrder(array $order): self
2291
        {
2292
                $this->columnsExportOrder = $order;
×
2293

2294
                return $this;
×
2295
        }
2296

2297
        /********************************************************************************
2298
         *                                SESSION & URL *
2299
         ********************************************************************************/
2300
        public function getSessionSectionName(): string
2301
        {
2302
                $presenter = $this->getPresenterInstance();
1✔
2303

2304
                return $presenter->getName() . ':' . $this->getUniqueId();
1✔
2305
        }
2306

2307
        /**
2308
         * @return static
2309
         */
2310
        public function setRememberState(bool $remember = true, bool $rememberHideableColumnsState = false): self
1✔
2311
        {
2312
                $this->rememberState = $remember;
1✔
2313
                $this->rememberHideableColumnsState = $rememberHideableColumnsState;
1✔
2314

2315
                return $this;
1✔
2316
        }
2317

2318
        /**
2319
         * @return static
2320
         */
2321
        public function setRefreshUrl(bool $refresh = true): self
2322
        {
UNCOV
2323
                $this->refreshURL = $refresh;
×
2324

UNCOV
2325
                return $this;
×
2326
        }
2327

2328
        public function getSessionData(?string $key = null, mixed $defaultValue = null): mixed
1✔
2329
        {
2330
                $getValue = fn () => ($key !== null ? $this->gridSession[$key] : $this->gridSession) ?? $defaultValue;
1✔
2331

2332
                if ($this->rememberState) {
1✔
2333
                        return ($getValue)();
1✔
2334
                }
2335

2336
                if ($this->rememberHideableColumnsState && in_array($key, self::HIDEABLE_COLUMNS_SESSION_KEYS, true)) {
1✔
UNCOV
2337
                        return ($getValue)();
×
2338
                }
2339

2340
                return $key === null
1✔
2341
                        ? []
1✔
2342
                        : $defaultValue;
1✔
2343
        }
2344

2345
        public function saveSessionData(string $key, mixed $value): void
1✔
2346
        {
2347
                if ($this->rememberState) {
1✔
2348
                        $this->gridSession[$key] = $value;
1✔
2349
                } elseif ($this->rememberHideableColumnsState && in_array($key, self::HIDEABLE_COLUMNS_SESSION_KEYS, true)) {
×
UNCOV
2350
                        $this->gridSession[$key] = $value;
×
2351
                }
2352
        }
1✔
2353

2354
        public function deleteSessionData(string $key): void
1✔
2355
        {
2356
                unset($this->gridSession[$key]);
1✔
2357
        }
1✔
2358

2359

2360
        /********************************************************************************
2361
         *                                  ITEM DETAIL *
2362
         ********************************************************************************/
2363

2364
        /**
2365
         * Get items detail parameters
2366
         */
2367
        public function getItemsDetail(): ?ItemDetail
2368
        {
UNCOV
2369
                return $this->itemsDetail;
×
2370
        }
2371

2372
        /**
2373
         * @param mixed $detail callable|string|bool
2374
         */
2375
        public function setItemsDetail(mixed $detail = true, ?string $primaryWhereColumn = null): ItemDetail
2376
        {
2377
                if ($this->isSortable()) {
×
NEW
2378
                        throw new DatagridException('You can not use both sortable datagrid and items detail.');
×
2379
                }
2380

2381
                $this->itemsDetail = new ItemDetail($this, $primaryWhereColumn ?? $this->primaryKey);
×
2382

UNCOV
2383
                if (is_string($detail)) {
×
2384
                        /**
2385
                         * Item detail will be in separate template
2386
                         */
UNCOV
2387
                        $this->itemsDetail->setType('template');
×
UNCOV
2388
                        $this->itemsDetail->setTemplate($detail);
×
2389

UNCOV
2390
                } elseif (is_callable($detail)) {
×
2391
                        /**
2392
                         * Item detail will be rendered via custom callback renderer
2393
                         */
UNCOV
2394
                        $this->itemsDetail->setType('renderer');
×
UNCOV
2395
                        $this->itemsDetail->setRenderer($detail);
×
2396

UNCOV
2397
                } elseif ($detail === true) {
×
2398
                        /**
2399
                         * Item detail will be rendered probably via block #detail
2400
                         */
UNCOV
2401
                        $this->itemsDetail->setType('block');
×
2402

2403
                } else {
NEW
2404
                        throw new DatagridException('::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.');
×
2405
                }
2406

2407
                return $this->itemsDetail;
×
2408
        }
2409

2410
        /**
2411
         * @return static
2412
         */
2413
        public function setItemsDetailForm(callable $callableSetContainer): self
2414
        {
UNCOV
2415
                if ($this->itemsDetail instanceof ItemDetail) {
×
UNCOV
2416
                        $this->itemsDetail->setForm(
×
UNCOV
2417
                                new ItemDetailForm($callableSetContainer)
×
2418
                        );
2419

2420
                        return $this;
×
2421
                }
2422

NEW
2423
                throw new DatagridException('Please set the ItemDetail first.');
×
2424
        }
2425

2426
        public function getItemDetailForm(): ?Container
2427
        {
2428
                if ($this->itemsDetail instanceof ItemDetail) {
1✔
UNCOV
2429
                        return $this->itemsDetail->getForm();
×
2430
                }
2431

2432
                return null;
1✔
2433
        }
2434

2435
        /********************************************************************************
2436
         *                                ROW PRIVILEGES *
2437
         ********************************************************************************/
2438
        public function allowRowsGroupAction(callable $condition): void
2439
        {
UNCOV
2440
                $this->rowConditions['group_action'] = $condition;
×
2441
        }
2442

2443
        public function allowRowsInlineEdit(callable $condition): void
2444
        {
UNCOV
2445
                $this->rowConditions['inline_edit'] = $condition;
×
2446
        }
2447

2448
        public function allowRowsAction(string $key, callable $condition): void
2449
        {
UNCOV
2450
                $this->rowConditions['action'][$key] = $condition;
×
2451
        }
2452

2453
        /**
2454
         * @throws DatagridException
2455
         */
2456
        public function allowRowsMultiAction(
2457
                string $multiActionKey,
2458
                string $actionKey,
2459
                callable $condition
2460
        ): void
2461
        {
UNCOV
2462
                if (!isset($this->actions[$multiActionKey])) {
×
NEW
2463
                        throw new DatagridException(
×
UNCOV
2464
                                sprintf('There is no action at key [%s] defined.', $multiActionKey)
×
2465
                        );
2466
                }
2467

UNCOV
2468
                if (!$this->actions[$multiActionKey] instanceof MultiAction) {
×
NEW
2469
                        throw new DatagridException(
×
2470
                                sprintf('Action at key [%s] is not a MultiAction.', $multiActionKey)
×
2471
                        );
2472
                }
2473

2474
                $this->actions[$multiActionKey]->setRowCondition($actionKey, $condition);
×
2475
        }
2476

2477
        public function getRowCondition(string $name, ?string $key = null): bool|callable
2478
        {
UNCOV
2479
                if (!isset($this->rowConditions[$name])) {
×
UNCOV
2480
                        return false;
×
2481
                }
2482

UNCOV
2483
                $condition = $this->rowConditions[$name];
×
2484

UNCOV
2485
                if ($key === null) {
×
UNCOV
2486
                        return $condition;
×
2487
                }
2488

2489
                return $condition[$key] ?? false;
×
2490
        }
2491

2492
        /********************************************************************************
2493
         *                               COLUMN CALLBACK *
2494
         ********************************************************************************/
2495
        public function addColumnCallback(string $key, callable $callback): void
2496
        {
UNCOV
2497
                $this->columnCallbacks[$key] = $callback;
×
2498
        }
2499

2500
        public function getColumnCallback(string $key): ?callable
2501
        {
UNCOV
2502
                return $this->columnCallbacks[$key] ?? null;
×
2503
        }
2504

2505
        /********************************************************************************
2506
         *                                 INLINE EDIT *
2507
         ********************************************************************************/
2508
        public function addInlineEdit(?string $primaryWhereColumn = null): InlineEdit
2509
        {
2510
                $this->inlineEdit = new InlineEdit($this, $primaryWhereColumn ?? $this->primaryKey);
×
2511

UNCOV
2512
                return $this->inlineEdit;
×
2513
        }
2514

2515
        public function getInlineEdit(): ?InlineEdit
2516
        {
UNCOV
2517
                return $this->inlineEdit;
×
2518
        }
2519

2520
        public function handleShowInlineAdd(): void
2521
        {
UNCOV
2522
                if ($this->inlineAdd !== null) {
×
UNCOV
2523
                        $this->inlineAdd->setShouldBeRendered(true);
×
2524
                }
2525

UNCOV
2526
                $presenter = $this->getPresenterInstance();
×
2527

UNCOV
2528
                if ($presenter->isAjax()) {
×
2529
                        $presenter->payload->_datagrid_inline_adding = true;
×
UNCOV
2530
                        $presenter->payload->_datagrid_name = $this->getFullName();
×
2531

UNCOV
2532
                        $this->redrawControl('tbody');
×
2533

UNCOV
2534
                        $this->onRedraw();
×
2535
                }
2536
        }
2537

2538
        public function handleInlineEdit(mixed $id): void
2539
        {
UNCOV
2540
                if ($this->inlineEdit !== null) {
×
UNCOV
2541
                        $this->inlineEdit->setItemId($id);
×
2542

UNCOV
2543
                        $primaryWhereColumn = $this->inlineEdit->getPrimaryWhereColumn();
×
2544

UNCOV
2545
                        $filterContainer = $this['filter'];
×
UNCOV
2546
                        $inlineEditContainer = $filterContainer['inline_edit'];
×
2547

UNCOV
2548
                        if (!$inlineEditContainer instanceof Container) {
×
NEW
2549
                                throw new UnexpectedValueException();
×
2550
                        }
2551

2552
                        $inlineEditContainer->addHidden('_id', $id);
×
UNCOV
2553
                        $inlineEditContainer->addHidden('_primary_where_column', $primaryWhereColumn);
×
2554

UNCOV
2555
                        $presenter = $this->getPresenterInstance();
×
2556

UNCOV
2557
                        if ($presenter->isAjax()) {
×
UNCOV
2558
                                $presenter->payload->_datagrid_inline_editing = true;
×
UNCOV
2559
                                $presenter->payload->_datagrid_name = $this->getFullName();
×
2560
                        }
2561

UNCOV
2562
                        $this->redrawItem($id, $primaryWhereColumn);
×
2563
                }
2564
        }
2565

2566
        /********************************************************************************
2567
         *                                  INLINE ADD *
2568
         ********************************************************************************/
2569
        public function addInlineAdd(): InlineAdd
2570
        {
UNCOV
2571
                $this->inlineAdd = new InlineAdd($this);
×
2572

UNCOV
2573
                $this->inlineAdd
×
NEW
2574
                        ->setTitle('contributte_datagrid.add')
×
UNCOV
2575
                        ->setIcon('plus');
×
2576

UNCOV
2577
                return $this->inlineAdd;
×
2578
        }
2579

2580
        public function getInlineAdd(): ?InlineAdd
2581
        {
UNCOV
2582
                return $this->inlineAdd;
×
2583
        }
2584

2585

2586
        /********************************************************************************
2587
         *                               COLUMNS HIDING *
2588
         ********************************************************************************/
2589

2590
        /**
2591
         * Can datagrid hide colums?
2592
         */
2593
        public function canHideColumns(): bool
2594
        {
UNCOV
2595
                return $this->canHideColumns;
×
2596
        }
2597

2598
        /**
2599
         * Order Grid to set columns hideable.
2600
         *
2601
         * @return static
2602
         */
2603
        public function setColumnsHideable(): self
2604
        {
UNCOV
2605
                $this->canHideColumns = true;
×
2606

UNCOV
2607
                return $this;
×
2608
        }
2609

2610
        /********************************************************************************
2611
         *                                COLUMNS SUMMARY *
2612
         ********************************************************************************/
2613
        public function hasColumnsSummary(): bool
2614
        {
UNCOV
2615
                return $this->columnsSummary instanceof ColumnsSummary;
×
2616
        }
2617

2618
        /**
2619
         * @param array|string[] $columns
2620
         */
2621
        public function setColumnsSummary(array $columns, ?callable $rowCallback = null): ColumnsSummary
2622
        {
UNCOV
2623
                if ($this->hasSomeAggregationFunction()) {
×
NEW
2624
                        throw new DatagridException('You can use either ColumnsSummary or AggregationFunctions');
×
2625
                }
2626

UNCOV
2627
                $this->columnsSummary = new ColumnsSummary($this, $columns, $rowCallback);
×
2628

UNCOV
2629
                return $this->columnsSummary;
×
2630
        }
2631

2632
        public function getColumnsSummary(): ?ColumnsSummary
2633
        {
2634
                return $this->columnsSummary;
1✔
2635
        }
2636

2637

2638
        /********************************************************************************
2639
         *                                   INTERNAL *
2640
         ********************************************************************************/
2641

2642
        /**
2643
         * Gets component's full name in component tree
2644
         *
2645
         * @throws DatagridHasToBeAttachedToPresenterComponentException
2646
         */
2647
        public function getFullName(): string
2648
        {
UNCOV
2649
                if ($this->componentFullName === null) {
×
NEW
2650
                        throw new DatagridHasToBeAttachedToPresenterComponentException('Datagrid needs to be attached to presenter in order to get its full name.');
×
2651
                }
2652

UNCOV
2653
                return $this->componentFullName;
×
2654
        }
2655

2656
        /**
2657
         * Tell grid filters to by submitted automatically
2658
         *
2659
         * @return static
2660
         */
2661
        public function setAutoSubmit(bool $autoSubmit = true): self
2662
        {
UNCOV
2663
                $this->autoSubmit = $autoSubmit;
×
2664

UNCOV
2665
                return $this;
×
2666
        }
2667

2668
        public function hasAutoSubmit(): bool
2669
        {
2670
                return $this->autoSubmit;
1✔
2671
        }
2672

2673
        public function getFilterSubmitButton(): SubmitButton
2674
        {
UNCOV
2675
                if ($this->hasAutoSubmit()) {
×
NEW
2676
                        throw new DatagridException('Datagrid has auto-submit. Turn it off before setting filter submit button.');
×
2677
                }
2678

UNCOV
2679
                if ($this->filterSubmitButton === null) {
×
UNCOV
2680
                        $this->filterSubmitButton = new SubmitButton($this);
×
2681
                }
2682

UNCOV
2683
                return $this->filterSubmitButton;
×
2684
        }
2685

2686

2687
        /********************************************************************************
2688
         *                                   INTERNAL *
2689
         ********************************************************************************/
2690

2691
        /**
2692
         * @internal
2693
         */
2694
        public function getColumnsCount(): int
2695
        {
NEW
2696
                $count = count($this->getColumns());
×
2697

UNCOV
2698
                if ($this->actions !== []
×
UNCOV
2699
                        || $this->isSortable()
×
UNCOV
2700
                        || $this->getItemsDetail() !== null
×
UNCOV
2701
                        || $this->getInlineEdit() !== null
×
UNCOV
2702
                        || $this->getInlineAdd() !== null) {
×
UNCOV
2703
                        $count++;
×
2704
                }
2705

UNCOV
2706
                if ($this->hasGroupActions()) {
×
UNCOV
2707
                        $count++;
×
2708
                }
2709

UNCOV
2710
                return $count;
×
2711
        }
2712

2713
        /**
2714
         * @internal
2715
         */
2716
        public function getPrimaryKey(): string
2717
        {
2718
                return $this->primaryKey;
1✔
2719
        }
2720

2721
        /**
2722
         * @return array<Column>
2723
         * @internal
2724
         */
2725
        public function getColumns(): array
2726
        {
2727
                $return = $this->columns;
1✔
2728

2729
                try {
2730
                        $this->getParentComponent();
1✔
2731

2732
                        if (! (bool) $this->getSessionData('_grid_hidden_columns_manipulated', false)) {
1✔
2733
                                $columns_to_hide = [];
1✔
2734

2735
                                foreach ($this->columns as $key => $column) {
1✔
UNCOV
2736
                                        if ($column->getDefaultHide()) {
×
UNCOV
2737
                                                $columns_to_hide[] = $key;
×
2738
                                        }
2739
                                }
2740

2741
                                if ($columns_to_hide !== []) {
1✔
UNCOV
2742
                                        $this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
×
UNCOV
2743
                                        $this->saveSessionData('_grid_hidden_columns_manipulated', true);
×
2744
                                }
2745
                        }
2746

2747
                        $hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
1✔
2748

2749
                        foreach ($hidden_columns as $column) {
1✔
UNCOV
2750
                                if (isset($this->columns[$column])) {
×
UNCOV
2751
                                        $this->columnsVisibility[$column] = [
×
2752
                                                'visible' => false,
2753
                                        ];
2754

UNCOV
2755
                                        unset($return[$column]);
×
2756
                                }
2757
                        }
NEW
2758
                } catch (DatagridHasToBeAttachedToPresenterComponentException) {
×
2759
                        // No need to worry
2760
                }
2761

2762
                return $return;
1✔
2763
        }
2764

2765
        /**
2766
         * @internal
2767
         */
2768
        public function getColumnsVisibility(): array
2769
        {
2770
                $return = $this->columnsVisibility;
1✔
2771

2772
                foreach (array_keys($this->columnsVisibility) as $key) {
1✔
UNCOV
2773
                        $return[$key]['column'] = $this->columns[$key];
×
2774
                }
2775

2776
                return $return;
1✔
2777
        }
2778

2779
        /**
2780
         * @internal
2781
         */
2782
        public function getParentComponent(): Component
2783
        {
2784
                $parent = parent::getParent();
1✔
2785

2786
                if (!$parent instanceof Component) {
1✔
NEW
2787
                        throw new DatagridHasToBeAttachedToPresenterComponentException(
×
UNCOV
2788
                                sprintf(
×
NEW
2789
                                        'Datagrid is attached to: "%s", but instance of %s is needed.',
×
NEW
2790
                                        ($parent !== null ? $parent::class : 'null'),
×
UNCOV
2791
                                        Component::class
×
2792
                                )
2793
                        );
2794
                }
2795

2796
                return $parent;
1✔
2797
        }
2798

2799
        /**
2800
         * @internal
2801
         * @throws UnexpectedValueException
2802
         */
2803
        public function getSortableParentPath(): string
2804
        {
UNCOV
2805
                if ($this->getParentComponent() instanceof IPresenter) {
×
UNCOV
2806
                        return '';
×
2807
                }
2808

UNCOV
2809
                $presenter = $this->getParentComponent()->lookupPath(IPresenter::class, false);
×
2810

UNCOV
2811
                if ($presenter === null) {
×
NEW
2812
                        throw new UnexpectedValueException(
×
UNCOV
2813
                                sprintf('%s needs %s', self::class, IPresenter::class)
×
2814
                        );
2815
                }
2816

UNCOV
2817
                return $presenter;
×
2818
        }
2819

2820
        /**
2821
         * Some of datagrid columns may be hidden by default
2822
         *
2823
         * @internal
2824
         * @return static
2825
         */
2826
        public function setSomeColumnDefaultHide(bool $defaultHide): self
2827
        {
UNCOV
2828
                $this->someColumnDefaultHide = $defaultHide;
×
2829

UNCOV
2830
                return $this;
×
2831
        }
2832

2833
        /**
2834
         * Are some of columns hidden bydefault?
2835
         *
2836
         * @internal
2837
         */
2838
        public function hasSomeColumnDefaultHide(): bool
2839
        {
UNCOV
2840
                return $this->someColumnDefaultHide;
×
2841
        }
2842

2843
        /**
2844
         * Simply refresh url
2845
         *
2846
         * @internal
2847
         */
2848
        public function handleRefreshState(): void
2849
        {
UNCOV
2850
                $this->findSessionValues();
×
UNCOV
2851
                $this->findDefaultFilter();
×
UNCOV
2852
                $this->findDefaultSort();
×
UNCOV
2853
                $this->findDefaultPerPage();
×
2854

UNCOV
2855
                $this->getPresenterInstance()->payload->_datagrid_url = $this->refreshURL;
×
UNCOV
2856
                $this->redrawControl('non-existing-snippet');
×
2857
        }
2858

2859
        /**
2860
         * @internal
2861
         */
2862
        public function setCustomPaginatorTemplate(string $templateFile): void
2863
        {
UNCOV
2864
                $this->customPaginatorTemplate = $templateFile;
×
2865
        }
2866

2867
        protected function createSorting(array $sort, ?callable $sortCallback = null): Sorting
1✔
2868
        {
2869
                foreach ($sort as $key => $order) {
1✔
2870
                        unset($sort[$key]);
1✔
2871

2872
                        if ($order !== 'ASC' && $order !== 'DESC') {
1✔
UNCOV
2873
                                continue;
×
2874
                        }
2875

2876
                        try {
2877
                                $column = $this->getColumn($key);
1✔
2878

2879
                        } catch (DatagridColumnNotFoundException) {
1✔
2880
                                continue;
1✔
2881
                        }
2882

UNCOV
2883
                        $sort[$column->getSortingColumn()] = $order;
×
2884
                }
2885

2886
                if ($sortCallback === null && isset($column)) {
1✔
UNCOV
2887
                        $sortCallback = $column->getSortableCallback();
×
2888
                }
2889

2890
                return new Sorting($sort, $sortCallback);
1✔
2891
        }
2892

2893
        /**
2894
         * @throws DatagridException
2895
         */
2896
        protected function addColumn(string $key, Column $column): Column
1✔
2897
        {
2898
                if (isset($this->columns[$key])) {
1✔
NEW
2899
                        throw new DatagridException(
×
UNCOV
2900
                                sprintf('There is already column at key [%s] defined.', $key)
×
2901
                        );
2902
                }
2903

2904
                $this->onColumnAdd($key, $column);
1✔
2905

2906
                $this->columnsVisibility[$key] = ['visible' => true];
1✔
2907

2908
                return $this->columns[$key] = $column;
1✔
2909
        }
2910

2911
        /**
2912
         * Check whether given key already exists in $this->filters
2913
         *
2914
         * @throws DatagridException
2915
         */
2916
        protected function addActionCheck(string $key): void
1✔
2917
        {
2918
                if (isset($this->actions[$key])) {
1✔
2919
                        throw new DatagridException(
1✔
2920
                                sprintf('There is already action at key [%s] defined.', $key)
1✔
2921
                        );
2922
                }
2923
        }/********************************************************************************
1✔
2924
          *                                    FILTERS *
2925
          ********************************************************************************/
2926

2927
        /**
2928
         * Check whether given key already exists in $this->filters
2929
         *
2930
         * @throws DatagridException
2931
         */
2932
        protected function addFilterCheck(string $key): void
1✔
2933
        {
2934
                if (isset($this->filters[$key])) {
1✔
NEW
2935
                        throw new DatagridException(
×
UNCOV
2936
                                sprintf('There is already action at key [%s] defined.', $key)
×
2937
                        );
2938
                }
2939
        }
1✔
2940

2941
        protected function addToExports(Export $export): Export
1✔
2942
        {
2943
                $id = count($this->exports) > 0 ? count($this->exports) + 1 : 1;
1✔
2944

2945
                $link = new Link($this, 'export!', ['id' => $id]);
1✔
2946

2947
                $export->setLink($link);
1✔
2948

2949
                return $this->exports[$id] = $export;
1✔
2950
        }
2951

2952
        private function getPresenterInstance(): Presenter
2953
        {
2954
                return $this->getPresenter();
1✔
2955
        }
2956

2957
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc