<?php
/**
 * This program is licenced under the The GNU General Public License (GPL).
 *
 * Copyright (C) Kjetil Hårtveit 2010
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
 * License for more details.
 * @license http://www.opensource.org/licenses/gpl-license.html
 *
 *---------------------------------------
 *
 * Makes pages overview out of a collection of items.
 *
 * Future ideas:
 * @todo implement cleaner error catching?
 * @todo add truncation
 * @todo add customizeable page values? Instead of 1 2 3, make it possible with A B C?
 *
 * @author Kjetil Hårtveit <kjetil@kjetil-hartveit.com> http://www.kjetil-hartveit.com
 * @version 1.2
 */
class PageLister
{
	protected $allItems = array();
	protected $page = 1;
	protected $pageList = array();

	/**
	 * Default options
	 *
	 * @var array
	 */
	protected $opts = array(
		'itemsPerPage'		 => 5,
		'urlFormat'				 => '%d',
		'customFormatting' => false,
		'urlCallback'			 => null,
		'urlCallbackArgs'	 => array(),
		'varPage'					 => '{pagenum}',
		'pageLabels'			 => array(),
		'prevAndNext'			 => true,
		'prev'						 => '«',
		'next'						 => '»'
	);

	/**
	 * <p>
	 *	Note that you can only use either normal url formatting, custom formatting or
	 *  callback formatting. Presedence order is: callback, custom formatting, normal formatting.
	 * </p>
	 *
	 * <h3>Possible options:</h3>
	 * <table>
	 *	 <tr>
	 *		<th>Type</th>
	 *		<th>Option</th>
	 *		<th>Default</th>
	 *		<th>Description</th>
	 *	 </tr>
	 *	 <tr>
	 *		 <td>string</td>
	 *		 <td>itemsPerPage</td>
	 *     <td>5</td>
	 *     <td>Number of items shown per page</td>
	 *   </tr>
	 *   <tr>
	 *     <td>string</td>
	 *     <td>urlFormat</td>
	 *     <td>%d</td>
	 *     <td>
	 *		  Here you can choose the format of the url. A replacement value for the pagenumber is
	 *			required. The syntax of the replacement value and how to set it depends on different
	 *			options.<br />
	 *			If the <i>customFormatting</i> option is set then the value of <i>varPage</i> is used.<br />
	 *			If the <i>urlCallback</i> option is set then at least one of the callback arguments must
	 *			contain the value of <i>varPage</i>.<br />
	 *			Lastly if neither of the mentioned options are set then <i>urlFormat</i> uses the sprintf
	 *			argument syntax (ie. %d).
	 *		 </td>
   *   </tr>
	 *	 <tr>
	 *		 <td>boolean</td>
	 *		 <td>customFormatting</td>
	 *     <td>false</td>
	 *     <td>
	 *			With custom formatting enabled you can define your own syntax for the pagenumber format.
	 *			This is handy in case your url format accidently includes format arguments
	 *			(like url characters encoded in utf-8) and which collides with the pagenumber argument.
	 *		 </td>
	 *   </tr>
	 *	 <tr>
	 *		 <td>string|array</td>
	 *		 <td>urlCallback</td>
	 *     <td>null</td>
	 *     <td>
	 *			If a callback is given then the pageurl will be formatted based on the result of this
	 *			callback.
	 *		 </td>
	 *   </tr>
	 *	 <tr>
	 *		 <td>array</td>
	 *		 <td>urlCallbackArgs</td>
	 *     <td>array()</td>
	 *     <td>
	 *			If the urlCallback option is set then you can specify additional callback arguments
	 *			with this option. <br />
	 *		  At least one of the callback arguments should contain the <i>varPage</i> value so the
	 *			corresponding pagenumber can be injected into the callback.
	 *		 </td>
	 *   </tr>
	 *	 <tr>
	 *		 <td>string</td>
	 *		 <td>varPage</td>
	 *     <td>{pagenum}</td>
	 *     <td>
	 *			The value of this option will be replaced by the pagenumber in numerous settings.<br />
	 *			It will be used when the option <i>customFormatting</i> is set, as well as when
	 *			a <i>urlCallback</i> is set. In the latter case then one of the callback arguments
	 *			should contain this value.
	 *		 </td>
	 *   </tr>
	 *   <tr>
	 *     <td>array</td>
	 *     <td>pageLabels</td>
	 *     <td>array()</td>
	 *     <td>Each individual page can be given a specific page label with this option. Each key in the array is the pagenumber and the value is the page label.</td>
   *   </tr>
	 *   <tr>
	 *     <td>boolean</td>
	 *     <td>prevAndNext</td>
	 *     <td>true</td>
	 *     <td>Whether or not to include the "previous" and "next" items in the page list</td>
   *   </tr>
	 *   <tr>
	 *     <td>string</td>
	 *     <td>prev</td>
	 *     <td>«</td>
	 *     <td>The text shown as "previous page"</td>
   *   </tr>
	 *   <tr>
	 *     <td>string</td>
	 *     <td>next</td>
	 *     <td>»</td>
	 *     <td>The text shown as "next page"</td>
   *   </tr>
	 * </table>
	 *
	 * @param array $allItems All of the items which you want to generate a pagelist from
	 * @param int $page Current page
	 * @param array $opts array with options given as key-value pair.
	 */
	function __construct($allItems, $page, $opts=array())
	{
		$this->setAllItems($allItems);
		$this->setPage($page);
		$this->setOpts(array_merge($this->getOpts(), $opts));
	}

	/**
	 * Makes page overview
	 *
	 * <p>The page list is an associative array consisting of the following key pairs:</p>
	 * <table>
	 *   <tr>
	 *	   <th>Type</th>
	 *	   <th>Key</th>
	 *	   <th>Value</th>
	 *   </tr>
	 *	 <tr>
	 *		 <td>string</td>
	 *		 <td>url</td>
	 *		 <td>The formatted url for the page. Note that when using the <i>prevAndNext</i> option and
				there is no valid previous or next page, then the url will be null. This is also the case
				if a pagelink points to the current page. You can use this info to remove links that the
				user should not click on.</td>
	 *   </tr>
	 *	 <tr>
	 *		 <td>int</td>
	 *		 <td>page</td>
	 *		 <td>The pagenumber</td>
	 *   </tr>
	 *	 <tr>
	 *		 <td>string</td>
	 *		 <td>label</td>
	 *		 <td>The page label</td>
	 *   </tr>
	 * </table>
	 *
	 * @return array Page list
	 */
	function makePageList()
	{
		$page = $this->getPage();
		$allItems = $this->getAllItems();
		$prevAndNext = $this->getOpt('prevAndNext');
		$pageLabels = $this->getOpt('pageLabels');
		$itemsPerPage = $this->getOpt('itemsPerPage');

		$numPages = ceil(count($allItems)/$itemsPerPage);

		$pageList = array();

		$prev = $page-1;
		$next = $page+1;

			if ($prevAndNext)
			{
				$pageList[] = array(
					'page'  => $prev,
					'url'	  => $prev>0 ? $this->formatUrl($prev) : null,
					'label' => $this->getOpt('prev')
				);
			}

			for ($i=1; $i<=$numPages; $i++)
			{
				$curUrl = $page!=$i ? $this->formatUrl($i) : null;
				$pageList[] = array(
					'page'  => $i,
					'url'	  => $curUrl,
					'label' => isset($pageLabels[$i]) ? $pageLabels[$i] : $i
				);
			}

			if ($prevAndNext)
			{
				$pageList[] = array(
					'page'  => $next,
					'url'	  => $next<=$numPages ? $this->formatUrl($next) : null,
					'label' => $this->getOpt('next')
				);
			}

		$this->setPageList($pageList);
		return $pageList;
	}

	/**
	 * Formats url based on options
	 *
	 * @param string $format
	 * @param int $page
	 * @return string
	 */
	protected function formatUrl($page)
	{
		$res = '';
			if ($this->getOpt('urlCallback')) {
				$args = str_replace($this->getOpt('varPage'), $page, $this->getOpt('urlCallbackArgs'));
				$res = call_user_func_array($this->getOpt('urlCallback'), $args);
			} else if ($this->getOpt('customFormatting')) {
				$res = str_replace($this->getOpt('varPage'), $page, $this->getOpt('urlFormat'));
			} else {
				$res = sprintf($this->getOpt('urlFormat'), $page);
			}

		return $res;
	}

	/**
	 * Returns current items
	 *
	 * @return array
	 */
	function getCurrentItems()
	{
		$allItems = $this->getAllItems();
		$page = $this->getPage();
		$itemsPerPage = $this->getOpt('itemsPerPage');

		return array_slice($allItems, ($page-1)*$itemsPerPage, $itemsPerPage);
	}

	/**
	 * Returns a single option
	 *
	 * @param string $opt
	 * @return mixed
	 */
	function getOpt($opt)
	{
		$opts = $this->getOpts();
			if (isset($opts[$opt]))
			{
				return $opts[$opt];
			}
		return null;
	}

	function setAllItems($allItems) { $this->allItems = $allItems; }
	function getAllItems() { return $this->allItems; }
	function setPage($page) { $this->page = $page; }
	function getPage() { return $this->page; }
	function setOpts($opts) { $this->opts = $opts; }
	function getOpts() { return $this->opts; }
	function setPageList($pageList) { $this->pageList = $pageList; }
	function getPageList() { return $this->pageList; }
}
?>