/**
* This file handles pagination using either MySQLi or PDO, and outputs the pagination
* in the form of a UL
*
* Copyright (c) 2012 Ben Griffiths. All rights reserved.
*
* @name pagination
*
* @author Ben
*
* @copyright 2012 Ben Griffiths
*
* @url https://github.com/BenGriffiths/pdo-mysqli-pagination
*
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
class pagination
{
/**
* Array of options for the class
*
* @access public
* @var array
*/
protected $options = array(
'results_per_page' => 10,
'max_pages_to_fetch' => 1000000,
'url' => '',
'url_page_number_var' => '*VAR*',
'text_prev' => '« Prev',
'text_next' => 'Next »',
'text_first' => '« First',
'text_last' => 'Last »',
'text_ellipses' => '...',
'class_ellipses' => 'ellipses',
'class_dead_links' => 'dead-link',
'class_live_links' => 'live-link',
'class_current_page' => 'current-link',
'class_ul' => 'pagination',
'current_page_is_link' => true,
'show_links_first_last' => true,
'show_links_prev_next' => true,
'show_links_first_last_if_dead' => true,
'show_links_prev_next_if_dead' => true,
'max_links_between_ellipses' => 7,
'max_links_outside_ellipses' => 2,
'db_conn_type' => 'pdo', /* Can be either: 'mysqli' or 'pdo' */
'db_handle' => 0,
'named_params' => false,
'using_bound_params' => false,
'using_bound_values' => false
);
/**
* True if all good, false if theres a problem
*
* @access public
* @var bool
*/
public $success = true;
/**
* The current page
*
* @access protected
* @var int
*/
protected $current_page;
/**
* The query to run on the database
*
* @access protected
* @var string
*/
protected $query;
/**
* The total total number of links to render before showing the ellipses
*
* @access protected
* @var int
*/
protected $number_of_links_before_showing_ellipses;
/**
* The PDO object - stored when users want to bind some params
*
* @access public
* @var object
*/
protected $pdos;
/**
* The resultset of the query
*
* @access public
* @var resource
*/
public $resultset;
/**
* The total results of the query
*
* @access public
* @var int
*/
public $total_results;
/**
* The total pages returned
*
* @access public
* @var int
*/
public $total_pages;
/**
* The pagination links (as an array)
*
* @access public
* @var array
*/
public $links_array;
/**
* The pagination links (Presented as an UL)
*
* @access public
* @var string
*/
public $links_html;
/**
* __construct(int $surrent_page, string $query, array $options)
*
* Class constructor
*
* @access public
* @param int $current_page The number of the current page (Starts at 1)
* @param string $query The query to run on the database
* @param array $options An array of options
* @return void
*/
public function __construct($current_page = 1, $query = '', $options = null)
{
$this->run($current_page, $query, $options);
}
/**
* run(int $surrent_page, string $query, array $options)
*
* Run the class
*
* @access public
* @param int $current_page The number of the current page (Starts at 1)
* @param string $query The query to run on the database
* @param array $options An array of options
* @return void
*/
public function run($current_page = 1, $query = '', $options = null)
{
/*
* Set the current page
*/
$this->current_page = $current_page;
/*
* Set the query to run
*/
$this->query = $query;
/*
* Populate the options array
*/
if($this->set_options($options) == true)
{
/*
* Add any extra code into the query
*/
$this->prepare_query();
/*
* Execute the SQL
*/
$this->excecute_query();
/*
* Calculate the total number of pages
*/
$this->calculate_number_of_pages();
/*
* Work out the total number of pages before an ellipses is shown
*/
$this->calculate_max_pages_before_ellipses();
/*
* Build the HTML to output
*/
$this->build_links();
/*
* Set success to true
*/
$this->success = true;
}
else
{
/*
* Set success to false
*/
$this->success = false;
}
}
/**
* set_options(array $options)
*
* Apply any options that have been provided
*
* @access protected
* @param array $options An array of options
* @return void
*/
protected function set_options($options = null)
{
if(!empty($options))
{
foreach($options as $key => $value)
{
if(array_key_exists($key, $this->options))
{
$this->options[$key] = $value;
}
else
{
throw new paginationException('Attempted to add setting \''.$key.'\' with the value \''.$value.'\' - option does not exist');
}
}
}
/*
* Check to make sure we've been given a db handle
*/
if(trim($this->options['url']) == '')
{
throw new paginationException('You have not provided a URL - please pass one with the option \'url\'');
return false;
}
/*
* Check to make sure we've been given a db handle
*/
if(is_int($this->options['db_handle']) && $this->options['db_handle'] == 0)
{
throw new paginationException('You have not provided a DB Handle (Object) - please pass one with the option \'db_handle\'');
return false;
}
/*
* Check to make sure 'max_links_between_ellipses' is an odd number
*/
if(!($this->options['max_links_between_ellipses'] & 1))
{
throw new paginationException('Setting \'max_links_between_ellipses\' has been set with the value \''.$this->options['max_links_between_ellipses'].'\' - This number must be an odd number');
return false;
}
/*
* Check to make sure the page number variable is in the URL
*/
$page_number_var_position = strpos($this->options['url'], $this->options['url_page_number_var']);
if($page_number_var_position === false)
{
throw new paginationException('You have not placed the variable in your URL that will be replaced with the page number - please add this variable where required: '.$this->options['url_page_number_var'].'');
return false;
}
/*
* If the checks have passed, return true
*/
return true;
}
/**
* prepare_query(void)
*
* Prepares the query to be run with the found rows and start/end limits
*
* @access protected
* @return void
*/
protected function prepare_query()
{
/*
* Add SQL_CALC_FOUND_ROWS (If it's not there) for finding out total amount of results later on
*/
if(substr($this->query, 0, 26) != 'SELECT SQL_CALC_FOUND_ROWS')
{
$this->query = substr_replace(trim($this->query), 'SELECT SQL_CALC_FOUND_ROWS', 0, 6);
}
/*
* Add our start/end limit
*/
if($this->current_page == 1)
{
$this->query .= ' LIMIT 0, '.$this->options['results_per_page'];
}
else
{
$this->query .= ' LIMIT '.(($this->current_page - 1) * $this->options['results_per_page']).', '.$this->options['results_per_page'];
}
}
/**
* excecute_query(void)
*
* Run's the query against the database
*
* @access protected
* @return void
*/
protected function excecute_query()
{
if($this->options['db_conn_type'] == 'mysqli')
{
/*
* Execute using MySQLi
*/
$this->resultset = $this->options['db_handle']->query($this->query);
/*
* Get the total results with FOUND_ROWS()
*/
$count_rows = $this->options['db_handle']->query('SELECT FOUND_ROWS();');
$found_rows = $count_rows->fetch_assoc();
$this->total_results = $found_rows['FOUND_ROWS()'];
}
elseif($this->options['db_conn_type'] == 'pdo')
{
if($this->options['using_bound_params'] == false && $this->options['using_bound_values'] == false)
{
/*
* Execute using PDO - not using bindParams
*/
$pdos = $this->options['db_handle']->prepare($this->query);
/*
* Use plain method or bind some named params
*
* Using alternate styles to avoid any errors with empty arrays
*/
if($this->options['named_params'] == false)
{
$pdos->execute();
}
else
{
$pdos->execute($this->options['named_params']);
}
$this->resultset = $pdos;
/*
* Get the total results with FOUND_ROWS()
*/
$pdos_fr = $this->options['db_handle']->prepare("SELECT FOUND_ROWS();");
$pdos_fr->execute();
$pdos_fr_result = $pdos_fr->fetch(PDO::FETCH_ASSOC);
$this->total_results = $pdos_fr_result['FOUND_ROWS()'];
}
else
{
/*
* Excecute using PDO, but pause for binding params
*/
$this->pdos = $this->options['db_handle']->prepare($this->query);
}
}
else
{
/*
* An unknown DB connection type has been set
*/
throw new paginationException('You have selected a \'db_conn_type\' of \''.$this->options['db_conn_type'].'\' - this method is not supported');
}
}
/**
* bindParam(standard params)
*
* Bind params to the query
*
* @access public
* @param multi Typical bindParam attr
* @return void
*/
public function bindParam($a = null, $b = null, $c = null, $d = null, $e = null)
{
$this->pdos->bindParam($a, $b, $c, $d, $e);
}
/**
* bindValue(standard params)
*
* Bind values to the query
*
* @access public
* @param multi Typical bindValue attr
* @return void
*/
public function bindValue($a = null, $b = null, $c = null)
{
$this->pdos->bindValue($a, $b, $c);
}
/**
* execute(void)
*
* Continues the execution of the query after binding params
*
* @access public
* @return void
*/
public function execute()
{
$this->pdos->execute();
$this->resultset = $this->pdos;
/*
* Get the total results with FOUND_ROWS()
*/
$pdos_fr = $this->options['db_handle']->prepare("SELECT FOUND_ROWS();");
$pdos_fr->execute();
$pdos_fr_result = $pdos_fr->fetch(PDO::FETCH_ASSOC);
$this->total_results = $pdos_fr_result['FOUND_ROWS()'];
/*
* Calculate the total number of pages
*/
$this->calculate_number_of_pages();
/*
* Work out the total number of pages before an ellipses is shown
*/
$this->calculate_max_pages_before_ellipses();
/*
* Build the HTML to output
*/
$this->build_links();
/*
* Set success to true
*/
$this->success = true;
}
/**
* calculate_number_of_pages(void)
*
* Calculates how many pages there will be
*
* @access protected
* @return void
*/
protected function calculate_number_of_pages()
{
if(ceil($this->total_results / $this->options['results_per_page']) > $this->options['max_pages_to_fetch'])
{
$this->total_pages = $this->options['max_pages_to_fetch'];
}
else
{
$this->total_pages = ceil($this->total_results / $this->options['results_per_page']);
}
}
/**
* calculate_max_pages_before_ellipses(void)
*
* Calculates the number of links to show before showing an ellipses
*
* @access protected
* @return void
*/
protected function calculate_max_pages_before_ellipses()
{
$this->number_of_links_before_showing_ellipses = $this->options['max_links_between_ellipses'] + ($this->options['max_links_outside_ellipses'] * 2);
}
/**
* build_link_url(int $page_number)
*
* Builds the URL to insert in links
*
* @access protected
* @param int $page_number The page number to insert into the link
* @return string The built URL
*/
protected function build_link_url($page_number)
{
return str_replace($this->options['url_page_number_var'], $page_number, $this->options['url']);
}
/**
* get_current_or_normal_class(int $page_number)
*
* Returns the live link class, or link link and current page class
*
* @access protected
* @param int $page_number The page number to insert into the link
* @return string The class to use
*/
protected function get_current_or_normal_class($page_number)
{
if($page_number == $this->current_page)
{
return $this->options['class_live_links'].' '.$this->options['class_current_page'];
}
else
{
return $this->options['class_live_links'];
}
}
/**
* build_links(void)
*
* Build the HTML links
*
* @access protected
* @return void
*/
protected function build_links()
{
/*
* Start the UL
*/
$this->links_html = ''.PHP_EOL;
$this->build_links_first_prev();
/*
* Build our main links
*/
if($this->total_pages <= $this->number_of_links_before_showing_ellipses)
{
$this->build_links_skip_all_ellipses();
}
else
{
/*
* We have enough links to show the ellipses, so run through other method
*/
if($this->current_page <= (($this->options['max_links_between_ellipses'] + $this->options['max_links_outside_ellipses'])) - 2)
{
$this->build_links_skip_first_ellipses();
}
elseif($this->current_page > (($this->options['max_links_between_ellipses'] + $this->options['max_links_outside_ellipses']) - 2) && $this->current_page < (($this->total_pages - ($this->options['max_links_between_ellipses'] + $this->options['max_links_outside_ellipses']) + 1) + 2))
{
$this->build_links_dont_skip_ellipses();
}
else
{
$this->build_links_skip_last_ellipses();
}
}
$this->build_links_next_last();
/*
* Close the UL
*/
$this->links_html .= '
'.PHP_EOL;
}
/**
* build_links_first_prev(void)
*
* Builds (if required) the First/Prev links
*
* @access protected
* @return void
*/
protected function build_links_first_prev()
{
/*
* The 'First' link
*/
if($this->options['show_links_first_last'] == true)
{
if($this->current_page == 1 && $this->options['show_links_first_last_if_dead'] == true)
{
$this->links_html .= ''.$this->options['text_first'].''.PHP_EOL;
}
elseif($this->current_page != 1)
{
$this->links_html .= ''.$this->options['text_first'].''.PHP_EOL;
}
$this->links_array['extras']['first'] = array(
'page_number' => 1,
'is_current_page' => ($this->current_page == 1 ? 1 : 0),
'link_url' => $this->build_link_url(1),
'label' => $this->options['text_first']
);
}
/*
* The 'Previous' link
*/
if($this->options['show_links_prev_next'] == true)
{
if($this->current_page == 1 && $this->options['show_links_prev_next_if_dead'] == true)
{
$this->links_html .= ''.$this->options['text_prev'].''.PHP_EOL;
}
elseif($this->current_page != 1)
{
$this->links_html .= ''.$this->options['text_prev'].''.PHP_EOL;
}
$this->links_array['extras']['previous'] = array(
'page_number' => ($this->current_page != 1 ? $this->current_page - 1 : 1),
'is_current_page' => ($this->current_page == 1 ? 1 : 0),
'link_url' => ($this->current_page != 1 ? $this->build_link_url($this->current_page - 1) : $this->build_link_url(1)),
'label' => $this->options['text_prev']
);
}
}
/**
* loop_through_links(int $start, int $finish)
*
* Loops through a given range of numbers and add's then as links in the html
*
* @access protected
* @param int $start The number to start looping
* @param int $finish The number to finish looping
* @return void
*/
protected function loop_through_links($start, $finish, $array_block_label = '')
{
$counter = $start;
while($counter <= $finish)
{
if($this->options['current_page_is_link'] == false && $counter == $this->current_page)
{
$this->links_html .= ''.$counter.''.PHP_EOL;
}
else
{
$this->links_html .= ''.$counter.''.PHP_EOL;
}
$this->links_array['links'][$array_block_label][] = array(
'page_number' => $counter,
'is_current_page' => ($counter == $this->current_page ? 1 : 0),
'link_url' => $this->build_link_url($counter)
);
$counter++;
}
}
/**
* add_ellipses(void)
*
* Add's an ellipses to the html
*
* @access protected
* @return void
*/
protected function add_ellipses()
{
$this->links_html .= ''.$this->options['text_ellipses'].''.PHP_EOL;
}
/**
* build_links_skip_all_ellipses(void)
*
* Add all links, with no ellipses at all
*
* @access protected
* @return void
*/
protected function build_links_skip_all_ellipses()
{
/*
* If there's not enough links to have an ellipses in the set, just run through them all
*/
$this->loop_through_links(1, $this->total_pages, 0);
}
/**
* build_links_skip_first_ellipses(void)
*
* Add all links, without the first ellipses
*
* @access protected
* @return void
*/
protected function build_links_skip_first_ellipses()
{
/*
* Type 1 - skipping the first ellipses due to being low in the current page number
*/
$this->loop_through_links(1, ($this->options['max_links_between_ellipses'] + $this->options['max_links_outside_ellipses']), 0);
$this->add_ellipses();
$this->loop_through_links((($this->total_pages - $this->options['max_links_outside_ellipses']) + 1), $this->total_pages, 1);
}
/**
* build_links_dont_skip_ellipses(void)
*
* Add all links, with both sets of ellipses
*
* @access protected
* @return void
*/
protected function build_links_dont_skip_ellipses()
{
/*
* Type 2 - Current page is between both sets of ellipses
*/
$this->loop_through_links(1, $this->options['max_links_outside_ellipses'], 0);
$this->add_ellipses();
$before_after = (($this->options['max_links_between_ellipses'] - 1) / 2);
$this->loop_through_links(($this->current_page - $before_after), ($this->current_page + $before_after), 1);
$this->add_ellipses();
$this->loop_through_links((($this->total_pages - $this->options['max_links_outside_ellipses']) + 1), $this->total_pages, 2);
}
/**
* build_links_dont_skip_ellipses(void)
*
* Add all links, without the last ellipses
*
* @access protected
* @return void
*/
protected function build_links_skip_last_ellipses()
{
/*
* Type 3 - skipping the last ellipses due to being high in the current page number
*/
$this->loop_through_links(1, $this->options['max_links_outside_ellipses'], 0);
$this->add_ellipses();
$this->loop_through_links((($this->total_pages - ($this->options['max_links_between_ellipses'] + $this->options['max_links_outside_ellipses'])) + 1), $this->total_pages, 1);
}
/**
* build_links_next_last(void)
*
* Builds (if required) the Next/Last links
*
* @access protected
* @return void
*/
protected function build_links_next_last()
{
/*
* The 'Next' link
*/
if($this->options['show_links_prev_next'] == true)
{
if($this->current_page == $this->total_pages && $this->options['show_links_prev_next_if_dead'] == true)
{
$this->links_html .= ''.$this->options['text_next'].''.PHP_EOL;
}
elseif($this->current_page != $this->total_pages)
{
$this->links_html .= ''.$this->options['text_next'].''.PHP_EOL;
}
$this->links_array['extras']['next'] = array(
'page_number' => ($this->current_page != $this->total_pages ? $this->current_page + 1 : $this->total_pages),
'is_current_page' => ($this->current_page == $this->total_pages ? 1 : 0),
'link_url' => ($this->current_page != $this->total_pages ? $this->build_link_url($this->current_page + 1) : $this->build_link_url($this->total_pages)),
'label' => $this->options['text_next']
);
}
/*
* The 'Last' link
*/
if($this->options['show_links_first_last'] == true)
{
if($this->current_page == $this->total_pages && $this->options['show_links_first_last_if_dead'] == true)
{
$this->links_html .= ''.$this->options['text_last'].''.PHP_EOL;
}
elseif($this->current_page != $this->total_pages)
{
$this->links_html .= ''.$this->options['text_last'].''.PHP_EOL;
}
$this->links_array['extras']['last'] = array(
'page_number' => $this->total_pages,
'is_current_page' => ($this->current_page == $this->total_pages ? 1 : 0),
'link_url' => $this->build_link_url($this->total_pages),
'label' => $this->options['text_last']
);
}
}
}
class paginationException extends Exception
{
/**
* __construct(string $message)
*
* Prepare the new exception class
*
* @access protected
* @param string $message The exception message
* @return void
*/
public function __construct($message)
{
parent::__construct($message);
}
/**
* __toString(void)
*
* Return the message for the exception
*
* @access protected
* @return string The exception message
*/
public function __toString()
{
return 'Pagination Class Error: '.$this->message.PHP_EOL;
}
}
?>