1<?php
2/**
3 * Zend Framework
4 *
5 * LICENSE
6 *
7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * It is also available through the world-wide-web at this URL:
10 * http://framework.zend.com/license/new-bsd
11 * If you did not receive a copy of the license and are unable to
12 * obtain it through the world-wide-web, please send an email
13 * to license@zend.com so we can send you a copy immediately.
14 *
15 * @category   Zend
16 * @package    Zend_Paginator
17 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
18 * @license    http://framework.zend.com/license/new-bsd     New BSD License
19 * @version    $Id$
20 */
21
22/**
23 * @see Zend_Loader_PluginLoader
24 */
25
26/**
27 * @see Zend_Json
28 */
29
30/**
31 * @category   Zend
32 * @package    Zend_Paginator
33 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
34 * @license    http://framework.zend.com/license/new-bsd     New BSD License
35 */
36class Zend_Paginator implements Countable, IteratorAggregate
37{
38    /**
39     * Specifies that the factory should try to detect the proper adapter type first
40     *
41     * @var string
42     */
43    const INTERNAL_ADAPTER = 'Zend_Paginator_Adapter_Internal';
44
45    /**
46     * The cache tag prefix used to namespace Paginator results in the cache
47     *
48     */
49    const CACHE_TAG_PREFIX = 'Zend_Paginator_';
50
51    /**
52     * Adapter plugin loader
53     *
54     * @var Zend_Loader_PluginLoader
55     */
56    protected static $_adapterLoader = null;
57
58    /**
59     * Configuration file
60     *
61     * @var Zend_Config
62     */
63    protected static $_config = null;
64
65    /**
66     * Default scrolling style
67     *
68     * @var string
69     */
70    protected static $_defaultScrollingStyle = 'Sliding';
71
72    /**
73     * Default item count per page
74     *
75     * @var int
76     */
77    protected static $_defaultItemCountPerPage = 10;
78
79    /**
80     * Default number of local pages (i.e., the number of discretes
81     * page numbers that will be displayed, including the current
82     * page number)
83     *
84     * @var int
85     */
86    protected static $_defaultPageRange = 10;
87
88    /**
89     * Scrolling style plugin loader
90     *
91     * @var Zend_Loader_PluginLoader
92     */
93    protected static $_scrollingStyleLoader = null;
94
95    /**
96     * Cache object
97     *
98     * @var Zend_Cache_Core
99     */
100    protected static $_cache;
101
102    /**
103     * Enable or disable the cache by Zend_Paginator instance
104     *
105     * @var bool
106     */
107    protected $_cacheEnabled = true;
108
109    /**
110     * Adapter
111     *
112     * @var Zend_Paginator_Adapter_Interface
113     */
114    protected $_adapter = null;
115
116    /**
117     * Number of items in the current page
118     *
119     * @var integer
120     */
121    protected $_currentItemCount = null;
122
123    /**
124     * Current page items
125     *
126     * @var Traversable
127     */
128    protected $_currentItems = null;
129
130    /**
131     * Current page number (starting from 1)
132     *
133     * @var integer
134     */
135    protected $_currentPageNumber = 1;
136
137    /**
138     * Result filter
139     *
140     * @var Zend_Filter_Interface
141     */
142    protected $_filter = null;
143
144    /**
145     * Number of items per page
146     *
147     * @var integer
148     */
149    protected $_itemCountPerPage = null;
150
151    /**
152     * Number of pages
153     *
154     * @var integer
155     */
156    protected $_pageCount = null;
157
158    /**
159     * Number of local pages (i.e., the number of discrete page numbers
160     * that will be displayed, including the current page number)
161     *
162     * @var integer
163     */
164    protected $_pageRange = null;
165
166    /**
167     * Pages
168     *
169     * @var array
170     */
171    protected $_pages = null;
172
173    /**
174     * View instance used for self rendering
175     *
176     * @var Zend_View_Interface
177     */
178    protected $_view = null;
179
180    /**
181     * Adds an adapter prefix path to the plugin loader.
182     *
183     * @param string $prefix
184     * @param string $path
185     */
186    public static function addAdapterPrefixPath($prefix, $path)
187    {
188        self::getAdapterLoader()->addPrefixPath($prefix, $path);
189    }
190
191    /**
192     * Adds an array of adapter prefix paths to the plugin
193     * loader.
194     *
195     * <code>
196     * $prefixPaths = array(
197     *     'My_Paginator_Adapter'   => 'My/Paginator/Adapter/',
198     *     'Your_Paginator_Adapter' => 'Your/Paginator/Adapter/'
199     * );
200     * </code>
201     *
202     * @param array $prefixPaths
203     */
204    public static function addAdapterPrefixPaths(array $prefixPaths)
205    {
206        if (isset($prefixPaths['prefix']) && isset($prefixPaths['path'])) {
207            self::addAdapterPrefixPath($prefixPaths['prefix'], $prefixPaths['path']);
208        } else {
209            foreach ($prefixPaths as $prefix => $path) {
210                if (is_array($path) && isset($path['prefix']) && isset($path['path'])) {
211                    $prefix = $path['prefix'];
212                    $path   = $path['path'];
213                }
214
215                self::addAdapterPrefixPath($prefix, $path);
216            }
217        }
218    }
219
220    /**
221     * Adds a scrolling style prefix path to the plugin loader.
222     *
223     * @param string $prefix
224     * @param string $path
225     */
226    public static function addScrollingStylePrefixPath($prefix, $path)
227    {
228        self::getScrollingStyleLoader()->addPrefixPath($prefix, $path);
229    }
230
231    /**
232     * Adds an array of scrolling style prefix paths to the plugin
233     * loader.
234     *
235     * <code>
236     * $prefixPaths = array(
237     *     'My_Paginator_ScrollingStyle'   => 'My/Paginator/ScrollingStyle/',
238     *     'Your_Paginator_ScrollingStyle' => 'Your/Paginator/ScrollingStyle/'
239     * );
240     * </code>
241     *
242     * @param array $prefixPaths
243     */
244    public static function addScrollingStylePrefixPaths(array $prefixPaths)
245    {
246        if (isset($prefixPaths['prefix']) && isset($prefixPaths['path'])) {
247            self::addScrollingStylePrefixPath($prefixPaths['prefix'], $prefixPaths['path']);
248        } else {
249            foreach ($prefixPaths as $prefix => $path) {
250                if (is_array($path) && isset($path['prefix']) && isset($path['path'])) {
251                    $prefix = $path['prefix'];
252                    $path   = $path['path'];
253                }
254
255                self::addScrollingStylePrefixPath($prefix, $path);
256            }
257        }
258    }
259
260    /**
261     * Factory.
262     *
263     * @param  mixed $data
264     * @param  string $adapter
265     * @param  array $prefixPaths
266     * @return Zend_Paginator
267     */
268    public static function factory($data, $adapter = self::INTERNAL_ADAPTER,
269                                   array $prefixPaths = null)
270    {
271        if ($data instanceof Zend_Paginator_AdapterAggregate) {
272            return new self($data->getPaginatorAdapter());
273        } else {
274            if ($adapter == self::INTERNAL_ADAPTER) {
275                if (is_array($data)) {
276                    $adapter = 'Array';
277                } else if ($data instanceof Zend_Db_Table_Select) {
278                    $adapter = 'DbTableSelect';
279                } else if ($data instanceof Zend_Db_Select) {
280                    $adapter = 'DbSelect';
281                } else if ($data instanceof Iterator) {
282                    $adapter = 'Iterator';
283                } else if (is_integer($data)) {
284                    $adapter = 'Null';
285                } else {
286                    $type = (is_object($data)) ? get_class($data) : gettype($data);
287
288                    /**
289                     * @see Zend_Paginator_Exception
290                     */
291
292                    throw new Zend_Paginator_Exception('No adapter for type ' . $type);
293                }
294            }
295
296            $pluginLoader = self::getAdapterLoader();
297
298            if (null !== $prefixPaths) {
299                foreach ($prefixPaths as $prefix => $path) {
300                    $pluginLoader->addPrefixPath($prefix, $path);
301                }
302            }
303
304            $adapterClassName = $pluginLoader->load($adapter);
305
306            return new self(new $adapterClassName($data));
307        }
308    }
309
310    /**
311     * Returns the adapter loader.  If it doesn't exist it's created.
312     *
313     * @return Zend_Loader_PluginLoader
314     */
315    public static function getAdapterLoader()
316    {
317        if (self::$_adapterLoader === null) {
318            self::$_adapterLoader = new Zend_Loader_PluginLoader(
319                array('Zend_Paginator_Adapter' => 'Zend/Paginator/Adapter')
320            );
321        }
322
323        return self::$_adapterLoader;
324    }
325
326    /**
327     * Set a global config
328     *
329     * @param Zend_Config $config
330     */
331    public static function setConfig(Zend_Config $config)
332    {
333        self::$_config = $config;
334
335        $adapterPaths = $config->get('adapterpaths');
336
337        if ($adapterPaths != null) {
338            self::addAdapterPrefixPaths($adapterPaths->adapterpath->toArray());
339        }
340
341        $prefixPaths = $config->get('prefixpaths');
342
343        if ($prefixPaths != null) {
344            self::addScrollingStylePrefixPaths($prefixPaths->prefixpath->toArray());
345        }
346
347        $scrollingStyle = $config->get('scrollingstyle');
348
349        if ($scrollingStyle != null) {
350            self::setDefaultScrollingStyle($scrollingStyle);
351        }
352    }
353
354    /**
355     * Returns the default scrolling style.
356     *
357     * @return  string
358     */
359    public static function getDefaultScrollingStyle()
360    {
361        return self::$_defaultScrollingStyle;
362    }
363
364    /**
365     * Get the default item count per page
366     *
367     * @return int
368     */
369    public static function getDefaultItemCountPerPage()
370    {
371        return self::$_defaultItemCountPerPage;
372    }
373
374    /**
375     * Set the default item count per page
376     *
377     * @param int $count
378     */
379    public static function setDefaultItemCountPerPage($count)
380    {
381        self::$_defaultItemCountPerPage = (int) $count;
382    }
383
384    /**
385     * Get the default page range
386     *
387     * @return int
388     */
389    public static function getDefaultPageRange()
390    {
391        return self::$_defaultPageRange;
392    }
393
394    /**
395     * Set the default page range
396     *
397     * @param int $count
398     */
399    public static function setDefaultPageRange($count)
400    {
401        self::$_defaultPageRange = (int) $count;
402    }
403
404    /**
405     * Sets a cache object
406     *
407     * @param Zend_Cache_Core $cache
408     */
409    public static function setCache(Zend_Cache_Core $cache)
410    {
411        self::$_cache = $cache;
412    }
413
414    /**
415     * Sets the default scrolling style.
416     *
417     * @param  string $scrollingStyle
418     */
419    public static function setDefaultScrollingStyle($scrollingStyle = 'Sliding')
420    {
421        self::$_defaultScrollingStyle = $scrollingStyle;
422    }
423
424    /**
425     * Returns the scrolling style loader.  If it doesn't exist it's
426     * created.
427     *
428     * @return Zend_Loader_PluginLoader
429     */
430    public static function getScrollingStyleLoader()
431    {
432        if (self::$_scrollingStyleLoader === null) {
433            self::$_scrollingStyleLoader = new Zend_Loader_PluginLoader(
434                array('Zend_Paginator_ScrollingStyle' => 'Zend/Paginator/ScrollingStyle')
435            );
436        }
437
438        return self::$_scrollingStyleLoader;
439    }
440
441    /**
442     * Constructor.
443     *
444     * @param Zend_Paginator_Adapter_Interface|Zend_Paginator_AdapterAggregate $adapter
445     */
446    public function __construct($adapter)
447    {
448        if ($adapter instanceof Zend_Paginator_Adapter_Interface) {
449            $this->_adapter = $adapter;
450        } else if ($adapter instanceof Zend_Paginator_AdapterAggregate) {
451            $this->_adapter = $adapter->getPaginatorAdapter();
452        } else {
453            /**
454             * @see Zend_Paginator_Exception
455             */
456
457            throw new Zend_Paginator_Exception(
458                'Zend_Paginator only accepts instances of the type ' .
459                'Zend_Paginator_Adapter_Interface or Zend_Paginator_AdapterAggregate.'
460            );
461        }
462
463        $config = self::$_config;
464
465        if ($config != null) {
466            $setupMethods = array('ItemCountPerPage', 'PageRange');
467
468            foreach ($setupMethods as $setupMethod) {
469                $value = $config->get(strtolower($setupMethod));
470
471                if ($value != null) {
472                    $setupMethod = 'set' . $setupMethod;
473                    $this->$setupMethod($value);
474                }
475            }
476        }
477    }
478
479    /**
480     * Serializes the object as a string.  Proxies to {@link render()}.
481     *
482     * @return string
483     */
484    public function __toString()
485    {
486        try {
487            $return = $this->render();
488            return $return;
489        } catch (Exception $e) {
490            trigger_error($e->getMessage(), E_USER_WARNING);
491        }
492
493        return '';
494    }
495
496    /**
497     * Enables/Disables the cache for this instance
498     *
499     * @param bool $enable
500     * @return Zend_Paginator
501     */
502    public function setCacheEnabled($enable)
503    {
504        $this->_cacheEnabled = (bool)$enable;
505        return $this;
506    }
507
508    /**
509     * Returns the number of pages.
510     *
511     * @return integer
512     */
513    public function count()
514    {
515        if (!$this->_pageCount) {
516            $this->_pageCount = $this->_calculatePageCount();
517        }
518
519        return $this->_pageCount;
520    }
521
522    /**
523     * Returns the total number of items available.  Uses cache if caching is enabled.
524     *
525     * @return integer
526     */
527    public function getTotalItemCount()
528    {
529        if (!$this->_cacheEnabled()) {
530            return count($this->getAdapter());
531        } else {
532            $cacheId   = md5($this->_getCacheInternalId(). '_itemCount');
533            $itemCount = self::$_cache->load($cacheId);
534
535            if ($itemCount === false) {
536                $itemCount = count($this->getAdapter());
537
538                self::$_cache->save($itemCount, $cacheId, array($this->_getCacheInternalId()));
539            }
540
541            return $itemCount;
542        }
543    }
544
545    /**
546     * Clear the page item cache.
547     *
548     * @param int $pageNumber
549     * @return Zend_Paginator
550     */
551    public function clearPageItemCache($pageNumber = null)
552    {
553        if (!$this->_cacheEnabled()) {
554            return $this;
555        }
556
557        if (null === $pageNumber) {
558            foreach (self::$_cache->getIdsMatchingTags(array($this->_getCacheInternalId())) as $id) {
559                if (preg_match('|'.self::CACHE_TAG_PREFIX."(\d+)_.*|", $id, $page)) {
560                    self::$_cache->remove($this->_getCacheId($page[1]));
561                }
562            }
563        } else {
564            $cleanId = $this->_getCacheId($pageNumber);
565            self::$_cache->remove($cleanId);
566        }
567        return $this;
568    }
569
570    /**
571     * Returns the absolute item number for the specified item.
572     *
573     * @param  integer $relativeItemNumber Relative item number
574     * @param  integer $pageNumber Page number
575     * @return integer
576     */
577    public function getAbsoluteItemNumber($relativeItemNumber, $pageNumber = null)
578    {
579        $relativeItemNumber = $this->normalizeItemNumber($relativeItemNumber);
580
581        if ($pageNumber == null) {
582            $pageNumber = $this->getCurrentPageNumber();
583        }
584
585        $pageNumber = $this->normalizePageNumber($pageNumber);
586
587        return (($pageNumber - 1) * $this->getItemCountPerPage()) + $relativeItemNumber;
588    }
589
590    /**
591     * Returns the adapter.
592     *
593     * @return Zend_Paginator_Adapter_Interface
594     */
595    public function getAdapter()
596    {
597        return $this->_adapter;
598    }
599
600    /**
601     * Returns the number of items for the current page.
602     *
603     * @return integer
604     */
605    public function getCurrentItemCount()
606    {
607        if ($this->_currentItemCount === null) {
608            $this->_currentItemCount = $this->getItemCount($this->getCurrentItems());
609        }
610
611        return $this->_currentItemCount;
612    }
613
614    /**
615     * Returns the items for the current page.
616     *
617     * @return Traversable
618     */
619    public function getCurrentItems()
620    {
621        if ($this->_currentItems === null) {
622            $this->_currentItems = $this->getItemsByPage($this->getCurrentPageNumber());
623        }
624
625        return $this->_currentItems;
626    }
627
628    /**
629     * Returns the current page number.
630     *
631     * @return integer
632     */
633    public function getCurrentPageNumber()
634    {
635        return $this->normalizePageNumber($this->_currentPageNumber);
636    }
637
638    /**
639     * Sets the current page number.
640     *
641     * @param  integer $pageNumber Page number
642     * @return Zend_Paginator $this
643     */
644    public function setCurrentPageNumber($pageNumber)
645    {
646        $this->_currentPageNumber = (integer) $pageNumber;
647        $this->_currentItems      = null;
648        $this->_currentItemCount  = null;
649
650        return $this;
651    }
652
653    /**
654     * Get the filter
655     *
656     * @return Zend_Filter_Interface
657     */
658    public function getFilter()
659    {
660        return $this->_filter;
661    }
662
663    /**
664     * Set a filter chain
665     *
666     * @param Zend_Filter_Interface $filter
667     * @return Zend_Paginator
668     */
669    public function setFilter(Zend_Filter_Interface $filter)
670    {
671        $this->_filter = $filter;
672
673        return $this;
674    }
675
676    /**
677     * Returns an item from a page.  The current page is used if there's no
678     * page sepcified.
679     *
680     * @param  integer $itemNumber Item number (1 to itemCountPerPage)
681     * @param  integer $pageNumber
682     * @return mixed
683     */
684    public function getItem($itemNumber, $pageNumber = null)
685    {
686        if ($pageNumber == null) {
687            $pageNumber = $this->getCurrentPageNumber();
688        } else if ($pageNumber < 0) {
689            $pageNumber = ($this->count() + 1) + $pageNumber;
690        }
691
692        $page = $this->getItemsByPage($pageNumber);
693        $itemCount = $this->getItemCount($page);
694
695        if ($itemCount == 0) {
696            /**
697             * @see Zend_Paginator_Exception
698             */
699
700            throw new Zend_Paginator_Exception('Page ' . $pageNumber . ' does not exist');
701        }
702
703        if ($itemNumber < 0) {
704            $itemNumber = ($itemCount + 1) + $itemNumber;
705        }
706
707        $itemNumber = $this->normalizeItemNumber($itemNumber);
708
709        if ($itemNumber > $itemCount) {
710            /**
711             * @see Zend_Paginator_Exception
712             */
713
714            throw new Zend_Paginator_Exception('Page ' . $pageNumber . ' does not'
715                                             . ' contain item number ' . $itemNumber);
716        }
717
718        return $page[$itemNumber - 1];
719    }
720
721    /**
722     * Returns the number of items per page.
723     *
724     * @return integer
725     */
726    public function getItemCountPerPage()
727    {
728        if (empty($this->_itemCountPerPage)) {
729            $this->_itemCountPerPage = self::getDefaultItemCountPerPage();
730        }
731
732        return $this->_itemCountPerPage;
733    }
734
735    /**
736     * Sets the number of items per page.
737     *
738     * @param  integer $itemCountPerPage
739     * @return Zend_Paginator $this
740     */
741    public function setItemCountPerPage($itemCountPerPage = -1)
742    {
743        $this->_itemCountPerPage = (integer) $itemCountPerPage;
744        if ($this->_itemCountPerPage < 1) {
745            $this->_itemCountPerPage = $this->getTotalItemCount();
746        }
747        $this->_pageCount        = $this->_calculatePageCount();
748        $this->_currentItems     = null;
749        $this->_currentItemCount = null;
750
751        return $this;
752    }
753
754    /**
755     * Returns the number of items in a collection.
756     *
757     * @param  mixed $items Items
758     * @return integer
759     */
760    public function getItemCount($items)
761    {
762        $itemCount = 0;
763
764        if (is_array($items) || $items instanceof Countable) {
765            $itemCount = count($items);
766        } else { // $items is something like LimitIterator
767            $itemCount = iterator_count($items);
768        }
769
770        return $itemCount;
771    }
772
773    /**
774     * Returns the items for a given page.
775     *
776     * @return Traversable
777     */
778    public function getItemsByPage($pageNumber)
779    {
780        $pageNumber = $this->normalizePageNumber($pageNumber);
781
782        if ($this->_cacheEnabled()) {
783            $data = self::$_cache->load($this->_getCacheId($pageNumber));
784            if ($data !== false) {
785                return $data;
786            }
787        }
788
789        $offset = ($pageNumber - 1) * $this->getItemCountPerPage();
790
791        $items = $this->_adapter->getItems($offset, $this->getItemCountPerPage());
792
793        $filter = $this->getFilter();
794
795        if ($filter !== null) {
796            $items = $filter->filter($items);
797        }
798
799        if (!$items instanceof Traversable) {
800            $items = new ArrayIterator($items);
801        }
802
803        if ($this->_cacheEnabled()) {
804            self::$_cache->save($items, $this->_getCacheId($pageNumber), array($this->_getCacheInternalId()));
805        }
806
807        return $items;
808    }
809
810    /**
811     * Returns a foreach-compatible iterator.
812     *
813     * @return Traversable
814     */
815    public function getIterator()
816    {
817        return $this->getCurrentItems();
818    }
819
820    /**
821     * Returns the page range (see property declaration above).
822     *
823     * @return integer
824     */
825    public function getPageRange()
826    {
827        if (null === $this->_pageRange) {
828            $this->_pageRange = self::getDefaultPageRange();
829        }
830
831        return $this->_pageRange;
832    }
833
834    /**
835     * Sets the page range (see property declaration above).
836     *
837     * @param  integer $pageRange
838     * @return Zend_Paginator $this
839     */
840    public function setPageRange($pageRange)
841    {
842        $this->_pageRange = (integer) $pageRange;
843
844        return $this;
845    }
846
847    /**
848     * Returns the page collection.
849     *
850     * @param  string $scrollingStyle Scrolling style
851     * @return array
852     */
853    public function getPages($scrollingStyle = null)
854    {
855        if ($this->_pages === null) {
856            $this->_pages = $this->_createPages($scrollingStyle);
857        }
858
859        return $this->_pages;
860    }
861
862    /**
863     * Returns a subset of pages within a given range.
864     *
865     * @param  integer $lowerBound Lower bound of the range
866     * @param  integer $upperBound Upper bound of the range
867     * @return array
868     */
869    public function getPagesInRange($lowerBound, $upperBound)
870    {
871        $lowerBound = $this->normalizePageNumber($lowerBound);
872        $upperBound = $this->normalizePageNumber($upperBound);
873
874        $pages = array();
875
876        for ($pageNumber = $lowerBound; $pageNumber <= $upperBound; $pageNumber++) {
877            $pages[$pageNumber] = $pageNumber;
878        }
879
880        return $pages;
881    }
882
883    /**
884     * Returns the page item cache.
885     *
886     * @return array
887     */
888    public function getPageItemCache()
889    {
890        $data = array();
891        if ($this->_cacheEnabled()) {
892            foreach (self::$_cache->getIdsMatchingTags(array($this->_getCacheInternalId())) as $id) {
893                    if (preg_match('|'.self::CACHE_TAG_PREFIX."(\d+)_.*|", $id, $page)) {
894                        $data[$page[1]] = self::$_cache->load($this->_getCacheId($page[1]));
895                    }
896            }
897        }
898        return $data;
899    }
900
901    /**
902     * Retrieves the view instance.  If none registered, attempts to pull f
903     * rom ViewRenderer.
904     *
905     * @return Zend_View_Interface|null
906     */
907    public function getView()
908    {
909        if ($this->_view === null) {
910            /**
911             * @see Zend_Controller_Action_HelperBroker
912             */
913
914            $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
915            if ($viewRenderer->view === null) {
916                $viewRenderer->initView();
917            }
918            $this->_view = $viewRenderer->view;
919        }
920
921        return $this->_view;
922    }
923
924    /**
925     * Sets the view object.
926     *
927     * @param  Zend_View_Interface $view
928     * @return Zend_Paginator
929     */
930    public function setView(Zend_View_Interface $view = null)
931    {
932        $this->_view = $view;
933
934        return $this;
935    }
936
937    /**
938     * Brings the item number in range of the page.
939     *
940     * @param  integer $itemNumber
941     * @return integer
942     */
943    public function normalizeItemNumber($itemNumber)
944    {
945        $itemNumber = (integer) $itemNumber;
946
947        if ($itemNumber < 1) {
948            $itemNumber = 1;
949        }
950
951        if ($itemNumber > $this->getItemCountPerPage()) {
952            $itemNumber = $this->getItemCountPerPage();
953        }
954
955        return $itemNumber;
956    }
957
958    /**
959     * Brings the page number in range of the paginator.
960     *
961     * @param  integer $pageNumber
962     * @return integer
963     */
964    public function normalizePageNumber($pageNumber)
965    {
966        $pageNumber = (integer) $pageNumber;
967
968        if ($pageNumber < 1) {
969            $pageNumber = 1;
970        }
971
972        $pageCount = $this->count();
973
974        if ($pageCount > 0 && $pageNumber > $pageCount) {
975            $pageNumber = $pageCount;
976        }
977
978        return $pageNumber;
979    }
980
981    /**
982     * Renders the paginator.
983     *
984     * @param  Zend_View_Interface $view
985     * @return string
986     */
987    public function render(Zend_View_Interface $view = null)
988    {
989        if (null !== $view) {
990            $this->setView($view);
991        }
992
993        $view = $this->getView();
994
995        return $view->paginationControl($this);
996    }
997
998    /**
999     * Returns the items of the current page as JSON.
1000     *
1001     * @return string
1002     */
1003    public function toJson()
1004    {
1005        $currentItems = $this->getCurrentItems();
1006
1007        if ($currentItems instanceof Zend_Db_Table_Rowset_Abstract) {
1008            return Zend_Json::encode($currentItems->toArray());
1009        } else {
1010            return Zend_Json::encode($currentItems);
1011        }
1012    }
1013
1014    /**
1015     * Tells if there is an active cache object
1016     * and if the cache has not been desabled
1017     *
1018     * @return bool
1019     */
1020    protected function _cacheEnabled()
1021    {
1022        return ((self::$_cache !== null) && $this->_cacheEnabled);
1023    }
1024
1025    /**
1026     * Makes an Id for the cache
1027     * Depends on the adapter object and the page number
1028     *
1029     * Used to store item in cache from that Paginator instance
1030     *  and that current page
1031     *
1032     * @param int $page
1033     * @return string
1034     */
1035    protected function _getCacheId($page = null)
1036    {
1037        if ($page === null) {
1038            $page = $this->getCurrentPageNumber();
1039        }
1040        return self::CACHE_TAG_PREFIX . $page . '_' . $this->_getCacheInternalId();
1041    }
1042
1043    /**
1044     * Get the internal cache id
1045     * Depends on the adapter and the item count per page
1046     *
1047     * Used to tag that unique Paginator instance in cache
1048     *
1049     * @return string
1050     */
1051    protected function _getCacheInternalId()
1052    {
1053        $adapter = $this->getAdapter();
1054
1055        if (method_exists($adapter, 'getCacheIdentifier')) {
1056            return md5(serialize(array(
1057                $adapter->getCacheIdentifier(), $this->getItemCountPerPage()
1058            )));
1059        } else {
1060            return md5(serialize(array(
1061                $adapter,
1062                $this->getItemCountPerPage()
1063            )));
1064        }
1065    }
1066
1067    /**
1068     * Calculates the page count.
1069     *
1070     * @return integer
1071     */
1072    protected function _calculatePageCount()
1073    {
1074        return (integer) ceil($this->getTotalItemCount() / $this->getItemCountPerPage());
1075    }
1076
1077    /**
1078     * Creates the page collection.
1079     *
1080     * @param  string $scrollingStyle Scrolling style
1081     * @return stdClass
1082     */
1083    protected function _createPages($scrollingStyle = null)
1084    {
1085        $pageCount         = $this->count();
1086        $currentPageNumber = $this->getCurrentPageNumber();
1087
1088        $pages = new stdClass();
1089        $pages->pageCount        = $pageCount;
1090        $pages->itemCountPerPage = $this->getItemCountPerPage();
1091        $pages->first            = 1;
1092        $pages->current          = $currentPageNumber;
1093        $pages->last             = $pageCount;
1094
1095        // Previous and next
1096        if ($currentPageNumber - 1 > 0) {
1097            $pages->previous = $currentPageNumber - 1;
1098        }
1099
1100        if ($currentPageNumber + 1 <= $pageCount) {
1101            $pages->next = $currentPageNumber + 1;
1102        }
1103
1104        // Pages in range
1105        $scrollingStyle = $this->_loadScrollingStyle($scrollingStyle);
1106        $pages->pagesInRange     = $scrollingStyle->getPages($this);
1107        $pages->firstPageInRange = min($pages->pagesInRange);
1108        $pages->lastPageInRange  = max($pages->pagesInRange);
1109
1110        // Item numbers
1111        if ($this->getCurrentItems() !== null) {
1112            $pages->currentItemCount = $this->getCurrentItemCount();
1113            $pages->itemCountPerPage = $this->getItemCountPerPage();
1114            $pages->totalItemCount   = $this->getTotalItemCount();
1115            $pages->firstItemNumber  = (($currentPageNumber - 1) * $this->getItemCountPerPage()) + 1;
1116            $pages->lastItemNumber   = $pages->firstItemNumber + $pages->currentItemCount - 1;
1117        }
1118
1119        return $pages;
1120    }
1121
1122    /**
1123     * Loads a scrolling style.
1124     *
1125     * @param string $scrollingStyle
1126     * @return Zend_Paginator_ScrollingStyle_Interface
1127     */
1128    protected function _loadScrollingStyle($scrollingStyle = null)
1129    {
1130        if ($scrollingStyle === null) {
1131            $scrollingStyle = self::$_defaultScrollingStyle;
1132        }
1133
1134        switch (strtolower(gettype($scrollingStyle))) {
1135            case 'object':
1136                if (!$scrollingStyle instanceof Zend_Paginator_ScrollingStyle_Interface) {
1137                    /**
1138                     * @see Zend_View_Exception
1139                     */
1140
1141                    throw new Zend_View_Exception('Scrolling style must implement ' .
1142                        'Zend_Paginator_ScrollingStyle_Interface');
1143                }
1144
1145                return $scrollingStyle;
1146
1147            case 'string':
1148                $className = self::getScrollingStyleLoader()->load($scrollingStyle);
1149
1150                return new $className();
1151
1152            case 'null':
1153                // Fall through to default case
1154
1155            default:
1156                /**
1157                 * @see Zend_View_Exception
1158                 */
1159
1160                throw new Zend_View_Exception('Scrolling style must be a class ' .
1161                    'name or object implementing Zend_Paginator_ScrollingStyle_Interface');
1162        }
1163    }
1164}
1165