Source for file zip.php

Documentation is available at zip.php

  1. <?php
  2. /**
  3.  * @package     Joomla.Platform
  4.  * @subpackage  Archive
  5.  *
  6.  * @copyright   Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  7.  * @license     GNU General Public License version 2 or later; see LICENSE
  8.  */
  9.  
  10. defined('JPATH_PLATFORM'or die;
  11.  
  12. jimport('joomla.filesystem.file');
  13. jimport('joomla.filesystem.folder');
  14.  
  15. /**
  16.  * ZIP format adapter for the JArchive class
  17.  *
  18.  * The ZIP compression code is partially based on code from:
  19.  * Eric Mueller <eric@themepark.com>
  20.  * http://www.zend.com/codex.php?id=535&single=1
  21.  *
  22.  * Deins125 <webmaster@atlant.ru>
  23.  * http://www.zend.com/codex.php?id=470&single=1
  24.  *
  25.  * The ZIP compression date code is partially based on code from
  26.  * Peter Listiak <mlady@users.sourceforge.net>
  27.  *
  28.  * This class is inspired from and draws heavily in code and concept from the Compress package of
  29.  * The Horde Project <http://www.horde.org>
  30.  *
  31.  * @contributor  Chuck Hagenbuch <chuck@horde.org>
  32.  * @contributor  Michael Slusarz <slusarz@horde.org>
  33.  * @contributor  Michael Cochrane <mike@graftonhall.co.nz>
  34.  *
  35.  * @package     Joomla.Platform
  36.  * @subpackage  Archive
  37.  * @since       11.1
  38.  */
  39. class JArchiveZip implements JArchiveExtractable
  40. {
  41.     /**
  42.      * ZIP compression methods.
  43.      *
  44.      * @var    array 
  45.      * @since  11.1
  46.      */
  47.     private $_methods array(0x0 => 'None'0x1 => 'Shrunk'0x2 => 'Super Fast'0x3 => 'Fast'0x4 => 'Normal'0x5 => 'Maximum'0x6 => 'Imploded',
  48.         0x8 => 'Deflated');
  49.  
  50.     /**
  51.      * Beginning of central directory record.
  52.      *
  53.      * @var    string 
  54.      * @since  11.1
  55.      */
  56.     private $_ctrlDirHeader "\x50\x4b\x01\x02";
  57.  
  58.     /**
  59.      * End of central directory record.
  60.      *
  61.      * @var    string 
  62.      * @since  11.1
  63.      */
  64.     private $_ctrlDirEnd "\x50\x4b\x05\x06\x00\x00\x00\x00";
  65.  
  66.     /**
  67.      * Beginning of file contents.
  68.      *
  69.      * @var    string 
  70.      * @since  11.1
  71.      */
  72.     private $_fileHeader "\x50\x4b\x03\x04";
  73.  
  74.     /**
  75.      * ZIP file data buffer
  76.      *
  77.      * @var    string 
  78.      * @since  11.1
  79.      */
  80.     private $_data null;
  81.  
  82.     /**
  83.      * ZIP file metadata array
  84.      *
  85.      * @var    array 
  86.      * @since  11.1
  87.      */
  88.     private $_metadata null;
  89.  
  90.     /**
  91.      * Create a ZIP compressed file from an array of file data.
  92.      *
  93.      * @param   string  $archive  Path to save archive.
  94.      * @param   array   $files    Array of files to add to archive.
  95.      *
  96.      * @return  boolean  True if successful.
  97.      *
  98.      * @since   11.1
  99.      *
  100.      * @todo    Finish Implementation
  101.      */
  102.     public function create($archive$files)
  103.     {
  104.         $contents array();
  105.         $ctrldir array();
  106.  
  107.         foreach ($files as $file)
  108.         {
  109.             $this->_addToZIPFile($file$contents$ctrldir);
  110.         }
  111.  
  112.         return $this->_createZIPFile($contents$ctrldir$archive);
  113.     }
  114.  
  115.     /**
  116.      * Extract a ZIP compressed file to a given path
  117.      *
  118.      * @param   string  $archive      Path to ZIP archive to extract
  119.      * @param   string  $destination  Path to extract archive into
  120.      * @param   array   $options      Extraction options [unused]
  121.      *
  122.      * @return  boolean  True if successful
  123.      *
  124.      * @since   11.1
  125.      * @throws RuntimeException
  126.      */
  127.     public function extract($archive$destinationarray $options array())
  128.     {
  129.         if (!is_file($archive))
  130.         {
  131.             if (class_exists('JError'))
  132.             {
  133.                 return JError::raiseWarning(100'Archive does not exist');
  134.             }
  135.             else
  136.             {
  137.                 throw new RuntimeException('Archive does not exist');
  138.             }
  139.         }
  140.  
  141.         if ($this->hasNativeSupport())
  142.         {
  143.             return $this->extractNative($archive$destination);
  144.         }
  145.         else
  146.         {
  147.             return $this->extractCustom($archive$destination);
  148.         }
  149.     }
  150.  
  151.     /**
  152.      * Tests whether this adapter can unpack files on this computer.
  153.      *
  154.      * @return  boolean  True if supported
  155.      *
  156.      * @since   11.3
  157.      */
  158.     public static function isSupported()
  159.     {
  160.         return (self::hasNativeSupport(|| extension_loaded('zlib'));
  161.     }
  162.  
  163.     /**
  164.      * Method to determine if the server has native zip support for faster handling
  165.      *
  166.      * @return  boolean  True if php has native ZIP support
  167.      *
  168.      * @since   11.1
  169.      */
  170.     public static function hasNativeSupport()
  171.     {
  172.         return (function_exists('zip_open'&& function_exists('zip_read'));
  173.     }
  174.  
  175.     /**
  176.      * Checks to see if the data is a valid ZIP file.
  177.      *
  178.      * @param   string  &$data  ZIP archive data buffer.
  179.      *
  180.      * @return  boolean  True if valid, false if invalid.
  181.      *
  182.      * @since   11.1
  183.      */
  184.     public function checkZipData(&$data)
  185.     {
  186.         if (strpos($data$this->_fileHeader=== false)
  187.         {
  188.             return false;
  189.         }
  190.         else
  191.         {
  192.             return true;
  193.         }
  194.     }
  195.  
  196.     /**
  197.      * Extract a ZIP compressed file to a given path using a php based algorithm that only requires zlib support
  198.      *
  199.      * @param   string  $archive      Path to ZIP archive to extract.
  200.      * @param   string  $destination  Path to extract archive into.
  201.      *
  202.      * @return  mixed   True if successful
  203.      *
  204.      * @since   11.1
  205.      * @throws  RuntimeException
  206.      */
  207.     protected function extractCustom($archive$destination)
  208.     {
  209.         $this->_data null;
  210.         $this->_metadata null;
  211.  
  212.         if (!extension_loaded('zlib'))
  213.         {
  214.             if (class_exists('JError'))
  215.             {
  216.                 return JError::raiseWarning(100'Zlib not supported');
  217.             }
  218.             else
  219.             {
  220.                 throw new RuntimeException('Zlib not supported');
  221.             }
  222.         }
  223.  
  224.         $this->_data file_get_contents($archive);
  225.  
  226.         if (!$this->_data)
  227.         {
  228.             if (class_exists('JError'))
  229.             {
  230.                 return JError::raiseWarning(100'Unable to read archive (zip)');
  231.             }
  232.             else
  233.             {
  234.                 throw new RuntimeException('Unable to read archive (zip)');
  235.             }
  236.         }
  237.  
  238.         if (!$this->_readZipInfo($this->_data))
  239.         {
  240.             if (class_exists('JError'))
  241.             {
  242.                 return JError::raiseWarning(100'Get ZIP Information failed');
  243.             }
  244.             else
  245.             {
  246.                 throw new RuntimeException('Get ZIP Information failed');
  247.             }
  248.         }
  249.  
  250.         for ($i 0$n count($this->_metadata)$i $n$i++)
  251.         {
  252.             $lastPathCharacter substr($this->_metadata[$i]['name']-11);
  253.  
  254.             if ($lastPathCharacter !== '/' && $lastPathCharacter !== '\\')
  255.             {
  256.                 $buffer $this->_getFileData($i);
  257.                 $path JPath::clean($destination '/' $this->_metadata[$i]['name']);
  258.  
  259.                 // Make sure the destination folder exists
  260.                 if (!JFolder::create(dirname($path)))
  261.                 {
  262.                     if (class_exists('JError'))
  263.                     {
  264.                         return JError::raiseWarning(100'Unable to create destination');
  265.                     }
  266.                     else
  267.                     {
  268.                         throw new RuntimeException('Unable to create destination');
  269.                     }
  270.                 }
  271.  
  272.                 if (JFile::write($path$buffer=== false)
  273.                 {
  274.                     if (class_exists('JError'))
  275.                     {
  276.                         return JError::raiseWarning(100'Unable to write entry');
  277.                     }
  278.                     else
  279.                     {
  280.                         throw new RuntimeException('Unable to write entry');
  281.                     }
  282.                 }
  283.             }
  284.         }
  285.  
  286.         return true;
  287.     }
  288.  
  289.     /**
  290.      * Extract a ZIP compressed file to a given path using native php api calls for speed
  291.      *
  292.      * @param   string  $archive      Path to ZIP archive to extract
  293.      * @param   string  $destination  Path to extract archive into
  294.      *
  295.      * @return  boolean  True on success
  296.      *
  297.      * @since   11.1
  298.      * @throws  RuntimeException
  299.      */
  300.     protected function extractNative($archive$destination)
  301.     {
  302.         $zip zip_open($archive);
  303.  
  304.         if (is_resource($zip))
  305.         {
  306.             // Make sure the destination folder exists
  307.             if (!JFolder::create($destination))
  308.             {
  309.                 if (class_exists('JError'))
  310.                 {
  311.                     return JError::raiseWarning(100'Unable to create destination');
  312.                 }
  313.                 else
  314.                 {
  315.                     throw new RuntimeException('Unable to create destination');
  316.                 }
  317.             }
  318.  
  319.             // Read files in the archive
  320.             while ($file @zip_read($zip))
  321.             {
  322.                 if (zip_entry_open($zip$file"r"))
  323.                 {
  324.                     if (substr(zip_entry_name($file)strlen(zip_entry_name($file)) 1!= "/")
  325.                     {
  326.                         $buffer zip_entry_read($filezip_entry_filesize($file));
  327.  
  328.                         if (JFile::write($destination '/' zip_entry_name($file)$buffer=== false)
  329.                         {
  330.                             if (class_exists('JError'))
  331.                             {
  332.                                 return JError::raiseWarning(100'Unable to write entry');
  333.                             }
  334.                             else
  335.                             {
  336.                                 throw new RuntimeException('Unable to write entry');
  337.                             }
  338.                         }
  339.  
  340.                         zip_entry_close($file);
  341.                     }
  342.                 }
  343.                 else
  344.                 {
  345.                     if (class_exists('JError'))
  346.                     {
  347.                         return JError::raiseWarning(100'Unable to read entry');
  348.                     }
  349.                     else
  350.                     {
  351.                         throw new RuntimeException('Unable to read entry');
  352.                     }
  353.                 }
  354.             }
  355.  
  356.             @zip_close($zip);
  357.         }
  358.         else
  359.         {
  360.             if (class_exists('JError'))
  361.             {
  362.                 return JError::raiseWarning(100'Unable to open archive');
  363.             }
  364.             else
  365.             {
  366.                 throw new RuntimeException('Unable to open archive');
  367.             }
  368.         }
  369.  
  370.         return true;
  371.     }
  372.  
  373.     /**
  374.      * Get the list of files/data from a ZIP archive buffer.
  375.      *
  376.      * <pre>
  377.      * KEY: Position in zipfile
  378.      * VALUES: 'attr'  --  File attributes
  379.      * 'crc'   --  CRC checksum
  380.      * 'csize' --  Compressed file size
  381.      * 'date'  --  File modification time
  382.      * 'name'  --  Filename
  383.      * 'method'--  Compression method
  384.      * 'size'  --  Original file size
  385.      * 'type'  --  File type
  386.      * </pre>
  387.      *
  388.      * @param   string  &$data  The ZIP archive buffer.
  389.      *
  390.      * @return  boolean True on success
  391.      *
  392.      * @since   11.1
  393.      * @throws  RuntimeException
  394.      */
  395.     private function _readZipInfo(&$data)
  396.     {
  397.         $entries array();
  398.  
  399.         // Find the last central directory header entry
  400.         $fhLast strpos($data$this->_ctrlDirEnd);
  401.  
  402.         do
  403.         {
  404.             $last $fhLast;
  405.         }
  406.         while (($fhLast strpos($data$this->_ctrlDirEnd$fhLast 1)) !== false);
  407.  
  408.         // Find the central directory offset
  409.         $offset 0;
  410.  
  411.         if ($last)
  412.         {
  413.             $endOfCentralDirectory unpack(
  414.                 'vNumberOfDisk/vNoOfDiskWithStartOfCentralDirectory/vNoOfCentralDirectoryEntriesOnDisk/' .
  415.                 'vTotalCentralDirectoryEntries/VSizeOfCentralDirectory/VCentralDirectoryOffset/vCommentLength',
  416.                 substr($data$last 4)
  417.             );
  418.             $offset $endOfCentralDirectory['CentralDirectoryOffset'];
  419.         }
  420.  
  421.         // Get details from central directory structure.
  422.         $fhStart strpos($data$this->_ctrlDirHeader$offset);
  423.         $dataLength strlen($data);
  424.  
  425.         do
  426.         {
  427.             if ($dataLength $fhStart 31)
  428.             {
  429.                 if (class_exists('JError'))
  430.                 {
  431.                     return JError::raiseWarning(100'Invalid Zip Data');
  432.                 }
  433.                 else
  434.                 {
  435.                     throw new RuntimeException('Invalid Zip Data');
  436.                 }
  437.             }
  438.  
  439.             $info unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength'substr($data$fhStart 1020));
  440.             $name substr($data$fhStart 46$info['Length']);
  441.  
  442.             $entries[$namearray(
  443.                 'attr' => null,
  444.                 'crc' => sprintf("%08s"dechex($info['CRC32'])),
  445.                 'csize' => $info['Compressed'],
  446.                 'date' => null,
  447.                 '_dataStart' => null,
  448.                 'name' => $name,
  449.                 'method' => $this->_methods[$info['Method']],
  450.                 '_method' => $info['Method'],
  451.                 'size' => $info['Uncompressed'],
  452.                 'type' => null
  453.             );
  454.  
  455.             $entries[$name]['date'mktime(
  456.                 (($info['Time'>> 110x1f),
  457.                 (($info['Time'>> 50x3f),
  458.                 (($info['Time'<< 10x3e),
  459.                 (($info['Time'>> 210x07),
  460.                 (($info['Time'>> 160x1f),
  461.                 ((($info['Time'>> 250x7f1980)
  462.             );
  463.  
  464.             if ($dataLength $fhStart 43)
  465.             {
  466.                 if (class_exists('JError'))
  467.                 {
  468.                     return JError::raiseWarning(100'Invalid ZIP data');
  469.                 }
  470.                 else
  471.                 {
  472.                     throw new RuntimeException('Invalid ZIP data');
  473.                 }
  474.             }
  475.  
  476.             $info unpack('vInternal/VExternal/VOffset'substr($data$fhStart 3610));
  477.  
  478.             $entries[$name]['type'($info['Internal'0x01'text' 'binary';
  479.             $entries[$name]['attr'(($info['External'0x10'D' '-'(($info['External'0x20'A' '-')
  480.                 . (($info['External'0x03'S' '-'(($info['External'0x02'H' '-'(($info['External'0x01'R' '-');
  481.             $entries[$name]['offset'$info['Offset'];
  482.  
  483.             // Get details from local file header since we have the offset
  484.             $lfhStart strpos($data$this->_fileHeader$entries[$name]['offset']);
  485.  
  486.             if ($dataLength $lfhStart 34)
  487.             {
  488.                 if (class_exists('JError'))
  489.                 {
  490.                     return JError::raiseWarning(100'Invalid Zip Data');
  491.                 }
  492.                 else
  493.                 {
  494.                     throw new RuntimeException('Invalid Zip Data');
  495.                 }
  496.             }
  497.  
  498.             $info unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength/vExtraLength'substr($data$lfhStart 825));
  499.             $name substr($data$lfhStart 30$info['Length']);
  500.             $entries[$name]['_dataStart'$lfhStart 30 $info['Length'$info['ExtraLength'];
  501.  
  502.             // Bump the max execution time because not using the built in php zip libs makes this process slow.
  503.             @set_time_limit(ini_get('max_execution_time'));
  504.         }
  505.         while ((($fhStart strpos($data$this->_ctrlDirHeader$fhStart 46)) !== false));
  506.  
  507.         $this->_metadata array_values($entries);
  508.  
  509.         return true;
  510.     }
  511.  
  512.     /**
  513.      * Returns the file data for a file by offsest in the ZIP archive
  514.      *
  515.      * @param   integer  $key  The position of the file in the archive.
  516.      *
  517.      * @return  string  Uncompressed file data buffer.
  518.      *
  519.      * @since   11.1
  520.      */
  521.     private function _getFileData($key)
  522.     {
  523.         if ($this->_metadata[$key]['_method'== 0x8)
  524.         {
  525.             return gzinflate(substr($this->_data$this->_metadata[$key]['_dataStart']$this->_metadata[$key]['csize']));
  526.         }
  527.         elseif ($this->_metadata[$key]['_method'== 0x0)
  528.         {
  529.             /* Files that aren't compressed. */
  530.             return substr($this->_data$this->_metadata[$key]['_dataStart']$this->_metadata[$key]['csize']);
  531.         }
  532.         elseif ($this->_metadata[$key]['_method'== 0x12)
  533.         {
  534.             // If bz2 extension is loaded use it
  535.             if (extension_loaded('bz2'))
  536.             {
  537.                 return bzdecompress(substr($this->_data$this->_metadata[$key]['_dataStart']$this->_metadata[$key]['csize']));
  538.             }
  539.         }
  540.  
  541.         return '';
  542.     }
  543.  
  544.     /**
  545.      * Converts a UNIX timestamp to a 4-byte DOS date and time format
  546.      * (date in high 2-bytes, time in low 2-bytes allowing magnitude
  547.      * comparison).
  548.      *
  549.      * @param   int  $unixtime  The current UNIX timestamp.
  550.      *
  551.      * @return  int  The current date in a 4-byte DOS format.
  552.      *
  553.      * @since   11.1
  554.      */
  555.     protected function _unix2DOSTime($unixtime null)
  556.     {
  557.         $timearray (is_null($unixtime)) getdate(getdate($unixtime);
  558.  
  559.         if ($timearray['year'1980)
  560.         {
  561.             $timearray['year'1980;
  562.             $timearray['mon'1;
  563.             $timearray['mday'1;
  564.             $timearray['hours'0;
  565.             $timearray['minutes'0;
  566.             $timearray['seconds'0;
  567.         }
  568.  
  569.         return (($timearray['year'1980<< 25($timearray['mon'<< 21($timearray['mday'<< 16($timearray['hours'<< 11|
  570.             ($timearray['minutes'<< 5($timearray['seconds'>> 1);
  571.     }
  572.  
  573.     /**
  574.      * Adds a "file" to the ZIP archive.
  575.      *
  576.      * @param   array  &$file      File data array to add
  577.      * @param   array  &$contents  An array of existing zipped files.
  578.      * @param   array  &$ctrldir   An array of central directory information.
  579.      *
  580.      * @return  void 
  581.      *
  582.      * @since   11.1
  583.      *
  584.      * @todo    Review and finish implementation
  585.      */
  586.     private function _addToZIPFile(array &$filearray &$contentsarray &$ctrldir)
  587.     {
  588.         $data &$file['data'];
  589.         $name str_replace('\\''/'$file['name']);
  590.  
  591.         /* See if time/date information has been provided. */
  592.         $ftime null;
  593.  
  594.         if (isset($file['time']))
  595.         {
  596.             $ftime $file['time'];
  597.         }
  598.  
  599.         // Get the hex time.
  600.         $dtime dechex($this->_unix2DosTime($ftime));
  601.         $hexdtime chr(hexdec($dtime[6$dtime[7])) chr(hexdec($dtime[4$dtime[5])) chr(hexdec($dtime[2$dtime[3]))
  602.             . chr(hexdec($dtime[0$dtime[1]));
  603.  
  604.         /* Begin creating the ZIP data. */
  605.         $fr $this->_fileHeader;
  606.         /* Version needed to extract. */
  607.         $fr .= "\x14\x00";
  608.         /* General purpose bit flag. */
  609.         $fr .= "\x00\x00";
  610.         /* Compression method. */
  611.         $fr .= "\x08\x00";
  612.         /* Last modification time/date. */
  613.         $fr .= $hexdtime;
  614.  
  615.         /* "Local file header" segment. */
  616.         $unc_len strlen($data);
  617.         $crc crc32($data);
  618.         $zdata gzcompress($data);
  619.         $zdata substr(substr($zdata0strlen($zdata4)2);
  620.         $c_len strlen($zdata);
  621.  
  622.         /* CRC 32 information. */
  623.         $fr .= pack('V'$crc);
  624.         /* Compressed filesize. */
  625.         $fr .= pack('V'$c_len);
  626.         /* Uncompressed filesize. */
  627.         $fr .= pack('V'$unc_len);
  628.         /* Length of filename. */
  629.         $fr .= pack('v'strlen($name));
  630.         /* Extra field length. */
  631.         $fr .= pack('v'0);
  632.         /* File name. */
  633.         $fr .= $name;
  634.  
  635.         /* "File data" segment. */
  636.         $fr .= $zdata;
  637.  
  638.         /* Add this entry to array. */
  639.         $old_offset strlen(implode(''$contents));
  640.         $contents[&$fr;
  641.  
  642.         /* Add to central directory record. */
  643.         $cdrec $this->_ctrlDirHeader;
  644.         /* Version made by. */
  645.         $cdrec .= "\x00\x00";
  646.         /* Version needed to extract */
  647.         $cdrec .= "\x14\x00";
  648.         /* General purpose bit flag */
  649.         $cdrec .= "\x00\x00";
  650.         /* Compression method */
  651.         $cdrec .= "\x08\x00";
  652.         /* Last mod time/date. */
  653.         $cdrec .= $hexdtime;
  654.         /* CRC 32 information. */
  655.         $cdrec .= pack('V'$crc);
  656.         /* Compressed filesize. */
  657.         $cdrec .= pack('V'$c_len);
  658.         /* Uncompressed filesize. */
  659.         $cdrec .= pack('V'$unc_len);
  660.         /* Length of filename. */
  661.         $cdrec .= pack('v'strlen($name));
  662.         /* Extra field length. */
  663.         $cdrec .= pack('v'0);
  664.         /* File comment length. */
  665.         $cdrec .= pack('v'0);
  666.         /* Disk number start. */
  667.         $cdrec .= pack('v'0);
  668.         /* Internal file attributes. */
  669.         $cdrec .= pack('v'0);
  670.         /* External file attributes -'archive' bit set. */
  671.         $cdrec .= pack('V'32);
  672.         /* Relative offset of local header. */
  673.         $cdrec .= pack('V'$old_offset);
  674.         /* File name. */
  675.         $cdrec .= $name;
  676.         /* Optional extra field, file comment goes here. */
  677.  
  678.         /* Save to central directory array. */
  679.         $ctrldir[&$cdrec;
  680.     }
  681.  
  682.     /**
  683.      * Creates the ZIP file.
  684.      *
  685.      * Official ZIP file format: http://www.pkware.com/appnote.txt
  686.      *
  687.      * @param   array   &$contents  An array of existing zipped files.
  688.      * @param   array   &$ctrlDir   An array of central directory information.
  689.      * @param   string  $path       The path to store the archive.
  690.      *
  691.      * @return  boolean  True if successful
  692.      *
  693.      * @since   11.1
  694.      *
  695.      * @todo    Review and finish implementation
  696.      */
  697.     private function _createZIPFile(array &$contentsarray &$ctrlDir$path)
  698.     {
  699.         $data implode(''$contents);
  700.         $dir implode(''$ctrlDir);
  701.  
  702.         $buffer $data $dir $this->_ctrlDirEnd /* Total # of entries "on this disk". */
  703.         pack('v'count($ctrlDir)) /* Total # of entries overall. */
  704.         pack('v'count($ctrlDir)) /* Size of central directory. */
  705.         pack('V'strlen($dir)) /* Offset to start of central dir. */
  706.         pack('V'strlen($data)) /* ZIP file comment length. */
  707.         "\x00\x00";
  708.  
  709.         if (JFile::write($path$buffer=== false)
  710.         {
  711.             return false;
  712.         }
  713.         else
  714.         {
  715.             return true;
  716.         }
  717.     }
  718. }

Documentation generated on Tue, 19 Nov 2013 15:18:40 +0100 by phpDocumentor 1.4.3