Completed
Push — master ( 546114...e895bf )
by Pavel
03:26
created

DataGrid::addToolbarButton()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 6
rs 9.4285
cc 1
eloc 3
nc 1
nop 3
1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid;
10
11
use Nette;
12
use Nette\Application\UI\Link;
13
use Nette\Application\UI\PresenterComponent;
14
use Ublaboo\DataGrid\Utils\ArraysHelper;
15
use Nette\Application\UI\Form;
16
use Ublaboo\DataGrid\Exception\DataGridException;
17
use Ublaboo\DataGrid\Exception\DataGridHasToBeAttachedToPresenterComponentException;
18
use Ublaboo\DataGrid\Utils\Sorting;
19
use Ublaboo\DataGrid\InlineEdit\InlineEdit;
20
use Ublaboo\DataGrid\ColumnsSummary;
21
use Ublaboo\DataGrid\Toolbar\ToolbarButton;
22
23
/**
24
 * @method onRedraw()
25
 * @method onRender()
26
 * @method onColumnAdd()
27
 */
28
class DataGrid extends Nette\Application\UI\Control
29
{
30
31
	/**
32
	 * @var callable[]
33
	 */
34
	public $onRedraw;
35
36
	/**
37
	 * @var callable[]
38
	 */
39
	public $onRender = [];
40
41
	/**
42
	 * @var callable[]
43
	 */
44
	public $onColumnAdd;
45
46
	/**
47
	 * @var string
48
	 */
49
	public static $icon_prefix = 'fa fa-';
50
51
	/**
52
	 * When set to TRUE, datagrid throws an exception
53
	 * 	when tring to get related entity within join and entity does not exist
54
	 * @var bool
55
	 */
56
	public $strict_entity_property = FALSE;
57
58
	/**
59
	 * @var int
60
	 * @persistent
61
	 */
62
	public $page = 1;
63
64
	/**
65
	 * @var int|string
66
	 * @persistent
67
	 */
68
	public $per_page;
69
70
	/**
71
	 * @var array
72
	 * @persistent
73
	 */
74
	public $sort = [];
75
76
	/**
77
	 * @var array
78
	 */
79
	public $default_sort = [];
80
81
	/**
82
	 * @var array
83
	 */
84
	public $default_filter = [];
85
86
	/**
87
	 * @var bool
88
	 */
89
	public $default_filter_use_on_reset = TRUE;
90
91
	/**
92
	 * @var array
93
	 * @persistent
94
	 */
95
	public $filter = [];
96
97
	/**
98
	 * @var callable|null
99
	 */
100
	protected $sort_callback = NULL;
101
102
	/**
103
	 * @var bool
104
	 */
105
	protected $use_happy_components = TRUE;
106
107
	/**
108
	 * @var callable
109
	 */
110
	protected $rowCallback;
111
112
	/**
113
	 * @var array
114
	 */
115
	protected $items_per_page_list;
116
117
	/**
118
	 * @var string
119
	 */
120
	protected $template_file;
121
122
	/**
123
	 * @var Column\IColumn[]
124
	 */
125
	protected $columns = [];
126
127
	/**
128
	 * @var Column\Action[]
129
	 */
130
	protected $actions = [];
131
132
	/**
133
	 * @var GroupAction\GroupActionCollection
134
	 */
135
	protected $group_action_collection;
136
137
	/**
138
	 * @var Filter\Filter[]
139
	 */
140
	protected $filters = [];
141
142
	/**
143
	 * @var Export\Export[]
144
	 */
145
	protected $exports = [];
146
147
	/**
148
	 * @var ToolbarButton[]
149
	 */
150
	protected $toolbar_buttons = [];
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
			 * Walkaround for item snippet - snippet is the <tr> element and its class has to be also updated
415
			 */
416
			if (!empty($this->redraw_item)) {
417
				$this->getPresenter()->payload->_datagrid_redraw_item_class = $row->getControlClass();
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

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

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
419
			}
420
		}
421
422
		if ($hasGroupActionOnRows){
423
			$hasGroupActionOnRows = $this->hasGroupActions();
424
		}
425
426
		if ($this->isTreeView()) {
427
			$this->getTemplate()->add('tree_view_has_children_column', $this->tree_view_has_children_column);
428
		}
429
430
		$this->getTemplate()->add('rows', $rows);
431
432
		$this->getTemplate()->add('columns', $this->getColumns());
433
		$this->getTemplate()->add('actions', $this->actions);
434
		$this->getTemplate()->add('exports', $this->exports);
435
		$this->getTemplate()->add('filters', $this->filters);
436
		$this->getTemplate()->add('toolbar_buttons', $this->toolbar_buttons);
437
438
		$this->getTemplate()->add('filter_active', $this->isFilterActive());
439
		$this->getTemplate()->add('original_template', $this->getOriginalTemplateFile());
440
		//$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...
441
		$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...
442
		$this->getTemplate()->add('items_detail', $this->items_detail);
443
		$this->getTemplate()->add('columns_visibility', $this->columns_visibility);
444
		$this->getTemplate()->add('columnsSummary', $this->columnsSummary);
445
446
		$this->getTemplate()->add('inlineEdit', $this->inlineEdit);
447
		$this->getTemplate()->add('inlineAdd', $this->inlineAdd);
448
449
		$this->getTemplate()->add('hasGroupActionOnRows', $hasGroupActionOnRows);
450
451
		/**
452
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
453
		 */
454
		$this->getTemplate()->add('filter', $this['filter']);
455
456
		/**
457
		 * Set template file and render it
458
		 */
459
		$this->getTemplate()->setFile($this->getTemplateFile());
460
		$this->getTemplate()->render();
461
	}
462
463
464
	/********************************************************************************
465
	 *                                 ROW CALLBACK                                 *
466
	 ********************************************************************************/
467
468
469
	/**
470
	 * Each row can be modified with user callback
471
	 * @param  callable  $callback
472
	 * @return static
473
	 */
474
	public function setRowCallback(callable $callback)
475
	{
476
		$this->rowCallback = $callback;
477
478
		return $this;
479
	}
480
481
482
	/********************************************************************************
483
	 *                                 DATA SOURCE                                  *
484
	 ********************************************************************************/
485
486
487
	/**
488
	 * By default ID, you can change that
489
	 * @param string $primary_key
490
	 * @return static
491
	 */
492
	public function setPrimaryKey($primary_key)
493
	{
494
		if ($this->dataModel instanceof DataModel) {
495
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
496
		}
497
498
		$this->primary_key = $primary_key;
499
500
		return $this;
501
	}
502
503
504
	/**
505
	 * Set Grid data source
506
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
507
	 * @return static
508
	 */
509
	public function setDataSource($source)
510
	{
511
		$this->dataModel = new DataModel($source, $this->primary_key);
512
513
		return $this;
514
	}
515
516
517
	/********************************************************************************
518
	 *                                  TEMPLATING                                  *
519
	 ********************************************************************************/
520
521
522
	/**
523
	 * Set custom template file to render
524
	 * @param string $template_file
525
	 * @return static
526
	 */
527
	public function setTemplateFile($template_file)
528
	{
529
		$this->template_file = $template_file;
530
531
		return $this;
532
	}
533
534
535
	/**
536
	 * Get DataGrid template file
537
	 * @return string
538
	 * @return static
539
	 */
540
	public function getTemplateFile()
541
	{
542
		return $this->template_file ?: $this->getOriginalTemplateFile();
543
	}
544
545
546
	/**
547
	 * Get DataGrid original template file
548
	 * @return string
549
	 */
550
	public function getOriginalTemplateFile()
551
	{
552
		return __DIR__.'/templates/datagrid.latte';
553
	}
554
555
556
	/**
557
	 * Tell datagrid wheteher to use or not happy components
558
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
559
	 * @return void|bool
560
	 */
561
	public function useHappyComponents($use = NULL)
562
	{
563
		if (NULL === $use) {
564
			return $this->use_happy_components;
565
		}
566
567
		$this->use_happy_components = (bool) $use;
568
	}
569
570
571
	/********************************************************************************
572
	 *                                   SORTING                                    *
573
	 ********************************************************************************/
574
575
576
	/**
577
	 * Set default sorting
578
	 * @param array $sort
579
	 * @return static
580
	 */
581
	public function setDefaultSort($sort)
582
	{
583
		if (is_string($sort)) {
584
			$sort = [$sort => 'ASC'];
585
		} else {
586
			$sort = (array) $sort;
587
		}
588
589
		$this->default_sort = $sort;
590
591
		return $this;
592
	}
593
594
595
	/**
596
	 * User may set default sorting, apply it
597
	 * @return void
598
	 */
599
	public function findDefaultSort()
600
	{
601
		if (!empty($this->sort)) {
602
			return;
603
		}
604
605
		if (!empty($this->default_sort)) {
606
			$this->sort = $this->default_sort;
607
		}
608
609
		$this->saveSessionData('_grid_sort', $this->sort);
610
	}
611
612
613
	/**
614
	 * Set grido to be sortable
615
	 * @param bool $sortable
616
	 * @return static
617
	 */
618
	public function setSortable($sortable = TRUE)
619
	{
620
		if ($this->getItemsDetail()) {
621
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
622
		}
623
624
		$this->sortable = (bool) $sortable;
625
626
		return $this;
627
	}
628
629
630
	/**
631
	 * Set sortable handle
632
	 * @param string $handler
633
	 * @return static
634
	 */
635
	public function setSortableHandler($handler = 'sort!')
636
	{
637
		$this->sortable_handler = (string) $handler;
638
639
		return $this;
640
	}
641
642
643
	/**
644
	 * Tell whether DataGrid is sortable
645
	 * @return bool
646
	 */
647
	public function isSortable()
648
	{
649
		return $this->sortable;
650
	}
651
652
	/**
653
	 * Return sortable handle name
654
	 * @return string
655
	 */
656
	public function getSortableHandler()
657
	{
658
		return $this->sortable_handler;
659
	}
660
661
662
	/********************************************************************************
663
	 *                                  TREE VIEW                                   *
664
	 ********************************************************************************/
665
666
667
	/**
668
	 * Is tree view set?
669
	 * @return boolean
670
	 */
671
	public function isTreeView()
672
	{
673
		return (bool) $this->tree_view_children_callback;
674
	}
675
676
677
	/**
678
	 * Setting tree view
679
	 * @param callable $get_children_callback
680
	 * @param string|callable $tree_view_has_children_column
681
	 * @return static
682
	 */
683
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
684
	{
685
		if (!is_callable($get_children_callback)) {
686
			throw new DataGridException(
687
				'Parameters to method DataGrid::setTreeView must be of type callable'
688
			);
689
		}
690
691
		if (is_callable($tree_view_has_children_column)) {
692
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
693
			$tree_view_has_children_column = NULL;
694
		}
695
696
		$this->tree_view_children_callback = $get_children_callback;
697
		$this->tree_view_has_children_column = $tree_view_has_children_column;
698
699
		/**
700
		 * TUrn off pagination
701
		 */
702
		$this->setPagination(FALSE);
703
704
		/**
705
		 * Set tree view template file
706
		 */
707
		if (!$this->template_file) {
708
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
709
		}
710
711
		return $this;
712
	}
713
714
715
	/**
716
	 * Is tree view children callback set?
717
	 * @return boolean
718
	 */
719
	public function hasTreeViewChildrenCallback()
720
	{
721
		return is_callable($this->tree_view_has_children_callback);
722
	}
723
724
725
	/**
726
	 * @param  mixed $item
727
	 * @return boolean
728
	 */
729
	public function treeViewChildrenCallback($item)
730
	{
731
		return call_user_func($this->tree_view_has_children_callback, $item);
732
	}
733
734
735
	/********************************************************************************
736
	 *                                    COLUMNS                                   *
737
	 ********************************************************************************/
738
739
740
	/**
741
	 * Add text column with no other formating
742
	 * @param  string      $key
743
	 * @param  string      $name
744
	 * @param  string|null $column
745
	 * @return Column\ColumnText
746
	 */
747
	public function addColumnText($key, $name, $column = NULL)
748
	{
749
		$this->addColumnCheck($key);
750
		$column = $column ?: $key;
751
752
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
753
	}
754
755
756
	/**
757
	 * Add column with link
758
	 * @param  string      $key
759
	 * @param  string      $name
760
	 * @param  string|null $column
761
	 * @return Column\ColumnLink
762
	 */
763
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
764
	{
765
		$this->addColumnCheck($key);
766
		$column = $column ?: $key;
767
		$href = $href ?: $key;
768
769
		if (NULL === $params) {
770
			$params = [$this->primary_key];
771
		}
772
773
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
774
	}
775
776
777
	/**
778
	 * Add column with possible number formating
779
	 * @param  string      $key
780
	 * @param  string      $name
781
	 * @param  string|null $column
782
	 * @return Column\ColumnNumber
783
	 */
784
	public function addColumnNumber($key, $name, $column = NULL)
785
	{
786
		$this->addColumnCheck($key);
787
		$column = $column ?: $key;
788
789
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
790
	}
791
792
793
	/**
794
	 * Add column with date formating
795
	 * @param  string      $key
796
	 * @param  string      $name
797
	 * @param  string|null $column
798
	 * @return Column\ColumnDateTime
799
	 */
800
	public function addColumnDateTime($key, $name, $column = NULL)
801
	{
802
		$this->addColumnCheck($key);
803
		$column = $column ?: $key;
804
805
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
806
	}
807
808
809
	/**
810
	 * Add column status
811
	 * @param  string      $key
812
	 * @param  string      $name
813
	 * @param  string|null $column
814
	 * @return Column\ColumnStatus
815
	 */
816
	public function addColumnStatus($key, $name, $column = NULL)
817
	{
818
		$this->addColumnCheck($key);
819
		$column = $column ?: $key;
820
821
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
822
	}
823
824
825
	/**
826
	 * @param string $key
827
	 * @param Column\Column $column
828
	 * @return Column\Column
829
	 */
830
	protected function addColumn($key, Column\Column $column)
831
	{
832
		$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...
833
834
		$this->columns_visibility[$key] = [
835
			'visible' => TRUE,
836
			'name' => $column->getName()
837
		];
838
839
		return $this->columns[$key] = $column;
840
	}
841
842
843
	/**
844
	 * Return existing column
845
	 * @param  string $key
846
	 * @return Column\Column
847
	 * @throws DataGridException
848
	 */
849
	public function getColumn($key)
850
	{
851
		if (!isset($this->columns[$key])) {
852
			throw new DataGridException("There is no column at key [$key] defined.");
853
		}
854
855
		return $this->columns[$key];
856
	}
857
858
859
	/**
860
	 * Remove column
861
	 * @param string $key
862
	 * @return void
863
	 */
864
	public function removeColumn($key)
865
	{
866
		unset($this->columns[$key]);
867
	}
868
869
870
	/**
871
	 * Check whether given key already exists in $this->columns
872
	 * @param  string $key
873
	 * @throws DataGridException
874
	 */
875
	protected function addColumnCheck($key)
876
	{
877
		if (isset($this->columns[$key])) {
878
			throw new DataGridException("There is already column at key [$key] defined.");
879
		}
880
	}
881
882
883
	/********************************************************************************
884
	 *                                    ACTIONS                                   *
885
	 ********************************************************************************/
886
887
888
	/**
889
	 * Create action
890
	 * @param string     $key
891
	 * @param string     $name
892
	 * @param string     $href
893
	 * @param array|null $params
894
	 * @return Column\Action
895
	 */
896
	public function addAction($key, $name, $href = NULL, array $params = NULL)
897
	{
898
		$this->addActionCheck($key);
899
		$href = $href ?: $key;
900
901
		if (NULL === $params) {
902
			$params = [$this->primary_key];
903
		}
904
905
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
906
	}
907
908
909
	/**
910
	 * Create action callback
911
	 * @param string     $key
912
	 * @param string     $name
913
	 * @return Column\Action
914
	 */
915
	public function addActionCallback($key, $name, $callback = NULL)
916
	{
917
		$this->addActionCheck($key);
918
		$params = ['__id' => $this->primary_key];
919
920
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
921
922
		if ($callback) {
923
			if (!is_callable($callback)) {
924
				throw new DataGridException('ActionCallback callback has to be callable.');
925
			}
926
927
			$action->onClick[] = $callback;
928
		}
929
930
		return $action;
931
	}
932
933
934
	/**
935
	 * Get existing action
936
	 * @param  string       $key
937
	 * @return Column\Action
938
	 * @throws DataGridException
939
	 */
940
	public function getAction($key)
941
	{
942
		if (!isset($this->actions[$key])) {
943
			throw new DataGridException("There is no action at key [$key] defined.");
944
		}
945
946
		return $this->actions[$key];
947
	}
948
949
950
	/**
951
	 * Remove action
952
	 * @param string $key
953
	 * @return void
954
	 */
955
	public function removeAction($key)
956
	{
957
		unset($this->actions[$key]);
958
	}
959
960
961
	/**
962
	 * Check whether given key already exists in $this->filters
963
	 * @param  string $key
964
	 * @throws DataGridException
965
	 */
966
	protected function addActionCheck($key)
967
	{
968
		if (isset($this->actions[$key])) {
969
			throw new DataGridException("There is already action at key [$key] defined.");
970
		}
971
	}
972
973
974
	/********************************************************************************
975
	 *                                    FILTERS                                   *
976
	 ********************************************************************************/
977
978
979
	/**
980
	 * Add filter fot text search
981
	 * @param string       $key
982
	 * @param string       $name
983
	 * @param array|string $columns
984
	 * @return Filter\FilterText
985
	 * @throws DataGridException
986
	 */
987
	public function addFilterText($key, $name, $columns = NULL)
988
	{
989
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
990
991
		if (!is_array($columns)) {
992
			throw new DataGridException("Filter Text can except only array or string.");
993
		}
994
995
		$this->addFilterCheck($key);
996
997
		return $this->filters[$key] = new Filter\FilterText($key, $name, $columns);
998
	}
999
1000
1001
	/**
1002
	 * Add select box filter
1003
	 * @param string $key
1004
	 * @param string $name
1005
	 * @param array  $options
1006
	 * @param string $column
1007
	 * @return Filter\FilterSelect
1008
	 * @throws DataGridException
1009
	 */
1010 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...
1011
	{
1012
		$column = $column ?: $key;
1013
1014
		if (!is_string($column)) {
1015
			throw new DataGridException("Filter Select can only filter in one column.");
1016
		}
1017
1018
		$this->addFilterCheck($key);
1019
1020
		return $this->filters[$key] = new Filter\FilterSelect($key, $name, $options, $column);
1021
	}
1022
1023
1024
	/**
1025
	 * Add multi select box filter
1026
	 * @param string $key
1027
	 * @param string $name
1028
	 * @param array  $options
1029
	 * @param string $column
1030
	 * @return Filter\FilterSelect
1031
	 * @throws DataGridException
1032
	 */
1033
	public function addFilterMultiSelect($key, $name, array $options, $column = NULL)
1034
	{
1035
		$column = $column ?: $key;
1036
1037
		if (!is_string($column)) {
1038
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1039
		}
1040
1041
		$this->addFilterCheck($key);
1042
1043
		return $this->filters[$key] = new Filter\FilterMultiSelect($key, $name, $options, $column);
1044
	}
1045
1046
1047
	/**
1048
	 * Add datepicker filter
1049
	 * @param string $key
1050
	 * @param string $name
1051
	 * @param string $column
1052
	 * @return Filter\FilterDate
1053
	 * @throws DataGridException
1054
	 */
1055
	public function addFilterDate($key, $name, $column = NULL)
1056
	{
1057
		$column = $column ?: $key;
1058
1059
		if (!is_string($column)) {
1060
			throw new DataGridException("FilterDate can only filter in one column.");
1061
		}
1062
1063
		$this->addFilterCheck($key);
1064
1065
		return $this->filters[$key] = new Filter\FilterDate($key, $name, $column);
1066
	}
1067
1068
1069
	/**
1070
	 * Add range filter (from - to)
1071
	 * @param string $key
1072
	 * @param string $name
1073
	 * @param string $column
1074
	 * @return Filter\FilterRange
1075
	 * @throws DataGridException
1076
	 */
1077 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...
1078
	{
1079
		$column = $column ?: $key;
1080
1081
		if (!is_string($column)) {
1082
			throw new DataGridException("FilterRange can only filter in one column.");
1083
		}
1084
1085
		$this->addFilterCheck($key);
1086
1087
		return $this->filters[$key] = new Filter\FilterRange($key, $name, $column, $name_second);
1088
	}
1089
1090
1091
	/**
1092
	 * Add datepicker filter (from - to)
1093
	 * @param string $key
1094
	 * @param string $name
1095
	 * @param string $column
1096
	 * @return Filter\FilterDateRange
1097
	 * @throws DataGridException
1098
	 */
1099 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...
1100
	{
1101
		$column = $column ?: $key;
1102
1103
		if (!is_string($column)) {
1104
			throw new DataGridException("FilterDateRange can only filter in one column.");
1105
		}
1106
1107
		$this->addFilterCheck($key);
1108
1109
		return $this->filters[$key] = new Filter\FilterDateRange($key, $name, $column, $name_second);
1110
	}
1111
1112
1113
	/**
1114
	 * Check whether given key already exists in $this->filters
1115
	 * @param  string $key
1116
	 * @throws DataGridException
1117
	 */
1118
	protected function addFilterCheck($key)
1119
	{
1120
		if (isset($this->filters[$key])) {
1121
			throw new DataGridException("There is already action at key [$key] defined.");
1122
		}
1123
	}
1124
1125
1126
	/**
1127
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1128
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1129
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1130
	 */
1131
	public function assableFilters()
1132
	{
1133
		foreach ($this->filter as $key => $value) {
1134
			if (!isset($this->filters[$key])) {
1135
				$this->deleteSesssionData($key);
1136
1137
				continue;
1138
			}
1139
1140
			if (is_array($value) || $value instanceof \Traversable) {
1141
				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...
1142
					$this->filters[$key]->setValue($value);
1143
				}
1144
			} else {
1145
				if ($value !== '' && $value !== NULL) {
1146
					$this->filters[$key]->setValue($value);
1147
				}
1148
			}
1149
		}
1150
1151
		foreach ($this->columns as $column) {
1152
			if (isset($this->sort[$column->getSortingColumn()])) {
1153
				$column->setSort($this->sort);
1154
			}
1155
		}
1156
1157
		return $this->filters;
1158
	}
1159
1160
1161
	/**
1162
	 * Remove filter
1163
	 * @param string $key
1164
	 * @return void
1165
	 */
1166
	public function removeFilter($key)
1167
	{
1168
		unset($this->filters[$key]);
1169
	}
1170
1171
1172
	/**
1173
	 * Get defined filter
1174
	 * @param  string $key
1175
	 * @return Filter\Filter
1176
	 */
1177
	public function getFilter($key)
1178
	{
1179
		if (!isset($this->filters[$key])) {
1180
			throw new DataGridException("Filter [{$key}] is not defined");
1181
		}
1182
1183
		return $this->filters[$key];
1184
	}
1185
1186
1187
	/********************************************************************************
1188
	 *                                  FILTERING                                   *
1189
	 ********************************************************************************/
1190
1191
1192
	/**
1193
	 * Is filter active?
1194
	 * @return boolean
1195
	 */
1196
	public function isFilterActive()
1197
	{
1198
		$is_filter = ArraysHelper::testTruthy($this->filter);
1199
1200
		return ($is_filter) || $this->force_filter_active;
1201
	}
1202
1203
1204
	/**
1205
	 * Tell that filter is active from whatever reasons
1206
	 * return static
1207
	 */
1208
	public function setFilterActive()
1209
	{
1210
		$this->force_filter_active = TRUE;
1211
1212
		return $this;
1213
	}
1214
1215
1216
	/**
1217
	 * Set filter values (force - overwrite user data)
1218
	 * @param array $filter
1219
	 * @return static
1220
	 */
1221
	public function setFilter(array $filter)
1222
	{
1223
		$this->filter = $filter;
1224
1225
		$this->saveSessionData('_grid_has_filtered', 1);
1226
1227
		return $this;
1228
	}
1229
1230
1231
	/**
1232
	 * If we want to sent some initial filter
1233
	 * @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...
1234
	 * @param bool  $use_on_reset
1235
	 * @return static
1236
	 */
1237
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1238
	{
1239
		foreach ($default_filter as $key => $value) {
1240
			$filter = $this->getFilter($key);
1241
1242
			if (!$filter) {
1243
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1244
			}
1245
1246
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1247
				throw new DataGridException(
1248
					"Default value of filter [$key] - MultiSelect has to be an array"
1249
				);
1250
			}
1251
1252
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1253
				if (!is_array($value)) {
1254
					throw new DataGridException(
1255
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1256
					);
1257
				}
1258
1259
				$temp = $value;
1260
				unset($temp['from'], $temp['to']);
1261
1262
				if (!empty($temp)) {
1263
					throw new DataGridException(
1264
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1265
					);
1266
				}
1267
			}
1268
		}
1269
1270
		$this->default_filter = $default_filter;
1271
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1272
1273
		return $this;
1274
	}
1275
1276
1277
	/**
1278
	 * User may set default filter, find it
1279
	 * @return void
1280
	 */
1281
	public function findDefaultFilter()
1282
	{
1283
		if (!empty($this->filter)) {
1284
			return;
1285
		}
1286
1287
		if ($this->getSessionData('_grid_has_filtered')) {
1288
			return;
1289
		}
1290
1291
		if (!empty($this->default_filter)) {
1292
			$this->filter = $this->default_filter;
1293
		}
1294
1295
		foreach ($this->filter as $key => $value) {
1296
			$this->saveSessionData($key, $value);
1297
		}
1298
	}
1299
1300
1301
	/**
1302
	 * FilterAndGroupAction form factory
1303
	 * @return Form
1304
	 */
1305
	public function createComponentFilter()
1306
	{
1307
		$form = new Form($this, 'filter');
1308
1309
		$form->setMethod('get');
1310
1311
		$form->setTranslator($this->getTranslator());
1312
1313
		/**
1314
		 * InlineEdit part
1315
		 */
1316
		$inline_edit_container = $form->addContainer('inline_edit');
1317
1318 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...
1319
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1320
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1321
				->setValidationScope(FALSE);
1322
1323
			$this->inlineEdit->onControlAdd($inline_edit_container);
1324
		}
1325
1326
		/**
1327
		 * InlineAdd part
1328
		 */
1329
		$inline_add_container = $form->addContainer('inline_add');
1330
1331 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...
1332
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save');
1333
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1334
				->setValidationScope(FALSE)
1335
				->setAttribute('data-datagrid-cancel-inline-add', TRUE);
1336
1337
			$this->inlineAdd->onControlAdd($inline_add_container);
1338
		}
1339
1340
		/**
1341
		 * ItemDetail form part
1342
		 */
1343
		$items_detail_form = $this->getItemDetailForm();
1344
1345
		if ($items_detail_form instanceof Nette\Forms\Container) {
1346
			$form['items_detail_form'] = $items_detail_form;
1347
		}
1348
1349
		/**
1350
		 * Filter part
1351
		 */
1352
		$filter_container = $form->addContainer('filter');
1353
1354
		foreach ($this->filters as $filter) {
1355
			$filter->addToFormContainer($filter_container);
1356
		}
1357
1358
		/**
1359
		 * Group action part
1360
		 */
1361
		$group_action_container = $form->addContainer('group_action');
1362
1363
		if ($this->hasGroupActions()) {
1364
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1365
		}
1366
1367
		$form->setDefaults(['filter' => $this->filter]);
1368
1369
		/**
1370
		 * Per page part
1371
		 */
1372
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1373
			->setTranslator(NULL);
1374
1375
		if (!$form->isSubmitted()) {
1376
			$form['per_page']->setValue($this->getPerPage());
1377
		}
1378
1379
		$form->addSubmit('per_page_submit', '');
1380
		
1381
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1382
1383
		return $form;
1384
	}
1385
1386
1387
	/**
1388
	 * Set $this->filter values after filter form submitted
1389
	 * @param  Form $form
1390
	 * @return void
1391
	 */
1392
	public function filterSucceeded(Form $form)
1393
	{
1394
		if ($this->snippets_set) {
1395
			return;
1396
		}
1397
1398
		$values = $form->getValues();
1399
1400
		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...
1401
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1402
				return;
1403
			}
1404
		}
1405
1406
		/**
1407
		 * Per page
1408
		 */
1409
		$this->saveSessionData('_grid_per_page', $values->per_page);
1410
		$this->per_page = $values->per_page;
1411
1412
		/**
1413
		 * Inline edit
1414
		 */
1415
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1416
			$edit = $form['inline_edit'];
1417
1418
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1419
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1420
				$primary_where_column = $form->getHttpData(
1421
					Form::DATA_LINE,
1422
					'inline_edit[_primary_where_column]'
1423
				);
1424
1425
				if ($edit['submit']->isSubmittedBy()) {
1426
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1427
					$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...
1428
				} else {
1429
					$this->getPresenter()->payload->_datagrid_inline_edit_cancel = $id;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

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

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1434
				} else {
1435
					$this->redrawItem($id, $primary_where_column);
1436
				}
1437
1438
				return;
1439
			}
1440
		}
1441
1442
		/**
1443
		 * Inline add
1444
		 */
1445
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1446
			$add = $form['inline_add'];
1447
1448
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1449
				if ($add['submit']->isSubmittedBy()) {
1450
					$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...
1451
1452
					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...
1453
						$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...
1454
					}
1455
				}
1456
1457
				return;
1458
			}
1459
		}
1460
1461
		/**
1462
		 * Filter itself
1463
		 */
1464
		$values = $values['filter'];
1465
1466
		foreach ($values as $key => $value) {
1467
			/**
1468
			 * Session stuff
1469
			 */
1470
			$this->saveSessionData($key, $value);
1471
1472
			/**
1473
			 * Other stuff
1474
			 */
1475
			$this->filter[$key] = $value;
1476
		}
1477
1478
		if (!empty($values)) {
1479
			$this->saveSessionData('_grid_has_filtered', 1);
1480
		}
1481
1482
		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...
1483
			$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...
1484
1485
			foreach ($this->columns as $key => $column) {
1486
				if ($column->isSortable()) {
1487
					$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...
1488
						'sort' => $column->getSortNext()
1489
					]);
1490
				}
1491
			}
1492
		}
1493
1494
		$this->reload();
1495
	}
1496
1497
1498
	/**
1499
	 * Should be datagrid filters rendered separately?
1500
	 * @param boolean $out
1501
	 * @return static
1502
	 */
1503
	public function setOuterFilterRendering($out = TRUE)
1504
	{
1505
		$this->outer_filter_rendering = (bool) $out;
1506
1507
		return $this;
1508
	}
1509
1510
1511
	/**
1512
	 * Are datagrid filters rendered separately?
1513
	 * @return boolean
1514
	 */
1515
	public function hasOuterFilterRendering()
1516
	{
1517
		return $this->outer_filter_rendering;
1518
	}
1519
1520
1521
	/**
1522
	 * Try to restore session stuff
1523
	 * @return void
1524
	 */
1525
	public function findSessionValues()
1526
	{
1527
		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...
1528
			return;
1529
		}
1530
1531
		if (!$this->remember_state) {
1532
			return;
1533
		}
1534
1535
		if ($page = $this->getSessionData('_grid_page')) {
1536
			$this->page = $page;
1537
		}
1538
1539
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1540
			$this->per_page = $per_page;
1541
		}
1542
1543
		if ($sort = $this->getSessionData('_grid_sort')) {
1544
			$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...
1545
		}
1546
1547
		foreach ($this->getSessionData() as $key => $value) {
1548
			$other_session_keys = [
1549
				'_grid_per_page',
1550
				'_grid_sort',
1551
				'_grid_page',
1552
				'_grid_has_filtered',
1553
				'_grid_hidden_columns',
1554
				'_grid_hidden_columns_manipulated'
1555
			];
1556
1557
			if (!in_array($key, $other_session_keys)) {
1558
				$this->filter[$key] = $value;
1559
			}
1560
		}
1561
1562
		/**
1563
		 * When column is sorted via custom callback, apply it
1564
		 */
1565
		if (empty($this->sort_callback) && !empty($this->sort)) {
1566
			foreach ($this->sort as $key => $order) {
1567
				$column = $this->getColumn($key);
1568
1569
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1570
					$this->sort_callback = $column->getSortableCallback();
1571
				}
1572
			}
1573
		}
1574
	}
1575
1576
1577
	/********************************************************************************
1578
	 *                                    EXPORTS                                   *
1579
	 ********************************************************************************/
1580
1581
1582
	/**
1583
	 * Add export of type callback
1584
	 * @param string $text
1585
	 * @param callable $callback
1586
	 * @param boolean $filtered
1587
	 * @return Export\Export
1588
	 */
1589
	public function addExportCallback($text, $callback, $filtered = FALSE)
1590
	{
1591
		if (!is_callable($callback)) {
1592
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1593
		}
1594
1595
		return $this->addToExports(new Export\Export($this, $text, $callback, $filtered));
1596
	}
1597
1598
1599
	/**
1600
	 * Add already implemented csv export
1601
	 * @param string $text
1602
	 * @param string $csv_file_name
1603
	 * @param string|null $output_encoding
1604
	 * @param string|null $delimiter
1605
	 * @param bool $include_bom
1606
	 * @return Export\Export
1607
	 */
1608 View Code Duplication
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL, $include_bom = FALSE)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1609
	{
1610
		return $this->addToExports(new Export\ExportCsv(
1611
			$this,
1612
			$text,
1613
			$csv_file_name,
1614
			FALSE,
1615
			$output_encoding,
1616
			$delimiter,
1617
			$include_bom
1618
		));
1619
	}
1620
1621
1622
	/**
1623
	 * Add already implemented csv export, but for filtered data
1624
	 * @param string $text
1625
	 * @param string $csv_file_name
1626
	 * @param string|null $output_encoding
1627
	 * @param string|null $delimiter
1628
	 * @param bool $include_bom
1629
	 * @return Export\Export
1630
	 */
1631 View Code Duplication
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL, $include_bom = FALSE)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1632
	{
1633
		return $this->addToExports(new Export\ExportCsv(
1634
			$this,
1635
			$text,
1636
			$csv_file_name,
1637
			TRUE,
1638
			$output_encoding,
1639
			$delimiter,
1640
			$include_bom
1641
		));
1642
	}
1643
1644
1645
	/**
1646
	 * Add export to array
1647
	 * @param Export\Export $export
1648
	 * @return Export\Export
1649
	 */
1650
	protected function addToExports(Export\Export $export)
1651
	{
1652
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1653
1654
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1655
1656
		return $this->exports[$id] = $export;
1657
	}
1658
1659
1660
	public function resetExportsLinks()
1661
	{
1662
		foreach ($this->exports as $id => $export) {
1663
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1664
		}
1665
	}
1666
1667
1668
	/********************************************************************************
1669
	 *                                TOOLBAR BUTTONS                               *
1670
	 ********************************************************************************/
1671
1672
1673
	/**
1674
	 * Add toolbar button
1675
	 * @param string $href
1676
	 * @param string $text
1677
	 * @param array  $params
1678
	 * @return ToolbarButton
1679
	 */
1680
	public function addToolbarButton($href, $text = '', $params = [])
1681
	{
1682
		$button = new ToolbarButton($this, $href, $text, $params);
1683
1684
		return $this->toolbar_buttons[] = $button;
1685
	}
1686
1687
1688
	/********************************************************************************
1689
	 *                                 GROUP ACTIONS                                *
1690
	 ********************************************************************************/
1691
1692
1693
	/**
1694
	 * Alias for add group select action
1695
	 * @param string $title
1696
	 * @param array  $options
1697
	 * @return GroupAction\GroupAction
1698
	 */
1699
	public function addGroupAction($title, $options = [])
1700
	{
1701
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1702
	}
1703
1704
	/**
1705
	 * Add group action (select box)
1706
	 * @param string $title
1707
	 * @param array  $options
1708
	 * @return GroupAction\GroupAction
1709
	 */
1710
	public function addGroupSelectAction($title, $options = [])
1711
	{
1712
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1713
	}
1714
1715
	/**
1716
	 * Add group action (text input)
1717
	 * @param string $title
1718
	 * @return GroupAction\GroupAction
1719
	 */
1720
	public function addGroupTextAction($title)
1721
	{
1722
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1723
	}
1724
1725
	/**
1726
	 * Get collection of all group actions
1727
	 * @return GroupAction\GroupActionCollection
1728
	 */
1729
	public function getGroupActionCollection()
1730
	{
1731
		if (!$this->group_action_collection) {
1732
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
1733
		}
1734
1735
		return $this->group_action_collection;
1736
	}
1737
1738
1739
	/**
1740
	 * Has datagrid some group actions?
1741
	 * @return boolean
1742
	 */
1743
	public function hasGroupActions()
1744
	{
1745
		return (bool) $this->group_action_collection;
1746
	}
1747
1748
1749
	/********************************************************************************
1750
	 *                                   HANDLERS                                   *
1751
	 ********************************************************************************/
1752
1753
1754
	/**
1755
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1756
	 * @param  int  $page
1757
	 * @return void
1758
	 */
1759
	public function handlePage($page)
1760
	{
1761
		/**
1762
		 * Session stuff
1763
		 */
1764
		$this->page = $page;
1765
		$this->saveSessionData('_grid_page', $page);
1766
1767
		$this->reload(['table']);
1768
	}
1769
1770
1771
	/**
1772
	 * Handler for sorting
1773
	 * @param array $sort
1774
	 * @return void
1775
	 */
1776
	public function handleSort(array $sort)
1777
	{
1778
		$new_sort = [];
1779
1780
		/**
1781
		 * Find apropirate column
1782
		 */
1783
		foreach ($sort as $key => $value) {
1784
			if (empty($this->columns[$key])) {
1785
				throw new DataGridException("Column <$key> not found");
1786
			}
1787
1788
			$column = $this->columns[$key];
1789
			$new_sort = [$column->getSortingColumn() => $value];
1790
1791
			/**
1792
			 * Pagination may be reseted after sorting
1793
			 */
1794
			if ($column->sortableResetPagination()) {
1795
				$this->page = 1;
1796
				$this->saveSessionData('_grid_page', 1);
1797
			}
1798
1799
			/**
1800
			 * Custom sorting callback may be applied
1801
			 */
1802
			if ($column->getSortableCallback()) {
1803
				$this->sort_callback = $column->getSortableCallback();
1804
			}
1805
		}
1806
1807
		/**
1808
		 * Session stuff
1809
		 */
1810
		$this->sort = $new_sort;
1811
		$this->saveSessionData('_grid_sort', $this->sort);
1812
1813
		$this->reload(['table']);
1814
	}
1815
1816
1817
	/**
1818
	 * handler for reseting the filter
1819
	 * @return void
1820
	 */
1821
	public function handleResetFilter()
1822
	{
1823
		/**
1824
		 * Session stuff
1825
		 */
1826
		$this->deleteSesssionData('_grid_page');
1827
1828
		if ($this->default_filter_use_on_reset) {
1829
			$this->deleteSesssionData('_grid_has_filtered');
1830
		}
1831
1832
		foreach ($this->getSessionData() as $key => $value) {
1833
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page', '_grid_has_filtered'])) {
1834
				$this->deleteSesssionData($key);
1835
			}
1836
		}
1837
1838
		$this->filter = [];
1839
1840
		$this->reload(['grid']);
1841
	}
1842
1843
1844
	/**
1845
	 * Handler for export
1846
	 * @param  int $id Key for particular export class in array $this->exports
1847
	 * @return void
1848
	 */
1849
	public function handleExport($id)
1850
	{
1851
		if (!isset($this->exports[$id])) {
1852
			throw new Nette\Application\ForbiddenRequestException;
1853
		}
1854
1855
		if (!empty($this->columns_export_order)) {
1856
			$this->setColumnsOrder($this->columns_export_order);
1857
		}
1858
1859
		$export = $this->exports[$id];
1860
1861
		if ($export->isFiltered()) {
1862
			$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...
1863
			$filter    = $this->assableFilters();
1864
		} else {
1865
			$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...
1866
			$filter    = [];
1867
		}
1868
1869
		if (NULL === $this->dataModel) {
1870
			throw new DataGridException('You have to set a data source first.');
1871
		}
1872
1873
		$rows = [];
1874
1875
		$items = Nette\Utils\Callback::invokeArgs(
1876
			[$this->dataModel, 'filterData'], [
1877
				NULL,
1878
				new Sorting($this->sort, $this->sort_callback),
1879
				$filter
1880
			]
1881
		);
1882
1883
		foreach ($items as $item) {
1884
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1885
		}
1886
1887
		if ($export instanceof Export\ExportCsv) {
1888
			$export->invoke($rows);
1889
		} else {
1890
			$export->invoke($items);
1891
		}
1892
1893
		if ($export->isAjax()) {
1894
			$this->reload();
1895
		}
1896
	}
1897
1898
1899
	/**
1900
	 * Handler for getting children of parent item (e.g. category)
1901
	 * @param  int $parent
1902
	 * @return void
1903
	 */
1904
	public function handleGetChildren($parent)
1905
	{
1906
		$this->setDataSource(
1907
			call_user_func($this->tree_view_children_callback, $parent)
1908
		);
1909
1910
		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...
1911
			$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...
1912
			$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...
1913
1914
			$this->redrawControl('items');
1915
1916
			$this->onRedraw();
1917
		} else {
1918
			$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...
1919
		}
1920
	}
1921
1922
1923
	/**
1924
	 * Handler for getting item detail
1925
	 * @param  mixed $id
1926
	 * @return void
1927
	 */
1928
	public function handleGetItemDetail($id)
1929
	{
1930
		$this->getTemplate()->add('toggle_detail', $id);
1931
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1932
1933
		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...
1934
			$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...
1935
			$this->redrawControl('items');
1936
1937
			$this->onRedraw();
1938
		} else {
1939
			$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...
1940
		}
1941
	}
1942
1943
1944
	/**
1945
	 * Handler for inline editing
1946
	 * @param  mixed $id
1947
	 * @param  mixed $key
1948
	 * @return void
1949
	 */
1950
	public function handleEdit($id, $key)
1951
	{
1952
		$column = $this->getColumn($key);
1953
		$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...
1954
1955
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1956
	}
1957
1958
1959
	/**
1960
	 * Redraw $this
1961
	 * @return void
1962
	 */
1963
	public function reload($snippets = [])
1964
	{
1965
		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...
1966
			$this->redrawControl('tbody');
1967
			$this->redrawControl('pagination');
1968
1969
			/**
1970
			 * manualy reset exports links...
1971
			 */
1972
			$this->resetExportsLinks();
1973
			$this->redrawControl('exports');
1974
1975
			foreach ($snippets as $snippet) {
1976
				$this->redrawControl($snippet);
1977
			}
1978
1979
			$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...
1980
1981
			$this->onRedraw();
1982
		} else {
1983
			$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...
1984
		}
1985
	}
1986
1987
1988
	/**
1989
	 * Handler for column status
1990
	 * @param  string $id
1991
	 * @param  string $key
1992
	 * @param  string $value
1993
	 * @return void
1994
	 */
1995
	public function handleChangeStatus($id, $key, $value)
1996
	{
1997
		if (empty($this->columns[$key])) {
1998
			throw new DataGridException("ColumnStatus[$key] does not exist");
1999
		}
2000
2001
		$this->columns[$key]->onChange($id, $value);
2002
	}
2003
2004
2005
	/**
2006
	 * Redraw just one row via ajax
2007
	 * @param  int   $id
2008
	 * @param  mixed $primary_where_column
2009
	 * @return void
2010
	 */
2011
	public function redrawItem($id, $primary_where_column = NULL)
2012
	{
2013
		$this->snippets_set = TRUE;
2014
2015
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
2016
2017
		$this->redrawControl('items');
2018
2019
		$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...
2020
2021
		$this->onRedraw();
2022
	}
2023
2024
2025
	/**
2026
	 * Tell datagrid to display all columns
2027
	 * @return void
2028
	 */
2029
	public function handleShowAllColumns()
2030
	{
2031
		$this->deleteSesssionData('_grid_hidden_columns');
2032
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2033
2034
		$this->redrawControl();
2035
2036
		$this->onRedraw();
2037
	}
2038
2039
2040
	/**
2041
	 * Tell datagrid to display default columns
2042
	 * @return void
2043
	 */
2044
	public function handleShowDefaultColumns()
2045
	{
2046
		$this->deleteSesssionData('_grid_hidden_columns');
2047
		$this->saveSessionData('_grid_hidden_columns_manipulated', FALSE);
2048
2049
		$this->redrawControl();
2050
2051
		$this->onRedraw();
2052
	}
2053
2054
2055
	/**
2056
	 * Reveal particular column
2057
	 * @param  string $column
2058
	 * @return void
2059
	 */
2060 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...
2061
	{
2062
		$columns = $this->getSessionData('_grid_hidden_columns');
2063
2064
		if (!empty($columns)) {
2065
			$pos = array_search($column, $columns);
2066
2067
			if ($pos !== FALSE) {
2068
				unset($columns[$pos]);
2069
			}
2070
		}
2071
2072
		$this->saveSessionData('_grid_hidden_columns', $columns);
2073
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2074
2075
		$this->redrawControl();
2076
2077
		$this->onRedraw();
2078
	}
2079
2080
2081
	/**
2082
	 * Notice datagrid to not display particular columns
2083
	 * @param  string $column
2084
	 * @return void
2085
	 */
2086 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...
2087
	{
2088
		/**
2089
		 * Store info about hiding a column to session
2090
		 */
2091
		$columns = $this->getSessionData('_grid_hidden_columns');
2092
2093
		if (empty($columns)) {
2094
			$columns = [$column];
2095
		} else if (!in_array($column, $columns)) {
2096
			array_push($columns, $column);
2097
		}
2098
2099
		$this->saveSessionData('_grid_hidden_columns', $columns);
2100
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2101
2102
		$this->redrawControl();
2103
2104
		$this->onRedraw();
2105
	}
2106
2107
2108
	public function handleActionCallback($__key, $__id)
2109
	{
2110
		$action = $this->getAction($__key);
2111
2112
		if (!($action instanceof Column\ActionCallback)) {
2113
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
2114
		}
2115
2116
		$action->onClick($__id);
2117
	}
2118
2119
2120
	/********************************************************************************
2121
	 *                                  PAGINATION                                  *
2122
	 ********************************************************************************/
2123
2124
2125
	/**
2126
	 * Set options of select "items_per_page"
2127
	 * @param array $items_per_page_list
2128
	 * @return static
2129
	 */
2130
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
2131
	{
2132
		$this->items_per_page_list = $items_per_page_list;
2133
2134
		if ($include_all) {
2135
			$this->items_per_page_list[] = 'all';
2136
		}
2137
2138
		return $this;
2139
	}
2140
2141
2142
	/**
2143
	 * Paginator factory
2144
	 * @return Components\DataGridPaginator\DataGridPaginator
2145
	 */
2146
	public function createComponentPaginator()
2147
	{
2148
		/**
2149
		 * Init paginator
2150
		 */
2151
		$component = new Components\DataGridPaginator\DataGridPaginator(
2152
			$this->getTranslator(),
2153
			static::$icon_prefix
2154
		);
2155
		$paginator = $component->getPaginator();
2156
2157
		$paginator->setPage($this->page);
2158
		$paginator->setItemsPerPage($this->getPerPage());
2159
2160
		return $component;
2161
	}
2162
2163
2164
	/**
2165
	 * Get parameter per_page
2166
	 * @return int
2167
	 */
2168
	public function getPerPage()
2169
	{
2170
		$items_per_page_list = $this->getItemsPerPageList();
2171
2172
		$per_page = $this->per_page ?: reset($items_per_page_list);
2173
2174
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
2175
			$per_page = reset($items_per_page_list);
2176
		}
2177
2178
		return $per_page;
2179
	}
2180
2181
2182
	/**
2183
	 * Get associative array of items_per_page_list
2184
	 * @return array
2185
	 */
2186
	public function getItemsPerPageList()
2187
	{
2188
		if (empty($this->items_per_page_list)) {
2189
			$this->setItemsPerPageList([10, 20, 50], TRUE);
2190
		}
2191
2192
		$list = array_flip($this->items_per_page_list);
2193
2194
		foreach ($list as $key => $value) {
2195
			$list[$key] = $key;
2196
		}
2197
2198
		if (array_key_exists('all', $list)) {
2199
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2200
		}
2201
2202
		return $list;
2203
	}
2204
2205
2206
	/**
2207
	 * Order Grid to "be paginated"
2208
	 * @param bool $do
2209
	 * @return static
2210
	 */
2211
	public function setPagination($do)
2212
	{
2213
		$this->do_paginate = (bool) $do;
2214
2215
		return $this;
2216
	}
2217
2218
2219
	/**
2220
	 * Tell whether Grid is paginated
2221
	 * @return bool
2222
	 */
2223
	public function isPaginated()
2224
	{
2225
		return $this->do_paginate;
2226
	}
2227
2228
2229
	/**
2230
	 * Return current paginator class
2231
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2232
	 */
2233
	public function getPaginator()
2234
	{
2235
		if ($this->isPaginated() && $this->per_page !== 'all') {
2236
			return $this['paginator'];
2237
		}
2238
2239
		return NULL;
2240
	}
2241
2242
2243
	/********************************************************************************
2244
	 *                                     I18N                                     *
2245
	 ********************************************************************************/
2246
2247
2248
	/**
2249
	 * Set datagrid translator
2250
	 * @param Nette\Localization\ITranslator $translator
2251
	 * @return static
2252
	 */
2253
	public function setTranslator(Nette\Localization\ITranslator $translator)
2254
	{
2255
		$this->translator = $translator;
2256
2257
		return $this;
2258
	}
2259
2260
2261
	/**
2262
	 * Get translator for datagrid
2263
	 * @return Nette\Localization\ITranslator
2264
	 */
2265
	public function getTranslator()
2266
	{
2267
		if (!$this->translator) {
2268
			$this->translator = new Localization\SimpleTranslator;
2269
		}
2270
2271
		return $this->translator;
2272
	}
2273
2274
2275
	/********************************************************************************
2276
	 *                                 COLUMNS ORDER                                *
2277
	 ********************************************************************************/
2278
2279
2280
	/**
2281
	 * Set order of datagrid columns
2282
	 * @param array $order
2283
	 * @return static
2284
	 */
2285
	public function setColumnsOrder($order)
2286
	{
2287
		$new_order = [];
2288
2289
		foreach ($order as $key) {
2290
			if (isset($this->columns[$key])) {
2291
				$new_order[$key] = $this->columns[$key];
2292
			}
2293
		}
2294
2295
		if (sizeof($new_order) === sizeof($this->columns)) {
2296
			$this->columns = $new_order;
2297
		} else {
2298
			throw new DataGridException('When changing columns order, you have to specify all columns');
2299
		}
2300
2301
		return $this;
2302
	}
2303
2304
2305
	/**
2306
	 * Columns order may be different for export and normal grid
2307
	 * @param array $order
2308
	 */
2309
	public function setColumnsExportOrder($order)
2310
	{
2311
		$this->columns_export_order = (array) $order;
2312
	}
2313
2314
2315
	/********************************************************************************
2316
	 *                                SESSION & URL                                 *
2317
	 ********************************************************************************/
2318
2319
2320
	/**
2321
	 * Find some unique session key name
2322
	 * @return string
2323
	 */
2324
	public function getSessionSectionName()
2325
	{
2326
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2327
	}
2328
2329
2330
	/**
2331
	 * Should datagrid remember its filters/pagination/etc using session?
2332
	 * @param bool $remember
2333
	 * @return static
2334
	 */
2335
	public function setRememberState($remember = TRUE)
2336
	{
2337
		$this->remember_state = (bool) $remember;
2338
2339
		return $this;
2340
	}
2341
2342
2343
	/**
2344
	 * Should datagrid refresh url using history API?
2345
	 * @param bool $refresh
2346
	 * @return static
2347
	 */
2348
	public function setRefreshUrl($refresh = TRUE)
2349
	{
2350
		$this->refresh_url = (bool) $refresh;
2351
2352
2353
		return $this;
2354
	}
2355
2356
2357
	/**
2358
	 * Get session data if functionality is enabled
2359
	 * @param  string $key
2360
	 * @return mixed
2361
	 */
2362
	public function getSessionData($key = NULL, $default_value = NULL)
2363
	{
2364
		if (!$this->remember_state) {
2365
			return $key ? $default_value : [];
2366
		}
2367
2368
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2369
	}
2370
2371
2372
	/**
2373
	 * Save session data - just if it is enabled
2374
	 * @param  string $key
2375
	 * @param  mixed  $value
2376
	 * @return void
2377
	 */
2378
	public function saveSessionData($key, $value)
2379
	{
2380
		if ($this->remember_state) {
2381
			$this->grid_session->{$key} = $value;
2382
		}
2383
	}
2384
2385
2386
	/**
2387
	 * Delete session data
2388
	 * @return void
2389
	 */
2390
	public function deleteSesssionData($key)
2391
	{
2392
		unset($this->grid_session->{$key});
2393
	}
2394
2395
2396
	/********************************************************************************
2397
	 *                                  ITEM DETAIL                                 *
2398
	 ********************************************************************************/
2399
2400
2401
	/**
2402
	 * Get items detail parameters
2403
	 * @return array
2404
	 */
2405
	public function getItemsDetail()
2406
	{
2407
		return $this->items_detail;
2408
	}
2409
2410
2411
	/**
2412
	 * Items can have thair detail - toggled
2413
	 * @param mixed $detail callable|string|bool
2414
	 * @param bool|NULL $primary_where_column
2415
	 * @return Column\ItemDetail
2416
	 */
2417
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2418
	{
2419
		if ($this->isSortable()) {
2420
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2421
		}
2422
2423
		$this->items_detail = new Column\ItemDetail(
2424
			$this,
2425
			$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...
2426
		);
2427
2428
		if (is_string($detail)) {
2429
			/**
2430
			 * Item detail will be in separate template
2431
			 */
2432
			$this->items_detail->setType('template');
2433
			$this->items_detail->setTemplate($detail);
2434
2435
		} else if (is_callable($detail)) {
2436
			/**
2437
			 * Item detail will be rendered via custom callback renderer
2438
			 */
2439
			$this->items_detail->setType('renderer');
2440
			$this->items_detail->setRenderer($detail);
2441
2442
		} else if (TRUE === $detail) {
2443
			/**
2444
			 * Item detail will be rendered probably via block #detail
2445
			 */
2446
			$this->items_detail->setType('block');
2447
2448
		} else {
2449
			throw new DataGridException(
2450
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2451
			);
2452
		}
2453
2454
		return $this->items_detail;
2455
	}
2456
2457
2458
	/**
2459
	 * @param callable $callable_set_container 
2460
	 * @return static
2461
	 */
2462
	public function setItemsDetailForm(callable $callable_set_container)
2463
	{
2464
		if ($this->items_detail instanceof Column\ItemDetail) {
2465
			$this->items_detail->setForm(
2466
				new Utils\ItemDetailForm($callable_set_container)
2467
			);
2468
2469
			return $this;
2470
		}
2471
2472
		throw new DataGridException('Please set the ItemDetail first.');
2473
	}
2474
2475
2476
	/**
2477
	 * @return Nette\Forms\Container|NULL
2478
	 */
2479
	public function getItemDetailForm()
2480
	{
2481
		if ($this->items_detail instanceof Column\ItemDetail) {
2482
			return $this->items_detail->getForm();
2483
		}
2484
2485
		return NULL;
2486
	}
2487
2488
2489
	/********************************************************************************
2490
	 *                                ROW PRIVILEGES                                *
2491
	 ********************************************************************************/
2492
2493
2494
	/**
2495
	 * @param  callable $condition
2496
	 * @return void
2497
	 */
2498
	public function allowRowsGroupAction(callable $condition)
2499
	{
2500
		$this->row_conditions['group_action'] = $condition;
2501
	}
2502
2503
2504
	/**
2505
	 * @param  callable $condition
2506
	 * @return void
2507
	 */
2508
	public function allowRowsInlineEdit(callable $condition)
2509
	{
2510
		$this->row_conditions['inline_edit'] = $condition;
2511
	}
2512
2513
2514
	/**
2515
	 * @param  string   $key
2516
	 * @param  callable $condition
2517
	 * @return void
2518
	 */
2519
	public function allowRowsAction($key, callable $condition)
2520
	{
2521
		$this->row_conditions['action'][$key] = $condition;
2522
	}
2523
2524
2525
	/**
2526
	 * @param  string      $name
2527
	 * @param  string|null $key
2528
	 * @return bool|callable
2529
	 */
2530
	public function getRowCondition($name, $key = NULL)
2531
	{
2532
		if (!isset($this->row_conditions[$name])) {
2533
			return FALSE;
2534
		}
2535
2536
		$condition = $this->row_conditions[$name];
2537
2538
		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...
2539
			return $condition;
2540
		}
2541
2542
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2543
	}
2544
2545
2546
	/********************************************************************************
2547
	 *                               COLUMN CALLBACK                                *
2548
	 ********************************************************************************/
2549
2550
2551
	/**
2552
	 * @param  string   $key
2553
	 * @param  callable $callback
2554
	 * @return void
2555
	 */
2556
	public function addColumnCallback($key, callable $callback)
2557
	{
2558
		$this->column_callbacks[$key] = $callback;
2559
	}
2560
2561
2562
	/**
2563
	 * @param  string $key
2564
	 * @return callable|null
2565
	 */
2566
	public function getColumnCallback($key)
2567
	{
2568
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2569
	}
2570
2571
2572
	/********************************************************************************
2573
	 *                                 INLINE EDIT                                  *
2574
	 ********************************************************************************/
2575
2576
2577
	/**
2578
	 * @return InlineEdit
2579
	 */
2580
	public function addInlineEdit($primary_where_column = NULL)
2581
	{
2582
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2583
2584
		return $this->inlineEdit;
2585
	}
2586
2587
2588
	/**
2589
	 * @return InlineEdit|null
2590
	 */
2591
	public function getInlineEdit()
2592
	{
2593
		return $this->inlineEdit;
2594
	}
2595
2596
2597
	/**
2598
	 * @param  mixed $id
2599
	 * @return void
2600
	 */
2601
	public function handleInlineEdit($id)
2602
	{
2603
		if ($this->inlineEdit) {
2604
			$this->inlineEdit->setItemId($id);
2605
2606
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2607
2608
			$this['filter']['inline_edit']->addHidden('_id', $id);
2609
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2610
2611
			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...
2612
				$this->getPresenter()->payload->_datagrid_inline_editing = TRUE;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2613
			}
2614
2615
			$this->redrawItem($id, $primary_where_column);
2616
		}
2617
	}
2618
2619
2620
	/********************************************************************************
2621
	 *                                  INLINE ADD                                  *
2622
	 ********************************************************************************/
2623
2624
2625
	/**
2626
	 * @return InlineEdit
2627
	 */
2628
	public function addInlineAdd()
2629
	{
2630
		$this->inlineAdd = new InlineEdit($this);
2631
2632
		$this->inlineAdd
2633
			->setIcon('plus')
2634
			->setClass('btn btn-xs btn-default');
2635
2636
		return $this->inlineAdd;
2637
	}
2638
2639
2640
	/**
2641
	 * @return InlineEdit|null
2642
	 */
2643
	public function getInlineAdd()
2644
	{
2645
		return $this->inlineAdd;
2646
	}
2647
2648
2649
	/********************************************************************************
2650
	 *                               HIDEABLE COLUMNS                               *
2651
	 ********************************************************************************/
2652
2653
2654
	/**
2655
	 * Can datagrid hide colums?
2656
	 * @return boolean
2657
	 */
2658
	public function canHideColumns()
2659
	{
2660
		return (bool) $this->can_hide_columns;
2661
	}
2662
2663
2664
	/**
2665
	 * Order Grid to set columns hideable.
2666
	 * @return static
2667
	 */
2668
	public function setColumnsHideable()
2669
	{
2670
		$this->can_hide_columns = TRUE;
2671
2672
		return $this;
2673
	}
2674
2675
2676
	/********************************************************************************
2677
	 *                                COLUMNS SUMMARY                               *
2678
	 ********************************************************************************/
2679
2680
2681
	/**
2682
	 * Will datagrid show summary in the end?
2683
	 * @return bool
2684
	 */
2685
	public function hasColumnsSummary()
2686
	{
2687
		return $this->columnsSummary instanceof ColumnsSummary;
2688
	}
2689
2690
2691
	/**
2692
	 * Set columns to be summarized in the end.
2693
	 * @param  array  $columns
2694
	 * @return ColumnsSummary
2695
	 */
2696
	public function setColumnsSummary(array $columns)
2697
	{
2698
		$this->columnsSummary = new ColumnsSummary($this, $columns);
2699
2700
		return $this->columnsSummary;
2701
	}
2702
2703
2704
	/**
2705
	 * @return ColumnsSummary|NULL
2706
	 */
2707
	public function getColumnsSummary()
2708
	{
2709
		return $this->columnsSummary;
2710
	}
2711
2712
2713
	/********************************************************************************
2714
	 *                                   INTERNAL                                   *
2715
	 ********************************************************************************/
2716
2717
2718
	/**
2719
	 * Get count of columns
2720
	 * @return int
2721
	 */
2722
	public function getColumnsCount()
2723
	{
2724
		$count = sizeof($this->getColumns());
2725
2726
		if (!empty($this->actions)
2727
			|| $this->isSortable()
2728
			|| $this->getItemsDetail()
2729
			|| $this->getInlineEdit()
2730
			|| $this->getInlineAdd()) {
2731
			$count++;
2732
		}
2733
2734
		if ($this->hasGroupActions()) {
2735
			$count++;
2736
		}
2737
2738
		return $count;
2739
	}
2740
2741
2742
	/**
2743
	 * Get primary key of datagrid data source
2744
	 * @return string
2745
	 */
2746
	public function getPrimaryKey()
2747
	{
2748
		return $this->primary_key;
2749
	}
2750
2751
2752
	/**
2753
	 * Get set of set columns
2754
	 * @return Column\IColumn[]
2755
	 */
2756
	public function getColumns()
2757
	{
2758
		if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
2759
			$columns_to_hide = [];
2760
2761
			foreach ($this->columns as $key => $column) {
2762
				if ($column->getDefaultHide()) {
2763
					$columns_to_hide[] = $key;
2764
				}
2765
			}
2766
2767
			if (!empty($columns_to_hide)) {
2768
				$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
2769
				$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2770
			}
2771
		}
2772
2773
		$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
2774
		
2775
		foreach ($hidden_columns as $column) {
2776
			if (!empty($this->columns[$column])) {
2777
				$this->columns_visibility[$column] = [
2778
					'visible' => FALSE,
2779
					'name' => $this->columns[$column]->getName()
2780
				];
2781
2782
				$this->removeColumn($column);
2783
			}
2784
		}
2785
2786
		return $this->columns;
2787
	}
2788
2789
2790
	/**
2791
	 * @return PresenterComponent
2792
	 */
2793
	public function getParent()
2794
	{
2795
		$parent = parent::getParent();
2796
2797
		if (!($parent instanceof PresenterComponent)) {
2798
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2799
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2800
			);
2801
		}
2802
2803
		return $parent;
2804
	}
2805
2806
2807
	/**
2808
	 * Some of datagrid columns is hidden by default
2809
	 * @param bool $default_hide
2810
	 */
2811
	public function setSomeColumnDefaultHide($default_hide)
2812
	{
2813
		$this->some_column_default_hide = $default_hide;
2814
	}
2815
2816
2817
	/**
2818
	 * Are some of columns hidden bydefault?
2819
	 */
2820
	public function hasSomeColumnDefaultHide()
2821
	{
2822
		return $this->some_column_default_hide;
2823
	}
2824
2825
}
2826