Completed
Pull Request — master (#263)
by AntikCz
24:54
created

Grid::setPerPageList()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
ccs 5
cts 5
cp 1
rs 9.4285
cc 3
eloc 5
nc 2
nop 1
crap 3
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 1
 * @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 1
120
    /** @var \Nette\Localization\ITranslator */
121
    protected $translator;
122
123
    /** @var PropertyAccessor */
124
    protected $propertyAccessor;
125
126
    /** @var bool */
127
    protected $strictMode = TRUE;
128
129
    /** @var array */
130
    protected $options = [
131
        self::CLIENT_SIDE_OPTIONS => []
132
    ];
133
134
    /** @var Customization */
135
    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
147 1
            ? $model
148 1
            : new DataSources\Model($model);
149
150 1
        return $this;
151
    }
152
153
    /**
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
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 1
        $this->onRender[] = function() use ($file) {
280 1
            $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 1
            $this->getTemplate()->setFile($file);
282 1
        };
283 1
284 1
        return $this;
285
    }
286
287
    /**
288
     * Sets saving state to session.
289
     * @param bool $state
290
     * @param string $sectionName
291
     * @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($this);
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($this->getCustomization()->getTemplateFiles()[Customization::TEMPLATE_DEFAULT]);
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();
0 ignored issues
show
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\Exports\BaseExport, Grido\Components\Exports\CsvExport, 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() : [];
0 ignored issues
show
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\Exports\BaseExport, Grido\Components\Exports\CsvExport, 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() : [];
0 ignored issues
show
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();
0 ignored issues
show
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