Completed
Pull Request — master (#244)
by
unknown
03:08
created

DataGrid::getPerPage()   B

Complexity

Conditions 7
Paths 20

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 12
rs 8.2222
cc 7
eloc 6
nc 20
nop 0
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
22
/**
23
 * @method onRedraw()
24
 * @method onRender()
25
 * @method onColumnAdd()
26
 */
27
class DataGrid extends Nette\Application\UI\Control
28
{
29
30
	/**
31
	 * @var callable[]
32
	 */
33
	public $onRedraw;
34
35
	/**
36
	 * @var callable[]
37
	 */
38
	public $onRender = [];
39
40
	/**
41
	 * @var callable[]
42
	 */
43
	public $onColumnAdd;
44
45
	/**
46
	 * @var string
47
	 */
48
	public static $icon_prefix = 'fa fa-';
49
50
	/**
51
	 * When set to TRUE, datagrid throws an exception
52
	 * 	when tring to get related entity within join and entity does not exist
53
	 * @var bool
54
	 */
55
	public $strict_entity_property = FALSE;
56
57
	/**
58
	 * @var int
59
	 * @persistent
60
	 */
61
	public $page = 1;
62
63
	/**
64
	 * @var int|string
65
	 * @persistent
66
	 */
67
	public $per_page;
68
69
	/**
70
	 * @var array
71
	 * @persistent
72
	 */
73
	public $sort = [];
74
75
	/**
76
	 * @var array
77
	 */
78
	public $default_sort = [];
79
80
	/**
81
	 * @var array
82
	 */
83
	public $default_filter = [];
84
85
	/**
86
	 * @var bool
87
	 */
88
	public $default_filter_use_on_reset = TRUE;
89
90
	/**
91
	 * @var array
92
	 * @persistent
93
	 */
94
	public $filter = [];
95
96
	/**
97
	 * @var callable|null
98
	 */
99
	protected $sort_callback = NULL;
100
101
	/**
102
	 * @var bool
103
	 */
104
	protected $use_happy_components = TRUE;
105
106
	/**
107
	 * @var callable
108
	 */
109
	protected $rowCallback;
110
111
	/**
112
	 * @var array
113
	 */
114
	protected $items_per_page_list;
115
116
117
	/**
118
	 * @var int
119
	 */
120
	protected $items_per_page_default;
121
122
	/**
123
	 * @var string
124
	 */
125
	protected $template_file;
126
127
	/**
128
	 * @var Column\IColumn[]
129
	 */
130
	protected $columns = [];
131
132
	/**
133
	 * @var Column\Action[]
134
	 */
135
	protected $actions = [];
136
137
	/**
138
	 * @var GroupAction\GroupActionCollection
139
	 */
140
	protected $group_action_collection;
141
142
	/**
143
	 * @var Filter\Filter[]
144
	 */
145
	protected $filters = [];
146
147
	/**
148
	 * @var Export\Export[]
149
	 */
150
	protected $exports = [];
151
152
	/**
153
	 * @var DataModel
154
	 */
155
	protected $dataModel;
156
157
	/**
158
	 * @var DataFilter
159
	 */
160
	protected $dataFilter;
161
162
	/**
163
	 * @var string
164
	 */
165
	protected $primary_key = 'id';
166
167
	/**
168
	 * @var bool
169
	 */
170
	protected $do_paginate = TRUE;
171
172
	/**
173
	 * @var bool
174
	 */
175
	protected $csv_export = TRUE;
176
177
	/**
178
	 * @var bool
179
	 */
180
	protected $csv_export_filtered = TRUE;
181
182
	/**
183
	 * @var bool
184
	 */
185
	protected $sortable = FALSE;
186
187
	/**
188
	 * @var string
189
	 */
190
	protected $sortable_handler = 'sort!';
191
192
	/**
193
	 * @var string
194
	 */
195
	protected $original_template;
196
197
	/**
198
	 * @var array
199
	 */
200
	protected $redraw_item;
201
202
	/**
203
	 * @var mixed
204
	 */
205
	protected $translator;
206
207
	/**
208
	 * @var bool
209
	 */
210
	protected $force_filter_active;
211
212
	/**
213
	 * @var callable
214
	 */
215
	protected $tree_view_children_callback;
216
217
	/**
218
	 * @var callable
219
	 */
220
	protected $tree_view_has_children_callback;
221
222
	/**
223
	 * @var string
224
	 */
225
	protected $tree_view_has_children_column;
226
227
	/**
228
	 * @var bool
229
	 */
230
	protected $outer_filter_rendering = FALSE;
231
232
	/**
233
	 * @var array
234
	 */
235
	protected $columns_export_order = [];
236
237
	/**
238
	 * @var bool
239
	 */
240
	protected $remember_state = TRUE;
241
242
	/**
243
	 * @var bool
244
	 */
245
	protected $refresh_url = TRUE;
246
247
	/**
248
	 * @var Nette\Http\SessionSection
249
	 */
250
	protected $grid_session;
251
252
	/**
253
	 * @var Column\ItemDetail
254
	 */
255
	protected $items_detail;
256
257
	/**
258
	 * @var array
259
	 */
260
	protected $row_conditions = [
261
		'group_action' => FALSE,
262
		'action' => []
263
	];
264
265
	/**
266
	 * @var array
267
	 */
268
	protected $column_callbacks = [];
269
270
	/**
271
	 * @var bool
272
	 */
273
	protected $can_hide_columns = FALSE;
274
275
	/**
276
	 * @var array
277
	 */
278
	protected $columns_visibility = [];
279
280
	/**
281
	 * @var InlineEdit
282
	 */
283
	protected $inlineEdit;
284
285
	/**
286
	 * @var InlineEdit
287
	 */
288
	protected $inlineAdd;
289
290
	/**
291
	 * @var bool
292
	 */
293
	protected $snippets_set = FALSE;
294
295
	/**
296
	 * @var bool
297
	 */
298
	protected $some_column_default_hide = FALSE;
299
300
	/**
301
	 * @var ColumnsSummary
302
	 */
303
	protected $columnsSummary;
304
305
306
	/**
307
	 * @param Nette\ComponentModel\IContainer|NULL $parent
308
	 * @param string                               $name
309
	 */
310
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
311
	{
312
		parent::__construct($parent, $name);
313
314
		$this->monitor('Nette\Application\UI\Presenter');
315
316
		/**
317
		 * Try to find previous filters, pagination, per_page and other values in session
318
		 */
319
		$this->onRender[] = [$this, 'findSessionValues'];
320
321
		/**
322
		 * Find default filter values
323
		 */
324
		$this->onRender[] = [$this, 'findDefaultFilter'];
325
326
		/**
327
		 * Find default sort
328
		 */
329
		$this->onRender[] = [$this, 'findDefaultSort'];
330
	}
331
332
333
	/**
334
	 * {inheritDoc}
335
	 * @return void
336
	 */
337
	public function attached($presenter)
338
	{
339
		parent::attached($presenter);
340
341
		if ($presenter instanceof Nette\Application\UI\Presenter) {
342
			/**
343
			 * Get session
344
			 */
345
			if ($this->remember_state) {
346
				$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...
347
			}
348
		}
349
	}
350
351
352
	/********************************************************************************
353
	 *                                  RENDERING                                   *
354
	 ********************************************************************************/
355
356
357
	/**
358
	 * Render template
359
	 * @return void
360
	 */
361
	public function render()
362
	{
363
		/**
364
		 * Check whether datagrid has set some columns, initiated data source, etc
365
		 */
366
		if (!($this->dataModel instanceof DataModel)) {
367
			throw new DataGridException('You have to set a data source first.');
368
		}
369
370
		if (empty($this->columns)) {
371
			throw new DataGridException('You have to add at least one column.');
372
		}
373
374
		$this->getTemplate()->setTranslator($this->getTranslator());
375
376
		/**
377
		 * Invoke possible events
378
		 */
379
		$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...
380
381
		/**
382
		 * Prepare data for rendering (datagrid may render just one item)
383
		 */
384
		$rows = [];
385
386
		if (!empty($this->redraw_item)) {
387
			$items = $this->dataModel->filterRow($this->redraw_item);
388
		} else {
389
			$items = Nette\Utils\Callback::invokeArgs(
390
				[$this->dataModel, 'filterData'],
391
				[
392
					$this->getPaginator(),
393
					new Sorting($this->sort, $this->sort_callback),
394
					$this->assableFilters()
395
				]
396
			);
397
		}
398
399
		$callback = $this->rowCallback ?: NULL;
400
		$hasGroupActionOnRows = FALSE;
401
402
		foreach ($items as $item) {
403
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
404
405
			if(!$hasGroupActionOnRows && $row->hasGroupAction()){
406
				$hasGroupActionOnRows = TRUE;
407
			}
408
			
409
			if ($callback) {
410
				$callback($item, $row->getControl());
411
			}
412
		}
413
414
		if($hasGroupActionOnRows){
415
			$hasGroupActionOnRows = $this->hasGroupActions();
416
		}
417
418
		if ($this->isTreeView()) {
419
			$this->getTemplate()->add('tree_view_has_children_column', $this->tree_view_has_children_column);
420
		}
421
422
		$this->getTemplate()->add('rows', $rows);
423
424
		$this->getTemplate()->add('columns', $this->getColumns());
425
		$this->getTemplate()->add('actions', $this->actions);
426
		$this->getTemplate()->add('exports', $this->exports);
427
		$this->getTemplate()->add('filters', $this->filters);
428
429
		$this->getTemplate()->add('filter_active', $this->isFilterActive());
430
		$this->getTemplate()->add('original_template', $this->getOriginalTemplateFile());
431
		//$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...
432
		$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...
433
		$this->getTemplate()->add('items_detail', $this->items_detail);
434
		$this->getTemplate()->add('columns_visibility', $this->columns_visibility);
435
		$this->getTemplate()->add('columnsSummary', $this->columnsSummary);
436
437
		$this->getTemplate()->add('inlineEdit', $this->inlineEdit);
438
		$this->getTemplate()->add('inlineAdd', $this->inlineAdd);
439
440
		$this->getTemplate()->add('hasGroupActionOnRows', $hasGroupActionOnRows);
441
442
		/**
443
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
444
		 */
445
		$this->getTemplate()->add('filter', $this['filter']);
446
447
		/**
448
		 * Set template file and render it
449
		 */
450
		$this->getTemplate()->setFile($this->getTemplateFile());
451
		$this->getTemplate()->render();
452
	}
453
454
455
	/********************************************************************************
456
	 *                                 ROW CALLBACK                                 *
457
	 ********************************************************************************/
458
459
460
	/**
461
	 * Each row can be modified with user callback
462
	 * @param  callable  $callback
463
	 * @return static
464
	 */
465
	public function setRowCallback(callable $callback)
466
	{
467
		$this->rowCallback = $callback;
468
469
		return $this;
470
	}
471
472
473
	/********************************************************************************
474
	 *                                 DATA SOURCE                                  *
475
	 ********************************************************************************/
476
477
478
	/**
479
	 * By default ID, you can change that
480
	 * @param string $primary_key
481
	 * @return static
482
	 */
483
	public function setPrimaryKey($primary_key)
484
	{
485
		if ($this->dataModel instanceof DataModel) {
486
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
487
		}
488
489
		$this->primary_key = $primary_key;
490
491
		return $this;
492
	}
493
494
495
	/**
496
	 * Set Grid data source
497
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
498
	 * @return static
499
	 */
500
	public function setDataSource($source)
501
	{
502
		$this->dataModel = new DataModel($source, $this->primary_key);
503
504
		return $this;
505
	}
506
507
508
	/********************************************************************************
509
	 *                                  TEMPLATING                                  *
510
	 ********************************************************************************/
511
512
513
	/**
514
	 * Set custom template file to render
515
	 * @param string $template_file
516
	 * @return static
517
	 */
518
	public function setTemplateFile($template_file)
519
	{
520
		$this->template_file = $template_file;
521
522
		return $this;
523
	}
524
525
526
	/**
527
	 * Get DataGrid template file
528
	 * @return string
529
	 * @return static
530
	 */
531
	public function getTemplateFile()
532
	{
533
		return $this->template_file ?: $this->getOriginalTemplateFile();
534
	}
535
536
537
	/**
538
	 * Get DataGrid original template file
539
	 * @return string
540
	 */
541
	public function getOriginalTemplateFile()
542
	{
543
		return __DIR__.'/templates/datagrid.latte';
544
	}
545
546
547
	/**
548
	 * Tell datagrid wheteher to use or not happy components
549
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
550
	 * @return void|bool
551
	 */
552
	public function useHappyComponents($use = NULL)
553
	{
554
		if (NULL === $use) {
555
			return $this->use_happy_components;
556
		}
557
558
		$this->use_happy_components = (bool) $use;
559
	}
560
561
562
	/********************************************************************************
563
	 *                                   SORTING                                    *
564
	 ********************************************************************************/
565
566
567
	/**
568
	 * Set default sorting
569
	 * @param array $sort
570
	 * @return static
571
	 */
572
	public function setDefaultSort($sort)
573
	{
574
		if (is_string($sort)) {
575
			$sort = [$sort => 'ASC'];
576
		} else {
577
			$sort = (array) $sort;
578
		}
579
580
		$this->default_sort = $sort;
581
582
		return $this;
583
	}
584
585
586
	/**
587
	 * User may set default sorting, apply it
588
	 * @return void
589
	 */
590
	public function findDefaultSort()
591
	{
592
		if (!empty($this->sort)) {
593
			return;
594
		}
595
596
		if (!empty($this->default_sort)) {
597
			$this->sort = $this->default_sort;
598
		}
599
600
		$this->saveSessionData('_grid_sort', $this->sort);
601
	}
602
603
604
	/**
605
	 * Set grido to be sortable
606
	 * @param bool $sortable
607
	 * @return static
608
	 */
609
	public function setSortable($sortable = TRUE)
610
	{
611
		if ($this->getItemsDetail()) {
612
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
613
		}
614
615
		$this->sortable = (bool) $sortable;
616
617
		return $this;
618
	}
619
620
621
	/**
622
	 * Set sortable handle
623
	 * @param string $handler
624
	 * @return static
625
	 */
626
	public function setSortableHandler($handler = 'sort!')
627
	{
628
		$this->sortable_handler = (string) $handler;
629
630
		return $this;
631
	}
632
633
634
	/**
635
	 * Tell whether DataGrid is sortable
636
	 * @return bool
637
	 */
638
	public function isSortable()
639
	{
640
		return $this->sortable;
641
	}
642
643
	/**
644
	 * Return sortable handle name
645
	 * @return string
646
	 */
647
	public function getSortableHandler()
648
	{
649
		return $this->sortable_handler;
650
	}
651
652
653
	/********************************************************************************
654
	 *                                  TREE VIEW                                   *
655
	 ********************************************************************************/
656
657
658
	/**
659
	 * Is tree view set?
660
	 * @return boolean
661
	 */
662
	public function isTreeView()
663
	{
664
		return (bool) $this->tree_view_children_callback;
665
	}
666
667
668
	/**
669
	 * Setting tree view
670
	 * @param callable $get_children_callback
671
	 * @param string|callable $tree_view_has_children_column
672
	 * @return static
673
	 */
674
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
675
	{
676
		if (!is_callable($get_children_callback)) {
677
			throw new DataGridException(
678
				'Parameters to method DataGrid::setTreeView must be of type callable'
679
			);
680
		}
681
682
		if (is_callable($tree_view_has_children_column)) {
683
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
684
			$tree_view_has_children_column = NULL;
685
		}
686
687
		$this->tree_view_children_callback = $get_children_callback;
688
		$this->tree_view_has_children_column = $tree_view_has_children_column;
689
690
		/**
691
		 * TUrn off pagination
692
		 */
693
		$this->setPagination(FALSE);
694
695
		/**
696
		 * Set tree view template file
697
		 */
698
		if (!$this->template_file) {
699
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
700
		}
701
702
		return $this;
703
	}
704
705
706
	/**
707
	 * Is tree view children callback set?
708
	 * @return boolean
709
	 */
710
	public function hasTreeViewChildrenCallback()
711
	{
712
		return is_callable($this->tree_view_has_children_callback);
713
	}
714
715
716
	/**
717
	 * @param  mixed $item
718
	 * @return boolean
719
	 */
720
	public function treeViewChildrenCallback($item)
721
	{
722
		return call_user_func($this->tree_view_has_children_callback, $item);
723
	}
724
725
726
	/********************************************************************************
727
	 *                                    COLUMNS                                   *
728
	 ********************************************************************************/
729
730
731
	/**
732
	 * Add text column with no other formating
733
	 * @param  string      $key
734
	 * @param  string      $name
735
	 * @param  string|null $column
736
	 * @return Column\ColumnText
737
	 */
738
	public function addColumnText($key, $name, $column = NULL)
739
	{
740
		$this->addColumnCheck($key);
741
		$column = $column ?: $key;
742
743
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
744
	}
745
746
747
	/**
748
	 * Add column with link
749
	 * @param  string      $key
750
	 * @param  string      $name
751
	 * @param  string|null $column
752
	 * @return Column\ColumnLink
753
	 */
754
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
755
	{
756
		$this->addColumnCheck($key);
757
		$column = $column ?: $key;
758
		$href = $href ?: $key;
759
760
		if (NULL === $params) {
761
			$params = [$this->primary_key];
762
		}
763
764
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
765
	}
766
767
768
	/**
769
	 * Add column with possible number formating
770
	 * @param  string      $key
771
	 * @param  string      $name
772
	 * @param  string|null $column
773
	 * @return Column\ColumnNumber
774
	 */
775
	public function addColumnNumber($key, $name, $column = NULL)
776
	{
777
		$this->addColumnCheck($key);
778
		$column = $column ?: $key;
779
780
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
781
	}
782
783
784
	/**
785
	 * Add column with date formating
786
	 * @param  string      $key
787
	 * @param  string      $name
788
	 * @param  string|null $column
789
	 * @return Column\ColumnDateTime
790
	 */
791
	public function addColumnDateTime($key, $name, $column = NULL)
792
	{
793
		$this->addColumnCheck($key);
794
		$column = $column ?: $key;
795
796
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
797
	}
798
799
800
	/**
801
	 * Add column status
802
	 * @param  string      $key
803
	 * @param  string      $name
804
	 * @param  string|null $column
805
	 * @return Column\ColumnStatus
806
	 */
807
	public function addColumnStatus($key, $name, $column = NULL)
808
	{
809
		$this->addColumnCheck($key);
810
		$column = $column ?: $key;
811
812
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
813
	}
814
815
816
	/**
817
	 * @param string $key
818
	 * @param Column\Column $column
819
	 * @return Column\Column
820
	 */
821
	protected function addColumn($key, Column\Column $column)
822
	{
823
		$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...
824
825
		$this->columns_visibility[$key] = [
826
			'visible' => TRUE,
827
			'name' => $column->getName()
828
		];
829
830
		return $this->columns[$key] = $column;
831
	}
832
833
834
	/**
835
	 * Return existing column
836
	 * @param  string $key
837
	 * @return Column\Column
838
	 * @throws DataGridException
839
	 */
840
	public function getColumn($key)
841
	{
842
		if (!isset($this->columns[$key])) {
843
			throw new DataGridException("There is no column at key [$key] defined.");
844
		}
845
846
		return $this->columns[$key];
847
	}
848
849
850
	/**
851
	 * Remove column
852
	 * @param string $key
853
	 * @return void
854
	 */
855
	public function removeColumn($key)
856
	{
857
		unset($this->columns[$key]);
858
	}
859
860
861
	/**
862
	 * Check whether given key already exists in $this->columns
863
	 * @param  string $key
864
	 * @throws DataGridException
865
	 */
866
	protected function addColumnCheck($key)
867
	{
868
		if (isset($this->columns[$key])) {
869
			throw new DataGridException("There is already column at key [$key] defined.");
870
		}
871
	}
872
873
874
	/********************************************************************************
875
	 *                                    ACTIONS                                   *
876
	 ********************************************************************************/
877
878
879
	/**
880
	 * Create action
881
	 * @param string     $key
882
	 * @param string     $name
883
	 * @param string     $href
884
	 * @param array|null $params
885
	 * @return Column\Action
886
	 */
887
	public function addAction($key, $name, $href = NULL, array $params = NULL)
888
	{
889
		$this->addActionCheck($key);
890
		$href = $href ?: $key;
891
892
		if (NULL === $params) {
893
			$params = [$this->primary_key];
894
		}
895
896
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
897
	}
898
899
900
	/**
901
	 * Create action callback
902
	 * @param string     $key
903
	 * @param string     $name
904
	 * @return Column\Action
905
	 */
906
	public function addActionCallback($key, $name, $callback = NULL)
907
	{
908
		$this->addActionCheck($key);
909
		$params = ['__id' => $this->primary_key];
910
911
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
912
913
		if ($callback) {
914
			if (!is_callable($callback)) {
915
				throw new DataGridException('ActionCallback callback has to be callable.');
916
			}
917
918
			$action->onClick[] = $callback;
919
		}
920
921
		return $action;
922
	}
923
924
925
	/**
926
	 * Get existing action
927
	 * @param  string       $key
928
	 * @return Column\Action
929
	 * @throws DataGridException
930
	 */
931
	public function getAction($key)
932
	{
933
		if (!isset($this->actions[$key])) {
934
			throw new DataGridException("There is no action at key [$key] defined.");
935
		}
936
937
		return $this->actions[$key];
938
	}
939
940
941
	/**
942
	 * Remove action
943
	 * @param string $key
944
	 * @return void
945
	 */
946
	public function removeAction($key)
947
	{
948
		unset($this->actions[$key]);
949
	}
950
951
952
	/**
953
	 * Check whether given key already exists in $this->filters
954
	 * @param  string $key
955
	 * @throws DataGridException
956
	 */
957
	protected function addActionCheck($key)
958
	{
959
		if (isset($this->actions[$key])) {
960
			throw new DataGridException("There is already action at key [$key] defined.");
961
		}
962
	}
963
964
965
	/********************************************************************************
966
	 *                                    FILTERS                                   *
967
	 ********************************************************************************/
968
969
970
	/**
971
	 * Add filter fot text search
972
	 * @param string       $key
973
	 * @param string       $name
974
	 * @param array|string $columns
975
	 * @return Filter\FilterText
976
	 * @throws DataGridException
977
	 */
978
	public function addFilterText($key, $name, $columns = NULL)
979
	{
980
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
981
982
		if (!is_array($columns)) {
983
			throw new DataGridException("Filter Text can except only array or string.");
984
		}
985
986
		$this->addFilterCheck($key);
987
988
		return $this->filters[$key] = new Filter\FilterText($key, $name, $columns);
989
	}
990
991
992
	/**
993
	 * Add select box filter
994
	 * @param string $key
995
	 * @param string $name
996
	 * @param array  $options
997
	 * @param string $column
998
	 * @return Filter\FilterSelect
999
	 * @throws DataGridException
1000
	 */
1001 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...
1002
	{
1003
		$column = $column ?: $key;
1004
1005
		if (!is_string($column)) {
1006
			throw new DataGridException("Filter Select can only filter in one column.");
1007
		}
1008
1009
		$this->addFilterCheck($key);
1010
1011
		return $this->filters[$key] = new Filter\FilterSelect($key, $name, $options, $column);
1012
	}
1013
1014
1015
	/**
1016
	 * Add multi select box filter
1017
	 * @param string $key
1018
	 * @param string $name
1019
	 * @param array  $options
1020
	 * @param string $column
1021
	 * @return Filter\FilterSelect
1022
	 * @throws DataGridException
1023
	 */
1024
	public function addFilterMultiSelect($key, $name, array $options, $column = NULL)
1025
	{
1026
		$column = $column ?: $key;
1027
1028
		if (!is_string($column)) {
1029
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1030
		}
1031
1032
		$this->addFilterCheck($key);
1033
1034
		return $this->filters[$key] = new Filter\FilterMultiSelect($key, $name, $options, $column);
1035
	}
1036
1037
1038
	/**
1039
	 * Add datepicker filter
1040
	 * @param string $key
1041
	 * @param string $name
1042
	 * @param string $column
1043
	 * @return Filter\FilterDate
1044
	 * @throws DataGridException
1045
	 */
1046
	public function addFilterDate($key, $name, $column = NULL)
1047
	{
1048
		$column = $column ?: $key;
1049
1050
		if (!is_string($column)) {
1051
			throw new DataGridException("FilterDate can only filter in one column.");
1052
		}
1053
1054
		$this->addFilterCheck($key);
1055
1056
		return $this->filters[$key] = new Filter\FilterDate($key, $name, $column);
1057
	}
1058
1059
1060
	/**
1061
	 * Add range filter (from - to)
1062
	 * @param string $key
1063
	 * @param string $name
1064
	 * @param string $column
1065
	 * @return Filter\FilterRange
1066
	 * @throws DataGridException
1067
	 */
1068 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...
1069
	{
1070
		$column = $column ?: $key;
1071
1072
		if (!is_string($column)) {
1073
			throw new DataGridException("FilterRange can only filter in one column.");
1074
		}
1075
1076
		$this->addFilterCheck($key);
1077
1078
		return $this->filters[$key] = new Filter\FilterRange($key, $name, $column, $name_second);
1079
	}
1080
1081
1082
	/**
1083
	 * Add datepicker filter (from - to)
1084
	 * @param string $key
1085
	 * @param string $name
1086
	 * @param string $column
1087
	 * @return Filter\FilterDateRange
1088
	 * @throws DataGridException
1089
	 */
1090 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...
1091
	{
1092
		$column = $column ?: $key;
1093
1094
		if (!is_string($column)) {
1095
			throw new DataGridException("FilterDateRange can only filter in one column.");
1096
		}
1097
1098
		$this->addFilterCheck($key);
1099
1100
		return $this->filters[$key] = new Filter\FilterDateRange($key, $name, $column, $name_second);
1101
	}
1102
1103
1104
	/**
1105
	 * Check whether given key already exists in $this->filters
1106
	 * @param  string $key
1107
	 * @throws DataGridException
1108
	 */
1109
	protected function addFilterCheck($key)
1110
	{
1111
		if (isset($this->filters[$key])) {
1112
			throw new DataGridException("There is already action at key [$key] defined.");
1113
		}
1114
	}
1115
1116
1117
	/**
1118
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1119
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1120
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1121
	 */
1122
	public function assableFilters()
1123
	{
1124
		foreach ($this->filter as $key => $value) {
1125
			if (!isset($this->filters[$key])) {
1126
				$this->deleteSesssionData($key);
1127
1128
				continue;
1129
			}
1130
1131
			if (is_array($value) || $value instanceof \Traversable) {
1132
				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...
1133
					$this->filters[$key]->setValue($value);
1134
				}
1135
			} else {
1136
				if ($value !== '' && $value !== NULL) {
1137
					$this->filters[$key]->setValue($value);
1138
				}
1139
			}
1140
		}
1141
1142
		foreach ($this->columns as $column) {
1143
			if (isset($this->sort[$column->getSortingColumn()])) {
1144
				$column->setSort($this->sort);
1145
			}
1146
		}
1147
1148
		return $this->filters;
1149
	}
1150
1151
1152
	/**
1153
	 * Remove filter
1154
	 * @param string $key
1155
	 * @return void
1156
	 */
1157
	public function removeFilter($key)
1158
	{
1159
		unset($this->filters[$key]);
1160
	}
1161
1162
1163
	/**
1164
	 * Get defined filter
1165
	 * @param  string $key
1166
	 * @return Filter\Filter
1167
	 */
1168
	public function getFilter($key)
1169
	{
1170
		if (!isset($this->filters[$key])) {
1171
			throw new DataGridException("Filter [{$key}] is not defined");
1172
		}
1173
1174
		return $this->filters[$key];
1175
	}
1176
1177
1178
	/********************************************************************************
1179
	 *                                  FILTERING                                   *
1180
	 ********************************************************************************/
1181
1182
1183
	/**
1184
	 * Is filter active?
1185
	 * @return boolean
1186
	 */
1187
	public function isFilterActive()
1188
	{
1189
		$is_filter = ArraysHelper::testTruthy($this->filter);
1190
1191
		return ($is_filter) || $this->force_filter_active;
1192
	}
1193
1194
1195
	/**
1196
	 * Tell that filter is active from whatever reasons
1197
	 * return static
1198
	 */
1199
	public function setFilterActive()
1200
	{
1201
		$this->force_filter_active = TRUE;
1202
1203
		return $this;
1204
	}
1205
1206
1207
	/**
1208
	 * Set filter values (force - overwrite user data)
1209
	 * @param array $filter
1210
	 * @return static
1211
	 */
1212
	public function setFilter(array $filter)
1213
	{
1214
		$this->filter = $filter;
1215
1216
		$this->saveSessionData('_grid_has_filtered', 1);
1217
1218
		return $this;
1219
	}
1220
1221
1222
	/**
1223
	 * If we want to sent some initial filter
1224
	 * @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...
1225
	 * @param bool  $use_on_reset
1226
	 * @return static
1227
	 */
1228
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1229
	{
1230
		foreach ($default_filter as $key => $value) {
1231
			$filter = $this->getFilter($key);
1232
1233
			if (!$filter) {
1234
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1235
			}
1236
1237
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1238
				throw new DataGridException(
1239
					"Default value of filter [$key] - MultiSelect has to be an array"
1240
				);
1241
			}
1242
1243
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1244
				if (!is_array($value)) {
1245
					throw new DataGridException(
1246
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1247
					);
1248
				}
1249
1250
				$temp = $value;
1251
				unset($temp['from'], $temp['to']);
1252
1253
				if (!empty($temp)) {
1254
					throw new DataGridException(
1255
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1256
					);
1257
				}
1258
			}
1259
		}
1260
1261
		$this->default_filter = $default_filter;
1262
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1263
1264
		return $this;
1265
	}
1266
1267
1268
	/**
1269
	 * User may set default filter, find it
1270
	 * @return void
1271
	 */
1272
	public function findDefaultFilter()
1273
	{
1274
		if (!empty($this->filter)) {
1275
			return;
1276
		}
1277
1278
		if ($this->getSessionData('_grid_has_filtered')) {
1279
			return;
1280
		}
1281
1282
		if (!empty($this->default_filter)) {
1283
			$this->filter = $this->default_filter;
1284
		}
1285
1286
		foreach ($this->filter as $key => $value) {
1287
			$this->saveSessionData($key, $value);
1288
		}
1289
	}
1290
1291
1292
	/**
1293
	 * FilterAndGroupAction form factory
1294
	 * @return Form
1295
	 */
1296
	public function createComponentFilter()
1297
	{
1298
		$form = new Form($this, 'filter');
1299
1300
		$form->setMethod('get');
1301
1302
		$form->setTranslator($this->getTranslator());
1303
1304
		/**
1305
		 * InlineEdit part
1306
		 */
1307
		$inline_edit_container = $form->addContainer('inline_edit');
1308
1309 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...
1310
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1311
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1312
				->setValidationScope(FALSE);
1313
1314
			$this->inlineEdit->onControlAdd($inline_edit_container);
1315
		}
1316
1317
		/**
1318
		 * InlineAdd part
1319
		 */
1320
		$inline_add_container = $form->addContainer('inline_add');
1321
1322 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...
1323
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save');
1324
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1325
				->setValidationScope(FALSE)
1326
				->setAttribute('data-datagrid-cancel-inline-add', TRUE);
1327
1328
			$this->inlineAdd->onControlAdd($inline_add_container);
1329
		}
1330
1331
		/**
1332
		 * ItemDetail form part
1333
		 */
1334
		$items_detail_form = $this->getItemDetailForm();
1335
1336
		if ($items_detail_form instanceof Nette\Forms\Container) {
1337
			$form['items_detail_form'] = $items_detail_form;
1338
		}
1339
1340
		/**
1341
		 * Filter part
1342
		 */
1343
		$filter_container = $form->addContainer('filter');
1344
1345
		foreach ($this->filters as $filter) {
1346
			$filter->addToFormContainer($filter_container);
1347
		}
1348
1349
		/**
1350
		 * Group action part
1351
		 */
1352
		$group_action_container = $form->addContainer('group_action');
1353
1354
		if ($this->hasGroupActions()) {
1355
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1356
		}
1357
1358
		$form->setDefaults(['filter' => $this->filter]);
1359
1360
		/**
1361
		 * Per page part
1362
		 */
1363
		$form->addSelect('per_page', '', $this->getItemsPerPageList());
1364
1365
		if (!$form->isSubmitted()) {
1366
			$form['per_page']->setValue($this->getPerPage());
1367
		}
1368
1369
		$form->addSubmit('per_page_submit', '');
1370
		
1371
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1372
1373
		return $form;
1374
	}
1375
1376
1377
	/**
1378
	 * Set $this->filter values after filter form submitted
1379
	 * @param  Form $form
1380
	 * @return void
1381
	 */
1382
	public function filterSucceeded(Form $form)
1383
	{
1384
		if ($this->snippets_set) {
1385
			return;
1386
		}
1387
1388
		$values = $form->getValues();
1389
1390
		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...
1391
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1392
				return;
1393
			}
1394
		}
1395
1396
		/**
1397
		 * Per page
1398
		 */
1399
		if (isset($form['per_page_submit']) && $form['per_page_submit']->isSubmittedBy()) {
1400
			/**
1401
			 * Session stuff
1402
			 */
1403
			$this->saveSessionData('_grid_per_page', $values->per_page);
1404
1405
			/**
1406
			 * Other stuff
1407
			 */
1408
			$this->per_page = $values->per_page;
1409
			$this->reload();
1410
1411
			return;
1412
		}
1413
1414
		/**
1415
		 * Inline edit
1416
		 */
1417
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1418
			$edit = $form['inline_edit'];
1419
1420
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1421
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1422
				$primary_where_column = $form->getHttpData(
1423
					Form::DATA_LINE,
1424
					'inline_edit[_primary_where_column]'
1425
				);
1426
1427 View Code Duplication
				if ($edit['submit']->isSubmittedBy()) {
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...
1428
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1429
1430
					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...
1431
						$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...
1432
					}
1433
				}
1434
1435
				$this->redrawItem($id, $primary_where_column);
1436
1437
				return;
1438
			}
1439
		}
1440
1441
		/**
1442
		 * Inline add
1443
		 */
1444
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1445
			$add = $form['inline_add'];
1446
1447
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1448 View Code Duplication
				if ($add['submit']->isSubmittedBy()) {
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...
1449
					$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...
1450
1451
					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...
1452
						$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...
1453
					}
1454
				}
1455
1456
				return;
1457
			}
1458
		}
1459
1460
		/**
1461
		 * Filter itself
1462
		 */
1463
		$values = $values['filter'];
1464
1465
		foreach ($values as $key => $value) {
1466
			/**
1467
			 * Session stuff
1468
			 */
1469
			$this->saveSessionData($key, $value);
1470
1471
			/**
1472
			 * Other stuff
1473
			 */
1474
			$this->filter[$key] = $value;
1475
		}
1476
1477
		if (!empty($values)) {
1478
			$this->saveSessionData('_grid_has_filtered', 1);
1479
		}
1480
1481
		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...
1482
			$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...
1483
1484
			foreach ($this->columns as $key => $column) {
1485
				if ($column->isSortable()) {
1486
					$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...
1487
						'sort' => $column->getSortNext()
1488
					]);
1489
				}
1490
			}
1491
		}
1492
1493
		$this->reload();
1494
	}
1495
1496
1497
	/**
1498
	 * Should be datagrid filters rendered separately?
1499
	 * @param boolean $out
1500
	 * @return static
1501
	 */
1502
	public function setOuterFilterRendering($out = TRUE)
1503
	{
1504
		$this->outer_filter_rendering = (bool) $out;
1505
1506
		return $this;
1507
	}
1508
1509
1510
	/**
1511
	 * Are datagrid filters rendered separately?
1512
	 * @return boolean
1513
	 */
1514
	public function hasOuterFilterRendering()
1515
	{
1516
		return $this->outer_filter_rendering;
1517
	}
1518
1519
1520
	/**
1521
	 * Try to restore session stuff
1522
	 * @return void
1523
	 */
1524
	public function findSessionValues()
1525
	{
1526
		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...
1527
			return;
1528
		}
1529
1530
		if (!$this->remember_state) {
1531
			return;
1532
		}
1533
1534
		if ($page = $this->getSessionData('_grid_page')) {
1535
			$this->page = $page;
1536
		}
1537
1538
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1539
			$this->per_page = $per_page;
1540
		}
1541
1542
		if ($sort = $this->getSessionData('_grid_sort')) {
1543
			$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...
1544
		}
1545
1546
		foreach ($this->getSessionData() as $key => $value) {
1547
			$other_session_keys = [
1548
				'_grid_per_page',
1549
				'_grid_sort',
1550
				'_grid_page',
1551
				'_grid_has_filtered',
1552
				'_grid_hidden_columns',
1553
				'_grid_hidden_columns_manipulated'
1554
			];
1555
1556
			if (!in_array($key, $other_session_keys)) {
1557
				$this->filter[$key] = $value;
1558
			}
1559
		}
1560
1561
		/**
1562
		 * When column is sorted via custom callback, apply it
1563
		 */
1564
		if (empty($this->sort_callback) && !empty($this->sort)) {
1565
			foreach ($this->sort as $key => $order) {
1566
				$column = $this->getColumn($key);
1567
1568
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1569
					$this->sort_callback = $column->getSortableCallback();
1570
				}
1571
			}
1572
		}
1573
	}
1574
1575
1576
	/********************************************************************************
1577
	 *                                    EXPORTS                                   *
1578
	 ********************************************************************************/
1579
1580
1581
	/**
1582
	 * Add export of type callback
1583
	 * @param string $text
1584
	 * @param callable $callback
1585
	 * @param boolean $filtered
1586
	 * @return Export\Export
1587
	 */
1588
	public function addExportCallback($text, $callback, $filtered = FALSE)
1589
	{
1590
		if (!is_callable($callback)) {
1591
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1592
		}
1593
1594
		return $this->addToExports(new Export\Export($text, $callback, $filtered));
1595
	}
1596
1597
1598
	/**
1599
	 * Add already implemented csv export
1600
	 * @param string      $text
1601
	 * @param string      $csv_file_name
1602
	 * @param string|null $output_encoding
1603
	 * @param string|null $delimiter
1604
	 * @return Export\Export
1605
	 */
1606
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1607
	{
1608
		return $this->addToExports(new Export\ExportCsv(
1609
			$text,
1610
			$csv_file_name,
1611
			FALSE,
1612
			$output_encoding,
1613
			$delimiter
1614
		));
1615
	}
1616
1617
1618
	/**
1619
	 * Add already implemented csv export, but for filtered data
1620
	 * @param string      $text
1621
	 * @param string      $csv_file_name
1622
	 * @param string|null $output_encoding
1623
	 * @param string|null $delimiter
1624
	 * @return Export\Export
1625
	 */
1626
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1627
	{
1628
		return $this->addToExports(new Export\ExportCsv(
1629
			$text,
1630
			$csv_file_name,
1631
			TRUE,
1632
			$output_encoding,
1633
			$delimiter
1634
		));
1635
	}
1636
1637
1638
	/**
1639
	 * Add export to array
1640
	 * @param Export\Export $export
1641
	 * @return Export\Export
1642
	 */
1643
	protected function addToExports(Export\Export $export)
1644
	{
1645
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1646
1647
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1648
1649
		return $this->exports[$id] = $export;
1650
	}
1651
1652
1653
	public function resetExportsLinks()
1654
	{
1655
		foreach ($this->exports as $id => $export) {
1656
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1657
		}
1658
	}
1659
1660
1661
	/********************************************************************************
1662
	 *                                 GROUP ACTIONS                                *
1663
	 ********************************************************************************/
1664
1665
1666
	/**
1667
	 * Alias for add group select action
1668
	 * @param string $title
1669
	 * @param array  $options
1670
	 * @return GroupAction\GroupAction
1671
	 */
1672
	public function addGroupAction($title, $options = [])
1673
	{
1674
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1675
	}
1676
1677
	/**
1678
	 * Add group action (select box)
1679
	 * @param string $title
1680
	 * @param array  $options
1681
	 * @return GroupAction\GroupAction
1682
	 */
1683
	public function addGroupSelectAction($title, $options = [])
1684
	{
1685
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1686
	}
1687
1688
	/**
1689
	 * Add group action (text input)
1690
	 * @param string $title
1691
	 * @return GroupAction\GroupAction
1692
	 */
1693
	public function addGroupTextAction($title)
1694
	{
1695
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1696
	}
1697
1698
	/**
1699
	 * Get collection of all group actions
1700
	 * @return GroupAction\GroupActionCollection
1701
	 */
1702
	public function getGroupActionCollection()
1703
	{
1704
		if (!$this->group_action_collection) {
1705
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
1706
		}
1707
1708
		return $this->group_action_collection;
1709
	}
1710
1711
1712
	/**
1713
	 * Has datagrid some group actions?
1714
	 * @return boolean
1715
	 */
1716
	public function hasGroupActions()
1717
	{
1718
		return (bool) $this->group_action_collection;
1719
	}
1720
1721
1722
	/********************************************************************************
1723
	 *                                   HANDLERS                                   *
1724
	 ********************************************************************************/
1725
1726
1727
	/**
1728
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1729
	 * @param  int  $page
1730
	 * @return void
1731
	 */
1732
	public function handlePage($page)
1733
	{
1734
		/**
1735
		 * Session stuff
1736
		 */
1737
		$this->page = $page;
1738
		$this->saveSessionData('_grid_page', $page);
1739
1740
		$this->reload(['table']);
1741
	}
1742
1743
1744
	/**
1745
	 * Handler for sorting
1746
	 * @param array $sort
1747
	 * @return void
1748
	 */
1749
	public function handleSort(array $sort)
1750
	{
1751
		$new_sort = [];
1752
1753
		/**
1754
		 * Find apropirate column
1755
		 */
1756
		foreach ($sort as $key => $value) {
1757
			if (empty($this->columns[$key])) {
1758
				throw new DataGridException("Column <$key> not found");
1759
			}
1760
1761
			$column = $this->columns[$key];
1762
			$new_sort = [$column->getSortingColumn() => $value];
1763
1764
			/**
1765
			 * Pagination may be reseted after sorting
1766
			 */
1767
			if ($column->sortableResetPagination()) {
1768
				$this->page = 1;
1769
				$this->saveSessionData('_grid_page', 1);
1770
			}
1771
1772
			/**
1773
			 * Custom sorting callback may be applied
1774
			 */
1775
			if ($column->getSortableCallback()) {
1776
				$this->sort_callback = $column->getSortableCallback();
1777
			}
1778
		}
1779
1780
		/**
1781
		 * Session stuff
1782
		 */
1783
		$this->sort = $new_sort;
1784
		$this->saveSessionData('_grid_sort', $this->sort);
1785
1786
		$this->reload(['table']);
1787
	}
1788
1789
1790
	/**
1791
	 * handler for reseting the filter
1792
	 * @return void
1793
	 */
1794
	public function handleResetFilter()
1795
	{
1796
		/**
1797
		 * Session stuff
1798
		 */
1799
		$this->deleteSesssionData('_grid_page');
1800
1801
		if ($this->default_filter_use_on_reset) {
1802
			$this->deleteSesssionData('_grid_has_filtered');
1803
		}
1804
1805
		foreach ($this->getSessionData() as $key => $value) {
1806
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page', '_grid_has_filtered'])) {
1807
				$this->deleteSesssionData($key);
1808
			}
1809
		}
1810
1811
		$this->filter = [];
1812
1813
		$this->reload(['grid']);
1814
	}
1815
1816
1817
	/**
1818
	 * Handler for export
1819
	 * @param  int $id Key for particular export class in array $this->exports
1820
	 * @return void
1821
	 */
1822
	public function handleExport($id)
1823
	{
1824
		if (!isset($this->exports[$id])) {
1825
			throw new Nette\Application\ForbiddenRequestException;
1826
		}
1827
1828
		if (!empty($this->columns_export_order)) {
1829
			$this->setColumnsOrder($this->columns_export_order);
1830
		}
1831
1832
		$export = $this->exports[$id];
1833
1834
		if ($export->isFiltered()) {
1835
			$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...
1836
			$filter    = $this->assableFilters();
1837
		} else {
1838
			$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...
1839
			$filter    = [];
1840
		}
1841
1842
		if (NULL === $this->dataModel) {
1843
			throw new DataGridException('You have to set a data source first.');
1844
		}
1845
1846
		$rows = [];
1847
1848
		$items = Nette\Utils\Callback::invokeArgs(
1849
			[$this->dataModel, 'filterData'], [
1850
				NULL,
1851
				new Sorting($this->sort, $this->sort_callback),
1852
				$filter
1853
			]
1854
		);
1855
1856
		foreach ($items as $item) {
1857
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1858
		}
1859
1860
		if ($export instanceof Export\ExportCsv) {
1861
			$export->invoke($rows, $this);
1862
		} else {
1863
			$export->invoke($items, $this);
1864
		}
1865
1866
		if ($export->isAjax()) {
1867
			$this->reload();
1868
		}
1869
	}
1870
1871
1872
	/**
1873
	 * Handler for getting children of parent item (e.g. category)
1874
	 * @param  int $parent
1875
	 * @return void
1876
	 */
1877
	public function handleGetChildren($parent)
1878
	{
1879
		$this->setDataSource(
1880
			call_user_func($this->tree_view_children_callback, $parent)
1881
		);
1882
1883
		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...
1884
			$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...
1885
			$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...
1886
1887
			$this->redrawControl('items');
1888
1889
			$this->onRedraw();
1890
		} else {
1891
			$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\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, 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...
1892
		}
1893
	}
1894
1895
1896
	/**
1897
	 * Handler for getting item detail
1898
	 * @param  mixed $id
1899
	 * @return void
1900
	 */
1901
	public function handleGetItemDetail($id)
1902
	{
1903
		$this->getTemplate()->add('toggle_detail', $id);
1904
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1905
1906
		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...
1907
			$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...
1908
			$this->redrawControl('items');
1909
1910
			$this->onRedraw();
1911
		} else {
1912
			$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\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, 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...
1913
		}
1914
	}
1915
1916
1917
	/**
1918
	 * Handler for inline editing
1919
	 * @param  mixed $id
1920
	 * @param  mixed $key
1921
	 * @return void
1922
	 */
1923
	public function handleEdit($id, $key)
1924
	{
1925
		$column = $this->getColumn($key);
1926
		$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...
1927
1928
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1929
	}
1930
1931
1932
	/**
1933
	 * Redraw $this
1934
	 * @return void
1935
	 */
1936
	public function reload($snippets = [])
1937
	{
1938
		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...
1939
			$this->redrawControl('tbody');
1940
			$this->redrawControl('pagination');
1941
1942
			/**
1943
			 * manualy reset exports links...
1944
			 */
1945
			$this->resetExportsLinks();
1946
			$this->redrawControl('exports');
1947
1948
			foreach ($snippets as $snippet) {
1949
				$this->redrawControl($snippet);
1950
			}
1951
1952
			$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...
1953
1954
			$this->onRedraw();
1955
		} else {
1956
			$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\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, 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...
1957
		}
1958
	}
1959
1960
1961
	/**
1962
	 * Handler for column status
1963
	 * @param  string $id
1964
	 * @param  string $key
1965
	 * @param  string $value
1966
	 * @return void
1967
	 */
1968
	public function handleChangeStatus($id, $key, $value)
1969
	{
1970
		if (empty($this->columns[$key])) {
1971
			throw new DataGridException("ColumnStatus[$key] does not exist");
1972
		}
1973
1974
		$this->columns[$key]->onChange($id, $value);
1975
	}
1976
1977
1978
	/**
1979
	 * Redraw just one row via ajax
1980
	 * @param  int   $id
1981
	 * @param  mixed $primary_where_column
1982
	 * @return void
1983
	 */
1984
	public function redrawItem($id, $primary_where_column = NULL)
1985
	{
1986
		$this->snippets_set = TRUE;
1987
1988
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
1989
1990
		$this->redrawControl('items');
1991
		$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...
1992
1993
		$this->onRedraw();
1994
	}
1995
1996
1997
	/**
1998
	 * Tell datagrid to display all columns
1999
	 * @return void
2000
	 */
2001
	public function handleShowAllColumns()
2002
	{
2003
		$this->deleteSesssionData('_grid_hidden_columns');
2004
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2005
2006
		$this->redrawControl();
2007
2008
		$this->onRedraw();
2009
	}
2010
2011
2012
	/**
2013
	 * Tell datagrid to display default columns
2014
	 * @return void
2015
	 */
2016
	public function handleShowDefaultColumns()
2017
	{
2018
		$this->deleteSesssionData('_grid_hidden_columns');
2019
		$this->saveSessionData('_grid_hidden_columns_manipulated', FALSE);
2020
2021
		$this->redrawControl();
2022
2023
		$this->onRedraw();
2024
	}
2025
2026
2027
	/**
2028
	 * Reveal particular column
2029
	 * @param  string $column
2030
	 * @return void
2031
	 */
2032 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...
2033
	{
2034
		$columns = $this->getSessionData('_grid_hidden_columns');
2035
2036
		if (!empty($columns)) {
2037
			$pos = array_search($column, $columns);
2038
2039
			if ($pos !== FALSE) {
2040
				unset($columns[$pos]);
2041
			}
2042
		}
2043
2044
		$this->saveSessionData('_grid_hidden_columns', $columns);
2045
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2046
2047
		$this->redrawControl();
2048
2049
		$this->onRedraw();
2050
	}
2051
2052
2053
	/**
2054
	 * Notice datagrid to not display particular columns
2055
	 * @param  string $column
2056
	 * @return void
2057
	 */
2058 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...
2059
	{
2060
		/**
2061
		 * Store info about hiding a column to session
2062
		 */
2063
		$columns = $this->getSessionData('_grid_hidden_columns');
2064
2065
		if (empty($columns)) {
2066
			$columns = [$column];
2067
		} else if (!in_array($column, $columns)) {
2068
			array_push($columns, $column);
2069
		}
2070
2071
		$this->saveSessionData('_grid_hidden_columns', $columns);
2072
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2073
2074
		$this->redrawControl();
2075
2076
		$this->onRedraw();
2077
	}
2078
2079
2080
	public function handleActionCallback($__key, $__id)
2081
	{
2082
		$action = $this->getAction($__key);
2083
2084
		if (!($action instanceof Column\ActionCallback)) {
2085
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
2086
		}
2087
2088
		$action->onClick($__id);
2089
	}
2090
2091
2092
	/********************************************************************************
2093
	 *                                  PAGINATION                                  *
2094
	 ********************************************************************************/
2095
2096
2097
	/**
2098
	 * Set options of select "items_per_page"
2099
	 * @param array $items_per_page_list
2100
	 * @return static
2101
	 */
2102
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE, $items_per_page_default = NULL)
2103
	{
2104
		$this->items_per_page_list = $items_per_page_list;
2105
		$this->items_per_page_default = $items_per_page_default;
2106
2107
		if ($include_all) {
2108
			$this->items_per_page_list[] = 'all';
2109
		}
2110
2111
		return $this;
2112
	}
2113
2114
2115
	/**
2116
	 * Paginator factory
2117
	 * @return Components\DataGridPaginator\DataGridPaginator
2118
	 */
2119
	public function createComponentPaginator()
2120
	{
2121
		/**
2122
		 * Init paginator
2123
		 */
2124
		$component = new Components\DataGridPaginator\DataGridPaginator(
2125
			$this->getTranslator(),
2126
			static::$icon_prefix
2127
		);
2128
		$paginator = $component->getPaginator();
2129
2130
		$paginator->setPage($this->page);
2131
2132
		$per_page = $this->getPerPage() === 'all' ? PHP_INT_MAX : $this->getPerPage();
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false 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...
2133
		$paginator->setItemsPerPage($per_page);
2134
2135
		$paginator->setItemsPerPage($this->getPerPage());
2136
2137
		return $component;
2138
	}
2139
2140
2141
	/**
2142
	 * Get parameter per_page
2143
	 * @return int
2144
	 */
2145
	public function getPerPage()
2146
	{
2147
		$items_per_page_list = $this->getItemsPerPageList();
2148
2149
		$per_page = $this->per_page ?: $this->items_per_page_default ?: reset($items_per_page_list);
2150
2151
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
2152
			$per_page = $this->items_per_page_default ?: reset($items_per_page_list) ?: reset($items_per_page_list);
2153
		}
2154
2155
		return $per_page;
2156
	}
2157
2158
2159
	/**
2160
	 * Get associative array of items_per_page_list
2161
	 * @return array
2162
	 */
2163
	public function getItemsPerPageList()
2164
	{
2165
		if (empty($this->items_per_page_list)) {
2166
			$this->setItemsPerPageList([10, 20, 50], TRUE);
2167
		}
2168
2169
		$list = array_flip($this->items_per_page_list);
2170
2171
		foreach ($list as $key => $value) {
2172
			$list[$key] = $key;
2173
		}
2174
2175
		if (array_key_exists('all', $list)) {
2176
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2177
		}
2178
2179
		return $list;
2180
	}
2181
2182
2183
	/**
2184
	 * Order Grid to "be paginated"
2185
	 * @param bool $do
2186
	 * @return static
2187
	 */
2188
	public function setPagination($do)
2189
	{
2190
		$this->do_paginate = (bool) $do;
2191
2192
		return $this;
2193
	}
2194
2195
2196
	/**
2197
	 * Tell whether Grid is paginated
2198
	 * @return bool
2199
	 */
2200
	public function isPaginated()
2201
	{
2202
		return $this->do_paginate;
2203
	}
2204
2205
2206
	/**
2207
	 * Return current paginator class
2208
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2209
	 */
2210
	public function getPaginator()
2211
	{
2212
		if ($this->isPaginated() && $this->per_page !== 'all') {
2213
			return $this['paginator'];
2214
		}
2215
2216
		return NULL;
2217
	}
2218
2219
2220
	/********************************************************************************
2221
	 *                                     I18N                                     *
2222
	 ********************************************************************************/
2223
2224
2225
	/**
2226
	 * Set datagrid translator
2227
	 * @param Nette\Localization\ITranslator $translator
2228
	 * @return static
2229
	 */
2230
	public function setTranslator(Nette\Localization\ITranslator $translator)
2231
	{
2232
		$this->translator = $translator;
2233
2234
		return $this;
2235
	}
2236
2237
2238
	/**
2239
	 * Get translator for datagrid
2240
	 * @return Nette\Localization\ITranslator
2241
	 */
2242
	public function getTranslator()
2243
	{
2244
		if (!$this->translator) {
2245
			$this->translator = new Localization\SimpleTranslator;
2246
		}
2247
2248
		return $this->translator;
2249
	}
2250
2251
2252
	/********************************************************************************
2253
	 *                                 COLUMNS ORDER                                *
2254
	 ********************************************************************************/
2255
2256
2257
	/**
2258
	 * Set order of datagrid columns
2259
	 * @param array $order
2260
	 * @return static
2261
	 */
2262
	public function setColumnsOrder($order)
2263
	{
2264
		$new_order = [];
2265
2266
		foreach ($order as $key) {
2267
			if (isset($this->columns[$key])) {
2268
				$new_order[$key] = $this->columns[$key];
2269
			}
2270
		}
2271
2272
		if (sizeof($new_order) === sizeof($this->columns)) {
2273
			$this->columns = $new_order;
2274
		} else {
2275
			throw new DataGridException('When changing columns order, you have to specify all columns');
2276
		}
2277
2278
		return $this;
2279
	}
2280
2281
2282
	/**
2283
	 * Columns order may be different for export and normal grid
2284
	 * @param array $order
2285
	 */
2286
	public function setColumnsExportOrder($order)
2287
	{
2288
		$this->columns_export_order = (array) $order;
2289
	}
2290
2291
2292
	/********************************************************************************
2293
	 *                                SESSION & URL                                 *
2294
	 ********************************************************************************/
2295
2296
2297
	/**
2298
	 * Find some unique session key name
2299
	 * @return string
2300
	 */
2301
	public function getSessionSectionName()
2302
	{
2303
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2304
	}
2305
2306
2307
	/**
2308
	 * Should datagrid remember its filters/pagination/etc using session?
2309
	 * @param bool $remember
2310
	 * @return static
2311
	 */
2312
	public function setRememberState($remember = TRUE)
2313
	{
2314
		$this->remember_state = (bool) $remember;
2315
2316
		return $this;
2317
	}
2318
2319
2320
	/**
2321
	 * Should datagrid refresh url using history API?
2322
	 * @param bool $refresh
2323
	 * @return static
2324
	 */
2325
	public function setRefreshUrl($refresh = TRUE)
2326
	{
2327
		$this->refresh_url = (bool) $refresh;
2328
2329
2330
		return $this;
2331
	}
2332
2333
2334
	/**
2335
	 * Get session data if functionality is enabled
2336
	 * @param  string $key
2337
	 * @return mixed
2338
	 */
2339
	public function getSessionData($key = NULL, $default_value = NULL)
2340
	{
2341
		if (!$this->remember_state) {
2342
			return $key ? $default_value : [];
2343
		}
2344
2345
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2346
	}
2347
2348
2349
	/**
2350
	 * Save session data - just if it is enabled
2351
	 * @param  string $key
2352
	 * @param  mixed  $value
2353
	 * @return void
2354
	 */
2355
	public function saveSessionData($key, $value)
2356
	{
2357
		if ($this->remember_state) {
2358
			$this->grid_session->{$key} = $value;
2359
		}
2360
	}
2361
2362
2363
	/**
2364
	 * Delete session data
2365
	 * @return void
2366
	 */
2367
	public function deleteSesssionData($key)
2368
	{
2369
		unset($this->grid_session->{$key});
2370
	}
2371
2372
2373
	/********************************************************************************
2374
	 *                                  ITEM DETAIL                                 *
2375
	 ********************************************************************************/
2376
2377
2378
	/**
2379
	 * Get items detail parameters
2380
	 * @return array
2381
	 */
2382
	public function getItemsDetail()
2383
	{
2384
		return $this->items_detail;
2385
	}
2386
2387
2388
	/**
2389
	 * Items can have thair detail - toggled
2390
	 * @param mixed $detail callable|string|bool
2391
	 * @param bool|NULL $primary_where_column
2392
	 * @return Column\ItemDetail
2393
	 */
2394
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2395
	{
2396
		if ($this->isSortable()) {
2397
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2398
		}
2399
2400
		$this->items_detail = new Column\ItemDetail(
2401
			$this,
2402
			$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...
2403
		);
2404
2405
		if (is_string($detail)) {
2406
			/**
2407
			 * Item detail will be in separate template
2408
			 */
2409
			$this->items_detail->setType('template');
2410
			$this->items_detail->setTemplate($detail);
2411
2412
		} else if (is_callable($detail)) {
2413
			/**
2414
			 * Item detail will be rendered via custom callback renderer
2415
			 */
2416
			$this->items_detail->setType('renderer');
2417
			$this->items_detail->setRenderer($detail);
2418
2419
		} else if (TRUE === $detail) {
2420
			/**
2421
			 * Item detail will be rendered probably via block #detail
2422
			 */
2423
			$this->items_detail->setType('block');
2424
2425
		} else {
2426
			throw new DataGridException(
2427
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2428
			);
2429
		}
2430
2431
		return $this->items_detail;
2432
	}
2433
2434
2435
	/**
2436
	 * @param callable $callable_set_container 
2437
	 * @return static
2438
	 */
2439
	public function setItemsDetailForm(callable $callable_set_container)
2440
	{
2441
		if ($this->items_detail instanceof Column\ItemDetail) {
2442
			$this->items_detail->setForm(
2443
				new Utils\ItemDetailForm($callable_set_container)
2444
			);
2445
2446
			return $this;
2447
		}
2448
2449
		throw new DataGridException('Please set the ItemDetail first.');
2450
	}
2451
2452
2453
	/**
2454
	 * @return Nette\Forms\Container|NULL
2455
	 */
2456
	public function getItemDetailForm()
2457
	{
2458
		if ($this->items_detail instanceof Column\ItemDetail) {
2459
			return $this->items_detail->getForm();
2460
		}
2461
2462
		return NULL;
2463
	}
2464
2465
2466
	/********************************************************************************
2467
	 *                                ROW PRIVILEGES                                *
2468
	 ********************************************************************************/
2469
2470
2471
	/**
2472
	 * @param  callable $condition
2473
	 * @return void
2474
	 */
2475
	public function allowRowsGroupAction(callable $condition)
2476
	{
2477
		$this->row_conditions['group_action'] = $condition;
2478
	}
2479
2480
2481
	/**
2482
	 * @param  string   $key
2483
	 * @param  callable $condition
2484
	 * @return void
2485
	 */
2486
	public function allowRowsAction($key, callable $condition)
2487
	{
2488
		$this->row_conditions['action'][$key] = $condition;
2489
	}
2490
2491
2492
	/**
2493
	 * @param  string      $name
2494
	 * @param  string|null $key
2495
	 * @return bool|callable
2496
	 */
2497
	public function getRowCondition($name, $key = NULL)
2498
	{
2499
		if (!isset($this->row_conditions[$name])) {
2500
			return FALSE;
2501
		}
2502
2503
		$condition = $this->row_conditions[$name];
2504
2505
		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...
2506
			return $condition;
2507
		}
2508
2509
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2510
	}
2511
2512
2513
	/********************************************************************************
2514
	 *                               COLUMN CALLBACK                                *
2515
	 ********************************************************************************/
2516
2517
2518
	/**
2519
	 * @param  string   $key
2520
	 * @param  callable $callback
2521
	 * @return void
2522
	 */
2523
	public function addColumnCallback($key, callable $callback)
2524
	{
2525
		$this->column_callbacks[$key] = $callback;
2526
	}
2527
2528
2529
	/**
2530
	 * @param  string $key
2531
	 * @return callable|null
2532
	 */
2533
	public function getColumnCallback($key)
2534
	{
2535
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2536
	}
2537
2538
2539
	/********************************************************************************
2540
	 *                                 INLINE EDIT                                  *
2541
	 ********************************************************************************/
2542
2543
2544
	/**
2545
	 * @return InlineEdit
2546
	 */
2547
	public function addInlineEdit($primary_where_column = NULL)
2548
	{
2549
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2550
2551
		return $this->inlineEdit;
2552
	}
2553
2554
2555
	/**
2556
	 * @return InlineEdit|null
2557
	 */
2558
	public function getInlineEdit()
2559
	{
2560
		return $this->inlineEdit;
2561
	}
2562
2563
2564
	/**
2565
	 * @param  mixed $id
2566
	 * @return void
2567
	 */
2568
	public function handleInlineEdit($id)
2569
	{
2570
		if ($this->inlineEdit) {
2571
			$this->inlineEdit->setItemId($id);
2572
2573
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2574
2575
			$this['filter']['inline_edit']->addHidden('_id', $id);
2576
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2577
2578
			$this->redrawItem($id, $primary_where_column);
2579
		}
2580
	}
2581
2582
2583
	/********************************************************************************
2584
	 *                                  INLINE ADD                                  *
2585
	 ********************************************************************************/
2586
2587
2588
	/**
2589
	 * @return InlineEdit
2590
	 */
2591
	public function addInlineAdd()
2592
	{
2593
		$this->inlineAdd = new InlineEdit($this);
2594
2595
		$this->inlineAdd
2596
			->setIcon('plus')
2597
			->setClass('btn btn-xs btn-default');
2598
2599
		return $this->inlineAdd;
2600
	}
2601
2602
2603
	/**
2604
	 * @return InlineEdit|null
2605
	 */
2606
	public function getInlineAdd()
2607
	{
2608
		return $this->inlineAdd;
2609
	}
2610
2611
2612
	/********************************************************************************
2613
	 *                               HIDEABLE COLUMNS                               *
2614
	 ********************************************************************************/
2615
2616
2617
	/**
2618
	 * Can datagrid hide colums?
2619
	 * @return boolean
2620
	 */
2621
	public function canHideColumns()
2622
	{
2623
		return (bool) $this->can_hide_columns;
2624
	}
2625
2626
2627
	/**
2628
	 * Order Grid to set columns hideable.
2629
	 * @return static
2630
	 */
2631
	public function setColumnsHideable()
2632
	{
2633
		$this->can_hide_columns = TRUE;
2634
2635
		return $this;
2636
	}
2637
2638
2639
	/********************************************************************************
2640
	 *                                COLUMNS SUMMARY                               *
2641
	 ********************************************************************************/
2642
2643
2644
	/**
2645
	 * Will datagrid show summary in the end?
2646
	 * @return bool
2647
	 */
2648
	public function hasColumnsSummary()
2649
	{
2650
		return $this->columnsSummary instanceof ColumnsSummary;
2651
	}
2652
2653
2654
	/**
2655
	 * Set columns to be summarized in the end.
2656
	 * @param  array  $columns
2657
	 * @return ColumnsSummary
2658
	 */
2659
	public function setColumnsSummary(array $columns)
2660
	{
2661
		$this->columnsSummary = new ColumnsSummary($this, $columns);
2662
2663
		return $this->columnsSummary;
2664
	}
2665
2666
2667
	/**
2668
	 * @return ColumnsSummary|NULL
2669
	 */
2670
	public function getColumnsSummary()
2671
	{
2672
		return $this->columnsSummary;
2673
	}
2674
2675
2676
	/********************************************************************************
2677
	 *                                   INTERNAL                                   *
2678
	 ********************************************************************************/
2679
2680
2681
	/**
2682
	 * Get count of columns
2683
	 * @return int
2684
	 */
2685
	public function getColumnsCount()
2686
	{
2687
		$count = sizeof($this->getColumns());
2688
2689
		if (!empty($this->actions)
2690
			|| $this->isSortable()
2691
			|| $this->getItemsDetail()
2692
			|| $this->getInlineEdit()
2693
			|| $this->getInlineAdd()) {
2694
			$count++;
2695
		}
2696
2697
		if ($this->hasGroupActions()) {
2698
			$count++;
2699
		}
2700
2701
		return $count;
2702
	}
2703
2704
2705
	/**
2706
	 * Get primary key of datagrid data source
2707
	 * @return string
2708
	 */
2709
	public function getPrimaryKey()
2710
	{
2711
		return $this->primary_key;
2712
	}
2713
2714
2715
	/**
2716
	 * Get set of set columns
2717
	 * @return Column\IColumn[]
2718
	 */
2719
	public function getColumns()
2720
	{
2721
		if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
2722
			$columns_to_hide = [];
2723
2724
			foreach ($this->columns as $key => $column) {
2725
				if ($column->getDefaultHide()) {
2726
					$columns_to_hide[] = $key;
2727
				}
2728
			}
2729
2730
			if (!empty($columns_to_hide)) {
2731
				$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
2732
				$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2733
			}
2734
		}
2735
2736
		$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
2737
		
2738
		foreach ($hidden_columns as $column) {
2739
			if (!empty($this->columns[$column])) {
2740
				$this->columns_visibility[$column] = [
2741
					'visible' => FALSE,
2742
					'name' => $this->columns[$column]->getName()
2743
				];
2744
2745
				$this->removeColumn($column);
2746
			}
2747
		}
2748
2749
		return $this->columns;
2750
	}
2751
2752
2753
	/**
2754
	 * @return PresenterComponent
2755
	 */
2756
	public function getParent()
2757
	{
2758
		$parent = parent::getParent();
2759
2760
		if (!($parent instanceof PresenterComponent)) {
2761
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2762
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2763
			);
2764
		}
2765
2766
		return $parent;
2767
	}
2768
2769
2770
	/**
2771
	 * Some of datagrid columns is hidden by default
2772
	 * @param bool $default_hide
2773
	 */
2774
	public function setSomeColumnDefaultHide($default_hide)
2775
	{
2776
		$this->some_column_default_hide = $default_hide;
2777
	}
2778
2779
2780
	/**
2781
	 * Are some of columns hidden bydefault?
2782
	 */
2783
	public function hasSomeColumnDefaultHide()
2784
	{
2785
		return $this->some_column_default_hide;
2786
	}
2787
2788
}
2789