Completed
Push — master ( 40203f...30bae4 )
by Pavel
02:45
created

DataGrid::addColumn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
315
316
		/**
317
		 * Prepare data for rendering (datagrid may render just one item)
318
		 */
319
		$rows = [];
320
321
		if (!empty($this->redraw_item)) {
322
			$items = $this->dataModel->filterRow($this->redraw_item);
323
		} else {
324
			$items = Nette\Utils\Callback::invokeArgs(
325
				[$this->dataModel, 'filterData'],
326
				[
327
					$this->getPaginator(),
328
					new Sorting($this->sort, $this->sort_callback),
329
					$this->assableFilters()
330
				]
331
			);
332
		}
333
334
		$callback = $this->rowCallback ?: NULL;
335
336
		foreach ($items as $item) {
337
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
338
339
			if ($callback) {
340
				$callback($item, $row->getControl());
341
			}
342
		}
343
344
		if ($this->isTreeView()) {
345
			$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...
346
		}
347
348
		$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...
349
350
		$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...
351
		$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...
352
		$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...
353
		$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...
354
355
		$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...
356
		$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...
357
		$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...
358
		$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...
359
		$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...
360
361
		/**
362
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
363
		 */
364
		$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...
365
366
		/**
367
		 * Set template file and render it
368
		 */
369
		$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...
370
		$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...
371
	}
372
373
374
	/********************************************************************************
375
	 *                                 ROW CALLBACK                                 *
376
	 ********************************************************************************/
377
378
379
	/**
380
	 * Each row can be modified with user callback
381
	 * @param  callable  $callback
382
	 * @return static
383
	 */
384
	public function setRowCallback(callable $callback)
385
	{
386
		$this->rowCallback = $callback;
387
388
		return $this;
389
	}
390
391
392
	/********************************************************************************
393
	 *                                 DATA SOURCE                                  *
394
	 ********************************************************************************/
395
396
397
	/**
398
	 * By default ID, you can change that
399
	 * @param string $primary_key
400
	 * @return static
401
	 */
402
	public function setPrimaryKey($primary_key)
403
	{
404
		if ($this->dataModel instanceof DataModel) {
405
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
406
		}
407
408
		$this->primary_key = $primary_key;
409
410
		return $this;
411
	}
412
413
414
	/**
415
	 * Set Grid data source
416
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
417
	 * @return static
418
	 */
419
	public function setDataSource($source)
420
	{
421
		$this->dataModel = new DataModel($source, $this->primary_key);
422
423
		return $this;
424
	}
425
426
427
	/********************************************************************************
428
	 *                                  TEMPLATING                                  *
429
	 ********************************************************************************/
430
431
432
	/**
433
	 * Set custom template file to render
434
	 * @param string $template_file
435
	 * @return static
436
	 */
437
	public function setTemplateFile($template_file)
438
	{
439
		$this->template_file = $template_file;
440
441
		return $this;
442
	}
443
444
445
	/**
446
	 * Get DataGrid template file
447
	 * @return string
448
	 * @return static
449
	 */
450
	public function getTemplateFile()
451
	{
452
		return $this->template_file ?: $this->getOriginalTemplateFile();
453
	}
454
455
456
	/**
457
	 * Get DataGrid original template file
458
	 * @return string
459
	 */
460
	public function getOriginalTemplateFile()
461
	{
462
		return __DIR__.'/templates/datagrid.latte';
463
	}
464
465
466
	/**
467
	 * Tell datagrid wheteher to use or not happy components
468
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
469
	 * @return void|bool
470
	 */
471
	public function useHappyComponents($use = NULL)
472
	{
473
		if (NULL === $use) {
474
			return $this->use_happy_components;
475
		}
476
477
		$this->use_happy_components = (bool) $use;
478
	}
479
480
481
	/********************************************************************************
482
	 *                                   SORTING                                    *
483
	 ********************************************************************************/
484
485
486
	/**
487
	 * Set default sorting
488
	 * @param array $sort
489
	 * @return static
490
	 */
491
	public function setDefaultSort($sort)
492
	{
493
		if (is_string($sort)) {
494
			$sort = [$sort => 'ASC'];
495
		} else {
496
			$sort = (array) $sort;
497
		}
498
499
		$this->default_sort = $sort;
500
501
		return $this;
502
	}
503
504
505
	/**
506
	 * User may set default sorting, apply it
507
	 * @return void
508
	 */
509
	public function findDefaultSort()
510
	{
511
		if (!empty($this->sort)) {
512
			return;
513
		}
514
515
		if (!empty($this->default_sort)) {
516
			$this->sort = $this->default_sort;
517
		}
518
519
		$this->saveSessionData('_grid_sort', $this->sort);
520
	}
521
522
523
	/**
524
	 * Set grido to be sortable
525
	 * @param bool $sortable
526
	 * @return static
527
	 */
528
	public function setSortable($sortable = TRUE)
529
	{
530
		if ($this->getItemsDetail()) {
531
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
532
		}
533
534
		$this->sortable = (bool) $sortable;
535
536
		return $this;
537
	}
538
539
540
	/**
541
	 * Set sortable handle
542
	 * @param string $handler
543
	 * @return static
544
	 */
545
	public function setSortableHandler($handler = 'sort!')
546
	{
547
		$this->sortable_handler = (string) $handler;
548
549
		return $this;
550
	}
551
552
553
	/**
554
	 * Tell whether DataGrid is sortable
555
	 * @return bool
556
	 */
557
	public function isSortable()
558
	{
559
		return $this->sortable;
560
	}
561
562
	/**
563
	 * Return sortable handle name
564
	 * @return string
565
	 */
566
	public function getSortableHandler()
567
	{
568
		return $this->sortable_handler;
569
	}
570
571
572
	/********************************************************************************
573
	 *                                  TREE VIEW                                   *
574
	 ********************************************************************************/
575
576
577
	/**
578
	 * Is tree view set?
579
	 * @return boolean
580
	 */
581
	public function isTreeView()
582
	{
583
		return (bool) $this->tree_view_children_callback;
584
	}
585
586
587
	/**
588
	 * Setting tree view
589
	 * @param callable $get_children_callback
590
	 * @param string|callable $tree_view_has_children_column
591
	 * @return static
592
	 */
593
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
594
	{
595
		if (!is_callable($get_children_callback)) {
596
			throw new DataGridException(
597
				'Parameters to method DataGrid::setTreeView must be of type callable'
598
			);
599
		}
600
601
		if (is_callable($tree_view_has_children_column)) {
602
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
603
			$tree_view_has_children_column = NULL;
604
		}
605
606
		$this->tree_view_children_callback = $get_children_callback;
607
		$this->tree_view_has_children_column = $tree_view_has_children_column;
608
609
		/**
610
		 * TUrn off pagination
611
		 */
612
		$this->setPagination(FALSE);
613
614
		/**
615
		 * Set tree view template file
616
		 */
617
		if (!$this->template_file) {
618
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
619
		}
620
621
		return $this;
622
	}
623
624
625
	/**
626
	 * Is tree view children callback set?
627
	 * @return boolean
628
	 */
629
	public function hasTreeViewChildrenCallback()
630
	{
631
		return is_callable($this->tree_view_has_children_callback);
632
	}
633
634
635
	/**
636
	 * @param  mixed $item
637
	 * @return boolean
638
	 */
639
	public function treeViewChildrenCallback($item)
640
	{
641
		return call_user_func($this->tree_view_has_children_callback, $item);
642
	}
643
644
645
	/********************************************************************************
646
	 *                                    COLUMNS                                   *
647
	 ********************************************************************************/
648
649
650
	/**
651
	 * Add text column with no other formating
652
	 * @param  string      $key
653
	 * @param  string      $name
654
	 * @param  string|null $column
655
	 * @return Column\ColumnText
656
	 */
657
	public function addColumnText($key, $name, $column = NULL)
658
	{
659
		$this->addColumnCheck($key);
660
		$column = $column ?: $key;
661
662
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
663
	}
664
665
666
	/**
667
	 * Add column with link
668
	 * @param  string      $key
669
	 * @param  string      $name
670
	 * @param  string|null $column
671
	 * @return Column\ColumnLink
672
	 */
673
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
674
	{
675
		$this->addColumnCheck($key);
676
		$column = $column ?: $key;
677
		$href = $href ?: $key;
678
679
		if (NULL === $params) {
680
			$params = [$this->primary_key];
681
		}
682
683
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
684
	}
685
686
687
	/**
688
	 * Add column with possible number formating
689
	 * @param  string      $key
690
	 * @param  string      $name
691
	 * @param  string|null $column
692
	 * @return Column\ColumnNumber
693
	 */
694
	public function addColumnNumber($key, $name, $column = NULL)
695
	{
696
		$this->addColumnCheck($key);
697
		$column = $column ?: $key;
698
699
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
700
	}
701
702
703
	/**
704
	 * Add column with date formating
705
	 * @param  string      $key
706
	 * @param  string      $name
707
	 * @param  string|null $column
708
	 * @return Column\ColumnDateTime
709
	 */
710
	public function addColumnDateTime($key, $name, $column = NULL)
711
	{
712
		$this->addColumnCheck($key);
713
		$column = $column ?: $key;
714
715
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
716
	}
717
718
719
	/**
720
	 * Add column status
721
	 * @param  string      $key
722
	 * @param  string      $name
723
	 * @param  string|null $column
724
	 * @return Column\ColumnStatus
725
	 */
726
	public function addColumnStatus($key, $name, $column = NULL)
727
	{
728
		$this->addColumnCheck($key);
729
		$column = $column ?: $key;
730
731
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
732
	}
733
734
735
	/**
736
	 * @param string $key
737
	 * @param Column\Column $column
738
	 * @return Column\Column
739
	 */
740
	protected function addColumn($key, Column\Column $column)
741
	{
742
		$this->onColumnAdd($key, $column);
0 ignored issues
show
Documentation Bug introduced by
The method onColumnAdd does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
743
744
		$this->columns_visibility[$key] = [
745
			'visible' => TRUE,
746
			'name' => $column->getName()
747
		];
748
749
		return $this->columns[$key] = $column;
750
	}
751
752
753
	/**
754
	 * Return existing column
755
	 * @param  string $key
756
	 * @return Column\Column
757
	 * @throws DataGridException
758
	 */
759
	public function getColumn($key)
760
	{
761
		if (!isset($this->columns[$key])) {
762
			throw new DataGridException("There is no column at key [$key] defined.");
763
		}
764
765
		return $this->columns[$key];
766
	}
767
768
769
	/**
770
	 * Remove column
771
	 * @param string $key
772
	 * @return void
773
	 */
774
	public function removeColumn($key)
775
	{
776
		unset($this->columns[$key]);
777
	}
778
779
780
	/**
781
	 * Check whether given key already exists in $this->columns
782
	 * @param  string $key
783
	 * @throws DataGridException
784
	 */
785
	protected function addColumnCheck($key)
786
	{
787
		if (isset($this->columns[$key])) {
788
			throw new DataGridException("There is already column at key [$key] defined.");
789
		}
790
	}
791
792
793
	/********************************************************************************
794
	 *                                    ACTIONS                                   *
795
	 ********************************************************************************/
796
797
798
	/**
799
	 * Create action
800
	 * @param string     $key
801
	 * @param string     $name
802
	 * @param string     $href
803
	 * @param array|null $params
804
	 * @return Column\Action
805
	 */
806
	public function addAction($key, $name, $href = NULL, array $params = NULL)
807
	{
808
		$this->addActionCheck($key);
809
		$href = $href ?: $key;
810
811
		if (NULL === $params) {
812
			$params = [$this->primary_key];
813
		}
814
815
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
816
	}
817
818
819
	/**
820
	 * Create action callback
821
	 * @param string     $key
822
	 * @param string     $name
823
	 * @return Column\Action
824
	 */
825
	public function addActionCallback($key, $name, $callback = NULL)
826
	{
827
		$this->addActionCheck($key);
828
		$params = ['__id' => $this->primary_key];
829
830
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
831
832
		if ($callback) {
833
			if (!is_callable($callback)) {
834
				throw new DataGridException('ActionCallback callback has to be callable.');
835
			}
836
837
			$action->onClick[] = $callback;
838
		}
839
840
		return $action;
841
	}
842
843
844
	/**
845
	 * Get existing action
846
	 * @param  string       $key
847
	 * @return Column\Action
848
	 * @throws DataGridException
849
	 */
850
	public function getAction($key)
851
	{
852
		if (!isset($this->actions[$key])) {
853
			throw new DataGridException("There is no action at key [$key] defined.");
854
		}
855
856
		return $this->actions[$key];
857
	}
858
859
860
	/**
861
	 * Remove action
862
	 * @param string $key
863
	 * @return void
864
	 */
865
	public function removeAction($key)
866
	{
867
		unset($this->actions[$key]);
868
	}
869
870
871
	/**
872
	 * Check whether given key already exists in $this->filters
873
	 * @param  string $key
874
	 * @throws DataGridException
875
	 */
876
	protected function addActionCheck($key)
877
	{
878
		if (isset($this->actions[$key])) {
879
			throw new DataGridException("There is already action at key [$key] defined.");
880
		}
881
	}
882
883
884
	/********************************************************************************
885
	 *                                    FILTERS                                   *
886
	 ********************************************************************************/
887
888
889
	/**
890
	 * Add filter fot text search
891
	 * @param string       $key
892
	 * @param string       $name
893
	 * @param array|string $columns
894
	 * @return Filter\FilterText
895
	 * @throws DataGridException
896
	 */
897
	public function addFilterText($key, $name, $columns = NULL)
898
	{
899
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
900
901
		if (!is_array($columns)) {
902
			throw new DataGridException("Filter Text can except only array or string.");
903
		}
904
905
		$this->addFilterCheck($key);
906
907
		return $this->filters[$key] = new Filter\FilterText($key, $name, $columns);
908
	}
909
910
911
	/**
912
	 * Add select box filter
913
	 * @param string $key
914
	 * @param string $name
915
	 * @param array  $options
916
	 * @param string $column
917
	 * @return Filter\FilterSelect
918
	 * @throws DataGridException
919
	 */
920 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...
921
	{
922
		$column = $column ?: $key;
923
924
		if (!is_string($column)) {
925
			throw new DataGridException("Filter Select can only filter through one column.");
926
		}
927
928
		$this->addFilterCheck($key);
929
930
		return $this->filters[$key] = new Filter\FilterSelect($key, $name, $options, $column);
931
	}
932
933
934
	/**
935
	 * Add datepicker filter
936
	 * @param string $key
937
	 * @param string $name
938
	 * @param string $column
939
	 * @return Filter\FilterDate
940
	 * @throws DataGridException
941
	 */
942
	public function addFilterDate($key, $name, $column = NULL)
943
	{
944
		$column = $column ?: $key;
945
946
		if (!is_string($column)) {
947
			throw new DataGridException("FilterDate can only filter through one column.");
948
		}
949
950
		$this->addFilterCheck($key);
951
952
		return $this->filters[$key] = new Filter\FilterDate($key, $name, $column);
953
	}
954
955
956
	/**
957
	 * Add range filter (from - to)
958
	 * @param string $key
959
	 * @param string $name
960
	 * @param string $column
961
	 * @return Filter\FilterRange
962
	 * @throws DataGridException
963
	 */
964 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...
965
	{
966
		$column = $column ?: $key;
967
968
		if (!is_string($column)) {
969
			throw new DataGridException("FilterRange can only filter through one column.");
970
		}
971
972
		$this->addFilterCheck($key);
973
974
		return $this->filters[$key] = new Filter\FilterRange($key, $name, $column, $name_second);
975
	}
976
977
978
	/**
979
	 * Add datepicker filter (from - to)
980
	 * @param string $key
981
	 * @param string $name
982
	 * @param string $column
983
	 * @return Filter\FilterDateRange
984
	 * @throws DataGridException
985
	 */
986 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...
987
	{
988
		$column = $column ?: $key;
989
990
		if (!is_string($column)) {
991
			throw new DataGridException("FilterDateRange can only filter through one column.");
992
		}
993
994
		$this->addFilterCheck($key);
995
996
		return $this->filters[$key] = new Filter\FilterDateRange($key, $name, $column, $name_second);
997
	}
998
999
1000
	/**
1001
	 * Check whether given key already exists in $this->filters
1002
	 * @param  string $key
1003
	 * @throws DataGridException
1004
	 */
1005
	protected function addFilterCheck($key)
1006
	{
1007
		if (isset($this->filters[$key])) {
1008
			throw new DataGridException("There is already action at key [$key] defined.");
1009
		}
1010
	}
1011
1012
1013
	/**
1014
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1015
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1016
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1017
	 */
1018
	public function assableFilters()
1019
	{
1020
		foreach ($this->filter as $key => $value) {
1021
			if (!isset($this->filters[$key])) {
1022
				$this->deleteSesssionData($key);
1023
1024
				continue;
1025
			}
1026
1027
			if (is_array($value) || $value instanceof \Traversable) {
1028
				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...
1029
					$this->filters[$key]->setValue($value);
1030
				}
1031
			} else {
1032
				if ($value !== '' && $value !== NULL) {
1033
					$this->filters[$key]->setValue($value);
1034
				}
1035
			}
1036
		}
1037
1038
		foreach ($this->columns as $column) {
1039
			if (isset($this->sort[$column->getSortingColumn()])) {
1040
				$column->setSort($this->sort);
1041
			}
1042
		}
1043
1044
		return $this->filters;
1045
	}
1046
1047
1048
	/**
1049
	 * Remove filter
1050
	 * @param string $key
1051
	 * @return void
1052
	 */
1053
	public function removeFilter($key)
1054
	{
1055
		unset($this->filters[$key]);
1056
	}
1057
1058
1059
	/********************************************************************************
1060
	 *                                  FILTERING                                   *
1061
	 ********************************************************************************/
1062
1063
1064
	/**
1065
	 * Is filter active?
1066
	 * @return boolean
1067
	 */
1068
	public function isFilterActive()
1069
	{
1070
		$is_filter = ArraysHelper::testTruthy($this->filter);
1071
1072
		return ($is_filter) || $this->force_filter_active;
1073
	}
1074
1075
1076
	/**
1077
	 * Tell that filter is active from whatever reasons
1078
	 * return static
1079
	 */
1080
	public function setFilterActive()
1081
	{
1082
		$this->force_filter_active = TRUE;
1083
1084
		return $this;
1085
	}
1086
1087
1088
	/**
1089
	 * If we want to sent some initial filter
1090
	 * @param array $filter
1091
	 * @return static
1092
	 */
1093
	public function setFilter(array $filter)
1094
	{
1095
		$this->filter = $filter;
1096
1097
		return $this;
1098
	}
1099
1100
1101
	/**
1102
	 * FilterAndGroupAction form factory
1103
	 * @return Form
1104
	 */
1105
	public function createComponentFilter()
1106
	{
1107
		$form = new Form($this, 'filter');
1108
1109
		$form->setTranslator($this->getTranslator());
1110
1111
		$form->setMethod('get');
1112
1113
		/**
1114
		 * Filter part
1115
		 */
1116
		$filter_container = $form->addContainer('filter');
1117
1118
		foreach ($this->filters as $filter) {
1119
			$filter->addToFormContainer($filter_container);
1120
		}
1121
1122
		/**
1123
		 * Group action part
1124
		 */
1125
		$group_action_container = $form->addContainer('group_action');
1126
1127
		if ($this->hasGroupActions()) {
1128
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1129
		}
1130
1131
		$form->setDefaults(['filter' => $this->filter]);
1132
1133
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1134
1135
		return $form;
1136
	}
1137
1138
1139
	/**
1140
	 * Set $this->filter values after filter form submitted
1141
	 * @param  Form $form
1142
	 * @return void
1143
	 */
1144
	public function filterSucceeded(Form $form)
1145
	{
1146
		$values = $form->getValues();
1147
1148
		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...
1149
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1150
				return;
1151
			}
1152
		}
1153
1154
		$values = $values['filter'];
1155
1156
		foreach ($values as $key => $value) {
1157
			/**
1158
			 * Session stuff
1159
			 */
1160
			$this->saveSessionData($key, $value);
1161
1162
			/**
1163
			 * Other stuff
1164
			 */
1165
			$this->filter[$key] = $value;
1166
		}
1167
1168
		$this->reload();
1169
	}
1170
1171
1172
	/**
1173
	 * Should be datagrid filters rendered separately?
1174
	 * @param boolean $out
1175
	 * @return static
1176
	 */
1177
	public function setOuterFilterRendering($out = TRUE)
1178
	{
1179
		$this->outer_filter_rendering = (bool) $out;
1180
1181
		return $this;
1182
	}
1183
1184
1185
	/**
1186
	 * Are datagrid filters rendered separately?
1187
	 * @return boolean
1188
	 */
1189
	public function hasOuterFilterRendering()
1190
	{
1191
		return $this->outer_filter_rendering;
1192
	}
1193
1194
1195
	/**
1196
	 * Try to restore session stuff
1197
	 * @return void
1198
	 */
1199
	public function findSessionFilters()
1200
	{
1201
		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...
1202
			return;
1203
		}
1204
1205
		if (!$this->remember_state) {
1206
			return;
1207
		}
1208
1209
		if ($page = $this->getSessionData('_grid_page')) {
1210
			$this->page = $page;
1211
		}
1212
1213
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1214
			$this->per_page = $per_page;
1215
		}
1216
1217
		if ($sort = $this->getSessionData('_grid_sort')) {
1218
			$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...
1219
		}
1220
1221
		foreach ($this->getSessionData() as $key => $value) {
1222
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page', '_grid_hidden_columns'])) {
1223
				$this->filter[$key] = $value;
1224
			}
1225
		}
1226
	}
1227
1228
1229
	/********************************************************************************
1230
	 *                                    EXPORTS                                   *
1231
	 ********************************************************************************/
1232
1233
1234
	/**
1235
	 * Add export of type callback
1236
	 * @param string $text
1237
	 * @param callable $callback
1238
	 * @param boolean $filtered
1239
	 * @return Export\Export
1240
	 */
1241
	public function addExportCallback($text, $callback, $filtered = FALSE)
1242
	{
1243
		if (!is_callable($callback)) {
1244
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1245
		}
1246
1247
		return $this->addToExports(new Export\Export($text, $callback, $filtered));
1248
	}
1249
1250
1251
	/**
1252
	 * Add already implemented csv export
1253
	 * @param string $text
1254
	 * @param string $csv_file_name
1255
	 * @return Export\Export
1256
	 */
1257
	public function addExportCsv($text, $csv_file_name)
1258
	{
1259
		return $this->addToExports(new Export\ExportCsv($text, $csv_file_name, FALSE));
1260
	}
1261
1262
1263
	/**
1264
	 * Add already implemented csv export, but for filtered data
1265
	 * @param string $text
1266
	 * @param string $csv_file_name
1267
	 * @return Export\Export
1268
	 */
1269
	public function addExportCsvFiltered($text, $csv_file_name)
1270
	{
1271
		return $this->addToExports(new Export\ExportCsv($text, $csv_file_name, TRUE));
1272
	}
1273
1274
1275
	/**
1276
	 * Add export to array
1277
	 * @param Export\Export $export
1278
	 * @return Export\Export
1279
	 */
1280
	protected function addToExports(Export\Export $export)
1281
	{
1282
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1283
1284
		$export->setLink($this->link('export!', ['id' => $id]));
1285
1286
		return $this->exports[$id] = $export;
1287
	}
1288
1289
1290
	public function resetExportsLinks()
1291
	{
1292
		foreach ($this->exports as $id => $export) {
1293
			$export->setLink($this->link('export!', ['id' => $id]));
1294
		}
1295
	}
1296
1297
1298
	/********************************************************************************
1299
	 *                                 GROUP ACTIONS                                *
1300
	 ********************************************************************************/
1301
1302
1303
	/**
1304
	 * Add group actino
1305
	 * @param string $title
1306
	 * @param array  $options
1307
	 * @return GroupAction\GroupAction
1308
	 */
1309
	public function addGroupAction($title, $options = [])
1310
	{
1311
		return $this->getGroupActionCollection()->addGroupAction($title, $options);
1312
	}
1313
1314
1315
	/**
1316
	 * Get collection of all group actions
1317
	 * @return GroupAction\GroupActionCollection
1318
	 */
1319
	public function getGroupActionCollection()
1320
	{
1321
		if (!$this->group_action_collection) {
1322
			$this->group_action_collection = new GroupAction\GroupActionCollection();
1323
		}
1324
1325
		return $this->group_action_collection;
1326
	}
1327
1328
1329
	/**
1330
	 * Has datagrid some group actions?
1331
	 * @return boolean
1332
	 */
1333
	public function hasGroupActions()
1334
	{
1335
		return (bool) $this->group_action_collection;
1336
	}
1337
1338
1339
	/********************************************************************************
1340
	 *                                   HANDLERS                                   *
1341
	 ********************************************************************************/
1342
1343
1344
	/**
1345
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1346
	 * @param  int  $page
1347
	 * @return void
1348
	 */
1349
	public function handlePage($page)
1350
	{
1351
		/**
1352
		 * Session stuff
1353
		 */
1354
		$this->page = $page;
1355
		$this->saveSessionData('_grid_page', $page);
1356
1357
		$this->reload(['table']);
1358
	}
1359
1360
1361
	/**
1362
	 * Handler for sorting
1363
	 * @param array $sort
1364
	 * @return void
1365
	 */
1366
	public function handleSort(array $sort)
1367
	{
1368
		$new_sort = [];
1369
1370
		/**
1371
		 * Find apropirate column
1372
		 */
1373
		foreach ($sort as $key => $value) {
1374
			if (empty($this->columns[$key])) {
1375
				throw new DataGridException("Column <$key> not found");
1376
			}
1377
1378
			$column = $this->columns[$key];
1379
			$new_sort = [$column->getSortingColumn() => $value];
1380
1381
			/**
1382
			 * Pagination may be reseted after sorting
1383
			 */
1384
			if ($column->sortableResetPagination()) {
1385
				$this->page = 1;
1386
				$this->saveSessionData('_grid_page', 1);
1387
			}
1388
1389
			/**
1390
			 * Custom sorting callback may be applied
1391
			 */
1392
			if ($column->getSortableCallback()) {
1393
				$this->sort_callback = $column->getSortableCallback();
1394
			}
1395
		}
1396
1397
		/**
1398
		 * Session stuff
1399
		 */
1400
		$this->sort = $new_sort;
1401
		$this->saveSessionData('_grid_sort', $this->sort);
1402
1403
		$this->reload(['table']);
1404
	}
1405
1406
1407
	/**
1408
	 * handler for reseting the filter
1409
	 * @return void
1410
	 */
1411
	public function handleResetFilter()
1412
	{
1413
		/**
1414
		 * Session stuff
1415
		 */
1416
		$this->deleteSesssionData('_grid_page');
1417
1418
		foreach ($this->getSessionData() as $key => $value) {
1419
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page'])) {
1420
				$this->deleteSesssionData($key);
1421
			}
1422
		}
1423
1424
		$this->filter = [];
1425
1426
		$this->reload(['grid']);
1427
	}
1428
1429
1430
	/**
1431
	 * Handler for export
1432
	 * @param  int $id Key for particular export class in array $this->exports
1433
	 * @return void
1434
	 */
1435
	public function handleExport($id)
1436
	{
1437
		if (!isset($this->exports[$id])) {
1438
			throw new Nette\Application\ForbiddenRequestException;
1439
		}
1440
1441
		if (!empty($this->columns_export_order)) {
1442
			$this->setColumnsOrder($this->columns_export_order);
1443
		}
1444
1445
		$export = $this->exports[$id];
1446
1447
		if ($export->isFiltered()) {
1448
			$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...
1449
			$filter    = $this->assableFilters();
1450
		} else {
1451
			$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...
1452
			$filter    = [];
1453
		}
1454
1455
		if (NULL === $this->dataModel) {
1456
			throw new DataGridException('You have to set a data source first.');
1457
		}
1458
1459
		$rows = [];
1460
1461
		$items = Nette\Utils\Callback::invokeArgs(
1462
			[$this->dataModel, 'filterData'], [
1463
				NULL,
1464
				new Sorting($this->sort, $this->sort_callback),
1465
				$filter
1466
			]
1467
		);
1468
1469
		foreach ($items as $item) {
1470
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1471
		}
1472
1473
		if ($export instanceof Export\ExportCsv) {
1474
			$export->invoke($rows, $this);
1475
		} else {
1476
			$export->invoke($items, $this);
1477
		}
1478
1479
		if ($export->isAjax()) {
1480
			$this->reload();
1481
		}
1482
	}
1483
1484
1485
	/**
1486
	 * Handler for getting children of parent item (e.g. category)
1487
	 * @param  int $parent
1488
	 * @return void
1489
	 */
1490
	public function handleGetChildren($parent)
1491
	{
1492
		$this->setDataSource(
1493
			call_user_func($this->tree_view_children_callback, $parent)
1494
		);
1495
1496
		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...
1497
			$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...
1498
			$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...
1499
1500
			$this->redrawControl('items');
1501
1502
			$this->onRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onRedraw does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1503
		} else {
1504
			$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...
1505
		}
1506
	}
1507
1508
1509
	/**
1510
	 * Handler for getting item detail
1511
	 * @param  mixed $id
1512
	 * @return void
1513
	 */
1514
	public function handleGetItemDetail($id)
1515
	{
1516
		$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...
1517
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1518
1519
		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...
1520
			$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...
1521
			$this->redrawControl('items');
1522
1523
			$this->onRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onRedraw does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1524
		} else {
1525
			$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...
1526
		}
1527
	}
1528
1529
1530
	/**
1531
	 * Handler for inline editing
1532
	 * @param  mixed $id
1533
	 * @param  mixed $key
1534
	 * @return void
1535
	 */
1536
	public function handleEdit($id, $key)
1537
	{
1538
		$column = $this->getColumn($key);
1539
		$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...
1540
1541
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1542
	}
1543
1544
1545
	/**
1546
	 * Redraw $this
1547
	 * @return void
1548
	 */
1549
	public function reload($snippets = [])
1550
	{
1551
		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...
1552
			$this->redrawControl('tbody');
1553
			$this->redrawControl('pagination');
1554
1555
			/**
1556
			 * manualy reset exports links...
1557
			 */
1558
			$this->resetExportsLinks();
1559
			$this->redrawControl('exports');
1560
1561
			foreach ($snippets as $snippet) {
1562
				$this->redrawControl($snippet);
1563
			}
1564
1565
			$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...
1566
1567
			$this->onRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onRedraw does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1568
		} else {
1569
			$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...
1570
		}
1571
	}
1572
1573
1574
	/**
1575
	 * Handler for column status
1576
	 * @param  string $id
1577
	 * @param  string $key
1578
	 * @param  string $value
1579
	 * @return void
1580
	 */
1581
	public function handleChangeStatus($id, $key, $value)
1582
	{
1583
		if (empty($this->columns[$key])) {
1584
			throw new DataGridException("ColumnStatus[$key] does not exist");
1585
		}
1586
1587
		$this->columns[$key]->onChange($id, $value);
1588
	}
1589
1590
1591
	/**
1592
	 * Redraw just one row via ajax
1593
	 * @param  int   $id
1594
	 * @param  mixed $primary_where_column
1595
	 * @return void
1596
	 */
1597
	public function redrawItem($id, $primary_where_column = NULL)
1598
	{
1599
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
1600
1601
		$this->redrawControl('items');
1602
		$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...
1603
1604
		$this->onRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onRedraw does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1605
	}
1606
1607
1608
	/**
1609
	 * Tell datagrid to display all columns
1610
	 * @return void
1611
	 */
1612
	public function handleShowAllColumns()
1613
	{
1614
		$this->deleteSesssionData('_grid_hidden_columns');
1615
1616
		$this->redrawControl();
1617
1618
		$this->onRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onRedraw does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1619
	}
1620
1621
1622
	/**
1623
	 * Reveal particular column
1624
	 * @param  string $column
1625
	 * @return void
1626
	 */
1627 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...
1628
	{
1629
		$columns = $this->getSessionData('_grid_hidden_columns');
1630
1631
		if (!empty($columns)) {
1632
			$pos = array_search($column, $columns);
1633
1634
			if ($pos !== FALSE) {
1635
				unset($columns[$pos]);
1636
			}
1637
		}
1638
1639
		$this->saveSessionData('_grid_hidden_columns', $columns);
1640
1641
		$this->redrawControl();
1642
1643
		$this->onRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onRedraw does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1644
	}
1645
1646
1647
	/**
1648
	 * Notice datagrid to not display particular columns
1649
	 * @param  string $column
1650
	 * @return void
1651
	 */
1652 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...
1653
	{
1654
		/**
1655
		 * Store info about hiding a column to session
1656
		 */
1657
		$columns = $this->getSessionData('_grid_hidden_columns');
1658
1659
		if (empty($columns)) {
1660
			$columns = [$column];
1661
		} else if (!in_array($column, $columns)) {
1662
			array_push($columns, $column);
1663
		}
1664
1665
		$this->saveSessionData('_grid_hidden_columns', $columns);
1666
1667
		$this->redrawControl();
1668
1669
		$this->onRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onRedraw does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1670
	}
1671
1672
1673
	public function handleActionCallback($__key, $__id)
1674
	{
1675
		$action = $this->getAction($__key);
1676
1677
		if (!($action instanceof Column\ActionCallback)) {
1678
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
1679
		}
1680
1681
		$action->onClick($__id);
1682
	}
1683
1684
1685
	/********************************************************************************
1686
	 *                                  PAGINATION                                  *
1687
	 ********************************************************************************/
1688
1689
1690
	/**
1691
	 * Set options of select "items_per_page"
1692
	 * @param array $items_per_page_list
1693
	 * @return static
1694
	 */
1695
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
1696
	{
1697
		$this->items_per_page_list = $items_per_page_list;
1698
1699
		if ($include_all) {
1700
			$this->items_per_page_list[] = 'all';
1701
		}
1702
1703
		return $this;
1704
	}
1705
1706
1707
	/**
1708
	 * Paginator factory
1709
	 * @return Components\DataGridPaginator\DataGridPaginator
1710
	 */
1711
	public function createComponentPaginator()
1712
	{
1713
		/**
1714
		 * Init paginator
1715
		 */
1716
		$component = new Components\DataGridPaginator\DataGridPaginator(
1717
			$this->getTranslator()
1718
		);
1719
		$paginator = $component->getPaginator();
1720
1721
		$paginator->setPage($this->page);
1722
		$paginator->setItemsPerPage($this->getPerPage());
1723
1724
		return $component;
1725
	}
1726
1727
1728
	/**
1729
	 * PerPage form factory
1730
	 * @return Form
1731
	 */
1732
	public function createComponentPerPage()
1733
	{
1734
		$form = new Form;
1735
1736
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1737
			->setValue($this->getPerPage());
1738
1739
		$form->addSubmit('submit', '');
1740
1741
		$saveSessionData = [$this, 'saveSessionData'];
1742
1743
		$form->onSuccess[] = function($form, $values) use ($saveSessionData) {
1744
			/**
1745
			 * Session stuff
1746
			 */
1747
			$saveSessionData('_grid_per_page', $values->per_page);
1748
1749
			/**
1750
			 * Other stuff
1751
			 */
1752
			$this->per_page = $values->per_page;
1753
			$this->reload();
1754
		};
1755
1756
		return $form;
1757
	}
1758
1759
1760
	/**
1761
	 * Get parameter per_page
1762
	 * @return int
1763
	 */
1764
	public function getPerPage()
1765
	{
1766
		$items_per_page_list = $this->getItemsPerPageList();
1767
1768
		$per_page = $this->per_page ?: reset($items_per_page_list);
1769
1770
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
1771
			$per_page = reset($items_per_page_list);
1772
		}
1773
1774
		return $per_page;
1775
	}
1776
1777
1778
	/**
1779
	 * Get associative array of items_per_page_list
1780
	 * @return array
1781
	 */
1782
	public function getItemsPerPageList()
1783
	{
1784
		if (empty($this->items_per_page_list)) {
1785
			$this->setItemsPerPageList([10, 20, 50], TRUE);
1786
		}
1787
1788
		$list = array_flip($this->items_per_page_list);
1789
1790
		foreach ($list as $key => $value) {
1791
			$list[$key] = $key;
1792
		}
1793
1794
		if (array_key_exists('all', $list)) {
1795
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
1796
		}
1797
1798
		return $list;
1799
	}
1800
1801
1802
	/**
1803
	 * Order Grid to "be paginated"
1804
	 * @param bool $do
1805
	 * @return static
1806
	 */
1807
	public function setPagination($do)
1808
	{
1809
		$this->do_paginate = (bool) $do;
1810
1811
		return $this;
1812
	}
1813
1814
1815
	/**
1816
	 * Tell whether Grid is paginated
1817
	 * @return bool
1818
	 */
1819
	public function isPaginated()
1820
	{
1821
		return $this->do_paginate;
1822
	}
1823
1824
1825
	/**
1826
	 * Return current paginator class
1827
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
1828
	 */
1829
	public function getPaginator()
1830
	{
1831
		if ($this->isPaginated() && $this->per_page !== 'all') {
1832
			return $this['paginator'];
1833
		}
1834
1835
		return NULL;
1836
	}
1837
1838
1839
	/********************************************************************************
1840
	 *                                     I18N                                     *
1841
	 ********************************************************************************/
1842
1843
1844
	/**
1845
	 * Set datagrid translator
1846
	 * @param Nette\Localization\ITranslator $translator
1847
	 * @return static
1848
	 */
1849
	public function setTranslator(Nette\Localization\ITranslator $translator)
1850
	{
1851
		$this->translator = $translator;
1852
1853
		return $this;
1854
	}
1855
1856
1857
	/**
1858
	 * Get translator for datagrid
1859
	 * @return Nette\Localization\ITranslator
1860
	 */
1861
	public function getTranslator()
1862
	{
1863
		if (!$this->translator) {
1864
			$this->translator = new Localization\SimpleTranslator;
1865
		}
1866
1867
		return $this->translator;
1868
	}
1869
1870
1871
	/********************************************************************************
1872
	 *                                 COLUMNS ORDER                                *
1873
	 ********************************************************************************/
1874
1875
1876
	/**
1877
	 * Set order of datagrid columns
1878
	 * @param array $order
1879
	 * @return static
1880
	 */
1881
	public function setColumnsOrder($order)
1882
	{
1883
		$new_order = [];
1884
1885
		foreach ($order as $key) {
1886
			if (isset($this->columns[$key])) {
1887
				$new_order[$key] = $this->columns[$key];
1888
			}
1889
		}
1890
1891
		if (sizeof($new_order) === sizeof($this->columns)) {
1892
			$this->columns = $new_order;
1893
		} else {
1894
			throw new DataGridException('When changing columns order, you have to specify all columns');
1895
		}
1896
1897
		return $this;
1898
	}
1899
1900
1901
	/**
1902
	 * Columns order may be different for export and normal grid
1903
	 * @param array $order
1904
	 */
1905
	public function setColumnsExportOrder($order)
1906
	{
1907
		$this->columns_export_order = (array) $order;
1908
	}
1909
1910
1911
	/********************************************************************************
1912
	 *                                SESSION & URL                                 *
1913
	 ********************************************************************************/
1914
1915
1916
	/**
1917
	 * Find some unique session key name
1918
	 * @return string
1919
	 */
1920
	public function getSessionSectionName()
1921
	{
1922
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
1923
	}
1924
1925
1926
	/**
1927
	 * Should datagrid remember its filters/pagination/etc using session?
1928
	 * @param bool $remember
1929
	 * @return static
1930
	 */
1931
	public function setRememberState($remember = TRUE)
1932
	{
1933
		$this->remember_state = (bool) $remember;
1934
1935
		return $this;
1936
	}
1937
1938
1939
	/**
1940
	 * Should datagrid refresh url using history API?
1941
	 * @param bool $refresh
1942
	 * @return static
1943
	 */
1944
	public function setRefreshUrl($refresh = TRUE)
1945
	{
1946
		$this->refresh_url = (bool) $refresh;
1947
1948
1949
		return $this;
1950
	}
1951
1952
1953
	/**
1954
	 * Get session data if functionality is enabled
1955
	 * @param  string $key
1956
	 * @return mixed
1957
	 */
1958
	public function getSessionData($key = NULL, $default_value = NULL)
1959
	{
1960
		if (!$this->remember_state) {
1961
			return $key ? $default_value : [];
1962
		}
1963
1964
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
1965
	}
1966
1967
1968
	/**
1969
	 * Save session data - just if it is enabled
1970
	 * @param  string $key
1971
	 * @param  mixed  $value
1972
	 * @return void
1973
	 */
1974
	public function saveSessionData($key, $value)
1975
	{
1976
		if ($this->remember_state) {
1977
			$this->grid_session->{$key} = $value;
1978
		}
1979
	}
1980
1981
1982
	/**
1983
	 * Delete session data
1984
	 * @return void
1985
	 */
1986
	public function deleteSesssionData($key)
1987
	{
1988
		unset($this->grid_session->{$key});
1989
	}
1990
1991
1992
	/********************************************************************************
1993
	 *                                  ITEM DETAIL                                 *
1994
	 ********************************************************************************/
1995
1996
1997
	/**
1998
	 * Get items detail parameters
1999
	 * @return array
2000
	 */
2001
	public function getItemsDetail()
2002
	{
2003
		return $this->items_detail;
2004
	}
2005
2006
2007
	/**
2008
	 * Items can have thair detail - toggled
2009
	 * @param mixed $detail callable|string|bool
2010
	 * @param bool|NULL $primary_where_column
2011
	 * @return Column\ItemDetail
2012
	 */
2013
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2014
	{
2015
		if ($this->isSortable()) {
2016
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2017
		}
2018
2019
		$this->items_detail = new Column\ItemDetail(
2020
			$this,
2021
			$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...
2022
		);
2023
2024
		if (is_string($detail)) {
2025
			/**
2026
			 * Item detail will be in separate template
2027
			 */
2028
			$this->items_detail->setType('template');
2029
			$this->items_detail->setTemplate($detail);
2030
2031
		} else if (is_callable($detail)) {
2032
			/**
2033
			 * Item detail will be rendered via custom callback renderer
2034
			 */
2035
			$this->items_detail->setType('renderer');
2036
			$this->items_detail->setRenderer($detail);
2037
2038
		} else if (TRUE === $detail) {
2039
			/**
2040
			 * Item detail will be rendered probably via block #detail
2041
			 */
2042
			$this->items_detail->setType('block');
2043
2044
		} else {
2045
			throw new DataGridException(
2046
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2047
			);
2048
		}
2049
2050
		return $this->items_detail;
2051
	}
2052
2053
2054
	/********************************************************************************
2055
	 *                                ROW PRIVILEGES                                *
2056
	 ********************************************************************************/
2057
2058
2059
	public function allowRowsGroupAction(callable $condition)
2060
	{
2061
		$this->row_conditions['group_action'] = $condition;
2062
	}
2063
2064
2065
	public function allowRowsAction($key, callable $condition)
2066
	{
2067
		$this->row_conditions['action'][$key] = $condition;
2068
	}
2069
2070
2071
	public function getRowCondition($name, $key = NULL)
2072
	{
2073
		if (!isset($this->row_conditions[$name])) {
2074
			return FALSE;
2075
		}
2076
2077
		$condition = $this->row_conditions[$name];
2078
2079
		if (!$key) {
2080
			return $condition;
2081
		}
2082
2083
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2084
	}
2085
2086
2087
	/********************************************************************************
2088
	 *                               HIDEABLE COLUMNS                               *
2089
	 ********************************************************************************/
2090
2091
2092
	/**
2093
	 * Can datagrid hide colums?
2094
	 * @return boolean
2095
	 */
2096
	public function canHideColumns()
2097
	{
2098
		return (bool) $this->can_hide_columns;
2099
	}
2100
2101
2102
	/**
2103
	 * Order Grid to set columns hideable.
2104
	 * @return static
2105
	 */
2106
	public function setColumnsHideable()
2107
	{
2108
		$this->can_hide_columns = TRUE;
2109
2110
		return $this;
2111
	}
2112
2113
2114
	/********************************************************************************
2115
	 *                                   INTERNAL                                   *
2116
	 ********************************************************************************/
2117
2118
2119
	/**
2120
	 * Get cont of columns
2121
	 * @return int
2122
	 */
2123
	public function getColumnsCount()
2124
	{
2125
		$count = sizeof($this->getColumns());
2126
2127
		if (!empty($this->actions) || $this->isSortable() || $this->getItemsDetail()) {
2128
			$count++;
2129
		}
2130
2131
		if ($this->hasGroupActions()) {
2132
			$count++;
2133
		}
2134
2135
		return $count;
2136
	}
2137
2138
2139
	/**
2140
	 * Get primary key of datagrid data source
2141
	 * @return string
2142
	 */
2143
	public function getPrimaryKey()
2144
	{
2145
		return $this->primary_key;
2146
	}
2147
2148
2149
	/**
2150
	 * Get set of set columns
2151
	 * @return Column\IColumn[]
2152
	 */
2153
	public function getColumns()
2154
	{
2155
		foreach ($this->getSessionData('_grid_hidden_columns', []) as $column) {
2156
			if (!empty($this->columns[$column])) {
2157
				$this->columns_visibility[$column] = [
2158
					'visible' => FALSE,
2159
					'name' => $this->columns[$column]->getName()
2160
				];
2161
			}
2162
2163
			$this->removeColumn($column);
2164
		}
2165
2166
		return $this->columns;
2167
	}
2168
2169
2170
	/**
2171
	 * @return PresenterComponent
2172
	 */
2173
	public function getParent()
2174
	{
2175
		$parent = parent::getParent();
2176
2177
		if (!($parent instanceof PresenterComponent)) {
2178
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2179
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2180
			);
2181
		}
2182
2183
		return $parent;
2184
	}
2185
2186
}
2187