'__get', 'set' => '__set', 'set_config' => '__set', 'create_links' => 'render', 'page_links' => 'pages_render', 'prev_link' => 'previous', 'next_link' => 'next', ); array_key_exists($name, $mapping) and $name = $mapping[$name]; // call the method on the default instance if ($instance = static::instance() and method_exists($instance, $name)) { return call_fuel_func_array(array($instance, $name), $arguments); } throw new \BadMethodCallException('The pagination class doesn\'t have a method called "'.$name.'"'); } /** * forge a new pagination instance * * @param string $name * @param array $config * @return \Pagination a new pagination instance */ public static function forge($name = 'default', $config = array()) { if ($exists = static::instance($name)) { \Errorhandler::notice('Pagination with this name exists already, cannot be overwritten.'); return $exists; } static::$_instances[$name] = new static($config); if ($name == 'default') { static::$_instance = static::$_instances[$name]; } return static::$_instances[$name]; } /** * retrieve an existing pagination instance * * @param string $name * @return \Pagination a existing pagination instance */ public static function instance($name = null) { if ($name !== null) { if ( ! array_key_exists($name, static::$_instances)) { return false; } return static::$_instances[$name]; } if (static::$_instance === null) { static::$_instance = static::forge(); } return static::$_instance; } // -------------------------------------------------------------------- /** * instance configuration values */ protected $config = array( 'current_page' => null, 'offset' => 0, 'per_page' => 10, 'total_pages' => 0, 'total_items' => 0, 'num_links' => 5, 'uri_segment' => 3, 'show_first' => false, 'show_last' => false, 'pagination_url' => null, 'link_offset' => 0.5, ); /** * instance template values */ protected $template = array( 'wrapper' => "
\n\t{pagination}\n
\n", 'first' => "\n\t{link}\n\n", 'first-marker' => "««", 'first-link' => "\t\t{page}\n", 'first-inactive' => "", 'first-inactive-link' => "", 'previous' => "\n\t{link}\n\n", 'previous-marker' => "«", 'previous-link' => "\t\t{page}\n", 'previous-inactive' => "\n\t{link}\n\n", 'previous-inactive-link' => "\t\t{page}\n", 'regular' => "\n\t{link}\n\n", 'regular-link' => "\t\t{page}\n", 'active' => "\n\t{link}\n\n", 'active-link' => "\t\t{page}\n", 'next' => "\n\t{link}\n\n", 'next-marker' => "»", 'next-link' => "\t\t{page}\n", 'next-inactive' => "\n\t{link}\n\n", 'next-inactive-link' => "\t\t{page}\n", 'last' => "\n\t{link}\n\n", 'last-marker' => "»»", 'last-link' => "\t\t{page}\n", 'last-inactive' => "", 'last-inactive-link' => "", ); /** * raw pagination results */ protected $raw_results = array(); /** * @param array $config */ public function __construct($config = array()) { // make sure config is an array is_array($config) or $config = array('name' => $config); // and we have a template name array_key_exists('name', $config) or $config['name'] = \Config::get('pagination.active', 'default'); // merge the config passed with the defined configuration $config = array_merge(\Config::get('pagination.'.$config['name'], array()), $config); // don't need the template name anymore unset($config['name']); // update the instance default config with the data passed foreach ($config as $key => $value) { $this->__set($key, $value); } } /** * configuration value getter * @param $name * @return mixed */ public function __get($name) { // use the calculated page if no current_page is passed if ($name === 'current_page' and $this->config[$name] === null) { $name = 'calculated_page'; } if (array_key_exists($name, $this->config)) { return $this->config[$name]; } elseif (array_key_exists($name, $this->template)) { return $this->template[$name]; } else { return null; } } /** * configuration value setter * * @param $name * @param mixed $value */ public function __set($name, $value = null) { if (is_array($name)) { foreach($name as $key => $value) { $this->__set($key, $value); } } else { $value = $this->_validate($name, $value); if (array_key_exists($name, $this->config)) { $this->config[$name] = $value; } elseif (array_key_exists($name, $this->template)) { $this->template[$name] = $value; } } // update the page counters $this->_recalculate(); } /** * Render the pagination when the object is cast to string */ public function __toString() { return $this->render(); } /** * Creates the pagination markup * * @param mixed $raw * @return mixed HTML Markup for page number links, or an array of raw pagination data */ public function render($raw = false) { // no links if we only have one page if ($this->config['total_pages'] == 1) { return $raw ? array() : ''; } $this->raw_results = array(); $html = str_replace( '{pagination}', $this->first().$this->previous().$this->pages_render().$this->next().$this->last(), $this->template['wrapper'] ); return $raw ? $this->raw_results : $html; } /** * generate the HTML for the page links only * * @return string Markup for the pagination block */ public function pages_render() { // no links if we only have one page if ($this->config['total_pages'] == 1) { return ''; } $html = ''; // calculate start- and end page numbers $start = $this->config['calculated_page'] - floor($this->config['num_links'] * $this->config['link_offset']); $end = $this->config['calculated_page'] + floor($this->config['num_links'] * ( 1 - $this->config['link_offset'])); // adjust for the first few pages if ($start < 1) { $end -= $start - 1; $start = 1; } // make sure we don't overshoot the current page due to rounding issues if ($end < $this->config['calculated_page']) { $start++; $end++; } // make sure we don't overshoot the total if ($end > $this->config['total_pages']) { $start = max(1, $start - $end + $this->config['total_pages']); $end = $this->config['total_pages']; } for($i = intval($start); $i <= intval($end); $i++) { if ($this->config['calculated_page'] == $i) { $html .= str_replace( '{link}', str_replace(array('{uri}', '{page}'), array('#', $i), $this->template['active-link']), $this->template['active'] ); $this->raw_results[] = array('uri' => '#', 'title' => $i, 'type' => 'active'); } else { $html .= str_replace( '{link}', str_replace(array('{uri}', '{page}'), array($this->_make_link($i), $i), $this->template['regular-link']), $this->template['regular'] ); $this->raw_results[] = array('uri' => $this->_make_link($i), 'title' => $i, 'type' => 'regular'); } } return $html; } /** * Pagination "First" link * * @param string $marker optional text to display in the link * @return string Markup for the 'first' page number link */ public function first($marker = null) { $html = ''; $marker === null and $marker = $this->template['first-marker']; if ($this->config['show_first']) { if ($this->config['total_pages'] > 1 and $this->config['calculated_page'] > 1) { $html = str_replace( '{link}', str_replace(array('{uri}', '{page}'), array($this->_make_link(1), $marker), $this->template['first-link']), $this->template['first'] ); $this->raw_results['first'] = array('uri' => $this->_make_link(1), 'title' => $marker, 'type' => 'first'); } else { $html = str_replace( '{link}', str_replace(array('{uri}', '{page}'), array('#', $marker), $this->template['first-inactive-link']), $this->template['first-inactive'] ); $this->raw_results['first'] = array('uri' => '#', 'title' => $marker, 'type' => 'first-inactive'); } } return $html; } /** * Pagination "Previous" link * * @param string $marker optional text to display in the link * @return string Markup for the 'previous' page number link */ public function previous($marker = null) { $html = ''; $marker === null and $marker = $this->template['previous-marker']; if ($this->config['total_pages'] > 1) { if ($this->config['calculated_page'] == 1) { $html = str_replace( '{link}', str_replace(array('{uri}', '{page}'), array('#', $marker), $this->template['previous-inactive-link']), $this->template['previous-inactive'] ); $this->raw_results['previous'] = array('uri' => '#', 'title' => $marker, 'type' => 'previous-inactive'); } else { $previous_page = $this->config['calculated_page'] - 1; $previous_page = ($previous_page == 1) ? '' : $previous_page; $html = str_replace( '{link}', str_replace(array('{uri}', '{page}'), array($this->_make_link($previous_page), $marker), $this->template['previous-link']), $this->template['previous'] ); $this->raw_results['previous'] = array('uri' => $this->_make_link($previous_page), 'title' => $marker, 'type' => 'previous'); } } return $html; } /** * Pagination "Next" link * * @param string $marker optional text to display in the link * @return string Markup for the 'next' page number link */ public function next($marker = null) { $html = ''; $marker === null and $marker = $this->template['next-marker']; if ($this->config['total_pages'] > 1) { if ($this->config['calculated_page'] == $this->config['total_pages']) { $html = str_replace( '{link}', str_replace(array('{uri}', '{page}'), array('#', $marker), $this->template['next-inactive-link']), $this->template['next-inactive'] ); $this->raw_results['next'] = array('uri' => '#', 'title' => $marker, 'type' => 'next-inactive'); } else { $next_page = $this->config['calculated_page'] + 1; $html = str_replace( '{link}', str_replace(array('{uri}', '{page}'), array($this->_make_link($next_page), $marker), $this->template['next-link']), $this->template['next'] ); $this->raw_results['next'] = array('uri' => $this->_make_link($next_page), 'title' => $marker, 'type' => 'next'); } } return $html; } /** * Pagination "Last" link * * @param string $marker optional text to display in the link * @return string Markup for the 'last' page number link */ public function last($marker = null) { $html = ''; $marker === null and $marker = $this->template['last-marker']; if ($this->config['show_last']) { if ($this->config['total_pages'] > 1 and $this->config['calculated_page'] != $this->config['total_pages']) { $html = str_replace( '{link}', str_replace(array('{uri}', '{page}'), array($this->_make_link($this->config['total_pages']), $marker), $this->template['last-link']), $this->template['last'] ); $this->raw_results['last'] = array('uri' => $this->_make_link($this->config['total_pages']), 'title' => $marker, 'type' => 'last'); } else { $html = str_replace( '{link}', str_replace(array('{uri}', '{page}'), array('#', $marker), $this->template['last-inactive-link']), $this->template['last-inactive'] ); $this->raw_results['last'] = array('uri' => '#', 'title' => $marker, 'type' => 'last-inactive'); } } return $html; } /** * Prepares vars for creating links */ protected function _recalculate() { // get the current page number, either from the one set, or from the URI or the query string if ($this->config['current_page']) { $this->config['calculated_page'] = $this->config['current_page']; } else { if (is_string($this->config['uri_segment'])) { $this->config['calculated_page'] = \Input::get($this->config['uri_segment'], 1); } else { $this->config['calculated_page'] = (int) \Request::main()->uri->get_segment($this->config['uri_segment'], 1); } } // do we have the total number of items? if ($this->config['total_items'] > 0) { // calculate the number of pages $this->config['total_pages'] = (int) ceil($this->config['total_items'] / $this->config['per_page']) ?: 1; // make sure the current page is within bounds if ($this->config['calculated_page'] > $this->config['total_pages']) { $this->config['calculated_page'] = $this->config['total_pages']; } elseif ($this->config['calculated_page'] < 1) { $this->config['calculated_page'] = 1; } } // the current page must be zero based so that the offset for page 1 is 0. $this->config['offset'] = ($this->config['calculated_page'] - 1) * $this->config['per_page']; } /** * Generate a pagination link */ protected function _make_link($page) { // make sure we have a valid page number empty($page) and $page = 1; // construct a pagination url if we don't have one if (is_null($this->config['pagination_url'])) { // start with the main uri $this->config['pagination_url'] = \Uri::main(); \Input::get() and $this->config['pagination_url'] .= '?'.http_build_query(\Input::get()); } // was a placeholder defined in the url? if (strpos($this->config['pagination_url'], '{page}') === false) { // break the url in bits so we can insert it $url = parse_url($this->config['pagination_url']); // parse the query string if (isset($url['query'])) { parse_str($url['query'], $url['query']); } else { $url['query'] = array(); } // make sure we don't destroy any fragments if (isset($url['fragment'])) { $url['fragment'] = '#'.$url['fragment']; } // do we have a segment offset due to the base_url containing segments? $seg_offset = parse_url(rtrim(\Uri::base(), '/')); $seg_offset = empty($seg_offset['path']) ? 0 : count(explode('/', trim($seg_offset['path'], '/'))); // is the page number a URI segment? if (is_numeric($this->config['uri_segment'])) { // get the URL segments $segs = isset($url['path']) ? explode('/', trim($url['path'], '/')) : array(); // do we have enough segments to insert? we can't fill in any blanks... if (count($segs) < $this->config['uri_segment'] - 1) { throw new \RuntimeException("Not enough segments in the URI, impossible to insert the page number"); } // replace the selected segment with the page placeholder $segs[$this->config['uri_segment'] - 1 + $seg_offset] = '{page}'; $url['path'] = '/'.implode('/', $segs); } else { // add our placeholder $url['query'][$this->config['uri_segment']] = '{page}'; } // re-assemble the url $query = empty($url['query']) ? '' : '?'.preg_replace('/%7Bpage%7D/', '{page}', http_build_query($url['query'], '', '&')); unset($url['query']); empty($url['scheme']) or $url['scheme'] .= '://'; empty($url['port']) or $url['host'] .= ':'; $this->config['pagination_url'] = implode($url).$query; } // return the page link return str_replace('{page}', $page, $this->config['pagination_url']); } /** * Validate the input configuration * * @param $name * @param $value * @return int|mixed */ protected function _validate($name, $value) { switch ($name) { case 'offset': case 'total_items': // make sure it's an integer if ($value != intval($value)) { $value = 0; } // and that it's within bounds $value = max(0, $value); break; // integer or string case 'uri_segment': if (is_numeric($value)) { // make sure it's an integer if ($value != intval($value)) { $value = 1; } // and that it's within bounds $value = max(1, $value); } break; // validate integer values case 'current_page': case 'per_page': case 'limit': case 'total_pages': case 'num_links': // make sure it's an integer if ($value != intval($value)) { $value = 1; } // and that it's within bounds $value = max(1, $value); break; // validate booleans case 'show_first': case 'show_last': if ( ! is_bool($value)) { $value = (bool) $value; } break; // validate the link offset, and adjust if needed case 'link_offset': // make sure we have a fraction between 0 and 1 if ($value > 1) { $value = $value / 100; } // and that it's within bounds $value = max(0.01, min($value, 0.99)); break; } return $value; } }