Completed
Pull Request — master (#315)
by Martin
03:32
created

DataGrid::setColumnsOrder()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 18
rs 9.2
cc 4
eloc 10
nc 6
nop 1
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\Link;
13
use Nette\Application\UI\PresenterComponent;
14
use Ublaboo\DataGrid\Utils\ArraysHelper;
15
use Nette\Application\UI\Form;
16
use Ublaboo\DataGrid\Exception\DataGridException;
17
use Ublaboo\DataGrid\Exception\DataGridHasToBeAttachedToPresenterComponentException;
18
use Ublaboo\DataGrid\Utils\Sorting;
19
use Ublaboo\DataGrid\InlineEdit\InlineEdit;
20
use Ublaboo\DataGrid\ColumnsSummary;
21
use Ublaboo\DataGrid\Toolbar\ToolbarButton;
22
23
/**
24
 * @method onRedraw()
25
 * @method onRender()
26
 * @method onColumnAdd()
27
 */
28
class DataGrid extends Nette\Application\UI\Control
29
{
30
31
	/**
32
	 * @var callable[]
33
	 */
34
	public $onRedraw;
35
36
	/**
37
	 * @var callable[]
38
	 */
39
	public $onRender = [];
40
41
	/**
42
	 * @var callable[]
43
	 */
44
	public $onColumnAdd;
45
46
	/**
47
	 * @var string
48
	 */
49
	public static $icon_prefix = 'fa fa-';
50
51
	/**
52
	 * Default form method
53
	 * @var string
54
	 */
55
	public static $form_method = 'post';
56
57
	/**
58
	 * When set to TRUE, datagrid throws an exception
59
	 * 	when tring to get related entity within join and entity does not exist
60
	 * @var bool
61
	 */
62
	public $strict_entity_property = FALSE;
63
64
	/**
65
	 * @var int
66
	 * @persistent
67
	 */
68
	public $page = 1;
69
70
	/**
71
	 * @var int|string
72
	 * @persistent
73
	 */
74
	public $per_page;
75
76
	/**
77
	 * @var array
78
	 * @persistent
79
	 */
80
	public $sort = [];
81
82
	/**
83
	 * @var array
84
	 */
85
	public $default_sort = [];
86
87
	/**
88
	 * @var array
89
	 */
90
	public $default_filter = [];
91
92
	/**
93
	 * @var bool
94
	 */
95
	public $default_filter_use_on_reset = TRUE;
96
97
	/**
98
	 * @var array
99
	 * @persistent
100
	 */
101
	public $filter = [];
102
103
	/**
104
	 * @var callable|null
105
	 */
106
	protected $sort_callback = NULL;
107
108
	/**
109
	 * @var bool
110
	 */
111
	protected $use_happy_components = TRUE;
112
113
	/**
114
	 * @var callable
115
	 */
116
	protected $rowCallback;
117
118
	/**
119
	 * @var array
120
	 */
121
	protected $items_per_page_list;
122
123
	/**
124
	 * @var int
125
	 */
126
	protected $default_per_page;
127
128
	/**
129
	 * @var string
130
	 */
131
	protected $template_file;
132
133
	/**
134
	 * @var Column\IColumn[]
135
	 */
136
	protected $columns = [];
137
138
	/**
139
	 * @var Column\Action[]
140
	 */
141
	protected $actions = [];
142
143
	/**
144
	 * @var GroupAction\GroupActionCollection
145
	 */
146
	protected $group_action_collection;
147
148
	/**
149
	 * @var Filter\Filter[]
150
	 */
151
	protected $filters = [];
152
153
	/**
154
	 * @var Export\Export[]
155
	 */
156
	protected $exports = [];
157
158
	/**
159
	 * @var ToolbarButton[]
160
	 */
161
	protected $toolbar_buttons = [];
162
163
	/**
164
	 * @var DataModel
165
	 */
166
	protected $dataModel;
167
168
	/**
169
	 * @var DataFilter
170
	 */
171
	protected $dataFilter;
172
173
	/**
174
	 * @var string
175
	 */
176
	protected $primary_key = 'id';
177
178
	/**
179
	 * @var bool
180
	 */
181
	protected $do_paginate = TRUE;
182
183
	/**
184
	 * @var bool
185
	 */
186
	protected $csv_export = TRUE;
187
188
	/**
189
	 * @var bool
190
	 */
191
	protected $csv_export_filtered = TRUE;
192
193
	/**
194
	 * @var bool
195
	 */
196
	protected $sortable = FALSE;
197
198
	/**
199
	 * @var string
200
	 */
201
	protected $sortable_handler = 'sort!';
202
203
	/**
204
	 * @var string
205
	 */
206
	protected $original_template;
207
208
	/**
209
	 * @var array
210
	 */
211
	protected $redraw_item;
212
213
	/**
214
	 * @var mixed
215
	 */
216
	protected $translator;
217
218
	/**
219
	 * @var bool
220
	 */
221
	protected $force_filter_active;
222
223
	/**
224
	 * @var callable
225
	 */
226
	protected $tree_view_children_callback;
227
228
	/**
229
	 * @var callable
230
	 */
231
	protected $tree_view_has_children_callback;
232
233
	/**
234
	 * @var string
235
	 */
236
	protected $tree_view_has_children_column;
237
238
	/**
239
	 * @var bool
240
	 */
241
	protected $outer_filter_rendering = FALSE;
242
243
	/**
244
	 * @var array
245
	 */
246
	protected $columns_export_order = [];
247
248
	/**
249
	 * @var bool
250
	 */
251
	protected $remember_state = TRUE;
252
253
	/**
254
	 * @var bool
255
	 */
256
	protected $refresh_url = TRUE;
257
258
	/**
259
	 * @var Nette\Http\SessionSection
260
	 */
261
	protected $grid_session;
262
263
	/**
264
	 * @var Column\ItemDetail
265
	 */
266
	protected $items_detail;
267
268
	/**
269
	 * @var array
270
	 */
271
	protected $row_conditions = [
272
		'group_action' => FALSE,
273
		'action' => []
274
	];
275
276
	/**
277
	 * @var array
278
	 */
279
	protected $column_callbacks = [];
280
281
	/**
282
	 * @var bool
283
	 */
284
	protected $can_hide_columns = FALSE;
285
286
	/**
287
	 * @var array
288
	 */
289
	protected $columns_visibility = [];
290
291
	/**
292
	 * @var InlineEdit
293
	 */
294
	protected $inlineEdit;
295
296
	/**
297
	 * @var InlineEdit
298
	 */
299
	protected $inlineAdd;
300
301
	/**
302
	 * @var bool
303
	 */
304
	protected $snippets_set = FALSE;
305
306
	/**
307
	 * @var bool
308
	 */
309
	protected $some_column_default_hide = FALSE;
310
311
	/**
312
	 * @var ColumnsSummary
313
	 */
314
	protected $columnsSummary;
315
316
	/**
317
	 * @var bool
318
	 */
319
	protected $auto_submit = TRUE;
320
321
	/**
322
	 * @var Filter\SubmitButton|NULL
323
	 */
324
	protected $filter_submit_button = NULL;
325
326
327
	/**
328
	 * @param Nette\ComponentModel\IContainer|NULL $parent
329
	 * @param string                               $name
330
	 */
331
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
332
	{
333
		parent::__construct($parent, $name);
334
335
		$this->monitor('Nette\Application\UI\Presenter');
336
337
		/**
338
		 * Try to find previous filters, pagination, per_page and other values in session
339
		 */
340
		$this->onRender[] = [$this, 'findSessionValues'];
341
342
		/**
343
		 * Find default filter values
344
		 */
345
		$this->onRender[] = [$this, 'findDefaultFilter'];
346
347
		/**
348
		 * Find default sort
349
		 */
350
		$this->onRender[] = [$this, 'findDefaultSort'];
351
352
		/**
353
		 * Find default items per page
354
		 */
355
		$this->onRender[] = [$this, 'findDefaultPerPage'];
356
	}
357
358
359
	/**
360
	 * {inheritDoc}
361
	 * @return void
362
	 */
363
	public function attached($presenter)
364
	{
365
		parent::attached($presenter);
366
367
		if ($presenter instanceof Nette\Application\UI\Presenter) {
368
			/**
369
			 * Get session
370
			 */
371
			if ($this->remember_state) {
372
				$this->grid_session = $presenter->getSession($this->getSessionSectionName());
0 ignored issues
show
Documentation Bug introduced by
It seems like $presenter->getSession($...etSessionSectionName()) can also be of type object<Nette\Http\Session>. However, the property $grid_session is declared as type object<Nette\Http\SessionSection>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
373
			}
374
		}
375
	}
376
377
378
	/********************************************************************************
379
	 *                                  RENDERING                                   *
380
	 ********************************************************************************/
381
382
383
	/**
384
	 * Render template
385
	 * @return void
386
	 */
387
	public function render()
388
	{
389
		/**
390
		 * Check whether datagrid has set some columns, initiated data source, etc
391
		 */
392
		if (!($this->dataModel instanceof DataModel)) {
393
			throw new DataGridException('You have to set a data source first.');
394
		}
395
396
		if (empty($this->columns)) {
397
			throw new DataGridException('You have to add at least one column.');
398
		}
399
400
		$this->getTemplate()->setTranslator($this->getTranslator());
401
402
		/**
403
		 * Invoke possible events
404
		 */
405
		$this->onRender($this);
0 ignored issues
show
Unused Code introduced by
The call to DataGrid::onRender() has too many arguments starting with $this.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
406
407
		/**
408
		 * Prepare data for rendering (datagrid may render just one item)
409
		 */
410
		$rows = [];
411
412
		if (!empty($this->redraw_item)) {
413
			$items = $this->dataModel->filterRow($this->redraw_item);
414
		} else {
415
			$items = Nette\Utils\Callback::invokeArgs(
416
				[$this->dataModel, 'filterData'],
417
				[
418
					$this->getPaginator(),
419
					$this->createSorting($this->sort, $this->sort_callback),
420
					$this->assableFilters()
421
				]
422
			);
423
		}
424
425
		$callback = $this->rowCallback ?: NULL;
426
		$hasGroupActionOnRows = FALSE;
427
428
		foreach ($items as $item) {
429
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
430
431
			if (!$hasGroupActionOnRows && $row->hasGroupAction()){
432
				$hasGroupActionOnRows = TRUE;
433
			}
434
			
435
			if ($callback) {
436
				$callback($item, $row->getControl());
437
			}
438
439
			/**
440
			 * Walkaround for item snippet - snippet is the <tr> element and its class has to be also updated
441
			 */
442
			if (!empty($this->redraw_item)) {
443
				$this->getPresenter()->payload->_datagrid_redraw_item_class = $row->getControlClass();
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
444
				$this->getPresenter()->payload->_datagrid_redraw_item_id = $row->getId();
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
445
			}
446
		}
447
448
		if ($hasGroupActionOnRows){
449
			$hasGroupActionOnRows = $this->hasGroupActions();
450
		}
451
452
		if ($this->isTreeView()) {
453
			$this->getTemplate()->add('tree_view_has_children_column', $this->tree_view_has_children_column);
454
		}
455
456
		$this->getTemplate()->add('rows', $rows);
457
458
		$this->getTemplate()->add('columns', $this->getColumns());
459
		$this->getTemplate()->add('actions', $this->actions);
460
		$this->getTemplate()->add('exports', $this->exports);
461
		$this->getTemplate()->add('filters', $this->filters);
462
		$this->getTemplate()->add('toolbar_buttons', $this->toolbar_buttons);
463
464
		$this->getTemplate()->add('filter_active', $this->isFilterActive());
465
		$this->getTemplate()->add('original_template', $this->getOriginalTemplateFile());
466
		//$this->getTemplate()->add('icon_prefix', static::$icon_prefix);
0 ignored issues
show
Unused Code Comprehensibility introduced by
82% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
467
		$this->getTemplate()->icon_prefix = static::$icon_prefix;
0 ignored issues
show
Bug introduced by
Accessing icon_prefix on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
468
		$this->getTemplate()->add('items_detail', $this->items_detail);
469
		$this->getTemplate()->add('columns_visibility', $this->getColumnsVisibility());
470
		$this->getTemplate()->add('columnsSummary', $this->columnsSummary);
471
472
		$this->getTemplate()->add('inlineEdit', $this->inlineEdit);
473
		$this->getTemplate()->add('inlineAdd', $this->inlineAdd);
474
475
		$this->getTemplate()->add('hasGroupActionOnRows', $hasGroupActionOnRows);
476
477
		/**
478
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
479
		 */
480
		$this->getTemplate()->add('filter', $this['filter']);
481
482
		/**
483
		 * Set template file and render it
484
		 */
485
		$this->getTemplate()->setFile($this->getTemplateFile());
486
		$this->getTemplate()->render();
487
	}
488
489
490
	/********************************************************************************
491
	 *                                 ROW CALLBACK                                 *
492
	 ********************************************************************************/
493
494
495
	/**
496
	 * Each row can be modified with user callback
497
	 * @param  callable  $callback
498
	 * @return static
499
	 */
500
	public function setRowCallback(callable $callback)
501
	{
502
		$this->rowCallback = $callback;
503
504
		return $this;
505
	}
506
507
508
	/********************************************************************************
509
	 *                                 DATA SOURCE                                  *
510
	 ********************************************************************************/
511
512
513
	/**
514
	 * By default ID, you can change that
515
	 * @param string $primary_key
516
	 * @return static
517
	 */
518
	public function setPrimaryKey($primary_key)
519
	{
520
		if ($this->dataModel instanceof DataModel) {
521
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
522
		}
523
524
		$this->primary_key = $primary_key;
525
526
		return $this;
527
	}
528
529
530
	/**
531
	 * Set Grid data source
532
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
533
	 * @return static
534
	 */
535
	public function setDataSource($source)
536
	{
537
		$this->dataModel = new DataModel($source, $this->primary_key);
538
539
		return $this;
540
	}
541
542
543
	/**
544
	 * @return DataSource\IDataSource|NULL
545
	 */
546
	public function getDataSource()
547
	{
548
		if (!$this->dataModel) {
549
			return NULL;
550
		}
551
552
		return $this->dataModel->getDataSource();
553
	}
554
555
556
	/********************************************************************************
557
	 *                                  TEMPLATING                                  *
558
	 ********************************************************************************/
559
560
561
	/**
562
	 * Set custom template file to render
563
	 * @param string $template_file
564
	 * @return static
565
	 */
566
	public function setTemplateFile($template_file)
567
	{
568
		$this->template_file = $template_file;
569
570
		return $this;
571
	}
572
573
574
	/**
575
	 * Get DataGrid template file
576
	 * @return string
577
	 * @return static
578
	 */
579
	public function getTemplateFile()
580
	{
581
		return $this->template_file ?: $this->getOriginalTemplateFile();
582
	}
583
584
585
	/**
586
	 * Get DataGrid original template file
587
	 * @return string
588
	 */
589
	public function getOriginalTemplateFile()
590
	{
591
		return __DIR__.'/templates/datagrid.latte';
592
	}
593
594
595
	/**
596
	 * Tell datagrid wheteher to use or not happy components
597
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
598
	 * @return void|bool
599
	 */
600
	public function useHappyComponents($use = NULL)
601
	{
602
		if (NULL === $use) {
603
			return $this->use_happy_components;
604
		}
605
606
		$this->use_happy_components = (bool) $use;
607
	}
608
609
610
	/********************************************************************************
611
	 *                                   SORTING                                    *
612
	 ********************************************************************************/
613
614
615
	/**
616
	 * Set default sorting
617
	 * @param array $sort
618
	 * @return static
619
	 */
620
	public function setDefaultSort($sort)
621
	{
622
		if (is_string($sort)) {
623
			$sort = [$sort => 'ASC'];
624
		} else {
625
			$sort = (array) $sort;
626
		}
627
628
		$this->default_sort = $sort;
629
630
		return $this;
631
	}
632
633
634
	/**
635
	 * User may set default sorting, apply it
636
	 * @return void
637
	 */
638
	public function findDefaultSort()
639
	{
640
		if (!empty($this->sort)) {
641
			return;
642
		}
643
644
		if (!empty($this->default_sort)) {
645
			$this->sort = $this->default_sort;
646
		}
647
648
		$this->saveSessionData('_grid_sort', $this->sort);
649
	}
650
651
652
	/**
653
	 * Set grido to be sortable
654
	 * @param bool $sortable
655
	 * @return static
656
	 */
657
	public function setSortable($sortable = TRUE)
658
	{
659
		if ($this->getItemsDetail()) {
660
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
661
		}
662
663
		$this->sortable = (bool) $sortable;
664
665
		return $this;
666
	}
667
668
669
	/**
670
	 * Set sortable handle
671
	 * @param string $handler
672
	 * @return static
673
	 */
674
	public function setSortableHandler($handler = 'sort!')
675
	{
676
		$this->sortable_handler = (string) $handler;
677
678
		return $this;
679
	}
680
681
682
	/**
683
	 * Tell whether DataGrid is sortable
684
	 * @return bool
685
	 */
686
	public function isSortable()
687
	{
688
		return $this->sortable;
689
	}
690
691
	/**
692
	 * Return sortable handle name
693
	 * @return string
694
	 */
695
	public function getSortableHandler()
696
	{
697
		return $this->sortable_handler;
698
	}
699
700
701
	protected function createSorting(array $sort, $sort_callback)
702
	{
703
		foreach ($sort as $key => $order) {
704
			$column = $this->columns[$key];
705
			$sort = [$column->getSortingColumn() => $order];
706
		}
707
708
		return new Sorting($sort, $sort_callback);
709
	}
710
711
712
	/********************************************************************************
713
	 *                                  TREE VIEW                                   *
714
	 ********************************************************************************/
715
716
717
	/**
718
	 * Is tree view set?
719
	 * @return boolean
720
	 */
721
	public function isTreeView()
722
	{
723
		return (bool) $this->tree_view_children_callback;
724
	}
725
726
727
	/**
728
	 * Setting tree view
729
	 * @param callable $get_children_callback
730
	 * @param string|callable $tree_view_has_children_column
731
	 * @return static
732
	 */
733
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
734
	{
735
		if (!is_callable($get_children_callback)) {
736
			throw new DataGridException(
737
				'Parameters to method DataGrid::setTreeView must be of type callable'
738
			);
739
		}
740
741
		if (is_callable($tree_view_has_children_column)) {
742
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
743
			$tree_view_has_children_column = NULL;
744
		}
745
746
		$this->tree_view_children_callback = $get_children_callback;
747
		$this->tree_view_has_children_column = $tree_view_has_children_column;
748
749
		/**
750
		 * TUrn off pagination
751
		 */
752
		$this->setPagination(FALSE);
753
754
		/**
755
		 * Set tree view template file
756
		 */
757
		if (!$this->template_file) {
758
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
759
		}
760
761
		return $this;
762
	}
763
764
765
	/**
766
	 * Is tree view children callback set?
767
	 * @return boolean
768
	 */
769
	public function hasTreeViewChildrenCallback()
770
	{
771
		return is_callable($this->tree_view_has_children_callback);
772
	}
773
774
775
	/**
776
	 * @param  mixed $item
777
	 * @return boolean
778
	 */
779
	public function treeViewChildrenCallback($item)
780
	{
781
		return call_user_func($this->tree_view_has_children_callback, $item);
782
	}
783
784
785
	/********************************************************************************
786
	 *                                    COLUMNS                                   *
787
	 ********************************************************************************/
788
789
790
	/**
791
	 * Add text column with no other formating
792
	 * @param  string      $key
793
	 * @param  string      $name
794
	 * @param  string|null $column
795
	 * @return Column\ColumnText
796
	 */
797
	public function addColumnText($key, $name, $column = NULL)
798
	{
799
		$this->addColumnCheck($key);
800
		$column = $column ?: $key;
801
802
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
803
	}
804
805
806
	/**
807
	 * Add column with link
808
	 * @param  string      $key
809
	 * @param  string      $name
810
	 * @param  string|null $column
811
	 * @return Column\ColumnLink
812
	 */
813
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
814
	{
815
		$this->addColumnCheck($key);
816
		$column = $column ?: $key;
817
		$href = $href ?: $key;
818
819
		if (NULL === $params) {
820
			$params = [$this->primary_key];
821
		}
822
823
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
824
	}
825
826
827
	/**
828
	 * Add column with possible number formating
829
	 * @param  string      $key
830
	 * @param  string      $name
831
	 * @param  string|null $column
832
	 * @return Column\ColumnNumber
833
	 */
834
	public function addColumnNumber($key, $name, $column = NULL)
835
	{
836
		$this->addColumnCheck($key);
837
		$column = $column ?: $key;
838
839
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
840
	}
841
842
843
	/**
844
	 * Add column with date formating
845
	 * @param  string      $key
846
	 * @param  string      $name
847
	 * @param  string|null $column
848
	 * @return Column\ColumnDateTime
849
	 */
850
	public function addColumnDateTime($key, $name, $column = NULL)
851
	{
852
		$this->addColumnCheck($key);
853
		$column = $column ?: $key;
854
855
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
856
	}
857
858
859
	/**
860
	 * Add column status
861
	 * @param  string      $key
862
	 * @param  string      $name
863
	 * @param  string|null $column
864
	 * @return Column\ColumnStatus
865
	 */
866
	public function addColumnStatus($key, $name, $column = NULL)
867
	{
868
		$this->addColumnCheck($key);
869
		$column = $column ?: $key;
870
871
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
872
	}
873
874
875
	/**
876
	 * @param string $key
877
	 * @param Column\Column $column
878
	 * @return Column\Column
879
	 */
880
	protected function addColumn($key, Column\Column $column)
881
	{
882
		$this->onColumnAdd($key, $column);
0 ignored issues
show
Unused Code introduced by
The call to DataGrid::onColumnAdd() has too many arguments starting with $key.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
883
884
		$this->columns_visibility[$key] = [
885
			'visible' => TRUE
886
		];
887
888
		return $this->columns[$key] = $column;
889
	}
890
891
892
	/**
893
	 * Return existing column
894
	 * @param  string $key
895
	 * @return Column\Column
896
	 * @throws DataGridException
897
	 */
898
	public function getColumn($key)
899
	{
900
		if (!isset($this->columns[$key])) {
901
			throw new DataGridException("There is no column at key [$key] defined.");
902
		}
903
904
		return $this->columns[$key];
905
	}
906
907
908
	/**
909
	 * Remove column
910
	 * @param string $key
911
	 * @return void
912
	 */
913
	public function removeColumn($key)
914
	{
915
		unset($this->columns[$key]);
916
	}
917
918
919
	/**
920
	 * Check whether given key already exists in $this->columns
921
	 * @param  string $key
922
	 * @throws DataGridException
923
	 */
924
	protected function addColumnCheck($key)
925
	{
926
		if (isset($this->columns[$key])) {
927
			throw new DataGridException("There is already column at key [$key] defined.");
928
		}
929
	}
930
931
932
	/********************************************************************************
933
	 *                                    ACTIONS                                   *
934
	 ********************************************************************************/
935
936
937
	/**
938
	 * Create action
939
	 * @param string     $key
940
	 * @param string     $name
941
	 * @param string     $href
942
	 * @param array|null $params
943
	 * @return Column\Action
944
	 */
945
	public function addAction($key, $name, $href = NULL, array $params = NULL)
946
	{
947
		$this->addActionCheck($key);
948
		$href = $href ?: $key;
949
950
		if (NULL === $params) {
951
			$params = [$this->primary_key];
952
		}
953
954
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
955
	}
956
957
958
	/**
959
	 * Create action callback
960
	 * @param string     $key
961
	 * @param string     $name
962
	 * @return Column\Action
963
	 */
964
	public function addActionCallback($key, $name, $callback = NULL)
965
	{
966
		$this->addActionCheck($key);
967
		$params = ['__id' => $this->primary_key];
968
969
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
970
971
		if ($callback) {
972
			if (!is_callable($callback)) {
973
				throw new DataGridException('ActionCallback callback has to be callable.');
974
			}
975
976
			$action->onClick[] = $callback;
977
		}
978
979
		return $action;
980
	}
981
982
983
	/**
984
	 * Get existing action
985
	 * @param  string       $key
986
	 * @return Column\Action
987
	 * @throws DataGridException
988
	 */
989
	public function getAction($key)
990
	{
991
		if (!isset($this->actions[$key])) {
992
			throw new DataGridException("There is no action at key [$key] defined.");
993
		}
994
995
		return $this->actions[$key];
996
	}
997
998
999
	/**
1000
	 * Remove action
1001
	 * @param string $key
1002
	 * @return void
1003
	 */
1004
	public function removeAction($key)
1005
	{
1006
		unset($this->actions[$key]);
1007
	}
1008
1009
1010
	/**
1011
	 * Check whether given key already exists in $this->filters
1012
	 * @param  string $key
1013
	 * @throws DataGridException
1014
	 */
1015
	protected function addActionCheck($key)
1016
	{
1017
		if (isset($this->actions[$key])) {
1018
			throw new DataGridException("There is already action at key [$key] defined.");
1019
		}
1020
	}
1021
1022
1023
	/********************************************************************************
1024
	 *                                    FILTERS                                   *
1025
	 ********************************************************************************/
1026
1027
1028
	/**
1029
	 * Add filter fot text search
1030
	 * @param string       $key
1031
	 * @param string       $name
1032
	 * @param array|string $columns
1033
	 * @return Filter\FilterText
1034
	 * @throws DataGridException
1035
	 */
1036
	public function addFilterText($key, $name, $columns = NULL)
1037
	{
1038
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
1039
1040
		if (!is_array($columns)) {
1041
			throw new DataGridException("Filter Text can except only array or string.");
1042
		}
1043
1044
		$this->addFilterCheck($key);
1045
1046
		return $this->filters[$key] = new Filter\FilterText($this, $key, $name, $columns);
1047
	}
1048
1049
1050
	/**
1051
	 * Add select box filter
1052
	 * @param string $key
1053
	 * @param string $name
1054
	 * @param array  $options
1055
	 * @param string $column
1056
	 * @return Filter\FilterSelect
1057
	 * @throws DataGridException
1058
	 */
1059 View Code Duplication
	public function addFilterSelect($key, $name, array $options, $column = NULL)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1060
	{
1061
		$column = $column ?: $key;
1062
1063
		if (!is_string($column)) {
1064
			throw new DataGridException("Filter Select can only filter in one column.");
1065
		}
1066
1067
		$this->addFilterCheck($key);
1068
1069
		return $this->filters[$key] = new Filter\FilterSelect($this, $key, $name, $options, $column);
1070
	}
1071
1072
1073
	/**
1074
	 * Add multi select box filter
1075
	 * @param string $key
1076
	 * @param string $name
1077
	 * @param array  $options
1078
	 * @param string $column
1079
	 * @return Filter\FilterSelect
1080
	 * @throws DataGridException
1081
	 */
1082 View Code Duplication
	public function addFilterMultiSelect($key, $name, array $options, $column = NULL)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1083
	{
1084
		$column = $column ?: $key;
1085
1086
		if (!is_string($column)) {
1087
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1088
		}
1089
1090
		$this->addFilterCheck($key);
1091
1092
		return $this->filters[$key] = new Filter\FilterMultiSelect($this, $key, $name, $options, $column);
1093
	}
1094
1095
1096
	/**
1097
	 * Add datepicker filter
1098
	 * @param string $key
1099
	 * @param string $name
1100
	 * @param string $column
1101
	 * @return Filter\FilterDate
1102
	 * @throws DataGridException
1103
	 */
1104 View Code Duplication
	public function addFilterDate($key, $name, $column = NULL)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1105
	{
1106
		$column = $column ?: $key;
1107
1108
		if (!is_string($column)) {
1109
			throw new DataGridException("FilterDate can only filter in one column.");
1110
		}
1111
1112
		$this->addFilterCheck($key);
1113
1114
		return $this->filters[$key] = new Filter\FilterDate($this, $key, $name, $column);
1115
	}
1116
1117
1118
	/**
1119
	 * Add range filter (from - to)
1120
	 * @param string $key
1121
	 * @param string $name
1122
	 * @param string $column
1123
	 * @return Filter\FilterRange
1124
	 * @throws DataGridException
1125
	 */
1126 View Code Duplication
	public function addFilterRange($key, $name, $column = NULL, $name_second = '-')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1127
	{
1128
		$column = $column ?: $key;
1129
1130
		if (!is_string($column)) {
1131
			throw new DataGridException("FilterRange can only filter in one column.");
1132
		}
1133
1134
		$this->addFilterCheck($key);
1135
1136
		return $this->filters[$key] = new Filter\FilterRange($this, $key, $name, $column, $name_second);
1137
	}
1138
1139
1140
	/**
1141
	 * Add datepicker filter (from - to)
1142
	 * @param string $key
1143
	 * @param string $name
1144
	 * @param string $column
1145
	 * @return Filter\FilterDateRange
1146
	 * @throws DataGridException
1147
	 */
1148 View Code Duplication
	public function addFilterDateRange($key, $name, $column = NULL, $name_second = '-')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1149
	{
1150
		$column = $column ?: $key;
1151
1152
		if (!is_string($column)) {
1153
			throw new DataGridException("FilterDateRange can only filter in one column.");
1154
		}
1155
1156
		$this->addFilterCheck($key);
1157
1158
		return $this->filters[$key] = new Filter\FilterDateRange($this, $key, $name, $column, $name_second);
1159
	}
1160
1161
1162
	/**
1163
	 * Check whether given key already exists in $this->filters
1164
	 * @param  string $key
1165
	 * @throws DataGridException
1166
	 */
1167
	protected function addFilterCheck($key)
1168
	{
1169
		if (isset($this->filters[$key])) {
1170
			throw new DataGridException("There is already action at key [$key] defined.");
1171
		}
1172
	}
1173
1174
1175
	/**
1176
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1177
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1178
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1179
	 */
1180
	public function assableFilters()
1181
	{
1182
		foreach ($this->filter as $key => $value) {
1183
			if (!isset($this->filters[$key])) {
1184
				$this->deleteSesssionData($key);
1185
1186
				continue;
1187
			}
1188
1189
			if (is_array($value) || $value instanceof \Traversable) {
1190
				if (!ArraysHelper::testEmpty($value)) {
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type object<Traversable>; however, Ublaboo\DataGrid\Utils\ArraysHelper::testEmpty() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1191
					$this->filters[$key]->setValue($value);
1192
				}
1193
			} else {
1194
				if ($value !== '' && $value !== NULL) {
1195
					$this->filters[$key]->setValue($value);
1196
				}
1197
			}
1198
		}
1199
1200
		foreach ($this->columns as $key => $column) {
1201
			if (isset($this->sort[$key])) {
1202
				$column->setSort($this->sort);
1203
			}
1204
		}
1205
1206
		return $this->filters;
1207
	}
1208
1209
1210
	/**
1211
	 * Remove filter
1212
	 * @param string $key
1213
	 * @return void
1214
	 */
1215
	public function removeFilter($key)
1216
	{
1217
		unset($this->filters[$key]);
1218
	}
1219
1220
1221
	/**
1222
	 * Get defined filter
1223
	 * @param  string $key
1224
	 * @return Filter\Filter
1225
	 */
1226
	public function getFilter($key)
1227
	{
1228
		if (!isset($this->filters[$key])) {
1229
			throw new DataGridException("Filter [{$key}] is not defined");
1230
		}
1231
1232
		return $this->filters[$key];
1233
	}
1234
1235
1236
	/********************************************************************************
1237
	 *                                  FILTERING                                   *
1238
	 ********************************************************************************/
1239
1240
1241
	/**
1242
	 * Is filter active?
1243
	 * @return boolean
1244
	 */
1245
	public function isFilterActive()
1246
	{
1247
		$is_filter = ArraysHelper::testTruthy($this->filter);
1248
1249
		return ($is_filter) || $this->force_filter_active;
1250
	}
1251
1252
1253
	/**
1254
	 * Tell that filter is active from whatever reasons
1255
	 * return static
1256
	 */
1257
	public function setFilterActive()
1258
	{
1259
		$this->force_filter_active = TRUE;
1260
1261
		return $this;
1262
	}
1263
1264
1265
	/**
1266
	 * Set filter values (force - overwrite user data)
1267
	 * @param array $filter
1268
	 * @return static
1269
	 */
1270
	public function setFilter(array $filter)
1271
	{
1272
		$this->filter = $filter;
1273
1274
		$this->saveSessionData('_grid_has_filtered', 1);
1275
1276
		return $this;
1277
	}
1278
1279
1280
	/**
1281
	 * If we want to sent some initial filter
1282
	 * @param array $filter
0 ignored issues
show
Documentation introduced by
There is no parameter named $filter. Did you maybe mean $default_filter?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1283
	 * @param bool  $use_on_reset
1284
	 * @return static
1285
	 */
1286
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1287
	{
1288
		foreach ($default_filter as $key => $value) {
1289
			$filter = $this->getFilter($key);
1290
1291
			if (!$filter) {
1292
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1293
			}
1294
1295
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1296
				throw new DataGridException(
1297
					"Default value of filter [$key] - MultiSelect has to be an array"
1298
				);
1299
			}
1300
1301
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1302
				if (!is_array($value)) {
1303
					throw new DataGridException(
1304
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1305
					);
1306
				}
1307
1308
				$temp = $value;
1309
				unset($temp['from'], $temp['to']);
1310
1311
				if (!empty($temp)) {
1312
					throw new DataGridException(
1313
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1314
					);
1315
				}
1316
			}
1317
		}
1318
1319
		$this->default_filter = $default_filter;
1320
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1321
1322
		return $this;
1323
	}
1324
1325
1326
	/**
1327
	 * User may set default filter, find it
1328
	 * @return void
1329
	 */
1330
	public function findDefaultFilter()
1331
	{
1332
		if (!empty($this->filter)) {
1333
			return;
1334
		}
1335
1336
		if ($this->getSessionData('_grid_has_filtered')) {
1337
			return;
1338
		}
1339
1340
		if (!empty($this->default_filter)) {
1341
			$this->filter = $this->default_filter;
1342
		}
1343
1344
		foreach ($this->filter as $key => $value) {
1345
			$this->saveSessionData($key, $value);
1346
		}
1347
	}
1348
1349
1350
	/**
1351
	 * FilterAndGroupAction form factory
1352
	 * @return Form
1353
	 */
1354
	public function createComponentFilter()
1355
	{
1356
		$form = new Form($this, 'filter');
1357
1358
		$form->setMethod(static::$form_method);
1359
1360
		$form->setTranslator($this->getTranslator());
1361
1362
		/**
1363
		 * InlineEdit part
1364
		 */
1365
		$inline_edit_container = $form->addContainer('inline_edit');
1366
1367 View Code Duplication
		if ($this->inlineEdit instanceof InlineEdit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1368
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1369
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1370
				->setValidationScope(FALSE);
1371
1372
			$this->inlineEdit->onControlAdd($inline_edit_container);
1373
			$this->inlineEdit->onControlAfterAdd($inline_edit_container);
1374
		}
1375
1376
		/**
1377
		 * InlineAdd part
1378
		 */
1379
		$inline_add_container = $form->addContainer('inline_add');
1380
1381 View Code Duplication
		if ($this->inlineAdd instanceof InlineEdit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1382
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save');
1383
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1384
				->setValidationScope(FALSE)
1385
				->setAttribute('data-datagrid-cancel-inline-add', TRUE);
1386
1387
			$this->inlineAdd->onControlAdd($inline_add_container);
1388
			$this->inlineAdd->onControlAfterAdd($inline_add_container);
1389
		}
1390
1391
		/**
1392
		 * ItemDetail form part
1393
		 */
1394
		$items_detail_form = $this->getItemDetailForm();
1395
1396
		if ($items_detail_form instanceof Nette\Forms\Container) {
1397
			$form['items_detail_form'] = $items_detail_form;
1398
		}
1399
1400
		/**
1401
		 * Filter part
1402
		 */
1403
		$filter_container = $form->addContainer('filter');
1404
1405
		foreach ($this->filters as $filter) {
1406
			$filter->addToFormContainer($filter_container);
1407
		}
1408
1409
		if (!$this->hasAutoSubmit()) {
1410
			$filter_container['submit'] = $this->getFilterSubmitButton();
1411
		}
1412
1413
		/**
1414
		 * Group action part
1415
		 */
1416
		$group_action_container = $form->addContainer('group_action');
1417
1418
		if ($this->hasGroupActions()) {
1419
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1420
		}
1421
1422
		$form->setDefaults(['filter' => $this->filter]);
1423
1424
		/**
1425
		 * Per page part
1426
		 */
1427
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1428
			->setTranslator(NULL);
1429
1430
		if (!$form->isSubmitted()) {
1431
			$form['per_page']->setValue($this->getPerPage());
1432
		}
1433
1434
		$form->addSubmit('per_page_submit', '');
1435
		
1436
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1437
1438
		return $form;
1439
	}
1440
1441
1442
	/**
1443
	 * Set $this->filter values after filter form submitted
1444
	 * @param  Form $form
1445
	 * @return void
1446
	 */
1447
	public function filterSucceeded(Form $form)
1448
	{
1449
		if ($this->snippets_set) {
1450
			return;
1451
		}
1452
1453
		$values = $form->getValues();
1454
1455
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1456
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1457
				return;
1458
			}
1459
		}
1460
1461
		/**
1462
		 * Per page
1463
		 */
1464
		$this->saveSessionData('_grid_per_page', $values->per_page);
1465
		$this->per_page = $values->per_page;
1466
1467
		/**
1468
		 * Inline edit
1469
		 */
1470
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1471
			$edit = $form['inline_edit'];
1472
1473
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1474
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1475
				$primary_where_column = $form->getHttpData(
1476
					Form::DATA_LINE,
1477
					'inline_edit[_primary_where_column]'
1478
				);
1479
1480
				if ($edit['submit']->isSubmittedBy()) {
1481
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1482
					$this->getPresenter()->payload->_datagrid_inline_edited = $id;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1483
				} else {
1484
					$this->getPresenter()->payload->_datagrid_inline_edit_cancel = $id;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1485
				}
1486
1487
				if ($edit['submit']->isSubmittedBy() && !empty($this->inlineEdit->onCustomRedraw)) {
1488
					$this->inlineEdit->onCustomRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onCustomRedraw does not exist on object<Ublaboo\DataGrid\InlineEdit\InlineEdit>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1489
				} else {
1490
					$this->redrawItem($id, $primary_where_column);
1491
				}
1492
1493
				return;
1494
			}
1495
		}
1496
1497
		/**
1498
		 * Inline add
1499
		 */
1500
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1501
			$add = $form['inline_add'];
1502
1503
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1504
				if ($add['submit']->isSubmittedBy()) {
1505
					$this->inlineAdd->onSubmit($values->inline_add);
0 ignored issues
show
Bug introduced by
The call to onSubmit() misses a required argument $values.

This check looks for function calls that miss required arguments.

Loading history...
1506
1507
					if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1508
						$this->getPresenter()->payload->_datagrid_inline_added = TRUE;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1509
					}
1510
				}
1511
1512
				return;
1513
			}
1514
		}
1515
1516
		/**
1517
		 * Filter itself
1518
		 */
1519
		$values = $values['filter'];
1520
1521
		foreach ($values as $key => $value) {
1522
			/**
1523
			 * Session stuff
1524
			 */
1525
			$this->saveSessionData($key, $value);
1526
1527
			/**
1528
			 * Other stuff
1529
			 */
1530
			$this->filter[$key] = $value;
1531
		}
1532
1533
		if (!empty($values)) {
1534
			$this->saveSessionData('_grid_has_filtered', 1);
1535
		}
1536
1537
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1538
			$this->getPresenter()->payload->_datagrid_sort = [];
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1539
1540
			foreach ($this->columns as $key => $column) {
1541
				if ($column->isSortable()) {
1542
					$this->getPresenter()->payload->_datagrid_sort[$key] = $this->link('sort!', [
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1543
						'sort' => $column->getSortNext()
1544
					]);
1545
				}
1546
			}
1547
		}
1548
1549
		$this->reload();
1550
	}
1551
1552
1553
	/**
1554
	 * Should be datagrid filters rendered separately?
1555
	 * @param boolean $out
1556
	 * @return static
1557
	 */
1558
	public function setOuterFilterRendering($out = TRUE)
1559
	{
1560
		$this->outer_filter_rendering = (bool) $out;
1561
1562
		return $this;
1563
	}
1564
1565
1566
	/**
1567
	 * Are datagrid filters rendered separately?
1568
	 * @return boolean
1569
	 */
1570
	public function hasOuterFilterRendering()
1571
	{
1572
		return $this->outer_filter_rendering;
1573
	}
1574
1575
1576
	/**
1577
	 * Try to restore session stuff
1578
	 * @return void
1579
	 */
1580
	public function findSessionValues()
1581
	{
1582
		if ($this->filter || ($this->page != 1) || !empty($this->sort) || $this->per_page) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->filter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1583
			return;
1584
		}
1585
1586
		if (!$this->remember_state) {
1587
			return;
1588
		}
1589
1590
		if ($page = $this->getSessionData('_grid_page')) {
1591
			$this->page = $page;
1592
		}
1593
1594
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1595
			$this->per_page = $per_page;
1596
		}
1597
1598
		if ($sort = $this->getSessionData('_grid_sort')) {
1599
			$this->sort = $sort;
0 ignored issues
show
Documentation Bug introduced by
It seems like $sort of type * is incompatible with the declared type array of property $sort.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1600
		}
1601
1602
		foreach ($this->getSessionData() as $key => $value) {
1603
			$other_session_keys = [
1604
				'_grid_per_page',
1605
				'_grid_sort',
1606
				'_grid_page',
1607
				'_grid_has_filtered',
1608
				'_grid_hidden_columns',
1609
				'_grid_hidden_columns_manipulated'
1610
			];
1611
1612
			if (!in_array($key, $other_session_keys)) {
1613
				$this->filter[$key] = $value;
1614
			}
1615
		}
1616
1617
		/**
1618
		 * When column is sorted via custom callback, apply it
1619
		 */
1620
		if (empty($this->sort_callback) && !empty($this->sort)) {
1621
			foreach ($this->sort as $key => $order) {
1622
				try {
1623
					$column = $this->getColumn($key);
1624
1625
				} catch (DataGridException $e) {
1626
					$this->deleteSesssionData('_grid_sort');
1627
					$this->sort = [];
1628
1629
					return;
1630
				}
1631
1632
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1633
					$this->sort_callback = $column->getSortableCallback();
1634
				}
1635
			}
1636
		}
1637
	}
1638
1639
1640
	/********************************************************************************
1641
	 *                                    EXPORTS                                   *
1642
	 ********************************************************************************/
1643
1644
1645
	/**
1646
	 * Add export of type callback
1647
	 * @param string $text
1648
	 * @param callable $callback
1649
	 * @param boolean $filtered
1650
	 * @return Export\Export
1651
	 */
1652
	public function addExportCallback($text, $callback, $filtered = FALSE)
1653
	{
1654
		if (!is_callable($callback)) {
1655
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1656
		}
1657
1658
		return $this->addToExports(new Export\Export($this, $text, $callback, $filtered));
1659
	}
1660
1661
1662
	/**
1663
	 * Add already implemented csv export
1664
	 * @param string $text
1665
	 * @param string $csv_file_name
1666
	 * @param string|null $output_encoding
1667
	 * @param string|null $delimiter
1668
	 * @param bool $include_bom
1669
	 * @return Export\Export
1670
	 */
1671 View Code Duplication
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL, $include_bom = FALSE)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1672
	{
1673
		return $this->addToExports(new Export\ExportCsv(
1674
			$this,
1675
			$text,
1676
			$csv_file_name,
1677
			FALSE,
1678
			$output_encoding,
1679
			$delimiter,
1680
			$include_bom
1681
		));
1682
	}
1683
1684
1685
	/**
1686
	 * Add already implemented csv export, but for filtered data
1687
	 * @param string $text
1688
	 * @param string $csv_file_name
1689
	 * @param string|null $output_encoding
1690
	 * @param string|null $delimiter
1691
	 * @param bool $include_bom
1692
	 * @return Export\Export
1693
	 */
1694 View Code Duplication
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL, $include_bom = FALSE)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1695
	{
1696
		return $this->addToExports(new Export\ExportCsv(
1697
			$this,
1698
			$text,
1699
			$csv_file_name,
1700
			TRUE,
1701
			$output_encoding,
1702
			$delimiter,
1703
			$include_bom
1704
		));
1705
	}
1706
1707
1708
	/**
1709
	 * Add export to array
1710
	 * @param Export\Export $export
1711
	 * @return Export\Export
1712
	 */
1713
	protected function addToExports(Export\Export $export)
1714
	{
1715
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1716
1717
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1718
1719
		return $this->exports[$id] = $export;
1720
	}
1721
1722
1723
	public function resetExportsLinks()
1724
	{
1725
		foreach ($this->exports as $id => $export) {
1726
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1727
		}
1728
	}
1729
1730
1731
	/********************************************************************************
1732
	 *                                TOOLBAR BUTTONS                               *
1733
	 ********************************************************************************/
1734
1735
1736
	/**
1737
	 * Add toolbar button
1738
	 * @param string $href
1739
	 * @param string $text
1740
	 * @param array  $params
1741
	 * @return ToolbarButton
1742
	 */
1743
	public function addToolbarButton($href, $text = '', $params = [])
1744
	{
1745
		$button = new ToolbarButton($this, $href, $text, $params);
1746
1747
		return $this->toolbar_buttons[] = $button;
1748
	}
1749
1750
1751
	/********************************************************************************
1752
	 *                                 GROUP ACTIONS                                *
1753
	 ********************************************************************************/
1754
1755
1756
	/**
1757
	 * Alias for add group select action
1758
	 * @param string $title
1759
	 * @param array  $options
1760
	 * @return GroupAction\GroupAction
1761
	 */
1762
	public function addGroupAction($title, $options = [])
1763
	{
1764
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1765
	}
1766
1767
1768
	/**
1769
	 * Add group action (select box)
1770
	 * @param string $title
1771
	 * @param array  $options
1772
	 * @return GroupAction\GroupAction
1773
	 */
1774
	public function addGroupSelectAction($title, $options = [])
1775
	{
1776
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1777
	}
1778
1779
1780
	/**
1781
	 * Add group action (text input)
1782
	 * @param string $title
1783
	 * @return GroupAction\GroupAction
1784
	 */
1785
	public function addGroupTextAction($title)
1786
	{
1787
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1788
	}
1789
1790
1791
	/**
1792
	 * Add group action (textarea)
1793
	 * @param string $title
1794
	 * @return GroupAction\GroupAction
1795
	 */
1796
	public function addGroupTextareaAction($title)
1797
	{
1798
		return $this->getGroupActionCollection()->addGroupTextareaAction($title);
1799
	}
1800
1801
1802
	/**
1803
	 * Get collection of all group actions
1804
	 * @return GroupAction\GroupActionCollection
1805
	 */
1806
	public function getGroupActionCollection()
1807
	{
1808
		if (!$this->group_action_collection) {
1809
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
1810
		}
1811
1812
		return $this->group_action_collection;
1813
	}
1814
1815
1816
	/**
1817
	 * Has datagrid some group actions?
1818
	 * @return boolean
1819
	 */
1820
	public function hasGroupActions()
1821
	{
1822
		return (bool) $this->group_action_collection;
1823
	}
1824
1825
1826
	/********************************************************************************
1827
	 *                                   HANDLERS                                   *
1828
	 ********************************************************************************/
1829
1830
1831
	/**
1832
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1833
	 * @param  int  $page
1834
	 * @return void
1835
	 */
1836
	public function handlePage($page)
1837
	{
1838
		/**
1839
		 * Session stuff
1840
		 */
1841
		$this->page = $page;
1842
		$this->saveSessionData('_grid_page', $page);
1843
1844
		$this->reload(['table']);
1845
	}
1846
1847
1848
	/**
1849
	 * Handler for sorting
1850
	 * @param array $sort
1851
	 * @return void
1852
	 */
1853
	public function handleSort(array $sort)
1854
	{
1855
		$new_sort = [];
1856
1857
		/**
1858
		 * Find apropirate column
1859
		 */
1860
		foreach ($sort as $key => $value) {
1861
			if (empty($this->columns[$key])) {
1862
				throw new DataGridException("Column <$key> not found");
1863
			}
1864
1865
			$column = $this->columns[$key];
1866
			$new_sort = [$key => $value];
1867
1868
			/**
1869
			 * Pagination may be reseted after sorting
1870
			 */
1871
			if ($column->sortableResetPagination()) {
1872
				$this->page = 1;
1873
				$this->saveSessionData('_grid_page', 1);
1874
			}
1875
1876
			/**
1877
			 * Custom sorting callback may be applied
1878
			 */
1879
			if ($column->getSortableCallback()) {
1880
				$this->sort_callback = $column->getSortableCallback();
1881
			}
1882
		}
1883
1884
		/**
1885
		 * Session stuff
1886
		 */
1887
		$this->sort = $new_sort;
1888
		$this->saveSessionData('_grid_sort', $this->sort);
1889
1890
		$this->reload(['table']);
1891
	}
1892
1893
1894
	/**
1895
	 * handler for reseting the filter
1896
	 * @return void
1897
	 */
1898
	public function handleResetFilter()
1899
	{
1900
		/**
1901
		 * Session stuff
1902
		 */
1903
		$this->deleteSesssionData('_grid_page');
1904
1905
		if ($this->default_filter_use_on_reset) {
1906
			$this->deleteSesssionData('_grid_has_filtered');
1907
		}
1908
1909
		foreach ($this->getSessionData() as $key => $value) {
1910
			if (!in_array($key, [
1911
				'_grid_per_page',
1912
				'_grid_sort',
1913
				'_grid_page',
1914
				'_grid_has_filtered',
1915
				'_grid_hidden_columns',
1916
				'_grid_hidden_columns_manipulated'
1917
				])) {
1918
1919
				$this->deleteSesssionData($key);
1920
			}
1921
		}
1922
1923
		$this->filter = [];
1924
1925
		$this->reload(['grid']);
1926
	}
1927
1928
1929
	/**
1930
	 * Handler for export
1931
	 * @param  int $id Key for particular export class in array $this->exports
1932
	 * @return void
1933
	 */
1934
	public function handleExport($id)
1935
	{
1936
		if (!isset($this->exports[$id])) {
1937
			throw new Nette\Application\ForbiddenRequestException;
1938
		}
1939
1940
		if (!empty($this->columns_export_order)) {
1941
			$this->setColumnsOrder($this->columns_export_order);
1942
		}
1943
1944
		$export = $this->exports[$id];
1945
1946
		if ($export->isFiltered()) {
1947
			$sort      = $this->sort;
0 ignored issues
show
Unused Code introduced by
$sort is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1948
			$filter    = $this->assableFilters();
1949
		} else {
1950
			$sort      = [$this->primary_key => 'ASC'];
0 ignored issues
show
Unused Code introduced by
$sort is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1951
			$filter    = [];
1952
		}
1953
1954
		if (NULL === $this->dataModel) {
1955
			throw new DataGridException('You have to set a data source first.');
1956
		}
1957
1958
		$rows = [];
1959
1960
		$items = Nette\Utils\Callback::invokeArgs(
1961
			[$this->dataModel, 'filterData'], [
1962
				NULL,
1963
				$this->createSorting($this->sort, $this->sort_callback),
1964
				$filter
1965
			]
1966
		);
1967
1968
		foreach ($items as $item) {
1969
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1970
		}
1971
1972
		if ($export instanceof Export\ExportCsv) {
1973
			$export->invoke($rows);
1974
		} else {
1975
			$export->invoke($items);
1976
		}
1977
1978
		if ($export->isAjax()) {
1979
			$this->reload();
1980
		}
1981
	}
1982
1983
1984
	/**
1985
	 * Handler for getting children of parent item (e.g. category)
1986
	 * @param  int $parent
1987
	 * @return void
1988
	 */
1989
	public function handleGetChildren($parent)
1990
	{
1991
		$this->setDataSource(
1992
			call_user_func($this->tree_view_children_callback, $parent)
1993
		);
1994
1995
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1996
			$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1997
			$this->getPresenter()->payload->_datagrid_tree = $parent;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1998
1999
			$this->redrawControl('items');
2000
2001
			$this->onRedraw();
2002
		} else {
2003
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Component, Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2004
		}
2005
	}
2006
2007
2008
	/**
2009
	 * Handler for getting item detail
2010
	 * @param  mixed $id
2011
	 * @return void
2012
	 */
2013
	public function handleGetItemDetail($id)
2014
	{
2015
		$this->getTemplate()->add('toggle_detail', $id);
2016
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
2017
2018
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2019
			$this->getPresenter()->payload->_datagrid_toggle_detail = $id;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2020
			$this->redrawControl('items');
2021
2022
			$this->onRedraw();
2023
		} else {
2024
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Component, Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2025
		}
2026
	}
2027
2028
2029
	/**
2030
	 * Handler for inline editing
2031
	 * @param  mixed $id
2032
	 * @param  mixed $key
2033
	 * @return void
2034
	 */
2035
	public function handleEdit($id, $key)
2036
	{
2037
		$column = $this->getColumn($key);
2038
		$value = $this->getPresenter()->getRequest()->getPost('value');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method getRequest() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2039
2040
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
2041
	}
2042
2043
2044
	/**
2045
	 * Redraw $this
2046
	 * @return void
2047
	 */
2048
	public function reload($snippets = [])
2049
	{
2050
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2051
			$this->redrawControl('tbody');
2052
			$this->redrawControl('pagination');
2053
2054
			/**
2055
			 * manualy reset exports links...
2056
			 */
2057
			$this->resetExportsLinks();
2058
			$this->redrawControl('exports');
2059
2060
			foreach ($snippets as $snippet) {
2061
				$this->redrawControl($snippet);
2062
			}
2063
2064
			$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2065
2066
			$this->onRedraw();
2067
		} else {
2068
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Component, Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2069
		}
2070
	}
2071
2072
2073
	/**
2074
	 * Handler for column status
2075
	 * @param  string $id
2076
	 * @param  string $key
2077
	 * @param  string $value
2078
	 * @return void
2079
	 */
2080
	public function handleChangeStatus($id, $key, $value)
2081
	{
2082
		if (empty($this->columns[$key])) {
2083
			throw new DataGridException("ColumnStatus[$key] does not exist");
2084
		}
2085
2086
		$this->columns[$key]->onChange($id, $value);
2087
	}
2088
2089
2090
	/**
2091
	 * Redraw just one row via ajax
2092
	 * @param  int   $id
2093
	 * @param  mixed $primary_where_column
2094
	 * @return void
2095
	 */
2096
	public function redrawItem($id, $primary_where_column = NULL)
2097
	{
2098
		$this->snippets_set = TRUE;
2099
2100
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
2101
2102
		$this->redrawControl('items');
2103
2104
		$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2105
2106
		$this->onRedraw();
2107
	}
2108
2109
2110
	/**
2111
	 * Tell datagrid to display all columns
2112
	 * @return void
2113
	 */
2114
	public function handleShowAllColumns()
2115
	{
2116
		$this->deleteSesssionData('_grid_hidden_columns');
2117
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2118
2119
		$this->redrawControl();
2120
2121
		$this->onRedraw();
2122
	}
2123
2124
2125
	/**
2126
	 * Tell datagrid to display default columns
2127
	 * @return void
2128
	 */
2129
	public function handleShowDefaultColumns()
2130
	{
2131
		$this->deleteSesssionData('_grid_hidden_columns');
2132
		$this->saveSessionData('_grid_hidden_columns_manipulated', FALSE);
2133
2134
		$this->redrawControl();
2135
2136
		$this->onRedraw();
2137
	}
2138
2139
2140
	/**
2141
	 * Reveal particular column
2142
	 * @param  string $column
2143
	 * @return void
2144
	 */
2145 View Code Duplication
	public function handleShowColumn($column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2146
	{
2147
		$columns = $this->getSessionData('_grid_hidden_columns');
2148
2149
		if (!empty($columns)) {
2150
			$pos = array_search($column, $columns);
2151
2152
			if ($pos !== FALSE) {
2153
				unset($columns[$pos]);
2154
			}
2155
		}
2156
2157
		$this->saveSessionData('_grid_hidden_columns', $columns);
2158
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2159
2160
		$this->redrawControl();
2161
2162
		$this->onRedraw();
2163
	}
2164
2165
2166
	/**
2167
	 * Notice datagrid to not display particular columns
2168
	 * @param  string $column
2169
	 * @return void
2170
	 */
2171 View Code Duplication
	public function handleHideColumn($column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2172
	{
2173
		/**
2174
		 * Store info about hiding a column to session
2175
		 */
2176
		$columns = $this->getSessionData('_grid_hidden_columns');
2177
2178
		if (empty($columns)) {
2179
			$columns = [$column];
2180
		} else if (!in_array($column, $columns)) {
2181
			array_push($columns, $column);
2182
		}
2183
2184
		$this->saveSessionData('_grid_hidden_columns', $columns);
2185
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2186
2187
		$this->redrawControl();
2188
2189
		$this->onRedraw();
2190
	}
2191
2192
2193
	public function handleActionCallback($__key, $__id)
2194
	{
2195
		$action = $this->getAction($__key);
2196
2197
		if (!($action instanceof Column\ActionCallback)) {
2198
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
2199
		}
2200
2201
		$action->onClick($__id);
2202
	}
2203
2204
2205
	/********************************************************************************
2206
	 *                                  PAGINATION                                  *
2207
	 ********************************************************************************/
2208
2209
2210
	/**
2211
	 * Set options of select "items_per_page"
2212
	 * @param array $items_per_page_list
2213
	 * @return static
2214
	 */
2215
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
2216
	{
2217
		$this->items_per_page_list = $items_per_page_list;
2218
2219
		if ($include_all) {
2220
			$this->items_per_page_list[] = 'all';
2221
		}
2222
2223
		return $this;
2224
	}
2225
2226
2227
	/**
2228
	 * Set default "items per page" value in pagination select
2229
	 * @param $count
2230
	 * @return static
2231
	 */
2232
	public function setDefaultPerPage($count)
2233
	{
2234
		$this->default_per_page = $count;
2235
2236
		return $this;
2237
	}
2238
2239
2240
	/**
2241
	 * User may set default "items per page" value, apply it
2242
	 * @return void
2243
	 */
2244
	public function findDefaultPerPage()
2245
	{
2246
		if (!empty($this->per_page)) {
2247
			return;
2248
		}
2249
2250
		if (!empty($this->default_per_page)) {
2251
			$this->per_page = $this->default_per_page;
2252
		}
2253
2254
		$this->saveSessionData('_grid_per_page', $this->per_page);
2255
	}
2256
2257
2258
	/**
2259
	 * Paginator factory
2260
	 * @return Components\DataGridPaginator\DataGridPaginator
2261
	 */
2262
	public function createComponentPaginator()
2263
	{
2264
		/**
2265
		 * Init paginator
2266
		 */
2267
		$component = new Components\DataGridPaginator\DataGridPaginator(
2268
			$this->getTranslator(),
2269
			static::$icon_prefix
2270
		);
2271
		$paginator = $component->getPaginator();
2272
2273
		$paginator->setPage($this->page);
2274
		$paginator->setItemsPerPage($this->getPerPage());
2275
2276
		return $component;
2277
	}
2278
2279
2280
	/**
2281
	 * Get parameter per_page
2282
	 * @return int
2283
	 */
2284
	public function getPerPage()
2285
	{
2286
		$items_per_page_list = $this->getItemsPerPageList();
2287
2288
		$per_page = $this->per_page ?: reset($items_per_page_list);
2289
2290
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
2291
			$per_page = reset($items_per_page_list);
2292
		}
2293
2294
		return $per_page;
2295
	}
2296
2297
2298
	/**
2299
	 * Get associative array of items_per_page_list
2300
	 * @return array
2301
	 */
2302
	public function getItemsPerPageList()
2303
	{
2304
		if (empty($this->items_per_page_list)) {
2305
			$this->setItemsPerPageList([10, 20, 50], TRUE);
2306
		}
2307
2308
		$list = array_flip($this->items_per_page_list);
2309
2310
		foreach ($list as $key => $value) {
2311
			$list[$key] = $key;
2312
		}
2313
2314
		if (array_key_exists('all', $list)) {
2315
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2316
		}
2317
2318
		return $list;
2319
	}
2320
2321
2322
	/**
2323
	 * Order Grid to "be paginated"
2324
	 * @param bool $do
2325
	 * @return static
2326
	 */
2327
	public function setPagination($do)
2328
	{
2329
		$this->do_paginate = (bool) $do;
2330
2331
		return $this;
2332
	}
2333
2334
2335
	/**
2336
	 * Tell whether Grid is paginated
2337
	 * @return bool
2338
	 */
2339
	public function isPaginated()
2340
	{
2341
		return $this->do_paginate;
2342
	}
2343
2344
2345
	/**
2346
	 * Return current paginator class
2347
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2348
	 */
2349
	public function getPaginator()
2350
	{
2351
		if ($this->isPaginated() && $this->getPerPage() !== 'all') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $this->getPerPage() (integer) and 'all' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
2352
			return $this['paginator'];
2353
		}
2354
2355
		return NULL;
2356
	}
2357
2358
2359
	/********************************************************************************
2360
	 *                                     I18N                                     *
2361
	 ********************************************************************************/
2362
2363
2364
	/**
2365
	 * Set datagrid translator
2366
	 * @param Nette\Localization\ITranslator $translator
2367
	 * @return static
2368
	 */
2369
	public function setTranslator(Nette\Localization\ITranslator $translator)
2370
	{
2371
		$this->translator = $translator;
2372
2373
		return $this;
2374
	}
2375
2376
2377
	/**
2378
	 * Get translator for datagrid
2379
	 * @return Nette\Localization\ITranslator
2380
	 */
2381
	public function getTranslator()
2382
	{
2383
		if (!$this->translator) {
2384
			$this->translator = new Localization\SimpleTranslator;
2385
		}
2386
2387
		return $this->translator;
2388
	}
2389
2390
2391
	/********************************************************************************
2392
	 *                                 COLUMNS ORDER                                *
2393
	 ********************************************************************************/
2394
2395
2396
	/**
2397
	 * Set order of datagrid columns
2398
	 * @param array $order
2399
	 * @return static
2400
	 */
2401
	public function setColumnsOrder($order)
2402
	{
2403
		$new_order = [];
2404
2405
		foreach ($order as $key) {
2406
			if (isset($this->columns[$key])) {
2407
				$new_order[$key] = $this->columns[$key];
2408
			}
2409
		}
2410
2411
		if (sizeof($new_order) === sizeof($this->columns)) {
2412
			$this->columns = $new_order;
2413
		} else {
2414
			throw new DataGridException('When changing columns order, you have to specify all columns');
2415
		}
2416
2417
		return $this;
2418
	}
2419
2420
2421
	/**
2422
	 * Columns order may be different for export and normal grid
2423
	 * @param array $order
2424
	 */
2425
	public function setColumnsExportOrder($order)
2426
	{
2427
		$this->columns_export_order = (array) $order;
2428
	}
2429
2430
2431
	/********************************************************************************
2432
	 *                                SESSION & URL                                 *
2433
	 ********************************************************************************/
2434
2435
2436
	/**
2437
	 * Find some unique session key name
2438
	 * @return string
2439
	 */
2440
	public function getSessionSectionName()
2441
	{
2442
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2443
	}
2444
2445
2446
	/**
2447
	 * Should datagrid remember its filters/pagination/etc using session?
2448
	 * @param bool $remember
2449
	 * @return static
2450
	 */
2451
	public function setRememberState($remember = TRUE)
2452
	{
2453
		$this->remember_state = (bool) $remember;
2454
2455
		return $this;
2456
	}
2457
2458
2459
	/**
2460
	 * Should datagrid refresh url using history API?
2461
	 * @param bool $refresh
2462
	 * @return static
2463
	 */
2464
	public function setRefreshUrl($refresh = TRUE)
2465
	{
2466
		$this->refresh_url = (bool) $refresh;
2467
2468
2469
		return $this;
2470
	}
2471
2472
2473
	/**
2474
	 * Get session data if functionality is enabled
2475
	 * @param  string $key
2476
	 * @return mixed
2477
	 */
2478
	public function getSessionData($key = NULL, $default_value = NULL)
2479
	{
2480
		if (!$this->remember_state) {
2481
			return $key ? $default_value : [];
2482
		}
2483
2484
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2485
	}
2486
2487
2488
	/**
2489
	 * Save session data - just if it is enabled
2490
	 * @param  string $key
2491
	 * @param  mixed  $value
2492
	 * @return void
2493
	 */
2494
	public function saveSessionData($key, $value)
2495
	{
2496
		if ($this->remember_state) {
2497
			$this->grid_session->{$key} = $value;
2498
		}
2499
	}
2500
2501
2502
	/**
2503
	 * Delete session data
2504
	 * @return void
2505
	 */
2506
	public function deleteSesssionData($key)
2507
	{
2508
		unset($this->grid_session->{$key});
2509
	}
2510
2511
2512
	/********************************************************************************
2513
	 *                                  ITEM DETAIL                                 *
2514
	 ********************************************************************************/
2515
2516
2517
	/**
2518
	 * Get items detail parameters
2519
	 * @return array
2520
	 */
2521
	public function getItemsDetail()
2522
	{
2523
		return $this->items_detail;
2524
	}
2525
2526
2527
	/**
2528
	 * Items can have thair detail - toggled
2529
	 * @param mixed $detail callable|string|bool
2530
	 * @param bool|NULL $primary_where_column
2531
	 * @return Column\ItemDetail
2532
	 */
2533
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2534
	{
2535
		if ($this->isSortable()) {
2536
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2537
		}
2538
2539
		$this->items_detail = new Column\ItemDetail(
2540
			$this,
2541
			$primary_where_column ?: $this->primary_key
0 ignored issues
show
Bug introduced by
It seems like $primary_where_column ?: $this->primary_key can also be of type boolean; however, Ublaboo\DataGrid\Column\ItemDetail::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2542
		);
2543
2544
		if (is_string($detail)) {
2545
			/**
2546
			 * Item detail will be in separate template
2547
			 */
2548
			$this->items_detail->setType('template');
2549
			$this->items_detail->setTemplate($detail);
2550
2551
		} else if (is_callable($detail)) {
2552
			/**
2553
			 * Item detail will be rendered via custom callback renderer
2554
			 */
2555
			$this->items_detail->setType('renderer');
2556
			$this->items_detail->setRenderer($detail);
2557
2558
		} else if (TRUE === $detail) {
2559
			/**
2560
			 * Item detail will be rendered probably via block #detail
2561
			 */
2562
			$this->items_detail->setType('block');
2563
2564
		} else {
2565
			throw new DataGridException(
2566
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2567
			);
2568
		}
2569
2570
		return $this->items_detail;
2571
	}
2572
2573
2574
	/**
2575
	 * @param callable $callable_set_container 
2576
	 * @return static
2577
	 */
2578
	public function setItemsDetailForm(callable $callable_set_container)
2579
	{
2580
		if ($this->items_detail instanceof Column\ItemDetail) {
2581
			$this->items_detail->setForm(
2582
				new Utils\ItemDetailForm($callable_set_container)
2583
			);
2584
2585
			return $this;
2586
		}
2587
2588
		throw new DataGridException('Please set the ItemDetail first.');
2589
	}
2590
2591
2592
	/**
2593
	 * @return Nette\Forms\Container|NULL
2594
	 */
2595
	public function getItemDetailForm()
2596
	{
2597
		if ($this->items_detail instanceof Column\ItemDetail) {
2598
			return $this->items_detail->getForm();
2599
		}
2600
2601
		return NULL;
2602
	}
2603
2604
2605
	/********************************************************************************
2606
	 *                                ROW PRIVILEGES                                *
2607
	 ********************************************************************************/
2608
2609
2610
	/**
2611
	 * @param  callable $condition
2612
	 * @return void
2613
	 */
2614
	public function allowRowsGroupAction(callable $condition)
2615
	{
2616
		$this->row_conditions['group_action'] = $condition;
2617
	}
2618
2619
2620
	/**
2621
	 * @param  callable $condition
2622
	 * @return void
2623
	 */
2624
	public function allowRowsInlineEdit(callable $condition)
2625
	{
2626
		$this->row_conditions['inline_edit'] = $condition;
2627
	}
2628
2629
2630
	/**
2631
	 * @param  string   $key
2632
	 * @param  callable $condition
2633
	 * @return void
2634
	 */
2635
	public function allowRowsAction($key, callable $condition)
2636
	{
2637
		$this->row_conditions['action'][$key] = $condition;
2638
	}
2639
2640
2641
	/**
2642
	 * @param  string      $name
2643
	 * @param  string|null $key
2644
	 * @return bool|callable
2645
	 */
2646
	public function getRowCondition($name, $key = NULL)
2647
	{
2648
		if (!isset($this->row_conditions[$name])) {
2649
			return FALSE;
2650
		}
2651
2652
		$condition = $this->row_conditions[$name];
2653
2654
		if (!$key) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $key of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2655
			return $condition;
2656
		}
2657
2658
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2659
	}
2660
2661
2662
	/********************************************************************************
2663
	 *                               COLUMN CALLBACK                                *
2664
	 ********************************************************************************/
2665
2666
2667
	/**
2668
	 * @param  string   $key
2669
	 * @param  callable $callback
2670
	 * @return void
2671
	 */
2672
	public function addColumnCallback($key, callable $callback)
2673
	{
2674
		$this->column_callbacks[$key] = $callback;
2675
	}
2676
2677
2678
	/**
2679
	 * @param  string $key
2680
	 * @return callable|null
2681
	 */
2682
	public function getColumnCallback($key)
2683
	{
2684
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2685
	}
2686
2687
2688
	/********************************************************************************
2689
	 *                                 INLINE EDIT                                  *
2690
	 ********************************************************************************/
2691
2692
2693
	/**
2694
	 * @return InlineEdit
2695
	 */
2696
	public function addInlineEdit($primary_where_column = NULL)
2697
	{
2698
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2699
2700
		return $this->inlineEdit;
2701
	}
2702
2703
2704
	/**
2705
	 * @return InlineEdit|null
2706
	 */
2707
	public function getInlineEdit()
2708
	{
2709
		return $this->inlineEdit;
2710
	}
2711
2712
2713
	/**
2714
	 * @param  mixed $id
2715
	 * @return void
2716
	 */
2717
	public function handleInlineEdit($id)
2718
	{
2719
		if ($this->inlineEdit) {
2720
			$this->inlineEdit->setItemId($id);
2721
2722
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2723
2724
			$this['filter']['inline_edit']->addHidden('_id', $id);
2725
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2726
2727
			if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2728
				$this->getPresenter()->payload->_datagrid_inline_editing = TRUE;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2729
			}
2730
2731
			$this->redrawItem($id, $primary_where_column);
2732
		}
2733
	}
2734
2735
2736
	/********************************************************************************
2737
	 *                                  INLINE ADD                                  *
2738
	 ********************************************************************************/
2739
2740
2741
	/**
2742
	 * @return InlineEdit
2743
	 */
2744
	public function addInlineAdd()
2745
	{
2746
		$this->inlineAdd = new InlineEdit($this);
2747
2748
		$this->inlineAdd
2749
			->setIcon('plus')
2750
			->setClass('btn btn-xs btn-default');
2751
2752
		return $this->inlineAdd;
2753
	}
2754
2755
2756
	/**
2757
	 * @return InlineEdit|null
2758
	 */
2759
	public function getInlineAdd()
2760
	{
2761
		return $this->inlineAdd;
2762
	}
2763
2764
2765
	/********************************************************************************
2766
	 *                               HIDEABLE COLUMNS                               *
2767
	 ********************************************************************************/
2768
2769
2770
	/**
2771
	 * Can datagrid hide colums?
2772
	 * @return boolean
2773
	 */
2774
	public function canHideColumns()
2775
	{
2776
		return (bool) $this->can_hide_columns;
2777
	}
2778
2779
2780
	/**
2781
	 * Order Grid to set columns hideable.
2782
	 * @return static
2783
	 */
2784
	public function setColumnsHideable()
2785
	{
2786
		$this->can_hide_columns = TRUE;
2787
2788
		return $this;
2789
	}
2790
2791
2792
	/********************************************************************************
2793
	 *                                COLUMNS SUMMARY                               *
2794
	 ********************************************************************************/
2795
2796
2797
	/**
2798
	 * Will datagrid show summary in the end?
2799
	 * @return bool
2800
	 */
2801
	public function hasColumnsSummary()
2802
	{
2803
		return $this->columnsSummary instanceof ColumnsSummary;
2804
	}
2805
2806
2807
	/**
2808
	 * Set columns to be summarized in the end.
2809
	 * @param  array  $columns
2810
	 * @return ColumnsSummary
2811
	 */
2812
	public function setColumnsSummary(array $columns)
2813
	{
2814
		$this->columnsSummary = new ColumnsSummary($this, $columns);
2815
2816
		return $this->columnsSummary;
2817
	}
2818
2819
2820
	/**
2821
	 * @return ColumnsSummary|NULL
2822
	 */
2823
	public function getColumnsSummary()
2824
	{
2825
		return $this->columnsSummary;
2826
	}
2827
2828
2829
	/********************************************************************************
2830
	 *                                   INTERNAL                                   *
2831
	 ********************************************************************************/
2832
2833
2834
	/**
2835
	 * Tell grid filters to by submitted automatically
2836
	 * @param bool $auto
2837
	 */
2838
	public function setAutoSubmit($auto = TRUE)
2839
	{
2840
		$this->auto_submit = (bool) $auto;
2841
2842
		return $this;
2843
	}
2844
2845
2846
	/**
2847
	 * @return bool
2848
	 */
2849
	public function hasAutoSubmit()
2850
	{
2851
		return $this->auto_submit;
2852
	}
2853
2854
2855
	/**
2856
	 * Submit button when no auto-submitting is used
2857
	 * @return Filter\SubmitButton
2858
	 */
2859
	public function getFilterSubmitButton()
2860
	{
2861
		if ($this->hasAutoSubmit()) {
2862
			throw new DataGridException(
2863
				'DataGrid has auto-submit. Turn it off before setting filter submit button.'
2864
			);
2865
		}
2866
2867
		if ($this->filter_submit_button === NULL) {
2868
			$this->filter_submit_button = new Filter\SubmitButton($this);
2869
		}
2870
2871
		return $this->filter_submit_button;
2872
	}
2873
2874
2875
	/********************************************************************************
2876
	 *                                   INTERNAL                                   *
2877
	 ********************************************************************************/
2878
2879
2880
	/**
2881
	 * Get count of columns
2882
	 * @return int
2883
	 */
2884
	public function getColumnsCount()
2885
	{
2886
		$count = sizeof($this->getColumns());
2887
2888
		if (!empty($this->actions)
2889
			|| $this->isSortable()
2890
			|| $this->getItemsDetail()
2891
			|| $this->getInlineEdit()
2892
			|| $this->getInlineAdd()) {
2893
			$count++;
2894
		}
2895
2896
		if ($this->hasGroupActions()) {
2897
			$count++;
2898
		}
2899
2900
		return $count;
2901
	}
2902
2903
2904
	/**
2905
	 * Get primary key of datagrid data source
2906
	 * @return string
2907
	 */
2908
	public function getPrimaryKey()
2909
	{
2910
		return $this->primary_key;
2911
	}
2912
2913
2914
	/**
2915
	 * Get set of set columns
2916
	 * @return Column\IColumn[]
2917
	 */
2918
	public function getColumns()
2919
	{
2920
		$return = $this->columns;
2921
2922
		if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
2923
			$columns_to_hide = [];
2924
2925
			foreach ($this->columns as $key => $column) {
2926
				if ($column->getDefaultHide()) {
2927
					$columns_to_hide[] = $key;
2928
				}
2929
			}
2930
2931
			if (!empty($columns_to_hide)) {
2932
				$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
2933
				$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2934
			}
2935
		}
2936
2937
		$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
2938
2939
		foreach ($hidden_columns as $column) {
2940
			if (!empty($this->columns[$column])) {
2941
				$this->columns_visibility[$column] = [
2942
					'visible' => FALSE
2943
				];
2944
2945
				unset($return[$column]);
2946
			}
2947
		}
2948
2949
		return $return;
2950
	}
2951
2952
2953
	public function getColumnsVisibility()
2954
	{
2955
		$return = $this->columns_visibility;
2956
2957
		foreach ($this->columns_visibility as $key => $column) {
2958
			$return[$key]['column'] = $this->columns[$key];
2959
		}
2960
2961
		return $return;
2962
	}
2963
2964
2965
	/**
2966
	 * @return PresenterComponent
2967
	 */
2968
	public function getParent()
2969
	{
2970
		$parent = parent::getParent();
2971
2972
		if (!($parent instanceof PresenterComponent)) {
0 ignored issues
show
Bug introduced by
The class Nette\Application\UI\PresenterComponent does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
2973
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2974
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2975
			);
2976
		}
2977
2978
		return $parent;
2979
	}
2980
2981
2982
	/**
2983
	 * Some of datagrid columns is hidden by default
2984
	 * @param bool $default_hide
2985
	 */
2986
	public function setSomeColumnDefaultHide($default_hide)
2987
	{
2988
		$this->some_column_default_hide = $default_hide;
2989
	}
2990
2991
2992
	/**
2993
	 * Are some of columns hidden bydefault?
2994
	 */
2995
	public function hasSomeColumnDefaultHide()
2996
	{
2997
		return $this->some_column_default_hide;
2998
	}
2999
3000
}
3001