Category: Developing


Pagination, Pagination, Pagination

For a recent project I’ve been working I needed an easy-to-use Pagination class but after a scour around I couldn’t find anything that would do the job in the way I wanted to (Zend’s came close, as I’ve used it before, but it’s not lightweight enough for this project). I decided to write my own one instead.

The constructor takes a minimum of two arguments – an array of the whole set of data to be paginated, and the page of results to display. Optional arguments that can also be passed include an array of data to be included in the query string for each navigation link (most likely the $_GET predefined variable), the number of results to display per-page and how many links to include in the navigation (this value doesn’t affect the First/Last, Previous/Next navigation links):

This class will most probably not be of use if you are trying to paginate more that 1000 or so rows of data to paginate as doing it this way is bad practice (if pulling the data from a database you should be selecting your subset there, much more efficient).

As ever, let me know your thoughts!

<?php
/**
 * Pagination Class
 *
 * Class that will take an array of data and split it into the subsection to be returned, based on the values passed
 */
 class Pagination {
	public $data; // Original Data
	public $query_data; // Data on current query string

	public $page; // Current page of results to display
	public $pages; // Pages to include, dependant upon $page_range value
	public $page_count; // Total Number of Pages
	public $row_count; // Number of rows passed to class in Constructor

	public $first_page; // Show first page link
	public $last_page; // Show last page link
	public $previous_page; // Show previous page link
	public $next_page; // Show next page link

	public $range_start; // The page number to start the range link at
	public $page_range; // Show number of page links to show
	public $query_string; // Query string to use for the links

	public $subsection_of_data;

	/**
	 * Contructor
	 * @param array An array of data to be paginated
	 * @param int Page of data to return as subsection
	 * @param array An array of key/value-paired data for the query string for the Pagination navigation
	 * @param int The number of results to show on one page
	 * @param int Number of pages to display in the Pagination Navigation (Excluding First/Last, Previous/Next links)
	 * @return boolean
	 */
	public function __construct($data, $page, $query_data = array(), $per_page = 25, $page_range = 5)
	{
		$this->data = $data;
		$this->query_data = $query_data;

		$this->subsection_of_data = array();

		$this->pages = array();
		$this->page = $page;
		$this->per_page = $per_page;
		$this->page_range = $page_range;

		$this->process_query_string();
		$this->process_number_of_rows();
		$this->process_page_count();

		$this->process_first_page();
		$this->process_last_page();
		$this->process_previous_page();
		$this->process_next_page();
		$this->process_page_range();

		$this->process_subsection_of_data();
	}

	/**
	 * Count total number of items
	 * @return void
	 */
	public function process_number_of_rows()
	{
		$this->row_count = count($this->data);
	}

	/**
	 * Count total number of pages on data based on $per_page variable
	 * @return void
	 */
	public function process_page_count()
	{
		$this->page_count = floor($this->row_count / $this->per_page) + ($this->row_count % $this->per_page > 0 ? 1 : 0); // Number of Pages in Total
	}

	/**
	 * Determining whether to include "First Page" link in navigation
	 * @return void
	 */
	public function process_first_page()
	{
		$this->first_page = false;

		if ($this->page > 1) :
			$this->first_page = 1;
		endif;
	}

	/**
	 * Determining whether to include "Last Page" link in navigation
	 * @return void
	 */
	public function process_last_page()
	{
		$this->last_page = false;

		if ($this->page < $this->page_count) :
			$this->last_page = $this->page_count;
		endif;
	}

	/**
	 * Determining whether to include "Previous Page" link in navigation
	 * @return void
	 */
	public function process_previous_page()
	{
		$this->previous_page = false;

		if ($this->page > 1) :
			$this->previous_page = $this->page - 1;
		endif;
	}

	/**
	 * Determining whether to include "Next Page" link in navigation
	 * @return void
	 */
	public function process_next_page()
	{
		$this->next_page = false;

		if ($this->page < $this->page_count) :
			$this->next_page = $this->page + 1;
		endif;
	}

	/**
	 * Determining what page links to include in "Page Range" navigation
	 * @return void
	 */
	public function process_page_range()
	{
		$this->range_split = floor(($this->page_range - 1) / 2);

		$this->range_start = $this->page - $this->range_split;

		if ($this->range_start < 1) :
			$this->range_start = 1;
		endif;

		$i = $this->range_start;

		while ($i < ($this->range_start + $this->page_range)) :
			if ($i > 0 && $i <= $this->page_count) :
				$this->pages[] = $i;
			endif;
			$i++;
		endwhile;
	}

	/**
	 * Process query string to use on navigation links (removing the currently selected page, if set)
	 * @return void
	 */
	public function process_query_string()
	{
		$query_data = $this->query_data;

		if (isset($query_data['page'])) {
			unset($query_data['page']);
		}

		$this->query_string = http_build_query($query_data);
	}

	/**
	 * Return set of data to display, based on Page Number and Per Page variable
	 * @return void
	 */
	public function process_subsection_of_data()
	{
		$this->subsection_of_data = $this->data;

		$offset = $this->per_page * ($this->page - 1);
		$limit = $this->per_page;

		$this->subsection_of_data = array_slice($this->data, $offset, $limit);
	}

	/**
	 * Render standard navigation (echoes immediately)
	 * @return void
	 */
	public function render()
	{
		if ($this->page_count > 0): ?>
        	<div class="pagination">
            <?php
			if ($this->page_count > 1): ?>
                <div class="navigation">
				<?php
                if ($this->first_page) : ?>
                    <span class="item"><a href="?<?php echo $this->query_string; ?>&page=<?php echo $this->first_page; ?>">First</a></span>
                <?php
                endif;

                if ($this->previous_page) : ?>
                    <span class="item"><a href="?<?php echo $this->query_string; ?>&page=<?php echo $this->previous_page; ?>">Previous</a></span>
                <?php
                endif; 

                foreach ($this->pages as $page_number) :
                    if ($page_number == $this->page) : ?>
                        <span class="item range current"><?php echo $page_number; ?></span>
                <?php
                    else : ?>
                        <span class="item range"><a href="?<?php echo $this->query_string; ?>&page=<?php echo $page_number; ?>"><?php echo $page_number; ?></a></span>
                <?php
                    endif;
                endforeach; 

                if ($this->next_page) : ?>
                    <span class="item"><a href="?<?php echo $this->query_string; ?>&page=<?php echo $this->next_page; ?>">Next</a></span>
                <?php
                endif;

                if ($this->last_page) : ?>
                    <span class="item"><a href="?<?php echo $this->query_string; ?>&page=<?php echo $this->last_page; ?>">Last</a></span>
                <?php
                endif; ?>
            	</div>
               	<?php
			endif; ?>
            	<span class="summary">Page <?php echo $this->page; ?> of <?php echo $this->page_count; ?></span>
            	<div class="clear"></div>
            </div>
        <?php
		endif;
	}

	/**
	 * Render naviation based on the specified template
	 * @return void
	 */
	public function render_by_template($path)
	{
		if (file_exists($path)) {
			include($path);
		} else {
			echo 'Error: Pagination template cannot be found';
		}
	}
}

Example of use:

<?php
$data = array('Apple', 'Banana', 'Blackberry', 'Blackcurrant', 'Cherry', 'Clementine', 'Elderberry', 'Fig', 'Grape', 'Gooseberry', 'Juniper', 'Kiwi');
$get_data = $_GET;
$page = (int) $get_data['page'];

$pagination = new Pagination($data, $page, $get_data, 5, 5);

$pagination->render(); ?>

<table><?php
foreach ($pagination->subsection_of_data as $row) { ?>
	<tr>
		<td><?php echo $row; ?></td>
	</tr>
} ?>
</table><?php
$pagination->render(); ?>

At my current company we needed a way to track links to downloads or external sites, so they would be logged in Google Analytics. I came up with this piece of code that will allow us to do so, the only prerequisite being at jQuery exists and is used on a particular website.

Here is the code:

<script type="text/javascript">
if (typeof(jQuery) != 'undefined') {
    jQuery(document).ready(function() {
        jQuery(".googleTrack").click(function() {
            var link_href = jQuery(this).attr("href");
            trackGoogleClick(link_href.substr(7));
			return true;
        });
    });
}

function trackGoogleClick(link_href)
{
    if(typeof(window.pageTracker) !== 'undefined') {
        pageTracker._trackPageview('tracked/-/' + link_href);
    } else if (typeof(window._gaq) !== 'undefined') {
		_gaq.push(['_trackPageview', link_href]);
    }
}
</script>

To track a link (or any element for that matter) this method just give the anchor a class or “googleTrack”, and that’s it, it will be tracked.

iTunes Library Parsing Class in PHP

Due to a recent project I’ve been working on I needed to be able to populate a song/artist database with a couple of thousand records for dummy/test data. I thought the best thing to do was to just import my iTunes library however after a quick scan around the net it became apparent that there was nothing around that could easily parse the contents on an exported iTunes XML playlist/library. I put together a rudimentary version for what I needed, but then thought it may be useful for others too, so spent a little longer building a more comprehensive version, which can be seen below:

<?php
/**
 * Itunes_Library - Class the allows user to parse an exported iTunes XML Playlist/Library
 * and convert it into an object
 *
 * @class Itunes_Playlist
 * @author Thomas McGregor <leegleeders@yahoo.co.uk>
 * @link http://thomasmcgregor.co.uk/blog
 * @copyright Copyright (c) 2012, Thomas McGregor
 * @version 0.9.0
 */

class Itunes_Library {
	/**
	 * string	Path to the XML file
     * @static
	 */
	public static $file_path;
	/**
	 * SimpleXML Object   The Parse XML as a SimpleXML object
     * @static
	 */
	public static $xml;

	/**
	 * Import Songs from XML iTunes export
	 * @param string
	 * @param int
	 * @param int|false
	 * @return mixed
	 */
    public static function import_library_xml($file_path, $offset = 0, $limit = false)
	{
		try {
			// Check file exists
			if (file_exists($file_path)) {
				self::$file_path = $file_path;
				// Check SimpleXML is installed
				if (function_exists('simplexml_load_file')) {
					// Instantiate new playlist object
					$playlist = new stdClass();

					// Load XML from file
					self::$xml = simplexml_load_file(self::$file_path);

					// Parse Application Info
					$playlist->info = self::parse_export_info();

					// Parse Track Info
					$playlist->tracks = self::parse_tracks($offset, $limit);

					// Parse Playlist Info
					$playlist->playlists = self::parse_playlists();

					return $playlist;
				} else {
					// SimpleXML not installed
					throw new Exception('SimpleXML does not appear to be installed on this server');
				}
			} else {
				// XML File does not exist
				throw new Exception("File does not exist");
			}
		} catch (Exception $e) {
			echo 'Caught exception: ',  $e->getMessage(), "\n";
			return false;
		}
	}

	/**
	 * Parse Playlist Info from xml, return object
	 * @return object
	 */
    public static function parse_export_info()
	{
		$info = (object) array('major_version' => (int) self::$xml->dict->integer[0],
			'minor_version' => (int) self::$xml->dict->integer[1],
			'date' => (string) self::$xml->dict->date,
			'application_version' => (string) self::$xml->dict->string[0],
			'features' => (int) self::$xml->dict->integer[2],
			'music_folder' => (string) self::$xml->dict->string[1],
			'library_persistent_id' => (string) self::$xml->dict->string[2]);

		return $info;
	}

	/**
	 * Parse Track Info from xml, return array
	 * @param int
	 * @param int|false
	 * @return array
	 */
    public static function parse_tracks($offset, $limit)
	{
		$tracks = array();

		// Initalise Track Counter
		$i = 0;

		// If Offset is not an integer
		if (!is_int($offset)) {
			$offset = $i;
		}

		// If limit is an integer
		if (is_int($limit)) {
			$limit = $offset + $limit;
		}

		// Loop through track nodes
		foreach(self::$xml->dict->dict->dict as $song) {
			if ($i >= $offset && ($limit === false || $i < $limit)) {
				// List properties related to track
				$keys = array();
				foreach($song->key as $key) {
					$keys[] = (string) $key;
				}

				$track = new stdClass();

				$string_index = 0;
				$integer_index = 0;
				$date_index = 0;

				$track->track_id = in_array("Track ID", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->name = in_array("Name", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->artist = in_array("Artist", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->album_artist = in_array("Album Artist", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->composer = in_array("Composer", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->album = in_array("Album", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->genre = in_array("Genre", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->kind = in_array("Kind", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->size = in_array("Size", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->total_time = in_array("Total Time", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->disc_number = in_array("Disc Number", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->disc_count = in_array("Disc Count", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->track_number = in_array("Track Number", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->track_count = in_array("Track Count", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->year = in_array("Year", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->date_modified = in_array("Date Modified", $keys) ? (string) $song->date[$date_index++] : NULL;
				$track->date_added = in_array("Date Added", $keys) ? (string) $song->date[$date_index++] : NULL;
				$track->bit_rate = in_array("Bit Rate", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->sample_rate = in_array("Sample Rate", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->comments = in_array("Comments", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->play_count = in_array("Play Count", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->play_date = in_array("Play Date", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->play_date_utc = in_array("Play Date UTC", $keys) ? (string) $song->date[$date_index++] : NULL;
				$track->rating = in_array("Rating", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->album_rating = in_array("Album Rating", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->release_date = in_array("Release Date", $keys) ? (string) $song->date[$date_index++] : NULL;
				$track->normalization = in_array("Normalization", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->artwork_count = in_array("Artwork Count", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->series = in_array("Series", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->season = in_array("Season", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->episode = in_array("Episode", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->episode_order = in_array("Episode Order", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->sort_album = in_array("Persisent ID", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->persistent_id = in_array("Persisent ID", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->content_rating = in_array("Content Rating", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->track_type = in_array("Track Type", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->protected = in_array("Protected", $keys) ? true : false;
				$track->purchased = in_array("Purchased", $keys) ? true : false;
				$track->podcast = in_array("Podcast", $keys) ? true : false;
				$track->unplayed = in_array("Unplayed", $keys) ? true : false;
				$track->has_video = in_array("Has Video", $keys) ? true : false;
				/*HD - No easy way to identify if HD flag is true or false*/
				$track->video_width = in_array("Video Width", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->video_height = in_array("Video Height", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->movie = in_array("Movie", $keys) ? true : false;
				$track->tv_show = in_array("TV Show", $keys) ? true : false;
				$track->music_video = in_array("Music Video", $keys) ? true : false;
				$track->location = in_array("Location", $keys) ? (string) $song->string[$string_index++] : NULL;
				$track->file_folder_count = in_array("File Folder Count", $keys) ? (int) $song->integer[$integer_index++] : NULL;
				$track->library_folder_count = in_array("Library Folder Count", $keys) ? (int) $song->integer[$integer_index++] : NULL;

				$tracks[] = $track;

			}
			$i++;
		}

		return $tracks;
	}

	/**
	 * Parse Playlist Info from xml, return array
	 * @return array
	 */
    public static function parse_playlists()
	{
		$playlists = array();

		// Loop through playlist nodes
		foreach(self::$xml->dict->array->dict as $playlist) {
			// List properties related to playlist
			$keys = array();
			foreach($playlist->key as $key) {
				$keys[] = (string) $key;
			}

			$object = new stdClass();

			$string_index = 0;
			$integer_index = 0;

			$object->name = in_array("Name", $keys) ? (string) $playlist->string[$string_index++] : NULL;
			$object->playlist_id = in_array("Playlist ID", $keys) ? (int) $playlist->integer[$integer_index++] : NULL;
			$object->playlist_persistent_id = in_array("Playlist Persistent ID", $keys) ? (string) $playlist->string[$string_index++] : NULL;

			$object->tracks = array();

			// If playlist has at least one track in it
			if (count($playlist->array->dict) > 0) {
				// Loop through track ids
				foreach ($playlist->array->dict as $track_id) {
					$object->tracks[] = (int) $track_id->integer;
				}
			}

			$playlists[] = $object;
		}

		return $playlists;
	}
}

The class parses the specified XML file, returning an object with the following properties:

“info” – An object containing info about the version of iTunes used to export the library/playlist, among other things
“track” – An array of objects containing every track listed in the exported library/playlist, along with all supplied data about each one
“playlists” – An array of containing all playlists (and data) listed in the exported library/playlist, along with a list of Track IDs of the tracks included in the playlist.

Many thanks to Steve Springett and his article on parsing iTunes Library XML, which was a great help, even if several of the tags in the XSL are in the wrong order.

Let me know what you think, you’re more than welcome to use it but please credit me in some way if you do. You can copy the code from above or download it from the link below.

Download Parse iTunes Library PHP Class