Completed
Push — master ( 968087...038853 )
by Pavel
03:16
created

DataGrid::getInlineAdd()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid;
10
11
use Nette;
12
use Nette\Application\UI\PresenterComponent;
13
use Ublaboo\DataGrid\Utils\ArraysHelper;
14
use Nette\Application\UI\Form;
15
use Ublaboo\DataGrid\Exception\DataGridException;
16
use Ublaboo\DataGrid\Exception\DataGridHasToBeAttachedToPresenterComponentException;
17
use Ublaboo\DataGrid\Utils\Sorting;
18
use Ublaboo\DataGrid\InlineEdit\InlineEdit;
19
20
/**
21
 * @method onRedraw()
22
 * @method onRender()
23
 * @method onColumnAdd()
24
 */
25
class DataGrid extends Nette\Application\UI\Control
26
{
27
28
	/**
29
	 * @var callable[]
30
	 */
31
	public $onRedraw;
32
33
	/**
34
	 * @var callable[]
35
	 */
36
	public $onRender = [];
37
38
	/**
39
	 * @var callable[]
40
	 */
41
	public $onColumnAdd;
42
43
	/**
44
	 * @var string
45
	 */
46
	public static $icon_prefix = 'fa fa-';
47
48
	/**
49
	 * When set to TRUE, datagrid throws an exception
50
	 * 	when tring to get related entity within join and entity does not exist
51
	 * @var bool
52
	 */
53
	public $strict_entity_property = FALSE;
54
55
	/**
56
	 * @var int
57
	 * @persistent
58
	 */
59
	public $page = 1;
60
61
	/**
62
	 * @var int|string
63
	 * @persistent
64
	 */
65
	public $per_page;
66
67
	/**
68
	 * @var array
69
	 * @persistent
70
	 */
71
	public $sort = [];
72
73
	/**
74
	 * @var array
75
	 */
76
	public $default_sort = [];
77
78
	/**
79
	 * @var array
80
	 * @persistent
81
	 */
82
	public $filter = [];
83
84
	/**
85
	 * @var callable|null
86
	 */
87
	protected $sort_callback = NULL;
88
89
	/**
90
	 * @var bool
91
	 */
92
	protected $use_happy_components = TRUE;
93
94
	/**
95
	 * @var callable
96
	 */
97
	protected $rowCallback;
98
99
	/**
100
	 * @var array
101
	 */
102
	protected $items_per_page_list;
103
104
	/**
105
	 * @var string
106
	 */
107
	protected $template_file;
108
109
	/**
110
	 * @var Column\IColumn[]
111
	 */
112
	protected $columns = [];
113
114
	/**
115
	 * @var Column\Action[]
116
	 */
117
	protected $actions = [];
118
119
	/**
120
	 * @var GroupAction\GroupActionCollection
121
	 */
122
	protected $group_action_collection;
123
124
	/**
125
	 * @var Filter\Filter[]
126
	 */
127
	protected $filters = [];
128
129
	/**
130
	 * @var Export\Export[]
131
	 */
132
	protected $exports = [];
133
134
	/**
135
	 * @var DataModel
136
	 */
137
	protected $dataModel;
138
139
	/**
140
	 * @var DataFilter
141
	 */
142
	protected $dataFilter;
143
144
	/**
145
	 * @var string
146
	 */
147
	protected $primary_key = 'id';
148
149
	/**
150
	 * @var bool
151
	 */
152
	protected $do_paginate = TRUE;
153
154
	/**
155
	 * @var bool
156
	 */
157
	protected $csv_export = TRUE;
158
159
	/**
160
	 * @var bool
161
	 */
162
	protected $csv_export_filtered = TRUE;
163
164
	/**
165
	 * @var bool
166
	 */
167
	protected $sortable = FALSE;
168
169
	/**
170
	 * @var string
171
	 */
172
	protected $sortable_handler = 'sort!';
173
174
	/**
175
	 * @var string
176
	 */
177
	protected $original_template;
178
179
	/**
180
	 * @var array
181
	 */
182
	protected $redraw_item;
183
184
	/**
185
	 * @var mixed
186
	 */
187
	protected $translator;
188
189
	/**
190
	 * @var bool
191
	 */
192
	protected $force_filter_active;
193
194
	/**
195
	 * @var callable
196
	 */
197
	protected $tree_view_children_callback;
198
199
	/**
200
	 * @var callable
201
	 */
202
	protected $tree_view_has_children_callback;
203
204
	/**
205
	 * @var string
206
	 */
207
	protected $tree_view_has_children_column;
208
209
	/**
210
	 * @var bool
211
	 */
212
	protected $outer_filter_rendering = FALSE;
213
214
	/**
215
	 * @var array
216
	 */
217
	protected $columns_export_order = [];
218
219
	/**
220
	 * @var bool
221
	 */
222
	protected $remember_state = TRUE;
223
224
	/**
225
	 * @var bool
226
	 */
227
	protected $refresh_url = TRUE;
228
229
	/**
230
	 * @var Nette\Http\SessionSection
231
	 */
232
	protected $grid_session;
233
234
	/**
235
	 * @var Column\ItemDetail
236
	 */
237
	protected $items_detail;
238
239
	/**
240
	 * @var array
241
	 */
242
	protected $row_conditions = [
243
		'group_action' => FALSE,
244
		'action' => []
245
	];
246
247
	/**
248
	 * @var array
249
	 */
250
	protected $column_callbacks = [];
251
252
	/**
253
	 * @var bool
254
	 */
255
	protected $can_hide_columns = FALSE;
256
257
	/**
258
	 * @var array
259
	 */
260
	protected $columns_visibility = [];
261
262
	/**
263
	 * @var InlineEdit
264
	 */
265
	protected $inlineEdit;
266
267
	/**
268
	 * @var InlineEdit
269
	 */
270
	protected $inlineAdd;
271
272
	/**
273
	 * @var bool
274
	 */
275
	protected $snippets_set = FALSE;
276
277
278
	/**
279
	 * @param Nette\ComponentModel\IContainer|NULL $parent
280
	 * @param string                               $name
281
	 */
282
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
283
	{
284
		parent::__construct($parent, $name);
285
286
		$this->monitor('Nette\Application\UI\Presenter');
287
288
		/**
289
		 * Try to find previous filters/pagination/sort in session
290
		 */
291
		$this->onRender[] = [$this, 'findSessionFilters'];
292
		$this->onRender[] = [$this, 'findDefaultSort'];
293
	}
294
295
296
	/**
297
	 * {inheritDoc}
298
	 * @return void
299
	 */
300
	public function attached($presenter)
301
	{
302
		parent::attached($presenter);
303
304
		if ($presenter instanceof Nette\Application\UI\Presenter) {
305
			/**
306
			 * Get session
307
			 */
308
			if ($this->remember_state) {
309
				$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...
310
			}
311
		}
312
	}
313
314
315
	/********************************************************************************
316
	 *                                  RENDERING                                   *
317
	 ********************************************************************************/
318
319
320
	/**
321
	 * Render template
322
	 * @return void
323
	 */
324
	public function render()
325
	{
326
		/**
327
		 * Check whether datagrid has set some columns, initiated data source, etc
328
		 */
329
		if (!($this->dataModel instanceof DataModel)) {
330
			throw new DataGridException('You have to set a data source first.');
331
		}
332
333
		if (empty($this->columns)) {
334
			throw new DataGridException('You have to add at least one column.');
335
		}
336
337
		$this->template->setTranslator($this->getTranslator());
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
338
339
		/**
340
		 * Invoke possible events
341
		 */
342
		$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...
343
344
		/**
345
		 * Prepare data for rendering (datagrid may render just one item)
346
		 */
347
		$rows = [];
348
349
		if (!empty($this->redraw_item)) {
350
			$items = $this->dataModel->filterRow($this->redraw_item);
351
		} else {
352
			$items = Nette\Utils\Callback::invokeArgs(
353
				[$this->dataModel, 'filterData'],
354
				[
355
					$this->getPaginator(),
356
					new Sorting($this->sort, $this->sort_callback),
357
					$this->assableFilters()
358
				]
359
			);
360
		}
361
362
		$callback = $this->rowCallback ?: NULL;
363
364
		foreach ($items as $item) {
365
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
366
367
			if ($callback) {
368
				$callback($item, $row->getControl());
369
			}
370
		}
371
372
		if ($this->isTreeView()) {
373
			$this->template->add('tree_view_has_children_column', $this->tree_view_has_children_column);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
374
		}
375
376
		$this->template->add('rows', $rows);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
377
378
		$this->template->add('columns', $this->getColumns());
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
379
		$this->template->add('actions', $this->actions);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
380
		$this->template->add('exports', $this->exports);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
381
		$this->template->add('filters', $this->filters);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
382
383
		$this->template->add('filter_active', $this->isFilterActive());
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
384
		$this->template->add('original_template', $this->getOriginalTemplateFile());
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
385
		$this->template->add('icon_prefix', static::$icon_prefix);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
386
		$this->template->add('items_detail', $this->items_detail);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
387
		$this->template->add('columns_visibility', $this->columns_visibility);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
388
389
		$this->template->add('inlineEdit', $this->inlineEdit);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
390
		$this->template->add('inlineAdd', $this->inlineAdd);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
391
392
		/**
393
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
394
		 */
395
		$this->template->add('filter', $this['filter']);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
396
397
		/**
398
		 * Set template file and render it
399
		 */
400
		$this->template->setFile($this->getTemplateFile());
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
401
		$this->template->render();
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

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

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

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

Loading history...
1239
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1240
1241
					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...
1242
						$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...
1243
					}
1244
				}
1245
1246
				$this->redrawItem($id, $primary_where_column);
1247
1248
				return;
1249
			}
1250
		}
1251
1252
		/**
1253
		 * Inline add
1254
		 */
1255
		$inline_add = $form['inline_add'];
1256
1257
		if (isset($inline_add) && isset($inline_add['submit']) && isset($inline_add['cancel'])) {
1258
			if ($inline_add['submit']->isSubmittedBy() || $inline_add['cancel']->isSubmittedBy()) {
1259 View Code Duplication
				if ($inline_add['submit']->isSubmittedBy()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1260
					$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...
1261
1262
					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...
1263
						$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...
1264
					}
1265
				}
1266
1267
				return;
1268
			}
1269
		}
1270
1271
		/**
1272
		 * Filter itself
1273
		 */
1274
		$values = $values['filter'];
1275
1276
		foreach ($values as $key => $value) {
1277
			/**
1278
			 * Session stuff
1279
			 */
1280
			$this->saveSessionData($key, $value);
1281
1282
			/**
1283
			 * Other stuff
1284
			 */
1285
			$this->filter[$key] = $value;
1286
		}
1287
1288
		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...
1289
			$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...
1290
1291
			foreach ($this->columns as $key => $column) {
1292
				if ($column->isSortable()) {
1293
					$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...
1294
						'sort' => $column->getSortNext()
1295
					]);
1296
				}
1297
			}
1298
		}
1299
1300
		$this->reload();
1301
	}
1302
1303
1304
	/**
1305
	 * Should be datagrid filters rendered separately?
1306
	 * @param boolean $out
1307
	 * @return static
1308
	 */
1309
	public function setOuterFilterRendering($out = TRUE)
1310
	{
1311
		$this->outer_filter_rendering = (bool) $out;
1312
1313
		return $this;
1314
	}
1315
1316
1317
	/**
1318
	 * Are datagrid filters rendered separately?
1319
	 * @return boolean
1320
	 */
1321
	public function hasOuterFilterRendering()
1322
	{
1323
		return $this->outer_filter_rendering;
1324
	}
1325
1326
1327
	/**
1328
	 * Try to restore session stuff
1329
	 * @return void
1330
	 */
1331
	public function findSessionFilters()
1332
	{
1333
		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...
1334
			return;
1335
		}
1336
1337
		if (!$this->remember_state) {
1338
			return;
1339
		}
1340
1341
		if ($page = $this->getSessionData('_grid_page')) {
1342
			$this->page = $page;
1343
		}
1344
1345
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1346
			$this->per_page = $per_page;
1347
		}
1348
1349
		if ($sort = $this->getSessionData('_grid_sort')) {
1350
			$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...
1351
		}
1352
1353
		foreach ($this->getSessionData() as $key => $value) {
1354
			$other_session_keys = [
1355
				'_grid_per_page',
1356
				'_grid_sort',
1357
				'_grid_page',
1358
				'_grid_hidden_columns',
1359
				'_grid_hidden_columns_manipulated'
1360
			];
1361
1362
			if (!in_array($key, $other_session_keys)) {
1363
				$this->filter[$key] = $value;
1364
			}
1365
		}
1366
	}
1367
1368
1369
	/********************************************************************************
1370
	 *                                    EXPORTS                                   *
1371
	 ********************************************************************************/
1372
1373
1374
	/**
1375
	 * Add export of type callback
1376
	 * @param string $text
1377
	 * @param callable $callback
1378
	 * @param boolean $filtered
1379
	 * @return Export\Export
1380
	 */
1381
	public function addExportCallback($text, $callback, $filtered = FALSE)
1382
	{
1383
		if (!is_callable($callback)) {
1384
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1385
		}
1386
1387
		return $this->addToExports(new Export\Export($text, $callback, $filtered));
1388
	}
1389
1390
1391
	/**
1392
	 * Add already implemented csv export
1393
	 * @param string      $text
1394
	 * @param string      $csv_file_name
1395
	 * @param string|null $output_encoding
1396
	 * @param string|null $delimiter
1397
	 * @return Export\Export
1398
	 */
1399
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1400
	{
1401
		return $this->addToExports(new Export\ExportCsv(
1402
			$text,
1403
			$csv_file_name,
1404
			FALSE,
1405
			$output_encoding,
1406
			$delimiter
1407
		));
1408
	}
1409
1410
1411
	/**
1412
	 * Add already implemented csv export, but for filtered data
1413
	 * @param string      $text
1414
	 * @param string      $csv_file_name
1415
	 * @param string|null $output_encoding
1416
	 * @param string|null $delimiter
1417
	 * @return Export\Export
1418
	 */
1419
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1420
	{
1421
		return $this->addToExports(new Export\ExportCsv(
1422
			$text,
1423
			$csv_file_name,
1424
			TRUE,
1425
			$output_encoding,
1426
			$delimiter
1427
		));
1428
	}
1429
1430
1431
	/**
1432
	 * Add export to array
1433
	 * @param Export\Export $export
1434
	 * @return Export\Export
1435
	 */
1436
	protected function addToExports(Export\Export $export)
1437
	{
1438
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1439
1440
		$export->setLink($this->link('export!', ['id' => $id]));
1441
1442
		return $this->exports[$id] = $export;
1443
	}
1444
1445
1446
	public function resetExportsLinks()
1447
	{
1448
		foreach ($this->exports as $id => $export) {
1449
			$export->setLink($this->link('export!', ['id' => $id]));
1450
		}
1451
	}
1452
1453
1454
	/********************************************************************************
1455
	 *                                 GROUP ACTIONS                                *
1456
	 ********************************************************************************/
1457
1458
1459
	/**
1460
	 * Alias for add group select action
1461
	 * @param string $title
1462
	 * @param array  $options
1463
	 * @return GroupAction\GroupAction
1464
	 */
1465
	public function addGroupAction($title, $options = [])
1466
	{
1467
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1468
	}
1469
1470
	/**
1471
	 * Add group action (select box)
1472
	 * @param string $title
1473
	 * @param array  $options
1474
	 * @return GroupAction\GroupAction
1475
	 */
1476
	public function addGroupSelectAction($title, $options = [])
1477
	{
1478
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1479
	}
1480
1481
	/**
1482
	 * Add group action (text input)
1483
	 * @param string $title
1484
	 * @return GroupAction\GroupAction
1485
	 */
1486
	public function addGroupTextAction($title)
1487
	{
1488
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1489
	}
1490
1491
	/**
1492
	 * Get collection of all group actions
1493
	 * @return GroupAction\GroupActionCollection
1494
	 */
1495
	public function getGroupActionCollection()
1496
	{
1497
		if (!$this->group_action_collection) {
1498
			$this->group_action_collection = new GroupAction\GroupActionCollection();
1499
		}
1500
1501
		return $this->group_action_collection;
1502
	}
1503
1504
1505
	/**
1506
	 * Has datagrid some group actions?
1507
	 * @return boolean
1508
	 */
1509
	public function hasGroupActions()
1510
	{
1511
		return (bool) $this->group_action_collection;
1512
	}
1513
1514
1515
	/********************************************************************************
1516
	 *                                   HANDLERS                                   *
1517
	 ********************************************************************************/
1518
1519
1520
	/**
1521
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1522
	 * @param  int  $page
1523
	 * @return void
1524
	 */
1525
	public function handlePage($page)
1526
	{
1527
		/**
1528
		 * Session stuff
1529
		 */
1530
		$this->page = $page;
1531
		$this->saveSessionData('_grid_page', $page);
1532
1533
		$this->reload(['table']);
1534
	}
1535
1536
1537
	/**
1538
	 * Handler for sorting
1539
	 * @param array $sort
1540
	 * @return void
1541
	 */
1542
	public function handleSort(array $sort)
1543
	{
1544
		$new_sort = [];
1545
1546
		/**
1547
		 * Find apropirate column
1548
		 */
1549
		foreach ($sort as $key => $value) {
1550
			if (empty($this->columns[$key])) {
1551
				throw new DataGridException("Column <$key> not found");
1552
			}
1553
1554
			$column = $this->columns[$key];
1555
			$new_sort = [$column->getSortingColumn() => $value];
1556
1557
			/**
1558
			 * Pagination may be reseted after sorting
1559
			 */
1560
			if ($column->sortableResetPagination()) {
1561
				$this->page = 1;
1562
				$this->saveSessionData('_grid_page', 1);
1563
			}
1564
1565
			/**
1566
			 * Custom sorting callback may be applied
1567
			 */
1568
			if ($column->getSortableCallback()) {
1569
				$this->sort_callback = $column->getSortableCallback();
1570
			}
1571
		}
1572
1573
		/**
1574
		 * Session stuff
1575
		 */
1576
		$this->sort = $new_sort;
1577
		$this->saveSessionData('_grid_sort', $this->sort);
1578
1579
		$this->reload(['table']);
1580
	}
1581
1582
1583
	/**
1584
	 * handler for reseting the filter
1585
	 * @return void
1586
	 */
1587
	public function handleResetFilter()
1588
	{
1589
		/**
1590
		 * Session stuff
1591
		 */
1592
		$this->deleteSesssionData('_grid_page');
1593
1594
		foreach ($this->getSessionData() as $key => $value) {
1595
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page'])) {
1596
				$this->deleteSesssionData($key);
1597
			}
1598
		}
1599
1600
		$this->filter = [];
1601
1602
		$this->reload(['grid']);
1603
	}
1604
1605
1606
	/**
1607
	 * Handler for export
1608
	 * @param  int $id Key for particular export class in array $this->exports
1609
	 * @return void
1610
	 */
1611
	public function handleExport($id)
1612
	{
1613
		if (!isset($this->exports[$id])) {
1614
			throw new Nette\Application\ForbiddenRequestException;
1615
		}
1616
1617
		if (!empty($this->columns_export_order)) {
1618
			$this->setColumnsOrder($this->columns_export_order);
1619
		}
1620
1621
		$export = $this->exports[$id];
1622
1623
		if ($export->isFiltered()) {
1624
			$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...
1625
			$filter    = $this->assableFilters();
1626
		} else {
1627
			$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...
1628
			$filter    = [];
1629
		}
1630
1631
		if (NULL === $this->dataModel) {
1632
			throw new DataGridException('You have to set a data source first.');
1633
		}
1634
1635
		$rows = [];
1636
1637
		$items = Nette\Utils\Callback::invokeArgs(
1638
			[$this->dataModel, 'filterData'], [
1639
				NULL,
1640
				new Sorting($this->sort, $this->sort_callback),
1641
				$filter
1642
			]
1643
		);
1644
1645
		foreach ($items as $item) {
1646
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1647
		}
1648
1649
		if ($export instanceof Export\ExportCsv) {
1650
			$export->invoke($rows, $this);
1651
		} else {
1652
			$export->invoke($items, $this);
1653
		}
1654
1655
		if ($export->isAjax()) {
1656
			$this->reload();
1657
		}
1658
	}
1659
1660
1661
	/**
1662
	 * Handler for getting children of parent item (e.g. category)
1663
	 * @param  int $parent
1664
	 * @return void
1665
	 */
1666 View Code Duplication
	public function handleGetChildren($parent)
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...
1667
	{
1668
		$this->setDataSource(
1669
			call_user_func($this->tree_view_children_callback, $parent)
1670
		);
1671
1672
		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...
1673
			$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...
1674
			$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...
1675
1676
			$this->redrawControl('items');
1677
1678
			$this->onRedraw();
1679
		} else {
1680
			$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...
1681
		}
1682
	}
1683
1684
1685
	/**
1686
	 * Handler for getting item detail
1687
	 * @param  mixed $id
1688
	 * @return void
1689
	 */
1690 View Code Duplication
	public function handleGetItemDetail($id)
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...
1691
	{
1692
		$this->template->add('toggle_detail', $id);
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1693
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1694
1695
		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...
1696
			$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...
1697
			$this->redrawControl('items');
1698
1699
			$this->onRedraw();
1700
		} else {
1701
			$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...
1702
		}
1703
	}
1704
1705
1706
	/**
1707
	 * Handler for inline editing
1708
	 * @param  mixed $id
1709
	 * @param  mixed $key
1710
	 * @return void
1711
	 */
1712
	public function handleEdit($id, $key)
1713
	{
1714
		$column = $this->getColumn($key);
1715
		$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...
1716
1717
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1718
	}
1719
1720
1721
	/**
1722
	 * Redraw $this
1723
	 * @return void
1724
	 */
1725
	public function reload($snippets = [])
1726
	{
1727
		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...
1728
			$this->redrawControl('tbody');
1729
			$this->redrawControl('pagination');
1730
1731
			/**
1732
			 * manualy reset exports links...
1733
			 */
1734
			$this->resetExportsLinks();
1735
			$this->redrawControl('exports');
1736
1737
			foreach ($snippets as $snippet) {
1738
				$this->redrawControl($snippet);
1739
			}
1740
1741
			$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...
1742
1743
			$this->onRedraw();
1744
		} else {
1745
			$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...
1746
		}
1747
	}
1748
1749
1750
	/**
1751
	 * Handler for column status
1752
	 * @param  string $id
1753
	 * @param  string $key
1754
	 * @param  string $value
1755
	 * @return void
1756
	 */
1757
	public function handleChangeStatus($id, $key, $value)
1758
	{
1759
		if (empty($this->columns[$key])) {
1760
			throw new DataGridException("ColumnStatus[$key] does not exist");
1761
		}
1762
1763
		$this->columns[$key]->onChange($id, $value);
1764
	}
1765
1766
1767
	/**
1768
	 * Redraw just one row via ajax
1769
	 * @param  int   $id
1770
	 * @param  mixed $primary_where_column
1771
	 * @return void
1772
	 */
1773
	public function redrawItem($id, $primary_where_column = NULL)
1774
	{
1775
		$this->snippets_set = TRUE;
1776
1777
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
1778
1779
		$this->redrawControl('items');
1780
		$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...
1781
1782
		$this->onRedraw();
1783
	}
1784
1785
1786
	/**
1787
	 * Tell datagrid to display all columns
1788
	 * @return void
1789
	 */
1790
	public function handleShowAllColumns()
1791
	{
1792
		$this->deleteSesssionData('_grid_hidden_columns');
1793
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
1794
1795
		$this->redrawControl();
1796
1797
		$this->onRedraw();
1798
	}
1799
1800
1801
	/**
1802
	 * Reveal particular column
1803
	 * @param  string $column
1804
	 * @return void
1805
	 */
1806 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...
1807
	{
1808
		$columns = $this->getSessionData('_grid_hidden_columns');
1809
1810
		if (!empty($columns)) {
1811
			$pos = array_search($column, $columns);
1812
1813
			if ($pos !== FALSE) {
1814
				unset($columns[$pos]);
1815
			}
1816
		}
1817
1818
		$this->saveSessionData('_grid_hidden_columns', $columns);
1819
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
1820
1821
		$this->redrawControl();
1822
1823
		$this->onRedraw();
1824
	}
1825
1826
1827
	/**
1828
	 * Notice datagrid to not display particular columns
1829
	 * @param  string $column
1830
	 * @return void
1831
	 */
1832 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...
1833
	{
1834
		/**
1835
		 * Store info about hiding a column to session
1836
		 */
1837
		$columns = $this->getSessionData('_grid_hidden_columns');
1838
1839
		if (empty($columns)) {
1840
			$columns = [$column];
1841
		} else if (!in_array($column, $columns)) {
1842
			array_push($columns, $column);
1843
		}
1844
1845
		$this->saveSessionData('_grid_hidden_columns', $columns);
1846
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
1847
1848
		$this->redrawControl();
1849
1850
		$this->onRedraw();
1851
	}
1852
1853
1854
	public function handleActionCallback($__key, $__id)
1855
	{
1856
		$action = $this->getAction($__key);
1857
1858
		if (!($action instanceof Column\ActionCallback)) {
1859
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
1860
		}
1861
1862
		$action->onClick($__id);
1863
	}
1864
1865
1866
	/********************************************************************************
1867
	 *                                  PAGINATION                                  *
1868
	 ********************************************************************************/
1869
1870
1871
	/**
1872
	 * Set options of select "items_per_page"
1873
	 * @param array $items_per_page_list
1874
	 * @return static
1875
	 */
1876
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
1877
	{
1878
		$this->items_per_page_list = $items_per_page_list;
1879
1880
		if ($include_all) {
1881
			$this->items_per_page_list[] = 'all';
1882
		}
1883
1884
		return $this;
1885
	}
1886
1887
1888
	/**
1889
	 * Paginator factory
1890
	 * @return Components\DataGridPaginator\DataGridPaginator
1891
	 */
1892
	public function createComponentPaginator()
1893
	{
1894
		/**
1895
		 * Init paginator
1896
		 */
1897
		$component = new Components\DataGridPaginator\DataGridPaginator(
1898
			$this->getTranslator()
1899
		);
1900
		$paginator = $component->getPaginator();
1901
1902
		$paginator->setPage($this->page);
1903
		$paginator->setItemsPerPage($this->getPerPage());
1904
1905
		return $component;
1906
	}
1907
1908
1909
	/**
1910
	 * PerPage form factory
1911
	 * @return Form
1912
	 */
1913
	public function createComponentPerPage()
1914
	{
1915
		$form = new Form;
1916
1917
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1918
			->setValue($this->getPerPage());
1919
1920
		$form->addSubmit('submit', '');
1921
1922
		$saveSessionData = [$this, 'saveSessionData'];
1923
1924
		$form->onSuccess[] = function($form, $values) use ($saveSessionData) {
1925
			/**
1926
			 * Session stuff
1927
			 */
1928
			$saveSessionData('_grid_per_page', $values->per_page);
1929
1930
			/**
1931
			 * Other stuff
1932
			 */
1933
			$this->per_page = $values->per_page;
1934
			$this->reload();
1935
		};
1936
1937
		return $form;
1938
	}
1939
1940
1941
	/**
1942
	 * Get parameter per_page
1943
	 * @return int
1944
	 */
1945
	public function getPerPage()
1946
	{
1947
		$items_per_page_list = $this->getItemsPerPageList();
1948
1949
		$per_page = $this->per_page ?: reset($items_per_page_list);
1950
1951
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
1952
			$per_page = reset($items_per_page_list);
1953
		}
1954
1955
		return $per_page;
1956
	}
1957
1958
1959
	/**
1960
	 * Get associative array of items_per_page_list
1961
	 * @return array
1962
	 */
1963
	public function getItemsPerPageList()
1964
	{
1965
		if (empty($this->items_per_page_list)) {
1966
			$this->setItemsPerPageList([10, 20, 50], TRUE);
1967
		}
1968
1969
		$list = array_flip($this->items_per_page_list);
1970
1971
		foreach ($list as $key => $value) {
1972
			$list[$key] = $key;
1973
		}
1974
1975
		if (array_key_exists('all', $list)) {
1976
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
1977
		}
1978
1979
		return $list;
1980
	}
1981
1982
1983
	/**
1984
	 * Order Grid to "be paginated"
1985
	 * @param bool $do
1986
	 * @return static
1987
	 */
1988
	public function setPagination($do)
1989
	{
1990
		$this->do_paginate = (bool) $do;
1991
1992
		return $this;
1993
	}
1994
1995
1996
	/**
1997
	 * Tell whether Grid is paginated
1998
	 * @return bool
1999
	 */
2000
	public function isPaginated()
2001
	{
2002
		return $this->do_paginate;
2003
	}
2004
2005
2006
	/**
2007
	 * Return current paginator class
2008
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2009
	 */
2010
	public function getPaginator()
2011
	{
2012
		if ($this->isPaginated() && $this->per_page !== 'all') {
2013
			return $this['paginator'];
2014
		}
2015
2016
		return NULL;
2017
	}
2018
2019
2020
	/********************************************************************************
2021
	 *                                     I18N                                     *
2022
	 ********************************************************************************/
2023
2024
2025
	/**
2026
	 * Set datagrid translator
2027
	 * @param Nette\Localization\ITranslator $translator
2028
	 * @return static
2029
	 */
2030
	public function setTranslator(Nette\Localization\ITranslator $translator)
2031
	{
2032
		$this->translator = $translator;
2033
2034
		return $this;
2035
	}
2036
2037
2038
	/**
2039
	 * Get translator for datagrid
2040
	 * @return Nette\Localization\ITranslator
2041
	 */
2042
	public function getTranslator()
2043
	{
2044
		if (!$this->translator) {
2045
			$this->translator = new Localization\SimpleTranslator;
2046
		}
2047
2048
		return $this->translator;
2049
	}
2050
2051
2052
	/********************************************************************************
2053
	 *                                 COLUMNS ORDER                                *
2054
	 ********************************************************************************/
2055
2056
2057
	/**
2058
	 * Set order of datagrid columns
2059
	 * @param array $order
2060
	 * @return static
2061
	 */
2062
	public function setColumnsOrder($order)
2063
	{
2064
		$new_order = [];
2065
2066
		foreach ($order as $key) {
2067
			if (isset($this->columns[$key])) {
2068
				$new_order[$key] = $this->columns[$key];
2069
			}
2070
		}
2071
2072
		if (sizeof($new_order) === sizeof($this->columns)) {
2073
			$this->columns = $new_order;
2074
		} else {
2075
			throw new DataGridException('When changing columns order, you have to specify all columns');
2076
		}
2077
2078
		return $this;
2079
	}
2080
2081
2082
	/**
2083
	 * Columns order may be different for export and normal grid
2084
	 * @param array $order
2085
	 */
2086
	public function setColumnsExportOrder($order)
2087
	{
2088
		$this->columns_export_order = (array) $order;
2089
	}
2090
2091
2092
	/********************************************************************************
2093
	 *                                SESSION & URL                                 *
2094
	 ********************************************************************************/
2095
2096
2097
	/**
2098
	 * Find some unique session key name
2099
	 * @return string
2100
	 */
2101
	public function getSessionSectionName()
2102
	{
2103
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2104
	}
2105
2106
2107
	/**
2108
	 * Should datagrid remember its filters/pagination/etc using session?
2109
	 * @param bool $remember
2110
	 * @return static
2111
	 */
2112
	public function setRememberState($remember = TRUE)
2113
	{
2114
		$this->remember_state = (bool) $remember;
2115
2116
		return $this;
2117
	}
2118
2119
2120
	/**
2121
	 * Should datagrid refresh url using history API?
2122
	 * @param bool $refresh
2123
	 * @return static
2124
	 */
2125
	public function setRefreshUrl($refresh = TRUE)
2126
	{
2127
		$this->refresh_url = (bool) $refresh;
2128
2129
2130
		return $this;
2131
	}
2132
2133
2134
	/**
2135
	 * Get session data if functionality is enabled
2136
	 * @param  string $key
2137
	 * @return mixed
2138
	 */
2139
	public function getSessionData($key = NULL, $default_value = NULL)
2140
	{
2141
		if (!$this->remember_state) {
2142
			return $key ? $default_value : [];
2143
		}
2144
2145
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2146
	}
2147
2148
2149
	/**
2150
	 * Save session data - just if it is enabled
2151
	 * @param  string $key
2152
	 * @param  mixed  $value
2153
	 * @return void
2154
	 */
2155
	public function saveSessionData($key, $value)
2156
	{
2157
		if ($this->remember_state) {
2158
			$this->grid_session->{$key} = $value;
2159
		}
2160
	}
2161
2162
2163
	/**
2164
	 * Delete session data
2165
	 * @return void
2166
	 */
2167
	public function deleteSesssionData($key)
2168
	{
2169
		unset($this->grid_session->{$key});
2170
	}
2171
2172
2173
	/********************************************************************************
2174
	 *                                  ITEM DETAIL                                 *
2175
	 ********************************************************************************/
2176
2177
2178
	/**
2179
	 * Get items detail parameters
2180
	 * @return array
2181
	 */
2182
	public function getItemsDetail()
2183
	{
2184
		return $this->items_detail;
2185
	}
2186
2187
2188
	/**
2189
	 * Items can have thair detail - toggled
2190
	 * @param mixed $detail callable|string|bool
2191
	 * @param bool|NULL $primary_where_column
2192
	 * @return Column\ItemDetail
2193
	 */
2194
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2195
	{
2196
		if ($this->isSortable()) {
2197
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2198
		}
2199
2200
		$this->items_detail = new Column\ItemDetail(
2201
			$this,
2202
			$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...
2203
		);
2204
2205
		if (is_string($detail)) {
2206
			/**
2207
			 * Item detail will be in separate template
2208
			 */
2209
			$this->items_detail->setType('template');
2210
			$this->items_detail->setTemplate($detail);
2211
2212
		} else if (is_callable($detail)) {
2213
			/**
2214
			 * Item detail will be rendered via custom callback renderer
2215
			 */
2216
			$this->items_detail->setType('renderer');
2217
			$this->items_detail->setRenderer($detail);
2218
2219
		} else if (TRUE === $detail) {
2220
			/**
2221
			 * Item detail will be rendered probably via block #detail
2222
			 */
2223
			$this->items_detail->setType('block');
2224
2225
		} else {
2226
			throw new DataGridException(
2227
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2228
			);
2229
		}
2230
2231
		return $this->items_detail;
2232
	}
2233
2234
2235
	/**
2236
	 * @param callable $callable_set_container 
2237
	 * @return static
2238
	 */
2239
	public function setItemsDetailForm(callable $callable_set_container)
2240
	{
2241
		if ($this->items_detail instanceof Column\ItemDetail) {
2242
			$this->items_detail->setForm(
2243
				new Utils\ItemDetailForm($callable_set_container)
2244
			);
2245
2246
			return $this;
2247
		}
2248
2249
		throw new DataGridException('Please set the ItemDetail first.');
2250
	}
2251
2252
2253
	/**
2254
	 * @return Nette\Forms\Container|NULL
2255
	 */
2256
	public function getItemDetailForm()
2257
	{
2258
		if ($this->items_detail instanceof Column\ItemDetail) {
2259
			return $this->items_detail->getForm();
2260
		}
2261
2262
		return NULL;
2263
	}
2264
2265
2266
	/********************************************************************************
2267
	 *                                ROW PRIVILEGES                                *
2268
	 ********************************************************************************/
2269
2270
2271
	/**
2272
	 * @param  callable $condition
2273
	 * @return void
2274
	 */
2275
	public function allowRowsGroupAction(callable $condition)
2276
	{
2277
		$this->row_conditions['group_action'] = $condition;
2278
	}
2279
2280
2281
	/**
2282
	 * @param  string   $key
2283
	 * @param  callable $condition
2284
	 * @return void
2285
	 */
2286
	public function allowRowsAction($key, callable $condition)
2287
	{
2288
		$this->row_conditions['action'][$key] = $condition;
2289
	}
2290
2291
2292
	/**
2293
	 * @param  string      $name
2294
	 * @param  string|null $key
2295
	 * @return bool|callable
2296
	 */
2297
	public function getRowCondition($name, $key = NULL)
2298
	{
2299
		if (!isset($this->row_conditions[$name])) {
2300
			return FALSE;
2301
		}
2302
2303
		$condition = $this->row_conditions[$name];
2304
2305
		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...
2306
			return $condition;
2307
		}
2308
2309
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2310
	}
2311
2312
2313
	/********************************************************************************
2314
	 *                               COLUMN CALLBACK                                *
2315
	 ********************************************************************************/
2316
2317
2318
	/**
2319
	 * @param  string   $key
2320
	 * @param  callable $callback
2321
	 * @return void
2322
	 */
2323
	public function addColumnCallback($key, callable $callback)
2324
	{
2325
		$this->column_callbacks[$key] = $callback;
2326
	}
2327
2328
2329
	/**
2330
	 * @param  string $key
2331
	 * @return callable|null
2332
	 */
2333
	public function getColumnCallback($key)
2334
	{
2335
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2336
	}
2337
2338
2339
	/********************************************************************************
2340
	 *                                 INLINE EDIT                                  *
2341
	 ********************************************************************************/
2342
2343
2344
	/**
2345
	 * @return InlineEdit
2346
	 */
2347
	public function addInlineEdit($primary_where_column = NULL)
2348
	{
2349
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2350
2351
		return $this->inlineEdit;
2352
	}
2353
2354
2355
	/**
2356
	 * @return InlineEdit|null
2357
	 */
2358
	public function getInlineEdit()
2359
	{
2360
		return $this->inlineEdit;
2361
	}
2362
2363
2364
	/**
2365
	 * @param  mixed $id
2366
	 * @return void
2367
	 */
2368
	public function handleInlineEdit($id)
2369
	{
2370
		if ($this->inlineEdit) {
2371
			$this->inlineEdit->setItemId($id);
2372
2373
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2374
2375
			$this['filter']['inline_edit']->addHidden('_id', $id);
2376
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2377
2378
			$this->redrawItem($id, $primary_where_column);
2379
		}
2380
	}
2381
2382
2383
	/********************************************************************************
2384
	 *                                  INLINE ADD                                  *
2385
	 ********************************************************************************/
2386
2387
2388
	/**
2389
	 * @return InlineAdd
2390
	 */
2391
	public function addInlineAdd()
2392
	{
2393
		$this->inlineAdd = new InlineEdit($this);
2394
2395
		$this->inlineAdd
2396
			->setIcon('plus')
2397
			->setClass('btn btn-xs btn-default');
2398
2399
		return $this->inlineAdd;
2400
	}
2401
2402
2403
	/**
2404
	 * @return inlineAdd|null
2405
	 */
2406
	public function getInlineAdd()
2407
	{
2408
		return $this->inlineAdd;
2409
	}
2410
2411
2412
	/********************************************************************************
2413
	 *                               HIDEABLE COLUMNS                               *
2414
	 ********************************************************************************/
2415
2416
2417
	/**
2418
	 * Can datagrid hide colums?
2419
	 * @return boolean
2420
	 */
2421
	public function canHideColumns()
2422
	{
2423
		return (bool) $this->can_hide_columns;
2424
	}
2425
2426
2427
	/**
2428
	 * Order Grid to set columns hideable.
2429
	 * @return static
2430
	 */
2431
	public function setColumnsHideable()
2432
	{
2433
		$this->can_hide_columns = TRUE;
2434
2435
		return $this;
2436
	}
2437
2438
2439
	/********************************************************************************
2440
	 *                                   INTERNAL                                   *
2441
	 ********************************************************************************/
2442
2443
2444
	/**
2445
	 * Get cont of columns
2446
	 * @return int
2447
	 */
2448
	public function getColumnsCount()
2449
	{
2450
		$count = sizeof($this->getColumns());
2451
2452
		if (!empty($this->actions) || $this->isSortable() || $this->getItemsDetail() || $this->getInlineEdit()) {
2453
			$count++;
2454
		}
2455
2456
		if ($this->hasGroupActions()) {
2457
			$count++;
2458
		}
2459
2460
		return $count;
2461
	}
2462
2463
2464
	/**
2465
	 * Get primary key of datagrid data source
2466
	 * @return string
2467
	 */
2468
	public function getPrimaryKey()
2469
	{
2470
		return $this->primary_key;
2471
	}
2472
2473
2474
	/**
2475
	 * Get set of set columns
2476
	 * @return Column\IColumn[]
2477
	 */
2478
	public function getColumns()
2479
	{
2480
		if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
2481
			$columns_to_hide = [];
2482
2483
			foreach ($this->columns as $key => $column) {
2484
				if ($column->getDefaultHide()) {
2485
					$columns_to_hide[] = $key;
2486
				}
2487
			}
2488
2489
			if (!empty($columns_to_hide)) {
2490
				$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
2491
				$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2492
			}
2493
		}
2494
2495
		$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
2496
		
2497
		foreach ($hidden_columns as $column) {
2498
			if (!empty($this->columns[$column])) {
2499
				$this->columns_visibility[$column] = [
2500
					'visible' => FALSE,
2501
					'name' => $this->columns[$column]->getName()
2502
				];
2503
2504
				$this->removeColumn($column);
2505
			}
2506
		}
2507
2508
		return $this->columns;
2509
	}
2510
2511
2512
	/**
2513
	 * @return PresenterComponent
2514
	 */
2515
	public function getParent()
2516
	{
2517
		$parent = parent::getParent();
2518
2519
		if (!($parent instanceof PresenterComponent)) {
2520
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2521
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2522
			);
2523
		}
2524
2525
		return $parent;
2526
	}
2527
2528
}
2529