<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

/**

 * CodeIgniter

 *

 * An open source application development framework for PHP 5.1.6 or newer

 *

 * @package		CodeIgniter

 * @author		ExpressionEngine Dev Team

 * @copyright	Copyright (c) 2008 - 2011, EllisLab, Inc.

 * @license		http://codeigniter.com/user_guide/license.html

 * @link		http://codeigniter.com

 * @since		Version 1.0

 * @filesource

 */



// ------------------------------------------------------------------------



/**

 * Zip Compression Class

 *

 * This class is based on a library I found at Zend:

 * http://www.zend.com/codex.php?id=696&single=1

 *

 * The original library is a little rough around the edges so I

 * refactored it and added several additional methods -- Rick Ellis

 *

 * @package		CodeIgniter

 * @subpackage	Libraries

 * @category	Encryption

 * @author		ExpressionEngine Dev Team

 * @link		http://codeigniter.com/user_guide/libraries/zip.html

 */

class CI_Zip  {



	var $zipdata	= '';

	var $directory	= '';

	var $entries	= 0;

	var $file_num	= 0;

	var $offset		= 0;

	var $now;



	/**

	 * Constructor

	 */

	public function __construct()

	{

		log_message('debug', "Zip Compression Class Initialized");



		$this->now = time();

	}



	// --------------------------------------------------------------------



	/**

	 * Add Directory

	 *

	 * Lets you add a virtual directory into which you can place files.

	 *

	 * @access	public

	 * @param	mixed	the directory name. Can be string or array

	 * @return	void

	 */

	function add_dir($directory)

	{

		foreach ((array)$directory as $dir)

		{

			if ( ! preg_match("|.+/$|", $dir))

			{

				$dir .= '/';

			}



			$dir_time = $this->_get_mod_time($dir);



			$this->_add_dir($dir, $dir_time['file_mtime'], $dir_time['file_mdate']);

		}

	}



	// --------------------------------------------------------------------



	/**

	 *	Get file/directory modification time

	 *

	 *	If this is a newly created file/dir, we will set the time to 'now'

	 *

	 *	@param string	path to file

	 *	@return array	filemtime/filemdate

	 */

	function _get_mod_time($dir)

	{

		// filemtime() will return false, but it does raise an error.

		$date = (@filemtime($dir)) ? filemtime($dir) : getdate($this->now);



		$time['file_mtime'] = ($date['hours'] << 11) + ($date['minutes'] << 5) + $date['seconds'] / 2;

		$time['file_mdate'] = (($date['year'] - 1980) << 9) + ($date['mon'] << 5) + $date['mday'];



		return $time;

	}



	// --------------------------------------------------------------------



	/**

	 * Add Directory

	 *

	 * @access	private

	 * @param	string	the directory name

	 * @return	void

	 */

	function _add_dir($dir, $file_mtime, $file_mdate)

	{

		$dir = str_replace("\\", "/", $dir);



		$this->zipdata .=

			"\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00"

			.pack('v', $file_mtime)

			.pack('v', $file_mdate)

			.pack('V', 0) // crc32

			.pack('V', 0) // compressed filesize

			.pack('V', 0) // uncompressed filesize

			.pack('v', strlen($dir)) // length of pathname

			.pack('v', 0) // extra field length

			.$dir

			// below is "data descriptor" segment

			.pack('V', 0) // crc32

			.pack('V', 0) // compressed filesize

			.pack('V', 0); // uncompressed filesize



		$this->directory .=

			"\x50\x4b\x01\x02\x00\x00\x0a\x00\x00\x00\x00\x00"

			.pack('v', $file_mtime)

			.pack('v', $file_mdate)

			.pack('V',0) // crc32

			.pack('V',0) // compressed filesize

			.pack('V',0) // uncompressed filesize

			.pack('v', strlen($dir)) // length of pathname

			.pack('v', 0) // extra field length

			.pack('v', 0) // file comment length

			.pack('v', 0) // disk number start

			.pack('v', 0) // internal file attributes

			.pack('V', 16) // external file attributes - 'directory' bit set

			.pack('V', $this->offset) // relative offset of local header

			.$dir;



		$this->offset = strlen($this->zipdata);

		$this->entries++;

	}



	// --------------------------------------------------------------------



	/**

	 * Add Data to Zip

	 *

	 * Lets you add files to the archive. If the path is included

	 * in the filename it will be placed within a directory.  Make

	 * sure you use add_dir() first to create the folder.

	 *

	 * @access	public

	 * @param	mixed

	 * @param	string

	 * @return	void

	 */

	function add_data($filepath, $data = NULL)

	{

		if (is_array($filepath))

		{

			foreach ($filepath as $path => $data)

			{

				$file_data = $this->_get_mod_time($path);



				$this->_add_data($path, $data, $file_data['file_mtime'], $file_data['file_mdate']);

			}

		}

		else

		{

			$file_data = $this->_get_mod_time($filepath);



			$this->_add_data($filepath, $data, $file_data['file_mtime'], $file_data['file_mdate']);

		}

	}



	// --------------------------------------------------------------------



	/**

	 * Add Data to Zip

	 *

	 * @access	private

	 * @param	string	the file name/path

	 * @param	string	the data to be encoded

	 * @return	void

	 */

	function _add_data($filepath, $data, $file_mtime, $file_mdate)

	{

		$filepath = str_replace("\\", "/", $filepath);



		$uncompressed_size = strlen($data);

		$crc32  = crc32($data);



		$gzdata = gzcompress($data);

		$gzdata = substr($gzdata, 2, -4);

		$compressed_size = strlen($gzdata);



		$this->zipdata .=

			"\x50\x4b\x03\x04\x14\x00\x00\x00\x08\x00"

			.pack('v', $file_mtime)

			.pack('v', $file_mdate)

			.pack('V', $crc32)

			.pack('V', $compressed_size)

			.pack('V', $uncompressed_size)

			.pack('v', strlen($filepath)) // length of filename

			.pack('v', 0) // extra field length

			.$filepath

			.$gzdata; // "file data" segment



		$this->directory .=

			"\x50\x4b\x01\x02\x00\x00\x14\x00\x00\x00\x08\x00"

			.pack('v', $file_mtime)

			.pack('v', $file_mdate)

			.pack('V', $crc32)

			.pack('V', $compressed_size)

			.pack('V', $uncompressed_size)

			.pack('v', strlen($filepath)) // length of filename

			.pack('v', 0) // extra field length

			.pack('v', 0) // file comment length

			.pack('v', 0) // disk number start

			.pack('v', 0) // internal file attributes

			.pack('V', 32) // external file attributes - 'archive' bit set

			.pack('V', $this->offset) // relative offset of local header

			.$filepath;



		$this->offset = strlen($this->zipdata);

		$this->entries++;

		$this->file_num++;

	}



	// --------------------------------------------------------------------



	/**

	 * Read the contents of a file and add it to the zip

	 *

	 * @access	public

	 * @return	bool

	 */

	function read_file($path, $preserve_filepath = FALSE)

	{

		if ( ! file_exists($path))

		{

			return FALSE;

		}



		if (FALSE !== ($data = file_get_contents($path)))

		{

			$name = str_replace("\\", "/", $path);



			if ($preserve_filepath === FALSE)

			{

				$name = preg_replace("|.*/(.+)|", "\\1", $name);

			}



			$this->add_data($name, $data);

			return TRUE;

		}

		return FALSE;

	}



	// ------------------------------------------------------------------------



	/**

	 * Read a directory and add it to the zip.

	 *

	 * This function recursively reads a folder and everything it contains (including

	 * sub-folders) and creates a zip based on it.  Whatever directory structure

	 * is in the original file path will be recreated in the zip file.

	 *

	 * @access	public

	 * @param	string	path to source

	 * @return	bool

	 */

	function read_dir($path, $preserve_filepath = TRUE, $root_path = NULL)

	{

		if ( ! $fp = @opendir($path))

		{

			return FALSE;

		}



		// Set the original directory root for child dir's to use as relative

		if ($root_path === NULL)

		{

			$root_path = dirname($path).'/';

		}



		while (FALSE !== ($file = readdir($fp)))

		{

			if (substr($file, 0, 1) == '.')

			{

				continue;

			}



			if (@is_dir($path.$file))

			{

				$this->read_dir($path.$file."/", $preserve_filepath, $root_path);

			}

			else

			{

				if (FALSE !== ($data = file_get_contents($path.$file)))

				{

					$name = str_replace("\\", "/", $path);



					if ($preserve_filepath === FALSE)

					{

						$name = str_replace($root_path, '', $name);

					}



					$this->add_data($name.$file, $data);

				}

			}

		}



		return TRUE;

	}



	// --------------------------------------------------------------------



	/**

	 * Get the Zip file

	 *

	 * @access	public

	 * @return	binary string

	 */

	function get_zip()

	{

		// Is there any data to return?

		if ($this->entries == 0)

		{

			return FALSE;

		}



		$zip_data = $this->zipdata;

		$zip_data .= $this->directory."\x50\x4b\x05\x06\x00\x00\x00\x00";

		$zip_data .= pack('v', $this->entries); // total # of entries "on this disk"

		$zip_data .= pack('v', $this->entries); // total # of entries overall

		$zip_data .= pack('V', strlen($this->directory)); // size of central dir

		$zip_data .= pack('V', strlen($this->zipdata)); // offset to start of central dir

		$zip_data .= "\x00\x00"; // .zip file comment length



		return $zip_data;

	}



	// --------------------------------------------------------------------



	/**

	 * Write File to the specified directory

	 *

	 * Lets you write a file

	 *

	 * @access	public

	 * @param	string	the file name

	 * @return	bool

	 */

	function archive($filepath)

	{

		if ( ! ($fp = @fopen($filepath, FOPEN_WRITE_CREATE_DESTRUCTIVE)))

		{

			return FALSE;

		}



		flock($fp, LOCK_EX);

		fwrite($fp, $this->get_zip());

		flock($fp, LOCK_UN);

		fclose($fp);



		return TRUE;

	}



	// --------------------------------------------------------------------



	/**

	 * Download

	 *

	 * @access	public

	 * @param	string	the file name

	 * @param	string	the data to be encoded

	 * @return	bool

	 */

	function download($filename = 'backup.zip')

	{

		if ( ! preg_match("|.+?\.zip$|", $filename))

		{

			$filename .= '.zip';

		}



		$CI =& get_instance();

		$CI->load->helper('download');



		$get_zip = $this->get_zip();



		$zip_content =& $get_zip;



		force_download($filename, $zip_content);

	}



	// --------------------------------------------------------------------



	/**

	 * Initialize Data

	 *

	 * Lets you clear current zip data.  Useful if you need to create

	 * multiple zips with different data.

	 *

	 * @access	public

	 * @return	void

	 */

	function clear_data()

	{

		$this->zipdata		= '';

		$this->directory	= '';

		$this->entries		= 0;

		$this->file_num		= 0;

		$this->offset		= 0;

	}



}



/* End of file Zip.php */

/* Location: ./system/libraries/Zip.php */