Completed
Push — master ( c2a6cc...5884a4 )
by Petr
08:06
created

Grid::setCustomization()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
/**
4
 * This file is part of the Grido (http://grido.bugyik.cz)
5
 *
6
 * Copyright (c) 2011 Petr Bugyík (http://petr.bugyik.cz)
7
 *
8
 * For the full copyright and license information, please view
9
 * the file LICENSE.md that was distributed with this source code.
10
 */
11
12
namespace Grido;
13
14
use Grido\Exception;
15
use Grido\Components\Paginator;
16
use Grido\Components\Columns\Column;
17
use Grido\Components\Filters\Filter;
18
use Grido\Components\Actions\Action;
19
20
use Symfony\Component\PropertyAccess\PropertyAccessor;
21
22
/**
23
 * Grido - DataGrid for Nette Framework.
24
 *
25
 * @package     Grido
26
 * @author      Petr Bugyík
27
 *
28
 * @property-read int $count
29 1
 * @property-read mixed $data
30
 * @property-read \Nette\Utils\Html $tablePrototype
31
 * @property-read PropertyAccessor $propertyAccessor
32
 * @property-read Customization $customization
33
 * @property-write string $templateFile
34
 * @property bool $rememberState
35
 * @property array $defaultPerPage
36
 * @property array $defaultFilter
37
 * @property array $defaultSort
38
 * @property array $perPageList
39
 * @property \Nette\Localization\ITranslator $translator
40 1
 * @property Paginator $paginator
41
 * @property string $primaryKey
42 1
 * @property string $filterRenderType
43
 * @property DataSources\IDataSource $model
44 1
 * @property callback $rowCallback
45
 * @property bool $strictMode
46 1
 * @method void onRegistered(Grid $grid)
47
 * @method void onRender(Grid $grid)
48
 * @method void onFetchData(Grid $grid)
49
 */
50 1
class Grid extends Components\Container
51 1
{
52
    /***** DEFAULTS ****/
53
    const BUTTONS = 'buttons';
54
55
    const CLIENT_SIDE_OPTIONS = 'grido-options';
56
57
    /** @var int @persistent */
58
    public $page = 1;
59
60
    /** @var int @persistent */
61
    public $perPage;
62
63
    /** @var array @persistent */
64
    public $sort = [];
65
66 1
    /** @var array @persistent */
67
    public $filter = [];
68
69
    /** @var array event on all grid's components registered */
70
    public $onRegistered;
71
72
    /** @var array event on render */
73
    public $onRender;
74
75
    /** @var array event for modifying data */
76
    public $onFetchData;
77
78
    /** @var callback returns tr html element; function($row, Html $tr) */
79
    protected $rowCallback;
80
81
    /** @var \Nette\Utils\Html */
82
    protected $tablePrototype;
83
84
    /** @var bool */
85
    protected $rememberState = FALSE;
86
87
    /** @var string */
88
    protected $rememberStateSectionName;
89
90
    /** @var string */
91
    protected $primaryKey = 'id';
92
93
    /** @var string */
94
    protected $filterRenderType;
95
96
    /** @var array */
97
    protected $perPageList = [10, 20, 30, 50, 100];
98
99
    /** @var int */
100
    protected $defaultPerPage = 20;
101
102
    /** @var array */
103
    protected $defaultFilter = [];
104
105
    /** @var array */
106
    protected $defaultSort = [];
107
108
    /** @var DataSources\IDataSource */
109
    protected $model;
110
111
    /** @var int total count of items */
112
    protected $count;
113
114
    /** @var mixed */
115
    protected $data;
116
117
    /** @var Paginator */
118
    protected $paginator;
119
120
    /** @var \Nette\Localization\ITranslator */
121
    protected $translator;
122
123
    /** @var PropertyAccessor */
124
    protected $propertyAccessor;
125
126
    /** @var bool */
127 1
    protected $strictMode = TRUE;
128
129
    /** @var array */
130
    protected $options = [
131
        self::CLIENT_SIDE_OPTIONS => []
132
    ];
133
134
    /** @var Customization */
135 1
    protected $customization;
136 1
137
    /**
138
     * Sets a model that implements the interface Grido\DataSources\IDataSource or data-source object.
139
     * @param mixed $model
140
     * @param bool $forceWrapper
141
     * @throws Exception
142
     * @return Grid
143
     */
144
    public function setModel($model, $forceWrapper = FALSE)
145
    {
146 1
        $this->model = $model instanceof DataSources\IDataSource && $forceWrapper === FALSE
1 ignored issue
show
Documentation Bug introduced by
It seems like $model instanceof \Grido...taSources\Model($model) can also be of type object<Grido\DataSources\Model>. However, the property $model is declared as type object<Grido\DataSources\IDataSource>. 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...
147 1
            ? $model
148 1
            : new DataSources\Model($model);
149
150 1
        return $this;
151
    }
152
153 1
    /**
154
     * Sets the default number of items per page.
155
     * @param int $perPage
156
     * @return Grid
157
     */
158
    public function setDefaultPerPage($perPage)
159
    {
160 1
        $perPage = (int) $perPage;
161 1
        $this->defaultPerPage = $perPage;
162
163 1
        if (!in_array($perPage, $this->perPageList)) {
164 1
            $this->perPageList[] = $perPage;
165 1
            sort($this->perPageList);
166 1
        }
167
168 1
        return $this;
169
    }
170
171
    /**
172
     * Sets default filtering.
173
     * @param array $filter
174
     * @return Grid
175
     */
176
    public function setDefaultFilter(array $filter)
177
    {
178 1
        $this->defaultFilter = array_merge($this->defaultFilter, $filter);
179 1
        return $this;
180
    }
181
182
    /**
183
     * Sets default sorting.
184
     * @param array $sort
185
     * @return Grid
186
     * @throws Exception
187 1
     */
188
    public function setDefaultSort(array $sort)
189
    {
190 1
        static $replace = ['asc' => Column::ORDER_ASC, 'desc' => Column::ORDER_DESC];
191 1
192 1
        foreach ($sort as $column => $dir) {
193 1
            $dir = strtr(strtolower($dir), $replace);
194 1
            if (!in_array($dir, $replace)) {
195 1
                throw new Exception("Dir '$dir' for column '$column' is not allowed.");
196
            }
197
198 1
            $this->defaultSort[$column] = $dir;
199 1
        }
200
201 1
        return $this;
202
    }
203
204
    /**
205
     * Sets items to per-page select.
206
     * @param array $perPageList
207
     * @return Grid
208 1
     */
209
    public function setPerPageList(array $perPageList)
210
    {
211 1
        $this->perPageList = $perPageList;
212
213 1
        if ($this->hasFilters(FALSE) || $this->hasOperation(FALSE)) {
214 1
            $this['form']['count']->setItems($this->getItemsForCountSelect());
215 1
        }
216
217 1
        return $this;
218
    }
219
220
    /**
221
     * Sets translator.
222
     * @param \Nette\Localization\ITranslator $translator
223
     * @return Grid
224
     */
225
    public function setTranslator(\Nette\Localization\ITranslator $translator)
226
    {
227 1
        $this->translator = $translator;
228 1
        return $this;
229
    }
230
231
    /**
232
     * Sets type of filter rendering.
233
     * Defaults inner (Filter::RENDER_INNER) if column does not exist then outer filter (Filter::RENDER_OUTER).
234
     * @param string $type
235 1
     * @throws Exception
236
     * @return Grid
237
     */
238
    public function setFilterRenderType($type)
239
    {
240 1
        $type = strtolower($type);
241 1
        if (!in_array($type, [Filter::RENDER_INNER, Filter::RENDER_OUTER])) {
242 1
            throw new Exception('Type must be Filter::RENDER_INNER or Filter::RENDER_OUTER.');
243
        }
244
245 1
        $this->filterRenderType = $type;
246 1
        return $this;
247
    }
248
249
    /**
250
     * Sets custom paginator.
251
     * @param Paginator $paginator
252
     * @return Grid
253
     */
254
    public function setPaginator(Paginator $paginator)
255
    {
256 1
        $this->paginator = $paginator;
257 1
        return $this;
258
    }
259
260
    /**
261
     * Sets grid primary key.
262
     * Defaults is "id".
263
     * @param string $key
264
     * @return Grid
265
     */
266
    public function setPrimaryKey($key)
267
    {
268 1
        $this->primaryKey = $key;
269 1
        return $this;
270
    }
271
272
    /**
273
     * Sets file name of custom template.
274
     * @param string $file
275
     * @return Grid
276
     */
277
    public function setTemplateFile($file)
278
    {
279
        $this->onRender[] = function() use ($file) {
280
            $this->template->gridoTemplate = $this->getTemplate()->getFile();
0 ignored issues
show
Bug introduced by
Accessing gridoTemplate on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
281
            $this->getTemplate()->setFile($file);
282
        };
283
284
        return $this;
285
    }
286
287
    /**
288
     * Sets saving state to session.
289
     * @param bool $state
290
     * @param string $sectionName
291 1
     * @return Grid
292
     */
293
    public function setRememberState($state = TRUE, $sectionName = NULL)
294
    {
295 1
        $this->getPresenter(); //component must be attached to presenter
296 1
        $this->getRememberSession(TRUE); //start session if not
297 1
        $this->rememberState = (bool) $state;
298 1
        $this->rememberStateSectionName = $sectionName;
299
300 1
        return $this;
301
    }
302
303
    /**
304
     * Sets callback for customizing tr html object.
305
     * Callback returns tr html element; function($row, Html $tr).
306
     * @param $callback
307
     * @return Grid
308
     */
309
    public function setRowCallback($callback)
310
    {
311 1
        $this->rowCallback = $callback;
312 1
        return $this;
313
    }
314
315
    /**
316
     * Sets client-side options.
317
     * @param array $options
318
     * @return Grid
319
     */
320
    public function setClientSideOptions(array $options)
321
    {
322 1
        $this->options[self::CLIENT_SIDE_OPTIONS] = $options;
323 1
        return $this;
324
    }
325
326
    /**
327
     * Determines whether any user error will cause a notice.
328
     * @param bool $mode
329
     * @return \Grido\Grid
330
     */
331
    public function setStrictMode($mode)
332
    {
333 1
        $this->strictMode = (bool) $mode;
334 1
        return $this;
335
    }
336
337
    /**
338
     * @param \Grido\Customization $customization
339
     */
340
    public function setCustomization(Customization $customization)
341
    {
342 1
        $this->customization = $customization;
343 1
    }
344
345
    /**********************************************************************************************/
346
347
    /**
348
     * Returns total count of data.
349
     * @return int
350
     */
351
    public function getCount()
352
    {
353 1
        if ($this->count === NULL) {
354 1
            $this->count = $this->getModel()->getCount();
355 1
        }
356
357 1
        return $this->count;
358
    }
359
360
    /**
361
     * Returns default per page.
362
     * @return int
363
     */
364
    public function getDefaultPerPage()
365
    {
366 1
        if (!in_array($this->defaultPerPage, $this->perPageList)) {
367 1
            $this->defaultPerPage = $this->perPageList[0];
368 1
        }
369
370 1
        return $this->defaultPerPage;
371
    }
372
373
    /**
374
     * Returns default filter.
375
     * @return array
376
     */
377
    public function getDefaultFilter()
378
    {
379 1
        return $this->defaultFilter;
380
    }
381
382
    /**
383
     * Returns default sort.
384
     * @return array
385
     */
386
    public function getDefaultSort()
387
    {
388 1
        return $this->defaultSort;
389
    }
390
391
    /**
392
     * Returns list of possible items per page.
393
     * @return array
394
     */
395
    public function getPerPageList()
396 1
    {
397 1
        return $this->perPageList;
398
    }
399
400
    /**
401
     * Returns primary key.
402
     * @return string
403
     */
404
    public function getPrimaryKey()
405
    {
406 1
        return $this->primaryKey;
407
    }
408
409
    /**
410
     * Returns remember state.
411
     * @return bool
412
     */
413
    public function getRememberState()
414
    {
415 1
        return $this->rememberState;
416
    }
417
418
    /**
419
     * Returns row callback.
420
     * @return callback
421
     */
422
    public function getRowCallback()
423
    {
424 1
        return $this->rowCallback;
425
    }
426
427
    /**
428
     * Returns items per page.
429
     * @return int
430
     */
431
    public function getPerPage()
432
    {
433 1
        return $this->perPage === NULL
434 1
            ? $this->getDefaultPerPage()
435 1
            : $this->perPage;
436
    }
437
438
    /**
439
     * Returns actual filter values.
440
     * @param string $key
441
     * @return mixed
442
     */
443
    public function getActualFilter($key = NULL)
444
    {
445 1
        $filter = $this->filter ? $this->filter : $this->defaultFilter;
446 1
        return $key !== NULL && isset($filter[$key]) ? $filter[$key] : $filter;
447
    }
448
449
    /**
450
     * Returns fetched data.
451
     * @param bool $applyPaging
452
     * @param bool $useCache
453
     * @param bool $fetch
454
     * @throws Exception
455
     * @return array|DataSources\IDataSource|\Nette\Database\Table\Selection
456
     */
457
    public function getData($applyPaging = TRUE, $useCache = TRUE, $fetch = TRUE)
458
    {
459 1
        if ($this->getModel() === NULL) {
460 1
            throw new Exception('Model cannot be empty, please use method $grid->setModel().');
461
        }
462
463 1
        $data = $this->data;
464 1
        if ($data === NULL || $useCache === FALSE) {
465 1
            $this->applyFiltering();
466 1
            $this->applySorting();
467
468 1
            if ($applyPaging) {
469 1
                $this->applyPaging();
470 1
            }
471
472 1
            if ($fetch === FALSE) {
473 1
                return $this->getModel();
474
            }
475
476 1
            $data = $this->getModel()->getData();
477
478 1
            if ($useCache === TRUE) {
479 1
                $this->data = $data;
480 1
            }
481
482 1
            if ($applyPaging && !empty($data) && !in_array($this->page, range(1, $this->getPaginator()->pageCount))) {
483 1
                $this->__triggerUserNotice("Page is out of range.");
484 1
                $this->page = 1;
485 1
            }
486
487 1
            if (!empty($this->onFetchData)) {
488
                $this->onFetchData($this);
489
            }
490 1
        }
491
492 1
        return $data;
493
    }
494
495
    /**
496
     * Returns translator.
497
     * @return Translations\FileTranslator
498
     */
499
    public function getTranslator()
500
    {
501 1
        if ($this->translator === NULL) {
502 1
            $this->setTranslator(new Translations\FileTranslator);
503 1
        }
504
505 1
        return $this->translator;
506
    }
507
508
    /**
509
     * Returns remember session for set expiration, etc.
510
     * @param bool $forceStart - if TRUE, session will be started if not
511
     * @return \Nette\Http\SessionSection|NULL
512
     */
513
    public function getRememberSession($forceStart = FALSE)
514
    {
515 1
        $presenter = $this->getPresenter();
516 1
        $session = $presenter->getSession();
517
518 1
        if (!$session->isStarted() && $forceStart) {
519 1
            $session->start();
520 1
        }
521
522 1
        return $session->isStarted()
523 1
            ? ($session->getSection($this->rememberStateSectionName ?: ($presenter->name . ':' . $this->getUniqueId())))
0 ignored issues
show
Bug introduced by
Accessing name 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...
524 1
            : NULL;
525
    }
526
527
    /**
528
     * Returns table html element of grid.
529
     * @return \Nette\Utils\Html
530
     */
531
    public function getTablePrototype()
532
    {
533 1
        if ($this->tablePrototype === NULL) {
534 1
            $this->tablePrototype = \Nette\Utils\Html::el('table');
535 1
            $this->tablePrototype->id($this->getName());
536 1
        }
537
538 1
        return $this->tablePrototype;
539
    }
540
541
    /**
542
     * @return string
543
     * @internal
544
     */
545
    public function getFilterRenderType()
546
    {
547 1
        if ($this->filterRenderType !== NULL) {
548 1
            return $this->filterRenderType;
549
        }
550
551 1
        $this->filterRenderType = Filter::RENDER_OUTER;
552 1
        if ($this->hasColumns() && $this->hasFilters() && $this->hasActions()) {
553 1
            $this->filterRenderType = Filter::RENDER_INNER;
554
555 1
            $filters = $this[Filter::ID]->getComponents();
556 1
            foreach ($filters as $filter) {
557 1
                if (!$this[Column::ID]->getComponent($filter->name, FALSE)) {
558 1
                    $this->filterRenderType = Filter::RENDER_OUTER;
559 1
                    break;
560
                }
561 1
            }
562 1
        }
563
564 1
        return $this->filterRenderType;
565
    }
566
567
    /**
568
     * @return DataSources\IDataSource
569
     */
570
    public function getModel()
571
    {
572 1
        return $this->model;
573
    }
574
575
    /**
576
     * @return Paginator
577
     * @internal
578
     */
579
    public function getPaginator()
580
    {
581 1
        if ($this->paginator === NULL) {
582 1
            $this->paginator = new Paginator;
583 1
            $this->paginator->setItemsPerPage($this->getPerPage())
584 1
                ->setGrid($this);
585 1
        }
586
587 1
        return $this->paginator;
588
    }
589
590
    /**
591
     * A simple wrapper around symfony/property-access with Nette Database dot notation support.
592
     * @param array|object $object
593
     * @param string $name
594
     * @return mixed
595
     * @internal
596
     */
597
    public function getProperty($object, $name)
598
    {
599 1
        if ($object instanceof \Nette\Database\Table\IRow && \Nette\Utils\Strings::contains($name, '.')) {
600 1
            $parts = explode('.', $name);
601 1
            foreach ($parts as $item) {
602 1
                if (is_object($object)) {
603 1
                    $object = $object->$item;
604 1
                }
605 1
            }
606
607 1
            return $object;
608
        }
609
610 1
        if (is_array($object)) {
611 1
            $name = "[$name]";
612 1
        }
613
614 1
        return $this->getPropertyAccessor()->getValue($object, $name);
615
    }
616
617
    /**
618
     * @return PropertyAccessor
619
     * @internal
620
     */
621
    public function getPropertyAccessor()
622
    {
623 1
        if ($this->propertyAccessor === NULL) {
624 1
            $this->propertyAccessor = new PropertyAccessor(TRUE, TRUE);
625 1
        }
626
627 1
        return $this->propertyAccessor;
628
    }
629
630
    /**
631
     * @param mixed $row item from db
632
     * @return \Nette\Utils\Html
633
     * @internal
634
     */
635
    public function getRowPrototype($row)
636
    {
637
        try {
638 1
            $primaryValue = $this->getProperty($row, $this->getPrimaryKey());
639 1
        } catch (\Exception $e) {
640 1
            $primaryValue = NULL;
641
        }
642
643 1
        $tr = \Nette\Utils\Html::el('tr');
644 1
        $primaryValue ? $tr->class[] = "grid-row-$primaryValue" : NULL;
645
646 1
        if ($this->rowCallback) {
647 1
            $tr = call_user_func_array($this->rowCallback, [$row, $tr]);
648 1
        }
649
650 1
        return $tr;
651
    }
652
653
    /**
654
     * Returns client-side options.
655
     * @return array
656
     */
657
    public function getClientSideOptions()
658
    {
659 1
        return (array) $this->options[self::CLIENT_SIDE_OPTIONS];
660
    }
661
662
    /**
663
     * @return bool
664
     */
665
    public function isStrictMode()
666
    {
667 1
        return $this->strictMode;
668
    }
669
670
    /**
671
     * @return Customization
672
     */
673
    public function getCustomization()
674
    {
675 1
        if ($this->customization === NULL) {
676 1
            $this->customization = new Customization;
677 1
        }
678
679 1
        return $this->customization;
680
    }
681
682
    /**********************************************************************************************/
683
684
    /**
685
     * Loads state informations.
686
     * @param array $params
687
     * @internal
688
     */
689
    public function loadState(array $params)
690
    {
691
        //loads state from session
692 1
        $session = $this->getRememberSession();
693 1
        if ($session && $this->getPresenter()->isSignalReceiver($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 isSignalReceiver() does only exist in the following implementations of said interface: KdybyModule\CliPresenter, 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...
694 1
            $session->remove();
695 1
        } elseif ($session && empty($params) && $session->params) {
696 1
            $params = (array) $session->params;
697 1
        }
698
699 1
        parent::loadState($params);
700 1
    }
701
702
    /**
703
     * Saves state informations for next request.
704
     * @param array $params
705
     * @param \Nette\Application\UI\PresenterComponentReflection $reflection (internal, used by Presenter)
706
     * @internal
707
     */
708
    public function saveState(array &$params, $reflection = NULL)
709
    {
710 1
        !empty($this->onRegistered) && $this->onRegistered($this);
711 1
        return parent::saveState($params, $reflection);
712
    }
713
714
    /**
715
     * Ajax method.
716
     * @internal
717
     */
718
    public function handleRefresh()
719
    {
720
        $this->reload();
721
    }
722
723
    /**
724
     * @param int $page
725
     * @internal
726
     */
727
    public function handlePage($page)
0 ignored issues
show
Unused Code introduced by
The parameter $page is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
728
    {
729 1
        $this->reload();
730
    }
731
732
    /**
733
     * @param array $sort
734
     * @internal
735
     */
736
    public function handleSort(array $sort)
0 ignored issues
show
Unused Code introduced by
The parameter $sort is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
737
    {
738 1
        $this->page = 1;
739 1
        $this->reload();
740
    }
741
742
    /**
743
     * @param \Nette\Forms\Controls\SubmitButton $button
744
     * @internal
745
     */
746
    public function handleFilter(\Nette\Forms\Controls\SubmitButton $button)
747
    {
748 1
        $values = $button->form->values[Filter::ID];
749 1
        $session = $this->rememberState //session filter
750 1
            ? isset($this->getRememberSession(TRUE)->params['filter'])
751 1
                ? $this->getRememberSession(TRUE)->params['filter']
752 1
                : []
753 1
            : [];
754
755 1
        foreach ($values as $name => $value) {
756 1
            if (is_numeric($value) || !empty($value) || isset($this->defaultFilter[$name]) || isset($session[$name])) {
757 1
                $this->filter[$name] = $this->getFilter($name)->changeValue($value);
758 1
            } elseif (isset($this->filter[$name])) {
759
                unset($this->filter[$name]);
760
            }
761 1
        }
762
763 1
        $this->page = 1;
764 1
        $this->reload();
765
    }
766
767
    /**
768
     * @param \Nette\Forms\Controls\SubmitButton $button
769
     * @internal
770
     */
771
    public function handleReset(\Nette\Forms\Controls\SubmitButton $button)
772
    {
773 1
        $this->sort = [];
774 1
        $this->filter = [];
775 1
        $this->perPage = NULL;
776
777 1
        if ($session = $this->getRememberSession()) {
778 1
            $session->remove();
779 1
        }
780
781 1
        $button->form->setValues([Filter::ID => $this->defaultFilter], TRUE);
782
783 1
        $this->page = 1;
784 1
        $this->reload();
785
    }
786
787
    /**
788
     * @param \Nette\Forms\Controls\SubmitButton $button
789
     * @internal
790
     */
791
    public function handlePerPage(\Nette\Forms\Controls\SubmitButton $button)
792
    {
793 1
        $perPage = (int) $button->form['count']->value;
794 1
        $this->perPage = $perPage == $this->defaultPerPage
795 1
            ? NULL
796
            : $perPage;
797
798 1
        $this->page = 1;
799 1
        $this->reload();
800
    }
801
802
    /**
803
     * Refresh wrapper.
804
     * @return void
805
     * @internal
806
     */
807
    public function reload()
808
    {
809 1
        if ($this->presenter->isAjax()) {
810
            $this->presenter->payload->grido = TRUE;
811
            $this->redrawControl();
812
        } else {
813 1
            $this->redirect('this');
814
        }
815
    }
816
817
    /**********************************************************************************************/
818
819
    /**
820
     * @return \Nette\Templating\FileTemplate
821
     * @internal
822
     */
823
    public function createTemplate()
824
    {
825 1
        $template = parent::createTemplate();
826 1
        $template->setFile(__DIR__ . '/templates/bootstrap.latte');
827 1
        $template->registerHelper('translate', [$this->getTranslator(), 'translate']);
828
829 1
        return $template;
830
    }
831
832
    /**
833
     * @internal
834
     * @throws Exception
835
     */
836
    public function render()
837
    {
838 1
        if (!$this->hasColumns()) {
839
            throw new Exception('Grid must have defined a column, please use method $grid->addColumn*().');
840
        }
841
842 1
        $this->saveRememberState();
843 1
        $data = $this->getData();
844
845 1
        if (!empty($this->onRender)) {
846 1
            $this->onRender($this);
847 1
        }
848
849 1
        $this->template->data = $data;
850 1
        $this->template->form = $form = $this['form'];
851 1
        $this->template->paginator = $this->getPaginator();
0 ignored issues
show
Bug introduced by
Accessing paginator on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
852
853 1
        $this->template->columns = $this->getComponent(Column::ID)->getComponents();
2 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...
Bug introduced by
Accessing columns on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method getComponents() does only exist in the following implementations of said interface: Grido\Components\Actions\Action, Grido\Components\Actions\Event, Grido\Components\Actions\Href, Grido\Components\Columns\Column, Grido\Components\Columns\Date, Grido\Components\Columns\Editable, Grido\Components\Columns\Email, Grido\Components\Columns\Link, Grido\Components\Columns\Number, Grido\Components\Columns\Text, Grido\Components\Component, Grido\Components\Container, Grido\Components\Export, Grido\Components\Filters\Check, Grido\Components\Filters\Custom, Grido\Components\Filters\Date, Grido\Components\Filters\DateRange, Grido\Components\Filters\Filter, Grido\Components\Filters\Number, Grido\Components\Filters\Select, Grido\Components\Filters\Text, Grido\Components\Operation, Grido\Grid, KdybyModule\CliPresenter, Nette\Application\UI\Control, Nette\Application\UI\Form, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Nette\ComponentModel\Container, Nette\Forms\Container, Nette\Forms\Form.

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...
854 1
        $this->template->actions = $this->hasActions() ? $this->getComponent(Action::ID)->getComponents() : [];
2 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...
Bug introduced by
Accessing actions on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method getComponents() does only exist in the following implementations of said interface: Grido\Components\Actions\Action, Grido\Components\Actions\Event, Grido\Components\Actions\Href, Grido\Components\Columns\Column, Grido\Components\Columns\Date, Grido\Components\Columns\Editable, Grido\Components\Columns\Email, Grido\Components\Columns\Link, Grido\Components\Columns\Number, Grido\Components\Columns\Text, Grido\Components\Component, Grido\Components\Container, Grido\Components\Export, Grido\Components\Filters\Check, Grido\Components\Filters\Custom, Grido\Components\Filters\Date, Grido\Components\Filters\DateRange, Grido\Components\Filters\Filter, Grido\Components\Filters\Number, Grido\Components\Filters\Select, Grido\Components\Filters\Text, Grido\Components\Operation, Grido\Grid, KdybyModule\CliPresenter, Nette\Application\UI\Control, Nette\Application\UI\Form, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Nette\ComponentModel\Container, Nette\Forms\Container, Nette\Forms\Form.

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...
855 1
        $this->template->formFilters = $this->hasFilters() ? $form->getComponent(Filter::ID)->getComponents() : [];
1 ignored issue
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...
Bug introduced by
Accessing formFilters on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
856 1
        $this->template->customization = $this->getCustomization();
1 ignored issue
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...
Bug introduced by
Accessing customization on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
857
858 1
        $form['count']->setValue($this->getPerPage());
859
860 1
        if ($options = $this->options[self::CLIENT_SIDE_OPTIONS]) {
861 1
            $this->getTablePrototype()->data[self::CLIENT_SIDE_OPTIONS] = json_encode($options);
862 1
        }
863
864 1
        $this->template->render();
865 1
    }
866
867
    protected function saveRememberState()
868
    {
869 1
        if ($this->rememberState) {
870 1
            $session = $this->getRememberSession(TRUE);
871 1
            $params = array_keys($this->getReflection()->getPersistentParams());
872 1
            foreach ($params as $param) {
873 1
                $session->params[$param] = $this->$param;
874 1
            }
875 1
        }
876 1
    }
877
878
    protected function applyFiltering()
879
    {
880 1
        $conditions = $this->__getConditions($this->getActualFilter());
881 1
        $this->getModel()->filter($conditions);
882 1
    }
883
884
    /**
885
     * @param array $filter
886
     * @return array
887
     * @internal
888
     */
889
    public function __getConditions(array $filter)
890
    {
891 1
        $conditions = [];
892 1
        if (!empty($filter)) {
893
            try {
894 1
                $this['form']->setDefaults([Filter::ID => $filter]);
895 1
            } catch (\Nette\InvalidArgumentException $e) {
896
                $this->__triggerUserNotice($e->getMessage());
897
                $filter = [];
898
                if ($session = $this->getRememberSession()) {
899
                    $session->remove();
900
                }
901
            }
902
903 1
            foreach ($filter as $column => $value) {
904 1
                if ($component = $this->getFilter($column, FALSE)) {
905 1
                    if ($condition = $component->__getCondition($value)) {
906 1
                        $conditions[] = $condition;
907 1
                    }
908 1
                } else {
909 1
                    $this->__triggerUserNotice("Filter with name '$column' does not exist.");
910
                }
911 1
            }
912 1
        }
913
914 1
        return $conditions;
915
    }
916
917
    protected function applySorting()
918
    {
919 1
        $sort = [];
920 1
        $this->sort = $this->sort ? $this->sort : $this->defaultSort;
921
922 1
        foreach ($this->sort as $column => $dir) {
923 1
            $component = $this->getColumn($column, FALSE);
924 1
            if (!$component) {
925 1
                if (!isset($this->defaultSort[$column])) {
926 1
                    $this->__triggerUserNotice("Column with name '$column' does not exist.");
927 1
                    break;
928
                }
929
930 1
            } elseif (!$component->isSortable()) {
931 1
                if (isset($this->defaultSort[$column])) {
932 1
                    $component->setSortable();
933 1
                } else {
934 1
                    $this->__triggerUserNotice("Column with name '$column' is not sortable.");
935 1
                    break;
936
                }
937 1
            }
938
939 1
            if (!in_array($dir, [Column::ORDER_ASC, Column::ORDER_DESC])) {
940 1
                if ($dir == '' && isset($this->defaultSort[$column])) {
941
                    unset($this->sort[$column]);
942
                    break;
943
                }
944
945 1
                $this->__triggerUserNotice("Dir '$dir' is not allowed.");
946 1
                break;
947
            }
948
949 1
            $sort[$component ? $component->column : $column] = $dir == Column::ORDER_ASC ? 'ASC' : 'DESC';
0 ignored issues
show
Documentation introduced by
The property $column is declared protected in Grido\Components\Columns\Column. 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...
950 1
        }
951
952 1
        if (!empty($sort)) {
953 1
            $this->getModel()->sort($sort);
954 1
        }
955 1
    }
956
957
    protected function applyPaging()
958
    {
959 1
        $paginator = $this->getPaginator()
960 1
            ->setItemCount($this->getCount())
961 1
            ->setPage($this->page);
962
963 1
        $perPage = $this->getPerPage();
964 1
        if ($perPage !== NULL && !in_array($perPage, $this->perPageList)) {
965 1
            $this->__triggerUserNotice("The number '$perPage' of items per page is out of range.");
966 1
        }
967
968 1
        $this->getModel()->limit($paginator->getOffset(), $paginator->getLength());
969 1
    }
970
971
    protected function createComponentForm($name)
972
    {
973 1
        $form = new \Nette\Application\UI\Form($this, $name);
974 1
        $form->setTranslator($this->getTranslator());
975 1
        $form->setMethod($form::GET);
976
977 1
        $buttons = $form->addContainer(self::BUTTONS);
978 1
        $buttons->addSubmit('search', 'Grido.Search')
979 1
            ->onClick[] = [$this, 'handleFilter'];
980 1
        $buttons->addSubmit('reset', 'Grido.Reset')
981 1
            ->onClick[] = [$this, 'handleReset'];
982 1
        $buttons->addSubmit('perPage', 'Grido.ItemsPerPage')
983 1
            ->onClick[] = [$this, 'handlePerPage'];
984
985 1
        $form->addSelect('count', 'Count', $this->getItemsForCountSelect())
986 1
            ->setTranslator(NULL)
987 1
            ->controlPrototype->attrs['title'] = $this->getTranslator()->translate('Grido.ItemsPerPage');
988 1
    }
989
990
    /**
991
     * @return array
992
     */
993
    protected function getItemsForCountSelect()
994
    {
995 1
        return array_combine($this->perPageList, $this->perPageList);
996
    }
997
998
    /**
999
     * @internal
1000
     * @param string $message
1001
     */
1002
    public function __triggerUserNotice($message)
1003
    {
1004 1
        if ($this->getPresenter(FALSE) && $session = $this->getRememberSession()) {
1005 1
            $session->remove();
1006 1
        }
1007
1008 1
        $this->strictMode && trigger_error($message, E_USER_NOTICE);
1009 1
    }
1010
}
1011