<?php

class Vframe_Pagination
{
  protected
    
$_iPage 0,
    
$_iItems 0,
    
$_iLimit 1,
    
$_aPatterns = array
    (
      
'page' => '<a href="?page=[$]">[$]</a>',
      
'page_current' => '<a href="?page=[$]"><strong>[$]</strong></a>',
      
'separator' => '...',
      
'nav_prev' => '<a href="?page=[$]" rel="nofollow">&laquo;</a>',
      
'nav_next' => '<a href="?page=[$]" rel="nofollow">&raquo;</a>',
    );
  
  
/**
   * Pattern of page number and text in html _aPatterns
   */
  
const PATTERN '[$]';
  const 
PATTERN_TEXT '[$$]';
  
  
/**
   * Constructor of paginator
   * 
   * By the way defining new paginator we can set items,
   * limit and current page at once.
   * 
   * @param int iItems
   * @param int iLimit
   * @param int iPage
   */
  
public function __construct($iItems null$iLimit null$iPage null)
  {
    if(
$iItems)
      
$this->items($iItems);
    
    if(
$iLimit)
      
$this->limit($iLimit);
    
    if(
$iPage)
      
$this->page($iPage);
  }
  
  
/**
   * Set number of items (0 or greater).
   * 
   * @param int iParam
   * @return int
   */
  
public function Items($iParam null)
  {
    if(
is_numeric($iParam))
    {
      if(
$iParam 0)
        throw new 
Vframe_Pagination_Exception('Limit must be greater or equal 0');
      
      
$this->_iItems = (int)$iParam;
    }
    
    return 
$this->_iItems;
  }
  
  
/**
   * Set limit (items per page). It must be at least 1
   * item per page.
   * 
   * @param int iParam
   * @return int
   */
  
public function Limit($iParam null)
  {
    if(
is_numeric($iParam))
    {
      if(
$iParam 1)
        throw new 
Vframe_Pagination_Exception('Limit must be greater than 0');
      
      
$this->_iLimit = (int)$iParam;
    }
    
    return 
$this->_iLimit;
  }
  
  
/**
   * Set number of current page. If number is less than
   * zero, method set page to 1 automatically
   * 
   * @param int iParam
   * @return int
   */
  
public function Page($iParam null)
  {
    if(
is_numeric($iParam))
    {
      if(
$iParam 0$iParam 1;
      
$this->_iPage = (int)$iParam;
    }
    
    return 
$this->_iPage;
  }
  
  
/**
   * Method return position of start item.
   * 
   * @return int
   */
  
public function Start()
  {
    return 
$this->render('start');
  }
  
  
/**
   * Method render data such as number of pages, position
   * of start item, validate current page.
   * 
   * @param mixed (bool true to draw pagination, string key name to return defined render data)
   * @return array informations about pagination
   */
  
public function Render($mMode null$iDrawCutLimit null$iDrawCutLimitCenter null$bNavigation true)
  {
    
$iPages = (int)ceil($this->_iItems $this->_iLimit);
    
$iPagesCurrent = ($this->_iPage $iPages || $this->_iPage 1) ? $this->_iPage;
    
    
$aRender = array
    (
      
'pages' => $iPages,
      
'pages_current' => $iPagesCurrent,
      
'start' => (($iPagesCurrent 1) * $this->_iLimit) + 1,
      
'limit' => $this->_iLimit,
      
'items' => $this->_iItems,
    );
    
    if(
$mMode === true)
      return 
$this->draw($aRender$iDrawCutLimit$iDrawCutLimitCenter$bNavigation);
    
    if(
$mMode)
    {
      if(!isset(
$aRender[$mMode]))
        throw new 
Vframe_Pagination_Exception('Unknown "' $mMode '" render data required. Avalible render data: ' implode(','array_keys($aRender)));
      
      return 
$aRender[$mMode];
    }
    
    return 
$aRender;
  }
  
  
/**
   * Method draw a paginator.
   * 
   * @param array aRender render data
   * @param int iCutLimit cut limit before separator and between
   *            center segment of pagination
   * @param int iCutLimitCenter limit between center segment (default iCutLimit)
   * @return string
   */
  
public function Draw(array $aRender$iCutLimit null$iCutLimitCenter null$bNavigation true)
  {
    
$aDraw $this->DrawData($aRender$iCutLimit$iCutLimitCenter);
    
$iDraw end(array_keys($aDraw));
    
$sDraw '';
    
    foreach(
$aDraw as $iPage => $bPage)
    {
      
$sDraw .= str_replace(self::PATTERN$iPage$this->_aPatterns[($bPage 'page_current' 'page')]);
      
      if(!isset(
$aDraw[$iPage 1]) && $iPage $iDraw)
        
$sDraw .= $this->_aPatterns['separator'];
    }
    
    if(
$bNavigation)
    {
      
$iDrawCurrent $aRender['pages_current'];
      
      if(
$iDrawCurrent 1)
        
$sDraw str_replace(self::PATTERN$iDrawCurrent 1$this->_aPatterns['nav_prev']) . $sDraw;
      
      if(
$aRender['pages_current'] < $iDraw)
        
$sDraw .= str_replace(self::PATTERN$iDrawCurrent 1$this->_aPatterns['nav_next']);
    }
    
    return 
$sDraw;
  }
  
  
/**
   * Return and set html pattern of defined mode
   * 
   * @param array aRender render data
   * @param int iCutLimit cut limit before separator and between
   *            center segment of pagination
   * @param int iCutLimitCenter limit between center segment (default iCutLimit)
   * @return string
   */
  
public function DrawData(array $aRender$iCutLimit null$iCutLimitCenter null)
  {
    if(!
is_numeric($iCutLimit)) $iCutLimit 1;
    if(!
is_numeric($iCutLimitCenter)) $iCutLimitCenter $iCutLimit;
    
    if(!
is_numeric($aRender['pages']) || !is_numeric($aRender['pages_current']))
      throw new 
Vframe_Pagination_Exception('There are missing render data.');
    
    
$aPager = array();
    
    
// if cut limit is not defined, or limit is grather than sum of
    // cut limit * 2 + cut center * 2 (2 sides) + 2 separators + 1 center
    // element, let's generate simple pagination
    
if(!$iCutLimit || (($iCutLimit 2) + ($iCutLimitCenter 2) + 3) >= $aRender['pages'])
    {
      foreach(
range(1$aRender['pages']) as $iPage)
        
$aPager[$iPage] = ($iPage == $aRender['pages_current']);
    }
    else
    {
      
$aRanges = array
      (
        array(
1$iCutLimit 1),
        array(
$aRender['pages_current'] - $iCutLimitCenter$aRender['pages_current'] + $iCutLimitCenter),
        array(
$aRender['pages'] - $iCutLimit$aRender['pages']),
      );
      
      foreach(
$aRanges as $aRange)
        foreach(
range($aRange[0], $aRange[1]) as $iPage)
          if(
$iPage >= && $iPage <= $aRender['pages'])
            
$aPager[$iPage] = ($iPage == $aRender['pages_current']);
      
      
ksort($aPager);
    }
    
    return 
$aPager;
  }
  
/**
   * Return and set html pattern of page link
   * 
   * @param string sPattern
   * @return string
   */
  
public function PatternPage($sPattern null)
  {
    return 
$this->Pattern($sPattern'page');
  }
  
  
/**
   * Return and set html pattern of current page link
   * 
   * @param string sPattern
   * @return string
   */
  
public function PatternPageCurrent($sPattern null)
  {
    return 
$this->Pattern($sPattern'page_current');
  }
  
  
/**
   * Return and set html pattern of separator
   * 
   * @param string sPattern
   * @return string
   */
  
public function PatternSeparator($sPattern null)
  {
    return 
$this->Pattern($sPattern'separator');
  }
  
  
/**
   * Return and set html pattern of navigation links
   * 
   * @param string sPatternPrev
   * @param string sPatternNext
   * @return array
   */
  
public function PatternPageNavigation($mPatternA null$mPatternB null)
  {
    if(
is_array($mPatternB))
    {
      
$aNavigators $mPatternB;
      
$mPatternB str_replace(self::PATTERN_TEXT$aNavigators[1], $mPatternA);
      
$mPatternA str_replace(self::PATTERN_TEXT$aNavigators[0], $mPatternA);
    }
    
    return array
    (
      
'prev' => $this->Pattern($mPatternA'nav_prev'),
      
'next' => $this->Pattern($mPatternB'nav_next'),
    );
  }
  
  
/**
   * Return and set html pattern of defined mode
   * 
   * @access protected
   * @param string sPattern
   * @param string sMode
   * @return string
   */
  
protected function Pattern($sPattern$sMode)
  {
    if(
$sPattern)
      
$this->_aPatterns[$sMode] = $sPattern;
    
    return 
$this->_aPatterns[$sMode];
  }
}

class 
Vframe_Pagination_Exception extends Vframe_Exception {}

?>