Completed
Push — master ( 468379...ad9d92 )
by Petr
23:49 queued 21:04
created

Grid::setDefaultSort()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
ccs 9
cts 9
cp 1
rs 9.4285
cc 3
eloc 8
nc 3
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\Button;
16
use Grido\Components\Paginator;
17
use Grido\Components\Columns\Column;
18
use Grido\Components\Filters\Filter;
19
use Grido\Components\Actions\Action;
20
21
use Symfony\Component\PropertyAccess\PropertyAccessor;
22
23
/**
24
 * Grido - DataGrid for Nette Framework.
25
 *
26
 * @package     Grido
27
 * @author      Petr Bugyík
28
 *
29 1
 * @property-read int $count
30
 * @property-read mixed $data
31
 * @property-read \Nette\Utils\Html $tablePrototype
32
 * @property-read PropertyAccessor $propertyAccessor
33
 * @property-read Customization $customization
34
 * @property-write string $templateFile
35
 * @property bool $rememberState
36
 * @property array $defaultPerPage
37
 * @property array $defaultFilter
38
 * @property array $defaultSort
39
 * @property array $perPageList
40 1
 * @property \Nette\Localization\ITranslator $translator
41
 * @property Paginator $paginator
42 1
 * @property string $primaryKey
43
 * @property string $filterRenderType
44 1
 * @property DataSources\IDataSource $model
45
 * @property callback $rowCallback
46 1
 * @property bool $strictMode
47
 * @method void onRegistered(Grid $grid)
48
 * @method void onRender(Grid $grid)
49
 * @method void onFetchData(Grid $grid)
50 1
 */
51
class Grid extends Components\Container
52 1
{
53
    /***** DEFAULTS ****/
54
    const BUTTONS = 'buttons';
55
56
    const CLIENT_SIDE_OPTIONS = 'grido-options';
57
58
    /** @var int @persistent */
59
    public $page = 1;
60
61
    /** @var int @persistent */
62
    public $perPage;
63
64
    /** @var array @persistent */
65
    public $sort = [];
66
67 1
    /** @var array @persistent */
68
    public $filter = [];
69
70
    /** @var array event on all grid's components registered */
71
    public $onRegistered;
72
73
    /** @var array event on render */
74
    public $onRender;
75
76
    /** @var array event for modifying data */
77
    public $onFetchData;
78
79
    /** @var callback returns tr html element; function($row, Html $tr) */
80
    protected $rowCallback;
81
82
    /** @var \Nette\Utils\Html */
83
    protected $tablePrototype;
84
85
    /** @var bool */
86
    protected $rememberState = FALSE;
87
88
    /** @var string */
89
    protected $rememberStateSectionName;
90
91
    /** @var string */
92
    protected $primaryKey = 'id';
93
94
    /** @var string */
95
    protected $filterRenderType;
96
97
    /** @var array */
98
    protected $perPageList = [10, 20, 30, 50, 100];
99
100
    /** @var int */
101
    protected $defaultPerPage = 20;
102
103
    /** @var array */
104
    protected $defaultFilter = [];
105
106
    /** @var array */
107
    protected $defaultSort = [];
108
109
    /** @var DataSources\IDataSource */
110
    protected $model;
111
112
    /** @var int total count of items */
113
    protected $count;
114
115
    /** @var mixed */
116
    protected $data;
117
118
    /** @var Paginator */
119
    protected $paginator;
120 1
121
    /** @var \Nette\Localization\ITranslator */
122
    protected $translator;
123
124
    /** @var PropertyAccessor */
125
    protected $propertyAccessor;
126
127
    /** @var bool */
128
    protected $strictMode = TRUE;
129
130
    /** @var array */
131
    protected $options = [
132
        self::CLIENT_SIDE_OPTIONS => []
133
    ];
134
135
    /** @var Customization */
136
    protected $customization;
137
138
    /**
139
     * Sets a model that implements the interface Grido\DataSources\IDataSource or data-source object.
140
     * @param mixed $model
141
     * @param bool $forceWrapper
142 1
     * @throws Exception
143
     * @return Grid
144
     */
145
    public function setModel($model, $forceWrapper = FALSE)
146
    {
147 1
        $this->model = $model instanceof DataSources\IDataSource && $forceWrapper === FALSE
148 1
            ? $model
149 1
            : new DataSources\Model($model);
150 1
151 1
        return $this;
152
    }
153
154
    /**
155
     * Sets the default number of items per page.
156
     * @param int $perPage
157
     * @return Grid
158
     */
159
    public function setDefaultPerPage($perPage)
160 1
    {
161 1
        $perPage = (int) $perPage;
162 1
        $this->defaultPerPage = $perPage;
163
164 1
        if (!in_array($perPage, $this->perPageList)) {
165 1
            $this->perPageList[] = $perPage;
166 1
            sort($this->perPageList);
167 1
        }
168 1
169 1
        return $this;
170
    }
171
172
    /**
173
     * Sets default filtering.
174
     * @param array $filter
175
     * @return Grid
176
     */
177
    public function setDefaultFilter(array $filter)
178
    {
179 1
        $this->defaultFilter = array_merge($this->defaultFilter, $filter);
180 1
        return $this;
181
    }
182
183
    /**
184
     * Sets default sorting.
185
     * @param array $sort
186
     * @return Grid
187 1
     * @throws Exception
188
     */
189
    public function setDefaultSort(array $sort)
190
    {
191 1
        static $replace = ['asc' => Column::ORDER_ASC, 'desc' => Column::ORDER_DESC];
192
193 1
        foreach ($sort as $column => $dir) {
194 1
            $dir = strtr(strtolower($dir), $replace);
195 1
            if (!in_array($dir, $replace)) {
196 1
                throw new Exception("Dir '$dir' for column '$column' is not allowed.");
197
            }
198
199 1
            $this->defaultSort[$column] = $dir;
200 1
        }
201
202 1
        return $this;
203 1
    }
204
205
    /**
206
     * Sets items to per-page select.
207
     * @param array $perPageList
208 1
     * @return Grid
209
     */
210
    public function setPerPageList(array $perPageList)
211
    {
212 1
        $this->perPageList = $perPageList;
213
214 1
        if ($this->hasFilters(FALSE) || $this->hasOperation(FALSE)) {
215 1
            $this['form']['count']->setItems($this->getItemsForCountSelect());
216 1
        }
217
218 1
        return $this;
219
    }
220 1
221
    /**
222
     * Sets translator.
223
     * @param \Nette\Localization\ITranslator $translator
224
     * @return Grid
225
     */
226
    public function setTranslator(\Nette\Localization\ITranslator $translator)
227
    {
228 1
        $this->translator = $translator;
229 1
        return $this;
230
    }
231
232
    /**
233
     * Sets type of filter rendering.
234
     * Defaults inner (Filter::RENDER_INNER) if column does not exist then outer filter (Filter::RENDER_OUTER).
235
     * @param string $type
236 1
     * @throws Exception
237
     * @return Grid
238
     */
239
    public function setFilterRenderType($type)
240
    {
241 1
        $type = strtolower($type);
242 1
        if (!in_array($type, [Filter::RENDER_INNER, Filter::RENDER_OUTER])) {
243 1
            throw new Exception('Type must be Filter::RENDER_INNER or Filter::RENDER_OUTER.');
244
        }
245
246 1
        $this->filterRenderType = $type;
247 1
        return $this;
248
    }
249
250
    /**
251
     * Sets custom paginator.
252
     * @param Paginator $paginator
253
     * @return Grid
254
     */
255
    public function setPaginator(Paginator $paginator)
256
    {
257 1
        $this->paginator = $paginator;
258 1
        return $this;
259
    }
260
261
    /**
262
     * Sets grid primary key.
263
     * Defaults is "id".
264
     * @param string $key
265
     * @return Grid
266
     */
267
    public function setPrimaryKey($key)
268
    {
269 1
        $this->primaryKey = $key;
270 1
        return $this;
271
    }
272
273
    /**
274
     * Sets file name of custom template.
275
     * @param string $file
276
     * @return Grid
277
     */
278
    public function setTemplateFile($file)
279
    {
280 1
        $this->onRender[] = function() use ($file) {
281 1
            $this->getTemplate()->add('gridoTemplate', $this->getTemplate()->getFile());
282 1
            $this->getTemplate()->setFile($file);
283 1
        };
284
285 1
        return $this;
286
    }
287
288
    /**
289
     * Sets saving state to session.
290
     * @param bool $state
291
     * @param string $sectionName
292
     * @return Grid
293
     */
294
    public function setRememberState($state = TRUE, $sectionName = NULL)
295
    {
296 1
        $this->getPresenter(); //component must be attached to presenter
297 1
        $this->getRememberSession(TRUE); //start session if not
298 1
        $this->rememberState = (bool) $state;
299 1
        $this->rememberStateSectionName = $sectionName;
300
301 1
        return $this;
302
    }
303
304
    /**
305
     * Sets callback for customizing tr html object.
306
     * Callback returns tr html element; function($row, Html $tr).
307
     * @param $callback
308
     * @return Grid
309
     */
310
    public function setRowCallback($callback)
311
    {
312 1
        $this->rowCallback = $callback;
313 1
        return $this;
314
    }
315
316
    /**
317
     * Sets client-side options.
318
     * @param array $options
319
     * @return Grid
320
     */
321
    public function setClientSideOptions(array $options)
322
    {
323 1
        $this->options[self::CLIENT_SIDE_OPTIONS] = $options;
324 1
        return $this;
325
    }
326
327
    /**
328
     * Determines whether any user error will cause a notice.
329
     * @param bool $mode
330
     * @return \Grido\Grid
331
     */
332
    public function setStrictMode($mode)
333
    {
334 1
        $this->strictMode = (bool) $mode;
335 1
        return $this;
336
    }
337
338
    /**
339
     * @param \Grido\Customization $customization
340
     */
341
    public function setCustomization(Customization $customization)
342
    {
343 1
        $this->customization = $customization;
344 1
    }
345
346
    /**********************************************************************************************/
347
348
    /**
349
     * Returns total count of data.
350
     * @return int
351
     */
352
    public function getCount()
353
    {
354 1
        if ($this->count === NULL) {
355 1
            $this->count = $this->getModel()->getCount();
356 1
        }
357
358 1
        return $this->count;
359
    }
360
361
    /**
362
     * Returns default per page.
363
     * @return int
364
     */
365
    public function getDefaultPerPage()
366
    {
367 1
        if (!in_array($this->defaultPerPage, $this->perPageList)) {
368 1
            $this->defaultPerPage = $this->perPageList[0];
369 1
        }
370
371 1
        return $this->defaultPerPage;
372
    }
373
374
    /**
375
     * Returns default filter.
376
     * @return array
377
     */
378
    public function getDefaultFilter()
379
    {
380 1
        return $this->defaultFilter;
381
    }
382
383
    /**
384
     * Returns default sort.
385
     * @return array
386
     */
387
    public function getDefaultSort()
388
    {
389 1
        return $this->defaultSort;
390
    }
391 1
392
    /**
393
     * Returns list of possible items per page.
394
     * @return array
395
     */
396
    public function getPerPageList()
397
    {
398 1
        return $this->perPageList;
399
    }
400
401
    /**
402
     * Returns primary key.
403
     * @return string
404
     */
405
    public function getPrimaryKey()
406
    {
407 1
        return $this->primaryKey;
408
    }
409
410
    /**
411
     * Returns remember state.
412
     * @return bool
413
     */
414
    public function getRememberState()
415
    {
416 1
        return $this->rememberState;
417
    }
418
419 1
    /**
420
     * Returns row callback.
421
     * @return callback
422
     */
423
    public function getRowCallback()
424
    {
425 1
        return $this->rowCallback;
426
    }
427
428
    /**
429
     * Returns items per page.
430
     * @return int
431
     */
432
    public function getPerPage()
433
    {
434 1
        return $this->perPage === NULL
435 1
            ? $this->getDefaultPerPage()
436 1
            : $this->perPage;
437
    }
438
439
    /**
440
     * Returns actual filter values.
441
     * @param string $key
442
     * @return mixed
443
     */
444
    public function getActualFilter($key = NULL)
445
    {
446 1
        $filter = $this->filter ? $this->filter : $this->defaultFilter;
447 1
        return $key !== NULL && isset($filter[$key]) ? $filter[$key] : $filter;
448
    }
449
450
    /**
451
     * Returns fetched data.
452
     * @param bool $applyPaging
453
     * @param bool $useCache
454
     * @param bool $fetch
455
     * @throws Exception
456
     * @return array|DataSources\IDataSource|\Nette\Database\Table\Selection
457
     */
458
    public function getData($applyPaging = TRUE, $useCache = TRUE, $fetch = TRUE)
459
    {
460 1
        if ($this->getModel() === NULL) {
461 1
            throw new Exception('Model cannot be empty, please use method $grid->setModel().');
462
        }
463
464 1
        $data = $this->data;
465 1
        if ($data === NULL || $useCache === FALSE) {
466 1
            $this->applyFiltering();
467 1
            $this->applySorting();
468
469 1
            if ($applyPaging) {
470 1
                $this->applyPaging();
471 1
            }
472
473 1
            if ($fetch === FALSE) {
474 1
                return $this->getModel();
475
            }
476
477 1
            $data = $this->getModel()->getData();
478
479 1
            if ($useCache === TRUE) {
480 1
                $this->data = $data;
481 1
            }
482
483 1
            if ($applyPaging && !empty($data) && !in_array($this->page, range(1, $this->getPaginator()->pageCount))) {
484 1
                $this->__triggerUserNotice("Page is out of range.");
485 1
                $this->page = 1;
486 1
            }
487
488 1
            if (!empty($this->onFetchData)) {
489
                $this->onFetchData($this);
490
            }
491 1
        }
492
493 1
        return $data;
494
    }
495
496
    /**
497
     * Returns translator.
498
     * @return Translations\FileTranslator
499
     */
500
    public function getTranslator()
501
    {
502 1
        if ($this->translator === NULL) {
503 1
            $this->setTranslator(new Translations\FileTranslator);
504 1
        }
505
506 1
        return $this->translator;
507
    }
508
509
    /**
510
     * Returns remember session for set expiration, etc.
511
     * @param bool $forceStart - if TRUE, session will be started if not
512
     * @return \Nette\Http\SessionSection|NULL
513
     */
514
    public function getRememberSession($forceStart = FALSE)
515
    {
516 1
        $presenter = $this->getPresenter();
517 1
        $session = $presenter->getSession();
518
519 1
        if (!$session->isStarted() && $forceStart) {
520 1
            $session->start();
521 1
        }
522
523 1
        return $session->isStarted()
524 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...
525 1
            : NULL;
526
    }
527
528
    /**
529
     * Returns table html element of grid.
530
     * @return \Nette\Utils\Html
531
     */
532
    public function getTablePrototype()
533
    {
534 1
        if ($this->tablePrototype === NULL) {
535 1
            $this->tablePrototype = \Nette\Utils\Html::el('table');
536 1
            $this->tablePrototype->id($this->getName());
537 1
        }
538
539 1
        return $this->tablePrototype;
540
    }
541
542
    /**
543
     * @return string
544
     * @internal
545
     */
546
    public function getFilterRenderType()
547
    {
548 1
        if ($this->filterRenderType !== NULL) {
549 1
            return $this->filterRenderType;
550
        }
551
552 1
        $this->filterRenderType = Filter::RENDER_OUTER;
553 1
        if ($this->hasColumns() && $this->hasFilters() && $this->hasActions()) {
554 1
            $this->filterRenderType = Filter::RENDER_INNER;
555
556 1
            $filters = $this[Filter::ID]->getComponents();
557 1
            foreach ($filters as $filter) {
558 1
                if (!$this[Column::ID]->getComponent($filter->name, FALSE)) {
559 1
                    $this->filterRenderType = Filter::RENDER_OUTER;
560 1
                    break;
561
                }
562 1
            }
563 1
        }
564
565 1
        return $this->filterRenderType;
566
    }
567
568
    /**
569
     * @return DataSources\IDataSource
570
     */
571
    public function getModel()
572
    {
573 1
        return $this->model;
574
    }
575
576
    /**
577
     * @return Paginator
578
     * @internal
579
     */
580
    public function getPaginator()
581
    {
582 1
        if ($this->paginator === NULL) {
583 1
            $this->paginator = new Paginator;
584 1
            $this->paginator->setItemsPerPage($this->getPerPage())
585 1
                ->setGrid($this);
586 1
        }
587
588 1
        return $this->paginator;
589
    }
590
591
    /**
592
     * A simple wrapper around symfony/property-access with Nette Database dot notation support.
593
     * @param array|object $object
594
     * @param string $name
595
     * @return mixed
596
     * @internal
597
     */
598
    public function getProperty($object, $name)
599
    {
600 1
        if ($object instanceof \Nette\Database\Table\IRow && \Nette\Utils\Strings::contains($name, '.')) {
601 1
            $parts = explode('.', $name);
602 1
            foreach ($parts as $item) {
603 1
                if (is_object($object)) {
604 1
                    $object = $object->$item;
605 1
                }
606 1
            }
607
608 1
            return $object;
609
        }
610
611 1
        if (is_array($object)) {
612 1
            $name = "[$name]";
613 1
        }
614
615 1
        return $this->getPropertyAccessor()->getValue($object, $name);
616
    }
617
618
    /**
619
     * @return PropertyAccessor
620
     * @internal
621
     */
622
    public function getPropertyAccessor()
623
    {
624 1
        if ($this->propertyAccessor === NULL) {
625 1
            $this->propertyAccessor = new PropertyAccessor(TRUE, TRUE);
626 1
        }
627
628 1
        return $this->propertyAccessor;
629
    }
630
631
    /**
632
     * @param mixed $row item from db
633
     * @return \Nette\Utils\Html
634
     * @internal
635
     */
636
    public function getRowPrototype($row)
637
    {
638
        try {
639 1
            $primaryValue = $this->getProperty($row, $this->getPrimaryKey());
640 1
        } catch (\Exception $e) {
641 1
            $primaryValue = NULL;
642
        }
643
644 1
        $tr = \Nette\Utils\Html::el('tr');
645 1
        $primaryValue ? $tr->class[] = "grid-row-$primaryValue" : NULL;
646
647 1
        if ($this->rowCallback) {
648 1
            $tr = call_user_func_array($this->rowCallback, [$row, $tr]);
649 1
        }
650
651 1
        return $tr;
652
    }
653
654
    /**
655
     * Returns client-side options.
656
     * @return array
657
     */
658
    public function getClientSideOptions()
659
    {
660 1
        return (array) $this->options[self::CLIENT_SIDE_OPTIONS];
661
    }
662
663
    /**
664
     * @return bool
665
     */
666
    public function isStrictMode()
667
    {
668 1
        return $this->strictMode;
669
    }
670
671
    /**
672
     * @return Customization
673
     */
674
    public function getCustomization()
675
    {
676 1
        if ($this->customization === NULL) {
677 1
            $this->customization = new Customization($this);
678 1
        }
679
680 1
        return $this->customization;
681
    }
682
683
    /**********************************************************************************************/
684
685
    /**
686
     * Loads state informations.
687
     * @param array $params
688
     * @internal
689
     */
690
    public function loadState(array $params)
691
    {
692
        //loads state from session
693 1
        $session = $this->getRememberSession();
694 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...
695 1
            $session->remove();
696 1
        } elseif ($session && empty($params) && $session->params) {
697 1
            $params = (array) $session->params;
698 1
        }
699
700 1
        parent::loadState($params);
701 1
    }
702
703
    /**
704
     * Saves state informations for next request.
705
     * @param array $params
706
     * @param \Nette\Application\UI\PresenterComponentReflection $reflection (internal, used by Presenter)
707
     * @internal
708
     */
709
    public function saveState(array &$params, $reflection = NULL)
710
    {
711 1
        !empty($this->onRegistered) && $this->onRegistered($this);
712 1
        return parent::saveState($params, $reflection);
713
    }
714
715
    /**
716
     * Ajax method.
717
     * @internal
718
     */
719
    public function handleRefresh()
720
    {
721
        $this->reload();
722
    }
723
724
    /**
725
     * @param int $page
726
     * @internal
727
     */
728
    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...
729
    {
730 1
        $this->reload();
731
    }
732
733
    /**
734
     * @param array $sort
735
     * @internal
736
     */
737
    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...
738
    {
739 1
        $this->page = 1;
740 1
        $this->reload();
741
    }
742
743
    /**
744
     * @param \Nette\Forms\Controls\SubmitButton $button
745
     * @internal
746
     */
747
    public function handleFilter(\Nette\Forms\Controls\SubmitButton $button)
748
    {
749 1
        $values = $button->form->values[Filter::ID];
750 1
        $session = $this->rememberState //session filter
751 1
            ? isset($this->getRememberSession(TRUE)->params['filter'])
752 1
                ? $this->getRememberSession(TRUE)->params['filter']
753 1
                : []
754 1
            : [];
755
756 1
        foreach ($values as $name => $value) {
757 1
            if (is_numeric($value) || !empty($value) || isset($this->defaultFilter[$name]) || isset($session[$name])) {
758 1
                $this->filter[$name] = $this->getFilter($name)->changeValue($value);
759 1
            } elseif (isset($this->filter[$name])) {
760
                unset($this->filter[$name]);
761
            }
762 1
        }
763
764 1
        $this->page = 1;
765 1
        $this->reload();
766
    }
767
768
    /**
769
     * @param \Nette\Forms\Controls\SubmitButton $button
770
     * @internal
771
     */
772
    public function handleReset(\Nette\Forms\Controls\SubmitButton $button)
773
    {
774 1
        $this->sort = [];
775 1
        $this->filter = [];
776 1
        $this->perPage = NULL;
777
778 1
        if ($session = $this->getRememberSession()) {
779 1
            $session->remove();
780 1
        }
781
782 1
        $button->form->setValues([Filter::ID => $this->defaultFilter], TRUE);
783
784 1
        $this->page = 1;
785 1
        $this->reload();
786
    }
787
788
    /**
789
     * @param \Nette\Forms\Controls\SubmitButton $button
790
     * @internal
791
     */
792
    public function handlePerPage(\Nette\Forms\Controls\SubmitButton $button)
793
    {
794 1
        $perPage = (int) $button->form['count']->value;
795 1
        $this->perPage = $perPage == $this->defaultPerPage
796 1
            ? NULL
797
            : $perPage;
798
799 1
        $this->page = 1;
800 1
        $this->reload();
801
    }
802
803
    /**
804
     * Refresh wrapper.
805
     * @return void
806
     * @internal
807
     */
808
    public function reload()
809
    {
810 1
        if ($this->presenter->isAjax()) {
811
            $this->presenter->payload->grido = TRUE;
812
            $this->redrawControl();
813
        } else {
814 1
            $this->redirect('this');
815
        }
816
    }
817
818
    /**********************************************************************************************/
819
820
    /**
821
     * @return \Nette\Templating\FileTemplate
822
     * @internal
823
     */
824
    public function createTemplate()
825
    {
826 1
        $template = parent::createTemplate();
827 1
        $template->setFile($this->getCustomization()->getTemplateFiles()[Customization::TEMPLATE_DEFAULT]);
828 1
        $template->getLatte()->addFilter('translate', [$this->getTranslator(), 'translate']);
829
830 1
        return $template;
831
    }
832
833
    /**
834
     * @internal
835
     * @throws Exception
836
     */
837
    public function render()
838
    {
839 1
        if (!$this->hasColumns()) {
840
            throw new Exception('Grid must have defined a column, please use method $grid->addColumn*().');
841
        }
842
843 1
        $this->saveRememberState();
844 1
        $data = $this->getData();
845
846 1
        if (!empty($this->onRender)) {
847 1
            $this->onRender($this);
848 1
        }
849
850 1
        $form = $this['form'];
851
852 1
        $this->getTemplate()->add('data', $data);
853 1
        $this->getTemplate()->add('form', $form);
854 1
        $this->getTemplate()->add('paginator', $this->getPaginator());
855 1
        $this->getTemplate()->add('customization', $this->getCustomization());
856 1
        $this->getTemplate()->add('columns', $this->getComponent(Column::ID)->getComponents());
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 getComponents() does only exist in the following implementations of said interface: Grido\Components\Container, Grido\Grid, KdybyModule\CliPresenter, Nette\Application\UI\Component, Nette\Application\UI\Control, Nette\Application\UI\Form, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, 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...
857 1
        $this->getTemplate()->add('actions', $this->hasActions()
858 1
            ? $this->getComponent(Action::ID)->getComponents()
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 getComponents() does only exist in the following implementations of said interface: Grido\Components\Container, Grido\Grid, KdybyModule\CliPresenter, Nette\Application\UI\Component, Nette\Application\UI\Control, Nette\Application\UI\Form, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, 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...
859 1
            : []
860 1
        );
861
862 1
        $this->getTemplate()->add('buttons', $this->hasButtons()
863 1
            ? $this->getComponent(Button::ID)->getComponents()
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 getComponents() does only exist in the following implementations of said interface: Grido\Components\Container, Grido\Grid, KdybyModule\CliPresenter, Nette\Application\UI\Component, Nette\Application\UI\Control, Nette\Application\UI\Form, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, 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...
864 1
            : []
865 1
        );
866
867 1
        $this->getTemplate()->add('formFilters', $this->hasFilters()
868 1
            ? $form->getComponent(Filter::ID)->getComponents()
869 1
            : []
870 1
        );
871
872 1
        $form['count']->setValue($this->getPerPage());
873
874 1
        if ($options = $this->options[self::CLIENT_SIDE_OPTIONS]) {
875 1
            $this->getTablePrototype()->setAttribute('data-' . self::CLIENT_SIDE_OPTIONS, json_encode($options));
876 1
        }
877
878 1
        $this->getTemplate()->render();
879 1
    }
880
881
    protected function saveRememberState()
882
    {
883 1
        if ($this->rememberState) {
884 1
            $session = $this->getRememberSession(TRUE);
885 1
            $params = array_keys($this->getReflection()->getPersistentParams());
886 1
            foreach ($params as $param) {
887 1
                $session->params[$param] = $this->$param;
888 1
            }
889 1
        }
890 1
    }
891
892
    protected function applyFiltering()
893
    {
894 1
        $conditions = $this->__getConditions($this->getActualFilter());
895 1
        $this->getModel()->filter($conditions);
896 1
    }
897
898
    /**
899
     * @param array $filter
900
     * @return array
901
     * @internal
902
     */
903
    public function __getConditions(array $filter)
904
    {
905 1
        $conditions = [];
906 1
        if (!empty($filter)) {
907
            try {
908 1
                $this['form']->setDefaults([Filter::ID => $filter]);
909 1
            } catch (\Nette\InvalidArgumentException $e) {
910
                $this->__triggerUserNotice($e->getMessage());
911
                $filter = [];
912
                if ($session = $this->getRememberSession()) {
913
                    $session->remove();
914
                }
915
            }
916
917 1
            foreach ($filter as $column => $value) {
918 1
                if ($component = $this->getFilter($column, FALSE)) {
919 1
                    if ($condition = $component->__getCondition($value)) {
920 1
                        $conditions[] = $condition;
921 1
                    }
922 1
                } else {
923 1
                    $this->__triggerUserNotice("Filter with name '$column' does not exist.");
924
                }
925 1
            }
926 1
        }
927
928 1
        return $conditions;
929
    }
930
931
    protected function applySorting()
932
    {
933 1
        $sort = [];
934 1
        $this->sort = $this->sort ? $this->sort : $this->defaultSort;
935
936 1
        foreach ($this->sort as $column => $dir) {
937 1
            $component = $this->getColumn($column, FALSE);
938 1
            if (!$component) {
939 1
                if (!isset($this->defaultSort[$column])) {
940 1
                    $this->__triggerUserNotice("Column with name '$column' does not exist.");
941 1
                    break;
942
                }
943
944 1
            } elseif (!$component->isSortable()) {
945 1
                if (isset($this->defaultSort[$column])) {
946 1
                    $component->setSortable();
947 1
                } else {
948 1
                    $this->__triggerUserNotice("Column with name '$column' is not sortable.");
949 1
                    break;
950
                }
951 1
            }
952
953 1
            if (!in_array($dir, [Column::ORDER_ASC, Column::ORDER_DESC])) {
954 1
                if ($dir == '' && isset($this->defaultSort[$column])) {
955
                    unset($this->sort[$column]);
956
                    break;
957
                }
958
959 1
                $this->__triggerUserNotice("Dir '$dir' is not allowed.");
960 1
                break;
961
            }
962
963 1
            $sort[$component ? $component->column : $column] = $dir == Column::ORDER_ASC ? 'ASC' : 'DESC';
0 ignored issues
show
Bug introduced by
The property column cannot be accessed from this context as it is declared protected in class Grido\Components\Columns\Column.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
964 1
        }
965
966 1
        if (!empty($sort)) {
967 1
            $this->getModel()->sort($sort);
968 1
        }
969 1
    }
970
971
    protected function applyPaging()
972
    {
973 1
        $paginator = $this->getPaginator()
974 1
            ->setItemCount($this->getCount())
975 1
            ->setPage($this->page);
976
977 1
        $perPage = $this->getPerPage();
978 1
        if ($perPage !== NULL && !in_array($perPage, $this->perPageList)) {
979 1
            $this->__triggerUserNotice("The number '$perPage' of items per page is out of range.");
980 1
        }
981
982 1
        $this->getModel()->limit($paginator->getOffset(), $paginator->getLength());
983 1
    }
984
985
    protected function createComponentForm($name)
986
    {
987 1
        $form = new \Nette\Application\UI\Form($this, $name);
988 1
        $form->setTranslator($this->getTranslator());
989 1
        $form->setMethod($form::GET);
990
991 1
        $buttons = $form->addContainer(self::BUTTONS);
992 1
        $buttons->addSubmit('search', 'Grido.Search')
993 1
            ->onClick[] = [$this, 'handleFilter'];
994 1
        $buttons->addSubmit('reset', 'Grido.Reset')
995 1
            ->onClick[] = [$this, 'handleReset'];
996 1
        $buttons->addSubmit('perPage', 'Grido.ItemsPerPage')
997 1
            ->onClick[] = [$this, 'handlePerPage'];
998
999 1
        $form->addSelect('count', 'Count', $this->getItemsForCountSelect())
1000 1
            ->setTranslator(NULL)
1001 1
            ->controlPrototype->attrs['title'] = $this->getTranslator()->translate('Grido.ItemsPerPage');
1002 1
    }
1003
1004
    /**
1005
     * @return array
1006
     */
1007
    protected function getItemsForCountSelect()
1008
    {
1009 1
        return array_combine($this->perPageList, $this->perPageList);
1010
    }
1011
1012
    /**
1013
     * @internal
1014
     * @param string $message
1015
     */
1016
    public function __triggerUserNotice($message)
1017
    {
1018 1
        if ($this->getPresenter(FALSE) && $session = $this->getRememberSession()) {
1019 1
            $session->remove();
1020 1
        }
1021
1022 1
        $this->strictMode && trigger_error($message, E_USER_NOTICE);
1023 1
    }
1024
}
1025