Issues (139)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/DataGrid.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid;
10
11
use Nette;
12
use Nette\Application\UI\Form;
13
use Nette\Application\UI\Link;
14
use Nette\Application\UI\PresenterComponent;
15
use Ublaboo\DataGrid\AggregationFunction\TDataGridAggregationFunction;
16
use Ublaboo\DataGrid\ColumnsSummary;
17
use Ublaboo\DataGrid\Exception\DataGridColumnNotFoundException;
18
use Ublaboo\DataGrid\Exception\DataGridException;
19
use Ublaboo\DataGrid\Exception\DataGridFilterNotFoundException;
20
use Ublaboo\DataGrid\Exception\DataGridHasToBeAttachedToPresenterComponentException;
21
use Ublaboo\DataGrid\Filter\IFilterDate;
22
use Ublaboo\DataGrid\InlineEdit\InlineEdit;
23
use Ublaboo\DataGrid\Toolbar\ToolbarButton;
24
use Ublaboo\DataGrid\Utils\ArraysHelper;
25
use Ublaboo\DataGrid\Utils\Sorting;
26
27
/**
28
 * @method onRedraw()
29
 * @method onRender()
30
 * @method onColumnAdd()
31
 */
32 1
class DataGrid extends Nette\Application\UI\Control
33
{
34 1
	use TDataGridAggregationFunction;
35
36
	/**
37
	 * @var callable[]
38
	 */
39
	public $onRedraw;
40
41
	/**
42
	 * @var callable[]
43
	 */
44
	public $onRender = [];
45
46
	/**
47
	 * @var callable[]
48
	 */
49
	public $onExport;
50
51
	/**
52
	 * @var callable[]
53
	 */
54
	public $onColumnAdd;
55
56
	/**
57
	 * @var callable[]
58
	 * @deprecated use $onFiltersAssembled
59
	 */
60
	public $onFiltersAssabled;
61
62
	/**
63
	 * @var callable[]
64
	 */
65
	public $onFiltersAssembled;
66
67
	/**
68
	 * @var string
69
	 */
70
	public static $icon_prefix = 'fa fa-';
71
72
	/**
73
	 * Default form method
74
	 * @var string
75
	 */
76
	public static $form_method = 'post';
77
78
	/**
79
	 * When set to TRUE, datagrid throws an exception
80
	 * 	when tring to get related entity within join and entity does not exist
81
	 * @var bool
82
	 */
83
	public $strict_entity_property = false;
84
85
	/**
86
	 * When set to TRUE, datagrid throws an exception
87
	 * 	when tring to set filter value, that does not exist (select, multiselect, etc)
88
	 * @var bool
89
	 */
90
	public $strict_session_filter_values = true;
91
92
	/**
93
	 * @var int
94
	 * @persistent
95
	 */
96
	public $page = 1;
97
98
	/**
99
	 * @var int|string
100
	 * @persistent
101
	 */
102
	public $per_page;
103
104
	/**
105
	 * @var array
106
	 * @persistent
107
	 */
108
	public $sort = [];
109
110
	/**
111
	 * @var array
112
	 */
113
	public $default_sort = [];
114
115
	/**
116
	 * @var array
117
	 */
118
	public $default_filter = [];
119
120
	/**
121
	 * @var bool
122
	 */
123
	public $default_filter_use_on_reset = true;
124
125
	/**
126
	 * @var bool
127
	 */
128
	public $default_sort_use_on_reset = true;
129
130
	/**
131
	 * @var array
132
	 * @persistent
133
	 */
134
	public $filter = [];
135
136
	/**
137
	 * @var callable|null
138
	 */
139
	protected $sort_callback = null;
140
141
	/**
142
	 * @var bool
143
	 */
144
	protected $use_happy_components = true;
145
146
	/**
147
	 * @var callable
148
	 */
149
	protected $rowCallback;
150
151
	/**
152
	 * @var array
153
	 */
154
	protected $items_per_page_list;
155
156
	/**
157
	 * @var int
158
	 */
159
	protected $default_per_page;
160
161
	/**
162
	 * @var string
163
	 */
164
	protected $template_file;
165
166
	/**
167
	 * @var Column\IColumn[]
168
	 */
169
	protected $columns = [];
170
171
	/**
172
	 * @var Column\Action[]
173
	 */
174
	protected $actions = [];
175
176
	/**
177
	 * @var GroupAction\GroupActionCollection
178
	 */
179
	protected $group_action_collection;
180
181
	/**
182
	 * @var Filter\Filter[]
183
	 */
184
	protected $filters = [];
185
186
	/**
187
	 * @var Export\Export[]
188
	 */
189
	protected $exports = [];
190
191
	/**
192
	 * @var ToolbarButton[]
193
	 */
194
	protected $toolbar_buttons = [];
195
196
	/**
197
	 * @var DataModel
198
	 */
199
	protected $dataModel;
200
201
	/**
202
	 * @var DataFilter
203
	 */
204
	protected $dataFilter;
205
206
	/**
207
	 * @var string
208
	 */
209
	protected $primary_key = 'id';
210
211
	/**
212
	 * @var bool
213
	 */
214
	protected $do_paginate = true;
215
216
	/**
217
	 * @var bool
218
	 */
219
	protected $csv_export = true;
220
221
	/**
222
	 * @var bool
223
	 */
224
	protected $csv_export_filtered = true;
225
226
	/**
227
	 * @var bool
228
	 */
229
	protected $sortable = false;
230
231
	/**
232
	 * @var bool
233
	 */
234
	protected $multiSort = false;
235
236
	/**
237
	 * @var string
238
	 */
239
	protected $sortable_handler = 'sort!';
240
241
	/**
242
	 * @var string
243
	 */
244
	protected $original_template;
245
246
	/**
247
	 * @var array
248
	 */
249
	protected $redraw_item;
250
251
	/**
252
	 * @var mixed
253
	 */
254
	protected $translator;
255
256
	/**
257
	 * @var bool
258
	 */
259
	protected $force_filter_active;
260
261
	/**
262
	 * @var callable
263
	 */
264
	protected $tree_view_children_callback;
265
266
	/**
267
	 * @var callable
268
	 */
269
	protected $tree_view_has_children_callback;
270
271
	/**
272
	 * @var string
273
	 */
274
	protected $tree_view_has_children_column;
275
276
	/**
277
	 * @var bool
278
	 */
279
	protected $outer_filter_rendering = false;
280
281
	/**
282
	 * @var int
283
	 */
284
	protected $outer_filter_columns_count = 2;
285
286
	/**
287
	 * @var bool
288
	 */
289
	protected $collapsible_outer_filters = true;
290
291
	/**
292
	 * @var array
293
	 */
294
	protected $columns_export_order = [];
295
296
	/**
297
	 * @var bool
298
	 */
299
	protected $remember_state = true;
300
301
	/**
302
	 * @var bool
303
	 */
304
	protected $refresh_url = true;
305
306
	/**
307
	 * @var Nette\Http\SessionSection
308
	 */
309
	protected $grid_session;
310
311
	/**
312
	 * @var Column\ItemDetail
313
	 */
314
	protected $items_detail;
315
316
	/**
317
	 * @var array
318
	 */
319
	protected $row_conditions = [
320
		'group_action' => false,
321
		'action' => [],
322
	];
323
324
	/**
325
	 * @var array
326
	 */
327
	protected $column_callbacks = [];
328
329
	/**
330
	 * @var bool
331
	 */
332
	protected $can_hide_columns = false;
333
334
	/**
335
	 * @var array
336
	 */
337
	protected $columns_visibility = [];
338
339
	/**
340
	 * @var InlineEdit
341
	 */
342
	protected $inlineEdit;
343
344
	/**
345
	 * @var InlineEdit
346
	 */
347
	protected $inlineAdd;
348
349
	/**
350
	 * @var bool
351
	 */
352
	protected $snippets_set = false;
353
354
	/**
355
	 * @var bool
356
	 */
357
	protected $some_column_default_hide = false;
358
359
	/**
360
	 * @var ColumnsSummary
361
	 */
362
	protected $columnsSummary;
363
364
	/**
365
	 * @var bool
366
	 */
367
	protected $auto_submit = true;
368
369
	/**
370
	 * @var Filter\SubmitButton|NULL
371
	 */
372
	protected $filter_submit_button = null;
373
374
	/**
375
	 * @var bool
376
	 */
377
	protected $has_column_reset = true;
378
379
	/**
380
	 * @var bool
381
	 */
382
	protected $show_selected_rows_count = true;
383
384
385
	/**
386
	 * @param Nette\ComponentModel\IContainer|NULL $parent
387
	 * @param string                               $name
388
	 */
389
	public function __construct(Nette\ComponentModel\IContainer $parent = null, $name = null)
390
	{
391 1
		parent::__construct();
392
393 1
		if ($parent !== null) {
394 1
			$parent->addComponent($this, $name);
395
		}
396
397 1
		$this->monitor('Nette\Application\UI\Presenter');
398
399
		/**
400
		 * Try to find previous filters, pagination, per_page and other values in session
401
		 */
402 1
		$this->onRender[] = [$this, 'findSessionValues'];
403 1
		$this->onExport[] = [$this, 'findSessionValues'];
404
405
		/**
406
		 * Find default filter values
407
		 */
408 1
		$this->onRender[] = [$this, 'findDefaultFilter'];
409 1
		$this->onExport[] = [$this, 'findDefaultFilter'];
410
411
		/**
412
		 * Find default sort
413
		 */
414 1
		$this->onRender[] = [$this, 'findDefaultSort'];
415 1
		$this->onExport[] = [$this, 'findDefaultSort'];
416
417
		/**
418
		 * Find default items per page
419
		 */
420 1
		$this->onRender[] = [$this, 'findDefaultPerPage'];
421
422
		/**
423
		 * Notify about that json js extension
424
		 */
425 1
		$this->onFiltersAssembled[] = [$this, 'sendNonEmptyFiltersInPayload'];
426 1
	}
427
428
429
	/**
430
	 * {inheritDoc}
431
	 * @return void
432
	 */
433
	public function attached($presenter)
434
	{
435 1
		parent::attached($presenter);
436
437 1
		if ($presenter instanceof Nette\Application\UI\Presenter) {
438
			/**
439
			 * Get session
440
			 */
441 1
			if ($this->remember_state) {
442 1
				$this->grid_session = $presenter->getSession($this->getSessionSectionName());
443
			}
444
		}
445 1
	}
446
447
448
	/********************************************************************************
449
	 *                                  RENDERING                                   *
450
	 ********************************************************************************/
451
452
453
	/**
454
	 * Render template
455
	 * @return void
456
	 */
457
	public function render()
458
	{
459
		/**
460
		 * Check whether datagrid has set some columns, initiated data source, etc
461
		 */
462
		if (!($this->dataModel instanceof DataModel)) {
463
			throw new DataGridException('You have to set a data source first.');
464
		}
465
466
		if (empty($this->columns)) {
467
			throw new DataGridException('You have to add at least one column.');
468
		}
469
470
		$template = $this->getTemplate();
471
		$template->setTranslator($this->getTranslator());
472
473
		/**
474
		 * Invoke possible events
475
		 */
476
		$this->onRender($this);
477
478
		/**
479
		 * Prepare data for rendering (datagrid may render just one item)
480
		 */
481
		$rows = [];
482
483
		if (!empty($this->redraw_item)) {
484
			$items = $this->dataModel->filterRow($this->redraw_item);
485
		} else {
486
			$items = Nette\Utils\Callback::invokeArgs(
487
				[$this->dataModel, 'filterData'],
488
				[
489
					$this->getPaginator(),
490
					$this->createSorting($this->sort, $this->sort_callback),
491
					$this->assembleFilters(),
492
				]
493
			);
494
		}
495
496
		$callback = $this->rowCallback ?: null;
497
		$hasGroupActionOnRows = false;
498
499
		foreach ($items as $item) {
500
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
501
502
			if (!$hasGroupActionOnRows && $row->hasGroupAction()) {
503
				$hasGroupActionOnRows = true;
504
			}
505
506
			if ($callback) {
507
				$callback($item, $row->getControl());
508
			}
509
510
			/**
511
			 * Walkaround for item snippet - snippet is the <tr> element and its class has to be also updated
512
			 */
513
			if (!empty($this->redraw_item)) {
514
				$this->getPresenter()->payload->_datagrid_redraw_item_class = $row->getControlClass();
515
				$this->getPresenter()->payload->_datagrid_redraw_item_id = $row->getId();
516
			}
517
		}
518
519
		if ($hasGroupActionOnRows) {
520
			$hasGroupActionOnRows = $this->hasGroupActions();
521
		}
522
523
		if ($this->isTreeView()) {
524
			$template->add('tree_view_has_children_column', $this->tree_view_has_children_column);
525
		}
526
527
		$template->rows = $rows;
528
529
		$template->columns = $this->getColumns();
530
		$template->actions = $this->actions;
531
		$template->exports = $this->exports;
532
		$template->filters = $this->filters;
533
		$template->toolbar_buttons = $this->toolbar_buttons;
534
		$template->aggregation_functions = $this->getAggregationFunctions();
535
		$template->multiple_aggregation_function = $this->getMultipleAggregationFunction();
536
537
		$template->filter_active = $this->isFilterActive();
538
		$template->original_template = $this->getOriginalTemplateFile();
539
		$template->icon_prefix = static::$icon_prefix;
540
		$template->icon_prefix = static::$icon_prefix;
541
		$template->items_detail = $this->items_detail;
542
		$template->columns_visibility = $this->getColumnsVisibility();
543
		$template->columnsSummary = $this->columnsSummary;
544
545
		$template->inlineEdit = $this->inlineEdit;
546
		$template->inlineAdd = $this->inlineAdd;
547
548
		$template->hasGroupActions = $this->hasGroupActions();
549
		$template->hasGroupActionOnRows = $hasGroupActionOnRows;
550
551
		/**
552
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
553
		 */
554
		$template->filter = $this['filter'];
555
556
		/**
557
		 * Set template file and render it
558
		 */
559
		$template->setFile($this->getTemplateFile());
560
		$template->render();
561
	}
562
563
564
	/********************************************************************************
565
	 *                                 ROW CALLBACK                                 *
566
	 ********************************************************************************/
567
568
569
	/**
570
	 * Each row can be modified with user callback
571
	 * @param  callable  $callback
572
	 * @return static
573
	 */
574
	public function setRowCallback(callable $callback)
575
	{
576
		$this->rowCallback = $callback;
577
578
		return $this;
579
	}
580
581
582
	/********************************************************************************
583
	 *                                 DATA SOURCE                                  *
584
	 ********************************************************************************/
585
586
587
	/**
588
	 * By default ID, you can change that
589
	 * @param string $primary_key
590
	 * @return static
591
	 */
592
	public function setPrimaryKey($primary_key)
593
	{
594
		if ($this->dataModel instanceof DataModel) {
595
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
596
		}
597
598
		$this->primary_key = $primary_key;
599
600
		return $this;
601
	}
602
603
604
	/**
605
	 * Set Grid data source
606
	 * @param DataSource\IDataSource|array|\Dibi\Fluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
607
	 * @return static
608
	 */
609
	public function setDataSource($source)
610
	{
611 1
		$this->dataModel = new DataModel($source, $this->primary_key);
612
613 1
		$this->dataModel->onBeforeFilter[] = [$this, 'beforeDataModelFilter'];
614 1
		$this->dataModel->onAfterFilter[] = [$this, 'afterDataModelFilter'];
615 1
		$this->dataModel->onAfterPaginated[] = [$this, 'afterDataModelPaginated'];
616
617 1
		return $this;
618
	}
619
620
621
	/**
622
	 * @return DataSource\IDataSource|NULL
623
	 */
624
	public function getDataSource()
625
	{
626
		if (!$this->dataModel) {
627
			return null;
628
		}
629
630
		return $this->dataModel->getDataSource();
631
	}
632
633
634
	/********************************************************************************
635
	 *                                  TEMPLATING                                  *
636
	 ********************************************************************************/
637
638
639
	/**
640
	 * Set custom template file to render
641
	 * @param string $template_file
642
	 * @return static
643
	 */
644
	public function setTemplateFile($template_file)
645
	{
646
		$this->template_file = $template_file;
647
648
		return $this;
649
	}
650
651
652
	/**
653
	 * Get DataGrid template file
654
	 * @return string
655
	 * @return static
656
	 */
657
	public function getTemplateFile()
658
	{
659
		return $this->template_file ?: $this->getOriginalTemplateFile();
660
	}
661
662
663
	/**
664
	 * Get DataGrid original template file
665
	 * @return string
666
	 */
667
	public function getOriginalTemplateFile()
668
	{
669
		return __DIR__ . '/templates/datagrid.latte';
670
	}
671
672
673
	/**
674
	 * Tell datagrid wheteher to use or not happy components
675
	 * @param  bool|NULL $use If not given, return value of static::$use_happy_components
676
	 * @return void|bool
677
	 */
678
	public function useHappyComponents($use = null)
679
	{
680
		if ($use === null) {
681
			return $this->use_happy_components;
682
		}
683
684
		$this->use_happy_components = (bool) $use;
685
	}
686
687
688
	/********************************************************************************
689
	 *                                   SORTING                                    *
690
	 ********************************************************************************/
691
692
693
	/**
694
	 * Set default sorting
695
	 * @param array $sort
696
	 * @param bool  $use_on_reset
697
	 * @return static
698
	 */
699
	public function setDefaultSort($sort, $use_on_reset = true)
700
	{
701
		if (is_string($sort)) {
702
			$sort = [$sort => 'ASC'];
703
		} else {
704
			$sort = (array) $sort;
705
		}
706
707
		$this->default_sort = $sort;
708
		$this->default_sort_use_on_reset = (bool) $use_on_reset;
709
710
		return $this;
711
	}
712
713
714
	/**
715
	 * Return default sort for column, if specified
716
	 * @param string $columnKey
717
	 * @return string|NULL
718
	 */
719
	public function getColumnDefaultSort($columnKey)
720
	{
721
		if (isset($this->default_sort[$columnKey])) {
722
			return $this->default_sort[$columnKey];
723
		}
724
725
		return NULL;
726
	}
727
728
729
	/**
730
	 * User may set default sorting, apply it
731
	 * @return void
732
	 */
733
	public function findDefaultSort()
734
	{
735 1
		if (!empty($this->sort)) {
736
			return;
737
		}
738
739 1
		if ($this->getSessionData('_grid_has_sorted')) {
740
			return;
741
		}
742
743 1
		if (!empty($this->default_sort)) {
744
			$this->sort = $this->default_sort;
745
		}
746
747 1
		$this->saveSessionData('_grid_sort', $this->sort);
748 1
	}
749
750
751
	/**
752
	 * Set grido to be sortable
753
	 * @param bool $sortable
754
	 * @return static
755
	 */
756
	public function setSortable($sortable = true)
757
	{
758
		if ($this->getItemsDetail()) {
759
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
760
		}
761
762
		$this->sortable = (bool) $sortable;
763
764
		return $this;
765
	}
766
767
768
	/**
769
	 * Tell whether DataGrid is sortable
770
	 * @return bool
771
	 */
772
	public function isSortable()
773
	{
774
		return $this->sortable;
775
	}
776
777
778
	/**
779
	 * Enable multi-sorting capability
780
	 * @param bool  $multiSort
781
	 * @return static
782
	 */
783
	public function setMultiSortEnabled($multiSort = true)
784
	{
785
		$this->multiSort = (bool) $multiSort;
786
787
		return $this;
788
	}
789
790
791
	/**
792
	 * Tell wether DataGrid can be sorted by multiple columns
793
	 * @return bool
794
	 */
795
	public function isMultiSortEnabled()
796
	{
797
		return $this->multiSort;
798
	}
799
800
801
	/**
802
	 * Set sortable handle
803
	 * @param string $handler
804
	 * @return static
805
	 */
806
	public function setSortableHandler($handler = 'sort!')
807
	{
808
		$this->sortable_handler = (string) $handler;
809
810
		return $this;
811
	}
812
813
814
	/**
815
	 * Return sortable handle name
816
	 * @return string
817
	 */
818
	public function getSortableHandler()
819
	{
820
		return $this->sortable_handler;
821
	}
822
823
824
	/**
825
	 * @param Column  $column
826
	 * @return array
827
	 * @internal
828
	 */
829
	public function getSortNext(\Ublaboo\DataGrid\Column\Column $column)
830
	{
831
		$sort = $column->getSortNext();
832
833
		if ($this->isMultiSortEnabled()) {
834
			$sort = array_merge($this->sort, $sort);
835
		}
836
837
		return array_filter($sort);
838
	}
839
840
841
	/**
842
	 * @param  array         $sort
843
	 * @param  callable|NULL $sort_callback
844
	 * @return Sorting
845
	 */
846
	protected function createSorting(array $sort, callable $sort_callback = null)
847
	{
848 1
		foreach ($sort as $key => $order) {
849
			unset($sort[$key]);
850
851
			try {
852
				$column = $this->getColumn($key);
853
854
			} catch (DataGridColumnNotFoundException $e) {
855
				continue;
856
			}
857
858
			$sort[$column->getSortingColumn()] = $order;
859
		}
860
861 1
		if (!$sort_callback && isset($column)) {
862
			$sort_callback = $column->getSortableCallback();
863
		}
864
865 1
		return new Sorting($sort, $sort_callback);
866
	}
867
868
869
	/********************************************************************************
870
	 *                                  TREE VIEW                                   *
871
	 ********************************************************************************/
872
873
874
	/**
875
	 * Is tree view set?
876
	 * @return bool
877
	 */
878
	public function isTreeView()
879
	{
880
		return (bool) $this->tree_view_children_callback;
881
	}
882
883
884
	/**
885
	 * Setting tree view
886
	 * @param callable $get_children_callback
887
	 * @param string|callable $tree_view_has_children_column
888
	 * @return static
889
	 */
890
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
891
	{
892
		if (!is_callable($get_children_callback)) {
893
			throw new DataGridException(
894
				'Parameters to method DataGrid::setTreeView must be of type callable'
895
			);
896
		}
897
898
		if (is_callable($tree_view_has_children_column)) {
899
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
900
			$tree_view_has_children_column = null;
901
		}
902
903
		$this->tree_view_children_callback = $get_children_callback;
904
		$this->tree_view_has_children_column = $tree_view_has_children_column;
905
906
		/**
907
		 * TUrn off pagination
908
		 */
909
		$this->setPagination(false);
910
911
		/**
912
		 * Set tree view template file
913
		 */
914
		if (!$this->template_file) {
915
			$this->setTemplateFile(__DIR__ . '/templates/datagrid_tree.latte');
916
		}
917
918
		return $this;
919
	}
920
921
922
	/**
923
	 * Is tree view children callback set?
924
	 * @return bool
925
	 */
926
	public function hasTreeViewChildrenCallback()
927
	{
928
		return is_callable($this->tree_view_has_children_callback);
929
	}
930
931
932
	/**
933
	 * @param  mixed $item
934
	 * @return bool
935
	 */
936
	public function treeViewChildrenCallback($item)
937
	{
938
		return call_user_func($this->tree_view_has_children_callback, $item);
939
	}
940
941
942
	/********************************************************************************
943
	 *                                    COLUMNS                                   *
944
	 ********************************************************************************/
945
946
947
	/**
948
	 * Add text column with no other formating
949
	 * @param  string      $key
950
	 * @param  string      $name
951
	 * @param  string|null $column
952
	 * @return Column\ColumnText
953
	 */
954
	public function addColumnText($key, $name, $column = null)
955
	{
956 1
		$this->addColumnCheck($key);
957 1
		$column = $column ?: $key;
958
959 1
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
960
	}
961
962
963
	/**
964
	 * Add column with link
965
	 * @param  string      $key
966
	 * @param  string      $name
967
	 * @param  string|null $column
968
	 * @return Column\ColumnLink
969
	 */
970
	public function addColumnLink($key, $name, $href = null, $column = null, array $params = null)
971
	{
972 1
		$this->addColumnCheck($key);
973 1
		$column = $column ?: $key;
974 1
		$href = $href ?: $key;
975
976 1
		if ($params === null) {
977 1
			$params = [$this->primary_key];
978
		}
979
980 1
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
981
	}
982
983
984
	/**
985
	 * Add column with possible number formating
986
	 * @param  string      $key
987
	 * @param  string      $name
988
	 * @param  string|null $column
989
	 * @return Column\ColumnNumber
990
	 */
991
	public function addColumnNumber($key, $name, $column = null)
992
	{
993 1
		$this->addColumnCheck($key);
994 1
		$column = $column ?: $key;
995
996 1
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
997
	}
998
999
1000
	/**
1001
	 * Add column with date formating
1002
	 * @param  string      $key
1003
	 * @param  string      $name
1004
	 * @param  string|null $column
1005
	 * @return Column\ColumnDateTime
1006
	 */
1007
	public function addColumnDateTime($key, $name, $column = null)
1008
	{
1009 1
		$this->addColumnCheck($key);
1010 1
		$column = $column ?: $key;
1011
1012 1
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
1013
	}
1014
1015
1016
	/**
1017
	 * Add column status
1018
	 * @param  string      $key
1019
	 * @param  string      $name
1020
	 * @param  string|null $column
1021
	 * @return Column\ColumnStatus
1022
	 */
1023
	public function addColumnStatus($key, $name, $column = null)
1024
	{
1025 1
		$this->addColumnCheck($key);
1026 1
		$column = $column ?: $key;
1027
1028 1
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
1029
	}
1030
1031
1032
	/**
1033
	 * @param string $key
1034
	 * @param Column\Column $column
1035
	 * @return Column\Column
1036
	 */
1037
	protected function addColumn($key, Column\Column $column)
1038
	{
1039 1
		$this->onColumnAdd($key, $column);
1040
1041 1
		$this->columns_visibility[$key] = [
1042
			'visible' => true,
1043
		];
1044
1045 1
		return $this->columns[$key] = $column;
1046
	}
1047
1048
1049
	/**
1050
	 * Return existing column
1051
	 * @param  string $key
1052
	 * @return Column\Column
1053
	 * @throws DataGridException
1054
	 */
1055
	public function getColumn($key)
1056
	{
1057 1
		if (!isset($this->columns[$key])) {
1058
			throw new DataGridColumnNotFoundException("There is no column at key [$key] defined.");
1059
		}
1060
1061 1
		return $this->columns[$key];
1062
	}
1063
1064
1065
	/**
1066
	 * Remove column
1067
	 * @param string $key
1068
	 * @return static
1069
	 */
1070
	public function removeColumn($key)
1071
	{
1072 1
		unset($this->columns_visibility[$key], $this->columns[$key]);
1073
1074
1075 1
		return $this;
1076
	}
1077
1078
1079
	/**
1080
	 * Check whether given key already exists in $this->columns
1081
	 * @param  string $key
1082
	 * @throws DataGridException
1083
	 */
1084
	protected function addColumnCheck($key)
1085
	{
1086 1
		if (isset($this->columns[$key])) {
1087
			throw new DataGridException("There is already column at key [$key] defined.");
1088
		}
1089 1
	}
1090
1091
1092
	/********************************************************************************
1093
	 *                                    ACTIONS                                   *
1094
	 ********************************************************************************/
1095
1096
1097
	/**
1098
	 * Create action
1099
	 * @param string     $key
1100
	 * @param string     $name
1101
	 * @param string     $href
1102
	 * @param array|null $params
1103
	 * @return Column\Action
1104
	 */
1105
	public function addAction($key, $name, $href = null, array $params = null)
1106
	{
1107 1
		$this->addActionCheck($key);
1108
1109 1
		$href = $href ?: $key;
1110
1111 1
		if ($params === null) {
1112 1
			$params = [$this->primary_key];
1113
		}
1114
1115 1
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
1116
	}
1117
1118
1119
	/**
1120
	 * Create action callback
1121
	 * @param string     $key
1122
	 * @param string     $name
1123
	 * @return Column\Action
1124
	 */
1125
	public function addActionCallback($key, $name, $callback = null)
1126
	{
1127
		$this->addActionCheck($key);
1128
1129
		$params = ['__id' => $this->primary_key];
1130
1131
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
1132
1133
		if ($callback) {
1134
			if (!is_callable($callback)) {
1135
				throw new DataGridException('ActionCallback callback has to be callable.');
1136
			}
1137
1138
			$action->onClick[] = $callback;
1139
		}
1140
1141
		return $action;
1142
	}
1143
1144
1145
	/**
1146
	 * @param string $key
1147
	 */
1148
	public function addMultiAction($key, $name)
1149
	{
1150
		$this->addActionCheck($key);
1151
1152
		$this->actions[$key] = $action = new Column\MultiAction($this, $name);
1153
1154
		return $action;
1155
	}
1156
1157
1158
	/**
1159
	 * Get existing action
1160
	 * @param  string       $key
1161
	 * @return Column\Action
1162
	 * @throws DataGridException
1163
	 */
1164 View Code Duplication
	public function getAction($key)
1165
	{
1166
		if (!isset($this->actions[$key])) {
1167
			throw new DataGridException("There is no action at key [$key] defined.");
1168
		}
1169
1170
		return $this->actions[$key];
1171
	}
1172
1173
1174
	/**
1175
	 * Remove action
1176
	 * @param string $key
1177
	 * @return static
1178
	 */
1179
	public function removeAction($key)
1180
	{
1181
		unset($this->actions[$key]);
1182
1183
		return $this;
1184
	}
1185
1186
1187
	/**
1188
	 * Check whether given key already exists in $this->filters
1189
	 * @param  string $key
1190
	 * @throws DataGridException
1191
	 */
1192
	protected function addActionCheck($key)
1193
	{
1194 1
		if (isset($this->actions[$key])) {
1195 1
			throw new DataGridException("There is already action at key [$key] defined.");
1196
		}
1197 1
	}
1198
1199
1200
	/********************************************************************************
1201
	 *                                    FILTERS                                   *
1202
	 ********************************************************************************/
1203
1204
1205
	/**
1206
	 * Add filter fot text search
1207
	 * @param string       $key
1208
	 * @param string       $name
1209
	 * @param array|string $columns
1210
	 * @return Filter\FilterText
1211
	 * @throws DataGridException
1212
	 */
1213
	public function addFilterText($key, $name, $columns = null)
1214
	{
1215 1
		$columns = $columns === null? [$key] : (is_string($columns) ? [$columns] : $columns);
1216
1217 1
		if (!is_array($columns)) {
1218
			throw new DataGridException('Filter Text can accept only array or string.');
1219
		}
1220
1221 1
		$this->addFilterCheck($key);
1222
1223 1
		return $this->filters[$key] = new Filter\FilterText($this, $key, $name, $columns);
1224
	}
1225
1226
1227
	/**
1228
	 * Add select box filter
1229
	 * @param string $key
1230
	 * @param string $name
1231
	 * @param array  $options
1232
	 * @param string $column
1233
	 * @return Filter\FilterSelect
1234
	 * @throws DataGridException
1235
	 */
1236 View Code Duplication
	public function addFilterSelect($key, $name, array $options, $column = null)
1237
	{
1238
		$column = $column ?: $key;
1239
1240
		if (!is_string($column)) {
1241
			throw new DataGridException('Filter Select can only filter in one column.');
1242
		}
1243
1244
		$this->addFilterCheck($key);
1245
1246
		return $this->filters[$key] = new Filter\FilterSelect($this, $key, $name, $options, $column);
1247
	}
1248
1249
1250
	/**
1251
	 * Add multi select box filter
1252
	 * @param string $key
1253
	 * @param string $name
1254
	 * @param array  $options
1255
	 * @param string $column
1256
	 * @return Filter\FilterSelect
1257
	 * @throws DataGridException
1258
	 */
1259 View Code Duplication
	public function addFilterMultiSelect($key, $name, array $options, $column = null)
1260
	{
1261
		$column = $column ?: $key;
1262
1263
		if (!is_string($column)) {
1264
			throw new DataGridException('Filter MultiSelect can only filter in one column.');
1265
		}
1266
1267
		$this->addFilterCheck($key);
1268
1269
		return $this->filters[$key] = new Filter\FilterMultiSelect($this, $key, $name, $options, $column);
1270
	}
1271
1272
1273
	/**
1274
	 * Add datepicker filter
1275
	 * @param string $key
1276
	 * @param string $name
1277
	 * @param string $column
1278
	 * @return Filter\FilterDate
1279
	 * @throws DataGridException
1280
	 */
1281 View Code Duplication
	public function addFilterDate($key, $name, $column = null)
1282
	{
1283
		$column = $column ?: $key;
1284
1285
		if (!is_string($column)) {
1286
			throw new DataGridException('FilterDate can only filter in one column.');
1287
		}
1288
1289
		$this->addFilterCheck($key);
1290
1291
		return $this->filters[$key] = new Filter\FilterDate($this, $key, $name, $column);
1292
	}
1293
1294
1295
	/**
1296
	 * Add range filter (from - to)
1297
	 * @param string $key
1298
	 * @param string $name
1299
	 * @param string $column
1300
	 * @return Filter\FilterRange
1301
	 * @throws DataGridException
1302
	 */
1303 View Code Duplication
	public function addFilterRange($key, $name, $column = null, $name_second = '-')
1304
	{
1305
		$column = $column ?: $key;
1306
1307
		if (!is_string($column)) {
1308
			throw new DataGridException('FilterRange can only filter in one column.');
1309
		}
1310
1311
		$this->addFilterCheck($key);
1312
1313
		return $this->filters[$key] = new Filter\FilterRange($this, $key, $name, $column, $name_second);
1314
	}
1315
1316
1317
	/**
1318
	 * Add datepicker filter (from - to)
1319
	 * @param string $key
1320
	 * @param string $name
1321
	 * @param string $column
1322
	 * @return Filter\FilterDateRange
1323
	 * @throws DataGridException
1324
	 */
1325 View Code Duplication
	public function addFilterDateRange($key, $name, $column = null, $name_second = '-')
1326
	{
1327
		$column = $column ?: $key;
1328
1329
		if (!is_string($column)) {
1330
			throw new DataGridException('FilterDateRange can only filter in one column.');
1331
		}
1332
1333
		$this->addFilterCheck($key);
1334
1335
		return $this->filters[$key] = new Filter\FilterDateRange($this, $key, $name, $column, $name_second);
1336
	}
1337
1338
1339
	/**
1340
	 * Check whether given key already exists in $this->filters
1341
	 * @param  string $key
1342
	 * @throws DataGridException
1343
	 */
1344
	protected function addFilterCheck($key)
1345
	{
1346 1
		if (isset($this->filters[$key])) {
1347
			throw new DataGridException("There is already action at key [$key] defined.");
1348
		}
1349 1
	}
1350
1351
1352
	/**
1353
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1354
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1355
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1356
	 */
1357
	public function assembleFilters()
1358
	{
1359 1
		foreach ($this->filter as $key => $value) {
1360
			if (!isset($this->filters[$key])) {
1361
				$this->deleteSessionData($key);
1362
1363
				continue;
1364
			}
1365
1366
			if (is_array($value) || $value instanceof \Traversable) {
1367
				if (!ArraysHelper::testEmpty($value)) {
1368
					$this->filters[$key]->setValue($value);
1369
				}
1370
			} else {
1371
				if ($value !== '' && $value !== null) {
1372
					$this->filters[$key]->setValue($value);
1373
				}
1374
			}
1375
		}
1376
1377 1
		foreach ($this->columns as $key => $column) {
1378
			if (isset($this->sort[$key])) {
1379
				$column->setSort($this->sort);
1380
			}
1381
		}
1382
1383
		/**
1384
		 * Invoke possible events
1385
		 */
1386 1
		if (!empty($this->onFiltersAssabled)) {
0 ignored issues
show
Deprecated Code introduced by
The property Ublaboo\DataGrid\DataGrid::$onFiltersAssabled has been deprecated with message: use $onFiltersAssembled

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1387
			@trigger_error('onFiltersAssabled is deprecated, use onFiltersAssembled instead', E_USER_DEPRECATED);
1388
			$this->onFiltersAssabled($this->filters);
1389
		}
1390
1391 1
		$this->onFiltersAssembled($this->filters);
1392 1
		return $this->filters;
1393
	}
1394
1395
1396
	/**
1397
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1398
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1399
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1400
	 * @deprecated use assembleFilters instead
1401
	 */
1402
	public function assableFilters()
1403
	{
1404
		@trigger_error('assableFilters is deprecated, use assembleFilters instead', E_USER_DEPRECATED);
1405
		return $this->assembleFilters();
1406
	}
1407
1408
1409
	/**
1410
	 * Remove filter
1411
	 * @param string $key
1412
	 * @return static
1413
	 */
1414
	public function removeFilter($key)
1415
	{
1416
		unset($this->filters[$key]);
1417
1418
		return $this;
1419
	}
1420
1421
1422
	/**
1423
	 * Get defined filter
1424
	 * @param  string $key
1425
	 * @return Filter\Filter
1426
	 */
1427
	public function getFilter($key)
1428
	{
1429
		if (!isset($this->filters[$key])) {
1430
			throw new DataGridException("Filter [{$key}] is not defined");
1431
		}
1432
1433
		return $this->filters[$key];
1434
	}
1435
1436
1437
	/**
1438
	 * @param bool $strict
1439
	 * @return static
1440
	 */
1441
	public function setStrictSessionFilterValues($strict = true)
1442
	{
1443
		$this->strict_session_filter_values = (bool) $strict;
1444
1445
		return $this;
1446
	}
1447
1448
1449
	/********************************************************************************
1450
	 *                                  FILTERING                                   *
1451
	 ********************************************************************************/
1452
1453
1454
	/**
1455
	 * Is filter active?
1456
	 * @return bool
1457
	 */
1458
	public function isFilterActive()
1459
	{
1460
		$is_filter = ArraysHelper::testTruthy($this->filter);
1461
1462
		return ($is_filter) || $this->force_filter_active;
1463
	}
1464
1465
1466
	/**
1467
	 * Tell that filter is active from whatever reasons
1468
	 * return static
1469
	 */
1470
	public function setFilterActive()
1471
	{
1472
		$this->force_filter_active = true;
1473
1474
		return $this;
1475
	}
1476
1477
1478
	/**
1479
	 * Set filter values (force - overwrite user data)
1480
	 * @param array $filter
1481
	 * @return static
1482
	 */
1483
	public function setFilter(array $filter)
1484
	{
1485
		$this->filter = $filter;
1486
1487
		$this->saveSessionData('_grid_has_filtered', 1);
1488
1489
		return $this;
1490
	}
1491
1492
1493
	/**
1494
	 * If we want to sent some initial filter
1495
	 * @param array $filter
1496
	 * @param bool  $use_on_reset
1497
	 * @return static
1498
	 */
1499
	public function setDefaultFilter(array $default_filter, $use_on_reset = true)
1500
	{
1501
		foreach ($default_filter as $key => $value) {
1502
			$filter = $this->getFilter($key);
1503
1504
			if (!$filter) {
1505
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1506
			}
1507
1508
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1509
				throw new DataGridException(
1510
					"Default value of filter [$key] - MultiSelect has to be an array"
1511
				);
1512
			}
1513
1514
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1515
				if (!is_array($value)) {
1516
					throw new DataGridException(
1517
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1518
					);
1519
				}
1520
1521
				$temp = $value;
1522
				unset($temp['from'], $temp['to']);
1523
1524
				if (!empty($temp)) {
1525
					throw new DataGridException(
1526
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1527
					);
1528
				}
1529
			}
1530
		}
1531
1532
		$this->default_filter = $default_filter;
1533
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1534
1535
		return $this;
1536
	}
1537
1538
1539
	/**
1540
	 * User may set default filter, find it
1541
	 * @return void
1542
	 */
1543
	public function findDefaultFilter()
1544
	{
1545 1
		if (!empty($this->filter)) {
1546
			return;
1547
		}
1548
1549 1
		if ($this->getSessionData('_grid_has_filtered')) {
1550
			return;
1551
		}
1552
1553 1
		if (!empty($this->default_filter)) {
1554
			$this->filter = $this->default_filter;
1555
		}
1556
1557 1
		foreach ($this->filter as $key => $value) {
1558
			$this->saveSessionData($key, $value);
1559
		}
1560 1
	}
1561
1562
1563
	/**
1564
	 * FilterAndGroupAction form factory
1565
	 * @return Form
1566
	 */
1567
	public function createComponentFilter()
1568
	{
1569
		$form = new Form($this, 'filter');
1570
1571
		$form->setMethod(static::$form_method);
1572
1573
		$form->setTranslator($this->getTranslator());
1574
1575
		/**
1576
		 * InlineEdit part
1577
		 */
1578
		$inline_edit_container = $form->addContainer('inline_edit');
1579
1580 View Code Duplication
		if ($this->inlineEdit instanceof InlineEdit) {
1581
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save')
1582
				->setValidationScope([$inline_edit_container]);
1583
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1584
				->setValidationScope(false);
1585
1586
			$this->inlineEdit->onControlAdd($inline_edit_container);
1587
			$this->inlineEdit->onControlAfterAdd($inline_edit_container);
1588
		}
1589
1590
		/**
1591
		 * InlineAdd part
1592
		 */
1593
		$inline_add_container = $form->addContainer('inline_add');
1594
1595 View Code Duplication
		if ($this->inlineAdd instanceof InlineEdit) {
1596
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save')
1597
				->setValidationScope([$inline_add_container]);
1598
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1599
				->setValidationScope(false)
1600
				->setAttribute('data-datagrid-cancel-inline-add', true);
1601
1602
			$this->inlineAdd->onControlAdd($inline_add_container);
1603
			$this->inlineAdd->onControlAfterAdd($inline_add_container);
1604
		}
1605
1606
		/**
1607
		 * ItemDetail form part
1608
		 */
1609
		$items_detail_form = $this->getItemDetailForm();
1610
1611
		if ($items_detail_form instanceof Nette\Forms\Container) {
1612
			$form['items_detail_form'] = $items_detail_form;
1613
		}
1614
1615
		/**
1616
		 * Filter part
1617
		 */
1618
		$filter_container = $form->addContainer('filter');
1619
1620
		foreach ($this->filters as $filter) {
1621
			$filter->addToFormContainer($filter_container);
1622
		}
1623
1624
		if (!$this->hasAutoSubmit()) {
1625
			$filter_container['submit'] = $this->getFilterSubmitButton();
1626
		}
1627
1628
		/**
1629
		 * Group action part
1630
		 */
1631
		$group_action_container = $form->addContainer('group_action');
1632
1633
		if ($this->hasGroupActions()) {
1634
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1635
		}
1636
1637
		if (!$form->isSubmitted()) {
1638
			$this->setFilterContainerDefaults($form['filter'], $this->filter);
1639
		}
1640
1641
		/**
1642
		 * Per page part
1643
		 */
1644
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1645
			->setTranslator(null);
1646
1647
		if (!$form->isSubmitted()) {
1648
			$form['per_page']->setValue($this->getPerPage());
1649
		}
1650
1651
		$form->addSubmit('per_page_submit', 'ublaboo_datagrid.per_page_submit')
1652
			->setValidationScope([$form['per_page']]);
1653
1654
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1655
	}
1656
1657
1658
	/**
1659
	 * @param  Nette\Forms\Container  $container
1660
	 * @param  array|\Iterator  $values
1661
	 * @return void
1662
	 */
1663
	public function setFilterContainerDefaults(Nette\Forms\Container $container, $values)
1664
	{
1665
		foreach ($container->getComponents() as $key => $control) {
1666
			if (!isset($values[$key])) {
1667
				continue;
1668
			}
1669
1670
			if ($control instanceof Nette\Forms\Container) {
1671
				$this->setFilterContainerDefaults($control, $values[$key]);
1672
1673
				continue;
1674
			}
1675
1676
			$value = $values[$key];
1677
1678
			if ($value instanceof \DateTime && ($filter = $this->getFilter($key)) instanceof IFilterDate) {
1679
				$value = $value->format($filter->getPhpFormat());
1680
			}
1681
1682
			try {
1683
				$control->setValue($value);
1684
1685
			} catch (Nette\InvalidArgumentException $e) {
1686
				if ($this->strict_session_filter_values) {
1687
					throw $e;
1688
				}
1689
			}
1690
		}
1691
	}
1692
1693
1694
	/**
1695
	 * Set $this->filter values after filter form submitted
1696
	 * @param  Form $form
1697
	 * @return void
1698
	 */
1699
	public function filterSucceeded(Form $form)
1700
	{
1701
		if ($this->snippets_set) {
1702
			return;
1703
		}
1704
1705
		$values = $form->getValues();
1706
1707
		if ($this->getPresenter()->isAjax()) {
1708
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1709
				return;
1710
			}
1711
		}
1712
1713
		/**
1714
		 * Per page
1715
		 */
1716
		$this->saveSessionData('_grid_per_page', $values->per_page);
1717
		$this->per_page = $values->per_page;
1718
1719
		/**
1720
		 * Inline edit
1721
		 */
1722
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1723
			$edit = $form['inline_edit'];
1724
1725
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1726
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1727
				$primary_where_column = $form->getHttpData(
1728
					Form::DATA_LINE,
1729
					'inline_edit[_primary_where_column]'
1730
				);
1731
1732
				if ($edit['submit']->isSubmittedBy() && !$edit->getErrors()) {
1733
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1734
					$this->getPresenter()->payload->_datagrid_inline_edited = $id;
1735
					$this->getPresenter()->payload->_datagrid_name = $this->getName();
1736
				} else {
1737
					$this->getPresenter()->payload->_datagrid_inline_edit_cancel = $id;
1738
					$this->getPresenter()->payload->_datagrid_name = $this->getName();
1739
				}
1740
1741
				if ($edit['submit']->isSubmittedBy() && !empty($this->inlineEdit->onCustomRedraw)) {
1742
					$this->inlineEdit->onCustomRedraw();
1743
				} else {
1744
					$this->redrawItem($id, $primary_where_column);
1745
					$this->redrawControl('summary');
1746
				}
1747
1748
				return;
1749
			}
1750
		}
1751
1752
		/**
1753
		 * Inline add
1754
		 */
1755
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1756
			$add = $form['inline_add'];
1757
1758
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1759
				if ($add['submit']->isSubmittedBy() && !$add->getErrors()) {
1760
					$this->inlineAdd->onSubmit($values->inline_add);
1761
1762
					if ($this->getPresenter()->isAjax()) {
1763
						$this->getPresenter()->payload->_datagrid_inline_added = true;
1764
					}
1765
				}
1766
1767
				return;
1768
			}
1769
		}
1770
1771
		/**
1772
		 * Filter itself
1773
		 */
1774
		$values = $values['filter'];
1775
1776
		foreach ($values as $key => $value) {
1777
			/**
1778
			 * Session stuff
1779
			 */
1780
			if ($this->remember_state && $this->getSessionData($key) != $value) {
1781
				/**
1782
				 * Has been filter changed?
1783
				 */
1784
				$this->page = 1;
1785
				$this->saveSessionData('_grid_page', 1);
1786
			}
1787
1788
			$this->saveSessionData($key, $value);
1789
1790
			/**
1791
			 * Other stuff
1792
			 */
1793
			$this->filter[$key] = $value;
1794
		}
1795
1796
		if (!empty($values)) {
1797
			$this->saveSessionData('_grid_has_filtered', 1);
1798
		}
1799
1800
		if ($this->getPresenter()->isAjax()) {
1801
			$this->getPresenter()->payload->_datagrid_sort = [];
1802
1803
			foreach ($this->columns as $key => $column) {
1804
				if ($column->isSortable()) {
1805
					$this->getPresenter()->payload->_datagrid_sort[$key] = $this->link('sort!', [
1806
						'sort' => $column->getSortNext(),
1807
					]);
1808
				}
1809
			}
1810
		}
1811
1812
		$this->reload();
1813
	}
1814
1815
1816
	/**
1817
	 * Should be datagrid filters rendered separately?
1818
	 * @param bool $out
1819
	 * @return static
1820
	 */
1821
	public function setOuterFilterRendering($out = true)
1822
	{
1823
		$this->outer_filter_rendering = (bool) $out;
1824
1825
		return $this;
1826
	}
1827
1828
1829
	/**
1830
	 * Are datagrid filters rendered separately?
1831
	 * @return bool
1832
	 */
1833
	public function hasOuterFilterRendering()
1834
	{
1835
		return $this->outer_filter_rendering;
1836
	}
1837
1838
1839
	/**
1840
	 * Set the number of columns in the outer filter
1841
	 * @param int $count
1842
	 * @return static
1843
	 * @throws \InvalidArgumentException
1844
	 */
1845
	public function setOuterFilterColumnsCount($count)
1846
	{
1847
		if (!in_array($count, [1, 2, 3, 4, 6, 12], true)) {
1848
			throw new \InvalidArgumentException(
1849
				"Columns count must be one of following values: 1, 2, 3, 4, 6, 12. Value {$count} given."
1850
			);
1851
		}
1852
1853
		$this->outer_filter_columns_count = (int) $count;
1854
1855
		return $this;
1856
	}
1857
1858
1859
	/**
1860
	 * @return bool
1861
	 */
1862
	public function getOuterFilterColumnsCount()
1863
	{
1864
		return $this->outer_filter_columns_count;
1865
	}
1866
1867
1868
	/**
1869
	 * @param bool $collapsible_outer_filters
1870
	 */
1871
	public function setCollapsibleOuterFilters($collapsible_outer_filters = true)
1872
	{
1873
		$this->collapsible_outer_filters = (bool) $collapsible_outer_filters;
1874
	}
1875
1876
1877
	/**
1878
	 * @return bool
1879
	 */
1880
	public function hasCollapsibleOuterFilters()
1881
	{
1882
		return $this->collapsible_outer_filters;
1883
	}
1884
1885
1886
	/**
1887
	 * Try to restore session stuff
1888
	 * @return void
1889
	 * @throws DataGridFilterNotFoundException
1890
	 */
1891
	public function findSessionValues()
1892
	{
1893 1
		if (!ArraysHelper::testEmpty($this->filter) || ($this->page != 1) || !empty($this->sort)) {
1894
			return;
1895
		}
1896
1897 1
		if (!$this->remember_state) {
1898
			return;
1899
		}
1900
1901 1
		if ($page = $this->getSessionData('_grid_page')) {
1902
			$this->page = $page;
1903
		}
1904
1905 1
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1906
			$this->per_page = $per_page;
1907
		}
1908
1909 1
		if ($sort = $this->getSessionData('_grid_sort')) {
1910
			$this->sort = $sort;
1911
		}
1912
1913 1
		foreach ($this->getSessionData() as $key => $value) {
1914
			$other_session_keys = [
1915 1
				'_grid_per_page',
1916
				'_grid_sort',
1917
				'_grid_page',
1918
				'_grid_has_sorted',
1919
				'_grid_has_filtered',
1920
				'_grid_hidden_columns',
1921
				'_grid_hidden_columns_manipulated',
1922
			];
1923
1924 1
			if (!in_array($key, $other_session_keys, true)) {
1925
				try {
1926
					$this->getFilter($key);
1927
1928
					$this->filter[$key] = $value;
1929
1930
				} catch (DataGridException $e) {
1931
					if ($this->strict_session_filter_values) {
1932 1
						throw new DataGridFilterNotFoundException("Session filter: Filter [$key] not found");
1933
					}
1934
				}
1935
			}
1936
		}
1937
1938
		/**
1939
		 * When column is sorted via custom callback, apply it
1940
		 */
1941 1
		if (empty($this->sort_callback) && !empty($this->sort)) {
1942
			foreach ($this->sort as $key => $order) {
1943
				try {
1944
					$column = $this->getColumn($key);
1945
1946
				} catch (DataGridColumnNotFoundException $e) {
1947
					$this->deleteSessionData('_grid_sort');
1948
					$this->sort = [];
1949
1950
					return;
1951
				}
1952
1953
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1954
					$this->sort_callback = $column->getSortableCallback();
1955
				}
1956
			}
1957
		}
1958 1
	}
1959
1960
1961
	/********************************************************************************
1962
	 *                                    EXPORTS                                   *
1963
	 ********************************************************************************/
1964
1965
1966
	/**
1967
	 * Add export of type callback
1968
	 * @param string $text
1969
	 * @param callable $callback
1970
	 * @param bool $filtered
1971
	 * @return Export\Export
1972
	 */
1973
	public function addExportCallback($text, $callback, $filtered = false)
1974
	{
1975 1
		if (!is_callable($callback)) {
1976
			throw new DataGridException('Second parameter of ExportCallback must be callable.');
1977
		}
1978
1979 1
		return $this->addToExports(new Export\Export($this, $text, $callback, $filtered));
1980
	}
1981
1982
1983
	/**
1984
	 * Add already implemented csv export
1985
	 * @param string $text
1986
	 * @param string $csv_file_name
1987
	 * @param string|null $output_encoding
1988
	 * @param string|null $delimiter
1989
	 * @param bool $include_bom
1990
	 * @return Export\Export
1991
	 */
1992 View Code Duplication
	public function addExportCsv(
1993
		$text,
1994
		$csv_file_name,
1995
		$output_encoding = null,
1996
		$delimiter = null,
1997
		$include_bom = false
1998
	) {
1999 1
		return $this->addToExports(new Export\ExportCsv(
2000 1
			$this,
2001 1
			$text,
2002 1
			$csv_file_name,
2003 1
			false,
2004 1
			$output_encoding,
2005 1
			$delimiter,
2006 1
			$include_bom
2007
		));
2008
	}
2009
2010
2011
	/**
2012
	 * Add already implemented csv export, but for filtered data
2013
	 * @param string $text
2014
	 * @param string $csv_file_name
2015
	 * @param string|null $output_encoding
2016
	 * @param string|null $delimiter
2017
	 * @param bool $include_bom
2018
	 * @return Export\Export
2019
	 */
2020 View Code Duplication
	public function addExportCsvFiltered(
2021
		$text,
2022
		$csv_file_name,
2023
		$output_encoding = null,
2024
		$delimiter = null,
2025
		$include_bom = false
2026
	) {
2027
		return $this->addToExports(new Export\ExportCsv(
2028
			$this,
2029
			$text,
2030
			$csv_file_name,
2031
			true,
2032
			$output_encoding,
2033
			$delimiter,
2034
			$include_bom
2035
		));
2036
	}
2037
2038
2039
	/**
2040
	 * Add export to array
2041
	 * @param Export\Export $export
2042
	 * @return Export\Export
2043
	 */
2044
	protected function addToExports(Export\Export $export)
2045
	{
2046 1
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
2047
2048 1
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
2049
2050 1
		return $this->exports[$id] = $export;
2051
	}
2052
2053
2054
	/**
2055
	 * @return void
2056
	 */
2057
	public function resetExportsLinks()
2058
	{
2059
		foreach ($this->exports as $id => $export) {
2060
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
2061
		}
2062
	}
2063
2064
2065
	/********************************************************************************
2066
	 *                                TOOLBAR BUTTONS                               *
2067
	 ********************************************************************************/
2068
2069
2070
	/**
2071
	 * Add toolbar button
2072
	 * @param  string  $href
2073
	 * @param  string  $text
2074
	 * @param  array   $params
2075
	 * @return ToolbarButton
2076
	 * @throws DataGridException
2077
	 */
2078
	public function addToolbarButton($href, $text = '', $params = [])
2079
	{
2080
		if (isset($this->toolbar_buttons[$href])) {
2081
			throw new DataGridException("There is already toolbar button at key [$href] defined.");
2082
		}
2083
2084
		return $this->toolbar_buttons[$href] = new ToolbarButton($this, $href, $text, $params);
2085
	}
2086
2087
2088
	/**
2089
	 * Get existing toolbar button
2090
	 * @param  string  $key
2091
	 * @return ToolbarButton
2092
	 * @throws DataGridException
2093
	 */
2094
	public function getToolbarButton($key)
2095
	{
2096
		if (!isset($this->toolbar_buttons[$key])) {
2097
			throw new DataGridException("There is no toolbar button at key [$key] defined.");
2098
		}
2099
2100
		return $this->toolbar_buttons[$key];
2101
	}
2102
2103
2104
	/**
2105
	 * Remove toolbar button.
2106
	 * @param  string $key
2107
	 * @return static
2108
	 */
2109
	public function removeToolbarButton($key)
2110
	{
2111
		unset($this->toolbar_buttons[$key]);
2112
2113
		return $this;
2114
	}
2115
2116
2117
	/********************************************************************************
2118
	 *                                 GROUP ACTIONS                                *
2119
	 ********************************************************************************/
2120
2121
2122
	/**
2123
	 * Alias for add group select action
2124
	 * @param string $title
2125
	 * @param array  $options
2126
	 * @return GroupAction\GroupAction
2127
	 */
2128
	public function addGroupAction($title, $options = [])
2129
	{
2130
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
2131
	}
2132
2133
2134
	/**
2135
	 * Add group action (select box)
2136
	 * @param string $title
2137
	 * @param array  $options
2138
	 * @return GroupAction\GroupAction
2139
	 */
2140
	public function addGroupSelectAction($title, $options = [])
2141
	{
2142
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
2143
	}
2144
2145
2146
	/**
2147
	 * Add group action (multiselect box)
2148
	 * @param string $title
2149
	 * @param array  $options
2150
	 * @return GroupAction\GroupAction
2151
	 */
2152
	public function addGroupMultiSelectAction($title, $options = [])
2153
	{
2154
		return $this->getGroupActionCollection()->addGroupMultiSelectAction($title, $options);
2155
	}
2156
2157
2158
	/**
2159
	 * Add group action (text input)
2160
	 * @param string $title
2161
	 * @return GroupAction\GroupAction
2162
	 */
2163
	public function addGroupTextAction($title)
2164
	{
2165
		return $this->getGroupActionCollection()->addGroupTextAction($title);
2166
	}
2167
2168
2169
	/**
2170
	 * Add group action (textarea)
2171
	 * @param string $title
2172
	 * @return GroupAction\GroupAction
2173
	 */
2174
	public function addGroupTextareaAction($title)
2175
	{
2176
		return $this->getGroupActionCollection()->addGroupTextareaAction($title);
2177
	}
2178
2179
2180
	/**
2181
	 * Get collection of all group actions
2182
	 * @return GroupAction\GroupActionCollection
2183
	 */
2184
	public function getGroupActionCollection()
2185
	{
2186
		if (!$this->group_action_collection) {
2187
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
2188
		}
2189
2190
		return $this->group_action_collection;
2191
	}
2192
2193
2194
	/**
2195
	 * Has datagrid some group actions?
2196
	 * @return bool
2197
	 */
2198
	public function hasGroupActions()
2199
	{
2200
		return (bool) $this->group_action_collection;
2201
	}
2202
2203
2204
	/**
2205
	 * @return bool
2206
	 */
2207
	public function shouldShowSelectedRowsCount()
2208
	{
2209
		return $this->show_selected_rows_count;
2210
	}
2211
2212
2213
	/**
2214
	 * @return static
2215
	 */
2216
	public function setShowSelectedRowsCount($show = true)
2217
	{
2218
		$this->show_selected_rows_count = (bool) $show;
2219
2220
		return $this;
2221
	}
2222
2223
2224
	/********************************************************************************
2225
	 *                                   HANDLERS                                   *
2226
	 ********************************************************************************/
2227
2228
2229
	/**
2230
	 * Handler for changind page (just refresh site with page as persistent paramter set)
2231
	 * @param  int  $page
2232
	 * @return void
2233
	 */
2234
	public function handlePage($page)
2235
	{
2236
		/**
2237
		 * Session stuff
2238
		 */
2239
		$this->page = $page;
2240
		$this->saveSessionData('_grid_page', $page);
2241
2242
		$this->reload(['table']);
2243
	}
2244
2245
2246
	/**
2247
	 * Handler for sorting
2248
	 * @param array $sort
2249
	 * @return void
2250
	 * @throws DataGridColumnNotFoundException
2251
	 */
2252
	public function handleSort(array $sort)
2253
	{
2254
		foreach ($sort as $key => $value) {
2255
			try {
2256
				$column = $this->getColumn($key);
2257
2258
			} catch (DataGridColumnNotFoundException $e) {
2259
				unset($sort[$key]);
2260
				continue;
2261
			}
2262
2263
			if ($column->sortableResetPagination()) {
2264
				$this->saveSessionData('_grid_page', $this->page = 1);
2265
			}
2266
2267
			if ($column->getSortableCallback()) {
2268
				$this->sort_callback = $column->getSortableCallback();
2269
			}
2270
		}
2271
2272
		$this->saveSessionData('_grid_has_sorted', 1);
2273
		$this->saveSessionData('_grid_sort', $this->sort = $sort);
2274
2275
		$this->reloadTheWholeGrid();
2276
	}
2277
2278
2279
	/**
2280
	 * Handler for reseting the filter
2281
	 * @return void
2282
	 */
2283
	public function handleResetFilter()
2284
	{
2285
		/**
2286
		 * Session stuff
2287
		 */
2288
		$this->deleteSessionData('_grid_page');
2289
2290
		if ($this->default_filter_use_on_reset) {
2291
			$this->deleteSessionData('_grid_has_filtered');
2292
		}
2293
2294
		if ($this->default_sort_use_on_reset) {
2295
			$this->deleteSessionData('_grid_has_sorted');
2296
		}
2297
2298
		foreach ($this->getSessionData() as $key => $value) {
2299
			if (!in_array($key, [
2300
				'_grid_per_page',
2301
				'_grid_sort',
2302
				'_grid_page',
2303
				'_grid_has_filtered',
2304
				'_grid_has_sorted',
2305
				'_grid_hidden_columns',
2306
				'_grid_hidden_columns_manipulated',
2307
			], true)) {
2308
				$this->deleteSessionData($key);
2309
			}
2310
		}
2311
2312
		$this->filter = [];
2313
2314
		$this->reloadTheWholeGrid();
2315
	}
2316
2317
2318
	/**
2319
	 * @param  string $key
2320
	 * @return void
2321
	 */
2322
	public function handleResetColumnFilter($key)
2323
	{
2324
		$this->deleteSessionData($key);
2325
		unset($this->filter[$key]);
2326
2327
		$this->reloadTheWholeGrid();
2328
	}
2329
2330
2331
	/**
2332
	 * @param bool $reset
2333
	 * @return static
2334
	 */
2335
	public function setColumnReset($reset = true)
2336
	{
2337
		$this->has_column_reset = (bool) $reset;
2338
2339
		return $this;
2340
	}
2341
2342
2343
	/**
2344
	 * @return bool
2345
	 */
2346
	public function hasColumnReset()
2347
	{
2348 1
		return $this->has_column_reset;
2349
	}
2350
2351
2352
	/**
2353
	 * @param  Filter\Filter[] $filters
2354
	 * @return void
2355
	 */
2356
	public function sendNonEmptyFiltersInPayload($filters)
2357
	{
2358 1
		if (!$this->hasColumnReset()) {
2359
			return;
2360
		}
2361
2362 1
		$non_empty_filters = [];
2363
2364 1
		foreach ($filters as $filter) {
2365 1
			if ($filter->isValueSet()) {
2366 1
				$non_empty_filters[] = $filter->getKey();
2367
			}
2368
		}
2369
2370 1
		$this->getPresenter()->payload->non_empty_filters = $non_empty_filters;
2371 1
	}
2372
2373
2374
	/**
2375
	 * Handler for export
2376
	 * @param  int $id Key for particular export class in array $this->exports
2377
	 * @return void
2378
	 */
2379
	public function handleExport($id)
2380
	{
2381 1
		if (!isset($this->exports[$id])) {
2382
			throw new Nette\Application\ForbiddenRequestException;
2383
		}
2384
2385 1
		if (!empty($this->columns_export_order)) {
2386
			$this->setColumnsOrder($this->columns_export_order);
2387
		}
2388
2389 1
		$export = $this->exports[$id];
2390
2391
		/**
2392
		 * Invoke possible events
2393
		 */
2394 1
		$this->onExport($this);
2395
2396 1
		if ($export->isFiltered()) {
2397 1
			$sort = $this->sort;
2398 1
			$filter = $this->assembleFilters();
2399
		} else {
2400 1
			$sort = [$this->primary_key => 'ASC'];
2401 1
			$filter = [];
2402
		}
2403
2404 1
		if ($this->dataModel === null) {
2405 1
			throw new DataGridException('You have to set a data source first.');
2406
		}
2407
2408 1
		$rows = [];
2409
2410 1
		$items = Nette\Utils\Callback::invokeArgs(
2411 1
			[$this->dataModel, 'filterData'], [
2412 1
				null,
2413 1
				$this->createSorting($this->sort, $this->sort_callback),
2414 1
				$filter,
2415
			]
2416
		);
2417
2418 1
		foreach ($items as $item) {
2419 1
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
2420
		}
2421
2422 1
		if ($export instanceof Export\ExportCsv) {
2423 1
			$export->invoke($rows);
2424
		} else {
2425 1
			$export->invoke($items);
2426
		}
2427
2428 1
		if ($export->isAjax()) {
2429
			$this->reload();
2430
		}
2431 1
	}
2432
2433
2434
	/**
2435
	 * Handler for getting children of parent item (e.g. category)
2436
	 * @param  int $parent
2437
	 * @return void
2438
	 */
2439 View Code Duplication
	public function handleGetChildren($parent)
2440
	{
2441
		$this->setDataSource(
2442
			call_user_func($this->tree_view_children_callback, $parent)
2443
		);
2444
2445
		if ($this->getPresenter()->isAjax()) {
2446
			$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
2447
			$this->getPresenter()->payload->_datagrid_tree = $parent;
2448
2449
			$this->redrawControl('items');
2450
2451
			$this->onRedraw();
2452
		} else {
2453
			$this->getPresenter()->redirect('this');
2454
		}
2455
	}
2456
2457
2458
	/**
2459
	 * Handler for getting item detail
2460
	 * @param  mixed $id
2461
	 * @return void
2462
	 */
2463
	public function handleGetItemDetail($id)
2464
	{
2465
		$this->getTemplate()->add('toggle_detail', $id);
2466
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
2467
2468
		if ($this->getPresenter()->isAjax()) {
2469
			$this->getPresenter()->payload->_datagrid_toggle_detail = $id;
2470
			$this->redrawControl('items');
2471
2472
			/**
2473
			 * Only for nette 2.4
2474
			 */
2475
			if (method_exists($this->getTemplate()->getLatte(), 'addProvider')) {
2476
				$this->redrawControl('gridSnippets');
2477
			}
2478
2479
			$this->onRedraw();
2480
		} else {
2481
			$this->getPresenter()->redirect('this');
2482
		}
2483
	}
2484
2485
2486
	/**
2487
	 * Handler for inline editing
2488
	 * @param  mixed $id
2489
	 * @param  mixed $key
2490
	 * @return void
2491
	 */
2492
	public function handleEdit($id, $key)
2493
	{
2494
		$column = $this->getColumn($key);
2495
		$value = $this->getPresenter()->getRequest()->getPost('value');
2496
2497
		/**
2498
		 * @var mixed Could be NULL of course
2499
		 */
2500
		$new_value = call_user_func_array($column->getEditableCallback(), [$id, $value]);
2501
2502
		$this->getPresenter()->payload->_datagrid_editable_new_value = $new_value;
2503
	}
2504
2505
2506
	/**
2507
	 * Redraw $this
2508
	 * @return void
2509
	 */
2510
	public function reload($snippets = [])
2511
	{
2512
		if ($this->getPresenter()->isAjax()) {
2513
			$this->redrawControl('tbody');
2514
			$this->redrawControl('pagination');
2515
			$this->redrawControl('summary');
2516
			$this->redrawControl('thead-group-action');
2517
2518
			/**
2519
			 * manualy reset exports links...
2520
			 */
2521
			$this->resetExportsLinks();
2522
			$this->redrawControl('exports');
2523
2524
			foreach ($snippets as $snippet) {
2525
				$this->redrawControl($snippet);
2526
			}
2527
2528
			$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
2529
			$this->getPresenter()->payload->_datagrid_name = $this->getName();
2530
2531
			$this->onRedraw();
2532
		} else {
2533
			$this->getPresenter()->redirect('this');
2534
		}
2535
	}
2536
2537
2538
	/**
2539
	 * @return void
2540
	 */
2541 View Code Duplication
	public function reloadTheWholeGrid()
2542
	{
2543
		if ($this->getPresenter()->isAjax()) {
2544
			$this->redrawControl('grid');
2545
2546
			$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
2547
			$this->getPresenter()->payload->_datagrid_name = $this->getName();
2548
2549
			$this->onRedraw();
2550
		} else {
2551
			$this->getPresenter()->redirect('this');
2552
		}
2553
	}
2554
2555
2556
	/**
2557
	 * Handler for column status
2558
	 * @param  string $id
2559
	 * @param  string $key
2560
	 * @param  string $value
2561
	 * @return void
2562
	 */
2563
	public function handleChangeStatus($id, $key, $value)
2564
	{
2565
		if (empty($this->columns[$key])) {
2566
			throw new DataGridException("ColumnStatus[$key] does not exist");
2567
		}
2568
2569
		$this->columns[$key]->onChange($id, $value);
2570
	}
2571
2572
2573
	/**
2574
	 * Redraw just one row via ajax
2575
	 * @param  int   $id
2576
	 * @param  mixed $primary_where_column
2577
	 * @return void
2578
	 */
2579
	public function redrawItem($id, $primary_where_column = null)
2580
	{
2581
		$this->snippets_set = true;
2582
2583
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
2584
2585
		$this->redrawControl('items');
2586
2587
		$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
2588
2589
		$this->onRedraw();
2590
	}
2591
2592
2593
	/**
2594
	 * Tell datagrid to display all columns
2595
	 * @return void
2596
	 */
2597
	public function handleShowAllColumns()
2598
	{
2599
		$this->deleteSessionData('_grid_hidden_columns');
2600
		$this->saveSessionData('_grid_hidden_columns_manipulated', true);
2601
2602
		$this->redrawControl();
2603
2604
		$this->onRedraw();
2605
	}
2606
2607
2608
	/**
2609
	 * Tell datagrid to display default columns
2610
	 * @return void
2611
	 */
2612
	public function handleShowDefaultColumns()
2613
	{
2614
		$this->deleteSessionData('_grid_hidden_columns');
2615
		$this->saveSessionData('_grid_hidden_columns_manipulated', false);
2616
2617
		$this->redrawControl();
2618
2619
		$this->onRedraw();
2620
	}
2621
2622
2623
	/**
2624
	 * Reveal particular column
2625
	 * @param  string $column
2626
	 * @return void
2627
	 */
2628 View Code Duplication
	public function handleShowColumn($column)
2629
	{
2630
		$columns = $this->getSessionData('_grid_hidden_columns');
2631
2632
		if (!empty($columns)) {
2633
			$pos = array_search($column, $columns, true);
2634
2635
			if ($pos !== false) {
2636
				unset($columns[$pos]);
2637
			}
2638
		}
2639
2640
		$this->saveSessionData('_grid_hidden_columns', $columns);
2641
		$this->saveSessionData('_grid_hidden_columns_manipulated', true);
2642
2643
		$this->redrawControl();
2644
2645
		$this->onRedraw();
2646
	}
2647
2648
2649
	/**
2650
	 * Notice datagrid to not display particular columns
2651
	 * @param  string $column
2652
	 * @return void
2653
	 */
2654 View Code Duplication
	public function handleHideColumn($column)
2655
	{
2656
		/**
2657
		 * Store info about hiding a column to session
2658
		 */
2659
		$columns = $this->getSessionData('_grid_hidden_columns');
2660
2661
		if (empty($columns)) {
2662
			$columns = [$column];
2663
		} elseif (!in_array($column, $columns, true)) {
2664
			array_push($columns, $column);
2665
		}
2666
2667
		$this->saveSessionData('_grid_hidden_columns', $columns);
2668
		$this->saveSessionData('_grid_hidden_columns_manipulated', true);
2669
2670
		$this->redrawControl();
2671
2672
		$this->onRedraw();
2673
	}
2674
2675
2676
	public function handleActionCallback($__key, $__id)
2677
	{
2678
		$action = $this->getAction($__key);
2679
2680
		if (!($action instanceof Column\ActionCallback)) {
2681
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
2682
		}
2683
2684
		$action->onClick($__id);
2685
	}
2686
2687
2688
	/********************************************************************************
2689
	 *                                  PAGINATION                                  *
2690
	 ********************************************************************************/
2691
2692
2693
	/**
2694
	 * Set options of select "items_per_page"
2695
	 * @param array $items_per_page_list
2696
	 * @return static
2697
	 */
2698
	public function setItemsPerPageList(array $items_per_page_list, $include_all = true)
2699
	{
2700
		$this->items_per_page_list = $items_per_page_list;
2701
2702
		if ($include_all) {
2703
			$this->items_per_page_list[] = 'all';
2704
		}
2705
2706
		return $this;
2707
	}
2708
2709
2710
	/**
2711
	 * Set default "items per page" value in pagination select
2712
	 * @param $count
2713
	 * @return static
2714
	 */
2715
	public function setDefaultPerPage($count)
2716
	{
2717
		$this->default_per_page = $count;
2718
2719
		return $this;
2720
	}
2721
2722
2723
	/**
2724
	 * User may set default "items per page" value, apply it
2725
	 * @return void
2726
	 */
2727
	public function findDefaultPerPage()
2728
	{
2729
		if (!empty($this->per_page)) {
2730
			return;
2731
		}
2732
2733
		if (!empty($this->default_per_page)) {
2734
			$this->per_page = $this->default_per_page;
2735
		}
2736
2737
		$this->saveSessionData('_grid_per_page', $this->per_page);
2738
	}
2739
2740
2741
	/**
2742
	 * Paginator factory
2743
	 * @return Components\DataGridPaginator\DataGridPaginator
2744
	 */
2745
	public function createComponentPaginator()
2746
	{
2747
		/**
2748
		 * Init paginator
2749
		 */
2750
		$component = new Components\DataGridPaginator\DataGridPaginator(
2751
			$this->getTranslator(),
2752
			static::$icon_prefix
2753
		);
2754
		$paginator = $component->getPaginator();
2755
2756
		$paginator->setPage($this->page);
2757
		$paginator->setItemsPerPage($this->getPerPage());
2758
2759
		return $component;
2760
	}
2761
2762
2763
	/**
2764
	 * Get parameter per_page
2765
	 * @return int
2766
	 */
2767
	public function getPerPage()
2768
	{
2769
		$items_per_page_list = $this->getItemsPerPageList();
2770
2771
		$per_page = $this->per_page ?: reset($items_per_page_list);
2772
2773
		if ($per_page !== 'all' && !in_array((int) $this->per_page, $items_per_page_list, true)) {
2774
			$per_page = reset($items_per_page_list);
2775
		}
2776
2777
		return $per_page;
2778
	}
2779
2780
2781
	/**
2782
	 * Get associative array of items_per_page_list
2783
	 * @return array
2784
	 */
2785
	public function getItemsPerPageList()
2786
	{
2787
		if (empty($this->items_per_page_list)) {
2788
			$this->setItemsPerPageList([10, 20, 50], true);
2789
		}
2790
2791
		$list = array_flip($this->items_per_page_list);
2792
2793
		foreach ($list as $key => $value) {
2794
			$list[$key] = $key;
2795
		}
2796
2797
		if (array_key_exists('all', $list)) {
2798
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2799
		}
2800
2801
		return $list;
2802
	}
2803
2804
2805
	/**
2806
	 * Order Grid to "be paginated"
2807
	 * @param bool $do
2808
	 * @return static
2809
	 */
2810
	public function setPagination($do)
2811
	{
2812
		$this->do_paginate = (bool) $do;
2813
2814
		return $this;
2815
	}
2816
2817
2818
	/**
2819
	 * Tell whether Grid is paginated
2820
	 * @return bool
2821
	 */
2822
	public function isPaginated()
2823
	{
2824
		return $this->do_paginate;
2825
	}
2826
2827
2828
	/**
2829
	 * Return current paginator class
2830
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2831
	 */
2832
	public function getPaginator()
2833
	{
2834
		if ($this->isPaginated() && $this->getPerPage() !== 'all') {
2835
			return $this['paginator'];
2836
		}
2837
2838
		return null;
2839
	}
2840
2841
2842
	/********************************************************************************
2843
	 *                                     I18N                                     *
2844
	 ********************************************************************************/
2845
2846
2847
	/**
2848
	 * Set datagrid translator
2849
	 * @param Nette\Localization\ITranslator $translator
2850
	 * @return static
2851
	 */
2852
	public function setTranslator(Nette\Localization\ITranslator $translator)
2853
	{
2854
		$this->translator = $translator;
2855
2856
		return $this;
2857
	}
2858
2859
2860
	/**
2861
	 * Get translator for datagrid
2862
	 * @return Nette\Localization\ITranslator
2863
	 */
2864
	public function getTranslator()
2865
	{
2866 1
		if (!$this->translator) {
2867 1
			$this->translator = new Localization\SimpleTranslator;
2868
		}
2869
2870 1
		return $this->translator;
2871
	}
2872
2873
2874
	/********************************************************************************
2875
	 *                                 COLUMNS ORDER                                *
2876
	 ********************************************************************************/
2877
2878
2879
	/**
2880
	 * Set order of datagrid columns
2881
	 * @param array $order
2882
	 * @return static
2883
	 */
2884
	public function setColumnsOrder($order)
2885
	{
2886
		$new_order = [];
2887
2888
		foreach ($order as $key) {
2889
			if (isset($this->columns[$key])) {
2890
				$new_order[$key] = $this->columns[$key];
2891
			}
2892
		}
2893
2894
		if (sizeof($new_order) === sizeof($this->columns)) {
2895
			$this->columns = $new_order;
2896
		} else {
2897
			throw new DataGridException('When changing columns order, you have to specify all columns');
2898
		}
2899
2900
		return $this;
2901
	}
2902
2903
2904
	/**
2905
	 * Columns order may be different for export and normal grid
2906
	 * @param array $order
2907
	 */
2908
	public function setColumnsExportOrder($order)
2909
	{
2910
		$this->columns_export_order = (array) $order;
2911
	}
2912
2913
2914
	/********************************************************************************
2915
	 *                                SESSION & URL                                 *
2916
	 ********************************************************************************/
2917
2918
2919
	/**
2920
	 * Find some unique session key name
2921
	 * @return string
2922
	 */
2923
	public function getSessionSectionName()
2924
	{
2925 1
		return $this->getPresenter()->getName() . ':' . $this->getUniqueId();
2926
	}
2927
2928
2929
	/**
2930
	 * Should datagrid remember its filters/pagination/etc using session?
2931
	 * @param bool $remember
2932
	 * @return static
2933
	 */
2934
	public function setRememberState($remember = true)
2935
	{
2936
		$this->remember_state = (bool) $remember;
2937
2938
		return $this;
2939
	}
2940
2941
2942
	/**
2943
	 * Should datagrid refresh url using history API?
2944
	 * @param bool $refresh
2945
	 * @return static
2946
	 */
2947
	public function setRefreshUrl($refresh = true)
2948
	{
2949
		$this->refresh_url = (bool) $refresh;
2950
2951
2952
		return $this;
2953
	}
2954
2955
2956
	/**
2957
	 * Get session data if functionality is enabled
2958
	 * @param  string $key
2959
	 * @return mixed
2960
	 */
2961
	public function getSessionData($key = null, $default_value = null)
2962
	{
2963 1
		if (!$this->remember_state) {
2964
			return $key ? $default_value : [];
2965
		}
2966
2967 1
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2968
	}
2969
2970
2971
	/**
2972
	 * Save session data - just if it is enabled
2973
	 * @param  string $key
2974
	 * @param  mixed  $value
2975
	 * @return void
2976
	 */
2977
	public function saveSessionData($key, $value)
2978
	{
2979 1
		if ($this->remember_state) {
2980 1
			$this->grid_session->{$key} = $value;
2981
		}
2982 1
	}
2983
2984
2985
	/**
2986
	 * Delete session data
2987
	 * @return void
2988
	 */
2989
	public function deleteSessionData($key)
2990
	{
2991
		unset($this->grid_session->{$key});
2992
	}
2993
2994
2995
	/**
2996
	 * Delete session data
2997
	 * @return void
2998
	 * @deprecated
2999
	 */
3000
	public function deleteSesssionData($key)
3001
	{
3002
		@trigger_error('deleteSesssionData is deprecated, use deleteSessionData instead', E_USER_DEPRECATED);
3003
		return $this->deleteSessionData($key);
3004
	}
3005
3006
3007
	/********************************************************************************
3008
	 *                                  ITEM DETAIL                                 *
3009
	 ********************************************************************************/
3010
3011
3012
	/**
3013
	 * Get items detail parameters
3014
	 * @return array
3015
	 */
3016
	public function getItemsDetail()
3017
	{
3018
		return $this->items_detail;
3019
	}
3020
3021
3022
	/**
3023
	 * Items can have thair detail - toggled
3024
	 * @param mixed $detail callable|string|bool
3025
	 * @param bool|NULL $primary_where_column
3026
	 * @return Column\ItemDetail
3027
	 */
3028
	public function setItemsDetail($detail = true, $primary_where_column = null)
3029
	{
3030
		if ($this->isSortable()) {
3031
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
3032
		}
3033
3034
		$this->items_detail = new Column\ItemDetail(
3035
			$this,
3036
			$primary_where_column ?: $this->primary_key
3037
		);
3038
3039
		if (is_string($detail)) {
3040
			/**
3041
			 * Item detail will be in separate template
3042
			 */
3043
			$this->items_detail->setType('template');
3044
			$this->items_detail->setTemplate($detail);
3045
3046
		} elseif (is_callable($detail)) {
3047
			/**
3048
			 * Item detail will be rendered via custom callback renderer
3049
			 */
3050
			$this->items_detail->setType('renderer');
3051
			$this->items_detail->setRenderer($detail);
3052
3053
		} elseif ($detail === true) {
3054
			/**
3055
			 * Item detail will be rendered probably via block #detail
3056
			 */
3057
			$this->items_detail->setType('block');
3058
3059
		} else {
3060
			throw new DataGridException(
3061
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
3062
			);
3063
		}
3064
3065
		return $this->items_detail;
3066
	}
3067
3068
3069
	/**
3070
	 * @param callable $callable_set_container
3071
	 * @return static
3072
	 */
3073
	public function setItemsDetailForm(callable $callable_set_container)
3074
	{
3075
		if ($this->items_detail instanceof Column\ItemDetail) {
3076
			$this->items_detail->setForm(
3077
				new Utils\ItemDetailForm($callable_set_container)
3078
			);
3079
3080
			return $this;
3081
		}
3082
3083
		throw new DataGridException('Please set the ItemDetail first.');
3084
	}
3085
3086
3087
	/**
3088
	 * @return Nette\Forms\Container|NULL
3089
	 */
3090
	public function getItemDetailForm()
3091
	{
3092
		if ($this->items_detail instanceof Column\ItemDetail) {
3093
			return $this->items_detail->getForm();
3094
		}
3095
3096
		return null;
3097
	}
3098
3099
3100
	/********************************************************************************
3101
	 *                                ROW PRIVILEGES                                *
3102
	 ********************************************************************************/
3103
3104
3105
	/**
3106
	 * @param  callable $condition
3107
	 * @return void
3108
	 */
3109
	public function allowRowsGroupAction(callable $condition)
3110
	{
3111
		$this->row_conditions['group_action'] = $condition;
3112
	}
3113
3114
3115
	/**
3116
	 * @param  callable $condition
3117
	 * @return void
3118
	 */
3119
	public function allowRowsInlineEdit(callable $condition)
3120
	{
3121
		$this->row_conditions['inline_edit'] = $condition;
3122
	}
3123
3124
3125
	/**
3126
	 * @param  string   $key
3127
	 * @param  callable $condition
3128
	 * @return void
3129
	 */
3130
	public function allowRowsAction($key, callable $condition)
3131
	{
3132
		$this->row_conditions['action'][$key] = $condition;
3133
	}
3134
3135
3136
	/**
3137
	 * @param  string   $multiActionKey
3138
	 * @param  string   $actionKey
3139
	 * @param  callable $condition
3140
	 * @return void
3141
	 */
3142
	public function allowRowsMultiAction($multiActionKey, $actionKey, callable $condition)
3143
	{
3144
		if (!isset($this->actions[$multiActionKey])) {
3145
			throw new DataGridException("There is no action at key [$multiActionKey] defined.");
3146
		}
3147
3148
		if (!$this->actions[$multiActionKey] instanceof Column\MultiAction) {
3149
			throw new DataGridException("Action at key [$multiActionKey] is not a MultiAction.");
3150
		}
3151
3152
		$this->actions[$multiActionKey]->setRowCondition((string) $actionKey, $condition);
3153
	}
3154
3155
3156
	/**
3157
	 * @param  string      $name
3158
	 * @param  string|null $key
3159
	 * @return bool|callable
3160
	 */
3161
	public function getRowCondition($name, $key = null)
3162
	{
3163
		if (!isset($this->row_conditions[$name])) {
3164
			return false;
3165
		}
3166
3167
		$condition = $this->row_conditions[$name];
3168
3169
		if (!$key) {
3170
			return $condition;
3171
		}
3172
3173
		return isset($condition[$key]) ? $condition[$key] : false;
3174
	}
3175
3176
3177
	/********************************************************************************
3178
	 *                               COLUMN CALLBACK                                *
3179
	 ********************************************************************************/
3180
3181
3182
	/**
3183
	 * @param  string   $key
3184
	 * @param  callable $callback
3185
	 * @return void
3186
	 */
3187
	public function addColumnCallback($key, callable $callback)
3188
	{
3189
		$this->column_callbacks[$key] = $callback;
3190
	}
3191
3192
3193
	/**
3194
	 * @param  string $key
3195
	 * @return callable|null
3196
	 */
3197
	public function getColumnCallback($key)
3198
	{
3199
		return empty($this->column_callbacks[$key]) ? null : $this->column_callbacks[$key];
3200
	}
3201
3202
3203
	/********************************************************************************
3204
	 *                                 INLINE EDIT                                  *
3205
	 ********************************************************************************/
3206
3207
3208
	/**
3209
	 * @return InlineEdit
3210
	 */
3211
	public function addInlineEdit($primary_where_column = null)
3212
	{
3213
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
3214
3215
		return $this->inlineEdit;
3216
	}
3217
3218
3219
	/**
3220
	 * @return InlineEdit|null
3221
	 */
3222
	public function getInlineEdit()
3223
	{
3224
		return $this->inlineEdit;
3225
	}
3226
3227
3228
	/**
3229
	 * @param  mixed $id
3230
	 * @return void
3231
	 */
3232
	public function handleInlineEdit($id)
3233
	{
3234
		if ($this->inlineEdit) {
3235
			$this->inlineEdit->setItemId($id);
3236
3237
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
3238
3239
			$this['filter']['inline_edit']->addHidden('_id', $id);
3240
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
3241
3242
			if ($this->getPresenter()->isAjax()) {
3243
				$this->getPresenter()->payload->_datagrid_inline_editing = true;
3244
				$this->getPresenter()->payload->_datagrid_name = $this->getName();
3245
			}
3246
3247
			$this->redrawItem($id, $primary_where_column);
3248
		}
3249
	}
3250
3251
3252
	/********************************************************************************
3253
	 *                                  INLINE ADD                                  *
3254
	 ********************************************************************************/
3255
3256
3257
	/**
3258
	 * @return InlineEdit
3259
	 */
3260
	public function addInlineAdd()
3261
	{
3262
		$this->inlineAdd = new InlineEdit($this);
3263
3264
		$this->inlineAdd
3265
			->setTitle('ublaboo_datagrid.add')
3266
			->setIcon('plus')
3267
			->setClass('btn btn-xs btn-default');
3268
3269
		return $this->inlineAdd;
3270
	}
3271
3272
3273
	/**
3274
	 * @return InlineEdit|null
3275
	 */
3276
	public function getInlineAdd()
3277
	{
3278
		return $this->inlineAdd;
3279
	}
3280
3281
3282
	/********************************************************************************
3283
	 *                               HIDEABLE COLUMNS                               *
3284
	 ********************************************************************************/
3285
3286
3287
	/**
3288
	 * Can datagrid hide colums?
3289
	 * @return bool
3290
	 */
3291
	public function canHideColumns()
3292
	{
3293
		return (bool) $this->can_hide_columns;
3294
	}
3295
3296
3297
	/**
3298
	 * Order Grid to set columns hideable.
3299
	 * @return static
3300
	 */
3301
	public function setColumnsHideable()
3302
	{
3303
		$this->can_hide_columns = true;
3304
3305
		return $this;
3306
	}
3307
3308
3309
	/********************************************************************************
3310
	 *                                COLUMNS SUMMARY                               *
3311
	 ********************************************************************************/
3312
3313
3314
	/**
3315
	 * Will datagrid show summary in the end?
3316
	 * @return bool
3317
	 */
3318
	public function hasColumnsSummary()
3319
	{
3320 1
		return $this->columnsSummary instanceof ColumnsSummary;
3321
	}
3322
3323
3324
	/**
3325
	 * Set columns to be summarized in the end.
3326
	 * @param array    $columns
3327
	 * @param callable $rowCallback
3328
	 * @return \Ublaboo\DataGrid\ColumnsSummary
3329
	 */
3330
	public function setColumnsSummary(array $columns, $rowCallback = null)
3331
	{
3332
		if ($this->hasSomeAggregationFunction()) {
3333
			throw new DataGridException('You can use either ColumnsSummary or AggregationFunctions');
3334
		}
3335
3336
		if (!empty($rowCallback)) {
3337
			if (!is_callable($rowCallback)) {
3338
				throw new \InvalidArgumentException('Row summary callback must be callable');
3339
			}
3340
		}
3341
3342
		$this->columnsSummary = new ColumnsSummary($this, $columns, $rowCallback);
3343
3344
		return $this->columnsSummary;
3345
	}
3346
3347
3348
	/**
3349
	 * @return ColumnsSummary|NULL
3350
	 */
3351
	public function getColumnsSummary()
3352
	{
3353
		return $this->columnsSummary;
3354
	}
3355
3356
3357
	/********************************************************************************
3358
	 *                                   INTERNAL                                   *
3359
	 ********************************************************************************/
3360
3361
3362
	/**
3363
	 * Tell grid filters to by submitted automatically
3364
	 * @param bool $auto
3365
	 */
3366
	public function setAutoSubmit($auto = true)
3367
	{
3368
		$this->auto_submit = (bool) $auto;
3369
3370
		return $this;
3371
	}
3372
3373
3374
	/**
3375
	 * @return bool
3376
	 */
3377
	public function hasAutoSubmit()
3378
	{
3379
		return $this->auto_submit;
3380
	}
3381
3382
3383
	/**
3384
	 * Submit button when no auto-submitting is used
3385
	 * @return Filter\SubmitButton
3386
	 */
3387
	public function getFilterSubmitButton()
3388
	{
3389
		if ($this->hasAutoSubmit()) {
3390
			throw new DataGridException(
3391
				'DataGrid has auto-submit. Turn it off before setting filter submit button.'
3392
			);
3393
		}
3394
3395
		if ($this->filter_submit_button === null) {
3396
			$this->filter_submit_button = new Filter\SubmitButton($this);
3397
		}
3398
3399
		return $this->filter_submit_button;
3400
	}
3401
3402
3403
	/********************************************************************************
3404
	 *                                   INTERNAL                                   *
3405
	 ********************************************************************************/
3406
3407
3408
	/**
3409
	 * Get count of columns
3410
	 * @return int
3411
	 */
3412
	public function getColumnsCount()
3413
	{
3414
		$count = sizeof($this->getColumns());
3415
3416
		if (!empty($this->actions)
3417
			|| $this->isSortable()
3418
			|| $this->getItemsDetail()
3419
			|| $this->getInlineEdit()
3420
			|| $this->getInlineAdd()) {
3421
			$count++;
3422
		}
3423
3424
		if ($this->hasGroupActions()) {
3425
			$count++;
3426
		}
3427
3428
		return $count;
3429
	}
3430
3431
3432
	/**
3433
	 * Get primary key of datagrid data source
3434
	 * @return string
3435
	 */
3436
	public function getPrimaryKey()
3437
	{
3438 1
		return $this->primary_key;
3439
	}
3440
3441
3442
	/**
3443
	 * Get set of set columns
3444
	 * @return Column\IColumn[]
3445
	 */
3446
	public function getColumns()
3447
	{
3448 1
		$return = $this->columns;
3449
3450
		try {
3451 1
			$this->getParent();
3452
3453 1
			if (!$this->getSessionData('_grid_hidden_columns_manipulated', false)) {
3454 1
				$columns_to_hide = [];
3455
3456 1
				foreach ($this->columns as $key => $column) {
3457
					if ($column->getDefaultHide()) {
3458
						$columns_to_hide[] = $key;
3459
					}
3460
				}
3461
3462 1
				if (!empty($columns_to_hide)) {
3463
					$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
3464
					$this->saveSessionData('_grid_hidden_columns_manipulated', true);
3465
				}
3466
			}
3467
3468 1
			$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
3469
3470 1
			foreach ($hidden_columns as $column) {
3471
				if (!empty($this->columns[$column])) {
3472
					$this->columns_visibility[$column] = [
3473
						'visible' => false,
3474
					];
3475
3476 1
					unset($return[$column]);
3477
				}
3478
			}
3479
3480
		} catch (DataGridHasToBeAttachedToPresenterComponentException $e) {
3481
		}
3482
3483 1
		return $return;
3484
	}
3485
3486
3487
	public function getColumnsVisibility()
3488
	{
3489 1
		$return = $this->columns_visibility;
3490
3491 1
		foreach ($this->columns_visibility as $key => $column) {
3492
			$return[$key]['column'] = $this->columns[$key];
3493
		}
3494
3495 1
		return $return;
3496
	}
3497
3498
3499
	/**
3500
	 * @return PresenterComponent
3501
	 */
3502
	public function getParent()
3503
	{
3504 1
		$parent = parent::getParent();
3505
3506 1
		if (!($parent instanceof PresenterComponent)) {
3507
			throw new DataGridHasToBeAttachedToPresenterComponentException(
3508
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
3509
			);
3510
		}
3511
3512 1
		return $parent;
3513
	}
3514
3515
3516
	/**
3517
	 * @return string
3518
	 */
3519
	public function getSortableParentPath()
3520
	{
3521
		return $this->getParent()->lookupPath(Nette\Application\IPresenter::class, false);
3522
	}
3523
3524
3525
	/**
3526
	 * Some of datagrid columns is hidden by default
3527
	 * @param bool $default_hide
3528
	 */
3529
	public function setSomeColumnDefaultHide($default_hide)
3530
	{
3531
		$this->some_column_default_hide = $default_hide;
3532
	}
3533
3534
3535
	/**
3536
	 * Are some of columns hidden bydefault?
3537
	 */
3538
	public function hasSomeColumnDefaultHide()
3539
	{
3540
		return $this->some_column_default_hide;
3541
	}
3542
3543
3544
	/**
3545
	 * Simply refresh url
3546
	 * @return void
3547
	 */
3548
	public function handleRefreshState()
3549
	{
3550
		$this->findSessionValues();
3551
		$this->findDefaultFilter();
3552
		$this->findDefaultSort();
3553
		$this->findDefaultPerPage();
3554
3555
		$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
3556
		$this->redrawControl('non-existing-snippet');
3557
	}
3558
}
3559
3560