<?php// test.php
include('docxtemplate.class.php');
$docx = new DOCXTemplate('test.tpl.docx');
$docx->set('NUM', 123456 );
$docx->set('DATE', date('m.d.Y'));
$docx->set('COMPANY', 'SIBVISION.RU
Russian Federation, Omsk
phone: +73812590554');
$docx->saveAs('test.docx'); // or $docx->downloadAs('test.docx');
*/
class DOCXTemplate {
private $data;
private $package;
private $error;
public function __construct( $template_filename, $is_data = false, $debug = false ) {
$this->data = array();
$this->error = false;
$this->debug = $debug;
$this->_unzip( $template_filename, $is_data );
}
public function set( $var, $value = NULL ) {
if (is_array($var) || is_object($var)) {
foreach( $var as $k => $v )
$this->data[ $k ] = $v;
} else {
$this->data[ $var ] = $value;
}
}
public function error( $set = false ) {
if ($set) {
$this->error = $set;
if ($this->debug)
trigger_error( __CLASS__.': '.$set, E_USER_WARNING );
} else {
return $this->error;
}
}
public function success() {
return !$this->error;
}
private function _parse() {
if ($doc = $this->getEntryData( 'word/document.xml' )) {
if (preg_match_all('/{[^}]+}/', $doc, $m )) {
foreach( $m[0] as $v ) {
$var = preg_replace('/[s{}]/','', strip_tags( $v ));
if (isset( $this->data[ $var ] )) {
if (is_scalar( $this->data[ $var ] ))
$doc = str_replace( $v, $this->_esc( $this->data[ $var ] ), $doc );
} else {
$doc = str_replace( $v, '{!404_'.__CLASS__.'_'.$var.'}', $doc );
}
}
}
$this->setEntryData( 'word/document.xml', $doc );
return true;
} else
return false;
}
private function _unzip( $filename, $is_data = false ) {
// Clear current file
$this->datasec = array();
if ($is_data) {
$this->package['filename'] = 'default.xlsx';
$this->package['mtime'] = time();
$this->package['size'] = strlen( $filename );
$vZ = $filename;
} else {
if (!is_readable($filename)) {
$this->error( 'File not found' );
return false;
}
// Package information
$this->package['filename'] = $filename;
$this->package['mtime'] = filemtime( $filename );
$this->package['size'] = filesize( $filename );
// Read file
$oF = fopen($filename, 'rb');
$vZ = fread($oF, $this->package['size']);
fclose($oF);
}
// Cut end of central directory
/*$aE = explode("x50x4bx05x06", $vZ);
if (count($aE) == 1) {
$this->error('Unknown format');
return false;
}
*/
if ( ($pcd = strrpos( $vZ, "x50x4bx05x06" )) === false ) {
$this->error('Unknown format');
return false;
}
$aE = array(
0 => substr( $vZ, 0, $pcd ),
1 => substr( $vZ, $pcd + 3 )
);
// Normal way
$aP = unpack('x16/v1CL', $aE[1]);
$this->package['comment'] = substr($aE[1], 18, $aP['CL']);
// Translates end of line from other operating systems
$this->package['comment'] = strtr($this->package['comment'], array("rn" => "n", "r" => "n"));
// Cut the entries from the central directory
$aE = explode("x50x4bx01x02", $vZ);
// Explode to each part
$aE = explode("x50x4bx03x04", $aE[0]);
// Shift out spanning signature or empty entry
array_shift($aE);
// Loop through the entries
foreach ($aE as $vZ) {
$aI = array();
$aI['E']= 0;
$aI['EM'] = '';
// Retrieving local file header information
//$aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL', $vZ);
$aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL/v1EFL', $vZ);
// Check if data is encrypted
//$bE = ($aP['GPF'] && 0x0001) ? TRUE : FALSE;
$bE = false;
$nF = $aP['FNL'];
$mF = $aP['EFL'];
// Special case : value block after the compressed data
if ($aP['GPF'] & 0x0008) {
$aP1 = unpack('V1CRC/V1CS/V1UCS', substr($vZ, -12));
$aP['CRC'] = $aP1['CRC'];
$aP['CS']= $aP1['CS'];
$aP['UCS'] = $aP1['UCS'];
// 2013-08-10
$vZ = substr($vZ, 0, -12);
if (substr($vZ,-4) === "x50x4bx07x08")
$vZ = substr($vZ, 0, -4);
}
// Getting stored filename
$aI['N'] = substr($vZ, 26, $nF);
if (substr($aI['N'], -1) == '/') {
// is a directory entry - will be skipped
continue;
}
// Truncate full filename in path and filename
$aI['P'] = dirname($aI['N']);
$aI['P'] = $aI['P'] == '.' ? '' : $aI['P'];
$aI['N'] = basename($aI['N']);
$vZ = substr($vZ, 26 + $nF + $mF);
if ( strlen($vZ) != $aP['CS'] ) { // check only if availabled
$aI['E']= 1;
$aI['EM'] = 'Compressed size is not equal with the value in header information.';
} else {
if ($bE) {
$aI['E']= 5;
$aI['EM'] = 'File is encrypted, which is not supported from this class.';
} else {
switch($aP['CM']) {
case 0: // Stored
// Here is nothing to do, the file ist flat.
break;
case 8: // Deflated
$vZ = gzinflate($vZ);
break;
case 12: // BZIP2
if (! extension_loaded('bz2')) {
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
@dl('php_bz2.dll');
} else {
@dl('bz2.so');
}
}
if (extension_loaded('bz2')) {
$vZ = bzdecompress($vZ);
} else {
$aI['E']= 7;
$aI['EM'] = "PHP BZIP2 extension not available.";
}
break;
default:
$aI['E']= 6;
$aI['EM'] = "De-/Compression method {$aP['CM']} is not supported.";
}
if (! $aI['E']) {
if ($vZ === FALSE) {
$aI['E']= 2;
$aI['EM'] = 'Decompression of data failed.';
} else {
if (strlen($vZ) != $aP['UCS']) {
$aI['E']= 3;
$aI['EM'] = 'Uncompressed size is not equal with the value in header information.';
} else {
if (crc32($vZ) != $aP['CRC']) {
$aI['E']= 4;
$aI['EM'] = 'CRC32 checksum is not equal with the value in header information.';
}
}
}
}
}
}
$aI['D'] = $vZ;
// DOS to UNIX timestamp
$aI['T'] = mktime(($aP['FT']& 0xf800) >> 11,
($aP['FT']& 0x07e0) >>5,
($aP['FT']& 0x001f) <<1,
($aP['FD']& 0x01e0) >>5,
($aP['FD']& 0x001f),
(($aP['FD'] & 0xfe00) >>9) + 1980);
//$this->Entries[] = &new SimpleUnzipEntry($aI);
$this->package['entries'][] = array(
'data' => $aI['D'],
'error' => $aI['E'],
'error_msg' => $aI['EM'],
'name' => $aI['N'],
'path' => $aI['P'],
'time' => $aI['T']
);
} // end for each entries
}
public function getPackage() {
return $this->package;
}
public function entryExists( $name ) { // 0.6.6
$dir = dirname( $name );
$name = basename( $name );
foreach( $this->package['entries'] as $entry)
if ( $entry['path'] == $dir && $entry['name'] == $name)
return true;
return false;
}
public function getEntryData( $name ) {
$dir = dirname( $name );
$name = basename( $name );
foreach( $this->package['entries'] as $entry)
if ( $entry['path'] == $dir && $entry['name'] == $name)
return $entry['data'];
$this->error('Unknown format');
return false;
}
public function setEntryData( $name, $data ) {
$dir = dirname( $name );
$name = basename( $name );
foreach( $this->package['entries'] as $k => $entry)
if ( $entry['path'] == $dir && $entry['name'] == $name) {
$this->package['entries'][$k]['data'] = $data;
return;
}
return false;
}
private function _esc( $s ) {
$s = str_replace(array('&','"',"'","<",">"),array('&','"',''','<','>'), $s);
$s = str_replace( array("rn", "r"),"n", $s );
$s = str_replace( "n", '</w:t><w:br/><w:t>', $s );
return $s;
}
private function _zip( $fh ) {
$zipSignature = "x50x4bx03x04"; // local file header signature
$dirSignature = "x50x4bx01x02"; // central dir header signature
$dirSignatureE= "x50x4bx05x06"; // end of central dir signature
$zipComments = 'Generated by '.__CLASS__.' PHP class, thanks Sergey Shuchkin';
//$fh = fopen( $filename, 'wb' );
if (!$fh)
return false;
$cdrec = '';
foreach ($this->package['entries'] as $e ) {
$cfilename = ($e['path']) ? $e['path'] .'/'. $e['name'] : $e['name'];
$e['uncsize'] = strlen($e['data']);
// if data to compress is too small, just store it
if($e['uncsize'] < 256){
$e['comsize'] = $e['uncsize'];
$e['vneeded'] = 10;
$e['cmethod'] = 0;
$zdata = $e['data'];
} else{ // otherwise, compress it
$zdata = gzcompress($e['data']);
$zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug (thanks to Eric Mueller)
$e['comsize'] = strlen($zdata);
$e['vneeded'] = 10;
$e['cmethod'] = 8;
}
$e['bitflag'] = 0;
$e['crc_32']= crc32($e['data']);
// Convert date and time to DOS Format, and set then
$lastmod_timeS= str_pad(decbin(date('s')>=32?date('s')-32:date('s')), 5, '0', STR_PAD_LEFT);
$lastmod_timeM= str_pad(decbin(date('i')), 6, '0', STR_PAD_LEFT);
$lastmod_timeH= str_pad(decbin(date('H')), 5, '0', STR_PAD_LEFT);
$lastmod_dateD= str_pad(decbin(date('d')), 5, '0', STR_PAD_LEFT);
$lastmod_dateM= str_pad(decbin(date('m')), 4, '0', STR_PAD_LEFT);
$lastmod_dateY= str_pad(decbin(date('Y')-1980), 7, '0', STR_PAD_LEFT);
# echo "ModTime: $lastmod_timeS-$lastmod_timeM-$lastmod_timeH (".date("s H H").")n";
# echo "ModDate: $lastmod_dateD-$lastmod_dateM-$lastmod_dateY (".date("d m Y").")n";
$e['modtime'] = bindec("$lastmod_timeH$lastmod_timeM$lastmod_timeS");
$e['moddate'] = bindec("$lastmod_dateY$lastmod_dateM$lastmod_dateD");
$e['offset'] = ftell($fh);
fwrite($fh, $zipSignature);
fwrite($fh, pack('s', $e['vneeded'])); // version_needed
fwrite($fh, pack('s', $e['bitflag'])); // general_bit_flag
fwrite($fh, pack('s', $e['cmethod'])); // compression_method
fwrite($fh, pack('s', $e['modtime'])); // lastmod_time
fwrite($fh, pack('s', $e['moddate'])); // lastmod_date
fwrite($fh, pack('V', $e['crc_32']));// crc-32
fwrite($fh, pack('I', $e['comsize'])); // compressed_size
fwrite($fh, pack('I', $e['uncsize'])); // uncompressed_size
fwrite($fh, pack('s', strlen($cfilename))); // file_name_length
fwrite($fh, pack('s', 0));// extra_field_length
fwrite($fh, $cfilename);// file_name
// ignoring extra_field
fwrite($fh, $zdata);
// Append it to central dir
$e['external_attributes']= (substr($cfilename, -1)=='/'&&!$zdata)?16:32; // Directory or file name
$e['comments'] = '';
$cdrec .= $dirSignature;
$cdrec .= "x0x0";// version made by
$cdrec .= pack('v', $e['vneeded']); // version needed to extract
$cdrec .= "x0x0";// general bit flag
$cdrec .= pack('v', $e['cmethod']); // compression method
$cdrec .= pack('v', $e['modtime']); // lastmod time
$cdrec .= pack('v', $e['moddate']); // lastmod date
$cdrec .= pack('V', $e['crc_32']);// crc32
$cdrec .= pack('V', $e['comsize']); // compressed filesize
$cdrec .= pack('V', $e['uncsize']); // uncompressed filesize
$cdrec .= pack('v', strlen($cfilename)); // file name length
$cdrec .= pack('v', 0);// extra field length
$cdrec .= pack('v', strlen($e['comments'])); // file comment length
$cdrec .= pack('v', 0); // disk number start
$cdrec .= pack('v', 0); // internal file attributes
$cdrec .= pack('V', $e['external_attributes']); // internal file attributes
$cdrec .= pack('V', $e['offset']); // relative offset of local header
$cdrec .= $cfilename;
$cdrec .= $e['comments'];
}
$before_cd = ftell($fh);
fwrite($fh, $cdrec);
// end of central dir
fwrite($fh, $dirSignatureE);
fwrite($fh, pack('v', 0)); // number of this disk
fwrite($fh, pack('v', 0)); // number of the disk with the start of the central directory
fwrite($fh, pack('v', count( $this->package['entries'] ))); // total # of entries "on this disk"
fwrite($fh, pack('v', count( $this->package['entries'] ))); // total # of entries overall
fwrite($fh, pack('V', strlen($cdrec))); // size of central dir
fwrite($fh, pack('V', $before_cd)); // offset to start of central dir
fwrite($fh, pack('v', strlen($zipComments))); // .zip file comment length
fwrite($fh, $zipComments);
return true;
}
function saveAs( $filename ) {
if (!$this->_parse())
return false;
$fh = fopen( $filename, 'wb' );
if (!$fh)
return false;
if (!$this->_zip($fh)) {
fclose($fh);
return false;
}
fclose($fh);
return true;
}
function downloadAs( $filename, $exit = true ) {
if (!$this->_parse())
return false;
//php://stdin
$fh = tmpfile();
if (!$fh)
return false;
if (!$this->_zip( $fh )) {
fclose( $fh );
return false;
}
$size = ftell($fh);
$filename = ($filename) ? $filename : gmdate('Ymdhi');
header('Content-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s GMT' , time() ));
header('Content-Length: '.$size);
fseek($fh,0);
echo fread($fh, $size );
fclose($fh);
if ($exit) exit();
return true;
}
}
?>