api.docx.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. <?php
  2. /**
  3. * DOCXTemplate class 0.1.10
  4. *
  5. * @author sergey.shuchkin@gmail.com
  6. */
  7. /**
  8. * Replace {var} in MS Word 2007+ documents (*.docx)
  9. */
  10. class DOCXTemplate {
  11. private $data;
  12. private $package;
  13. private $error;
  14. public function __construct($template_filename, $is_data = false, $debug = false) {
  15. $this->data = array();
  16. $this->error = false;
  17. $this->debug = $debug;
  18. $this->_unzip($template_filename, $is_data);
  19. }
  20. public function set($var, $value = NULL) {
  21. if (is_array($var) || is_object($var)) {
  22. foreach ($var as $k => $v)
  23. $this->data[$k] = $v;
  24. } else {
  25. $this->data[$var] = $value;
  26. }
  27. }
  28. public function error($set = false) {
  29. if ($set) {
  30. $this->error = $set;
  31. if ($this->debug)
  32. trigger_error(__CLASS__ . ': ' . $set, E_USER_WARNING);
  33. } else {
  34. return $this->error;
  35. }
  36. }
  37. public function success() {
  38. return !$this->error;
  39. }
  40. private function _parse() {
  41. if ($doc = $this->getEntryData('word/document.xml')) {
  42. if (preg_match_all('/\{[^}]+\}/', $doc, $m)) {
  43. foreach ($m[0] as $v) {
  44. $var = preg_replace('/[\s\{\}]/', '', strip_tags($v));
  45. if (isset($this->data[$var])) {
  46. if (is_scalar($this->data[$var]))
  47. $doc = str_replace($v, $this->_esc($this->data[$var]), $doc);
  48. } else {
  49. $doc = str_replace($v, '{!404_' . __CLASS__ . '_' . $var . '}', $doc);
  50. }
  51. }
  52. }
  53. $this->setEntryData('word/document.xml', $doc);
  54. return true;
  55. } else
  56. return false;
  57. }
  58. private function _unzip($filename, $is_data = false) {
  59. // Clear current file
  60. $this->datasec = array();
  61. if ($is_data) {
  62. $this->package['filename'] = 'default.xlsx';
  63. $this->package['mtime'] = time();
  64. $this->package['size'] = strlen($filename);
  65. $vZ = $filename;
  66. } else {
  67. if (!is_readable($filename)) {
  68. $this->error('File not found');
  69. return false;
  70. }
  71. // Package information
  72. $this->package['filename'] = $filename;
  73. $this->package['mtime'] = filemtime($filename);
  74. $this->package['size'] = filesize($filename);
  75. // Read file
  76. $oF = fopen($filename, 'rb');
  77. $vZ = fread($oF, $this->package['size']);
  78. fclose($oF);
  79. }
  80. // Cut end of central directory
  81. /* $aE = explode("\x50\x4b\x05\x06", $vZ);
  82. if (count($aE) == 1) {
  83. $this->error('Unknown format');
  84. return false;
  85. }
  86. */
  87. if (($pcd = strrpos($vZ, "\x50\x4b\x05\x06")) === false) {
  88. $this->error('Unknown format');
  89. return false;
  90. }
  91. $aE = array(
  92. 0 => substr($vZ, 0, $pcd),
  93. 1 => substr($vZ, $pcd + 3)
  94. );
  95. // Normal way
  96. $aP = unpack('x16/v1CL', $aE[1]);
  97. $this->package['comment'] = substr($aE[1], 18, $aP['CL']);
  98. // Translates end of line from other operating systems
  99. $this->package['comment'] = strtr($this->package['comment'], array("\r\n" => "\n", "\r" => "\n"));
  100. // Cut the entries from the central directory
  101. $aE = explode("\x50\x4b\x01\x02", $vZ);
  102. // Explode to each part
  103. $aE = explode("\x50\x4b\x03\x04", $aE[0]);
  104. // Shift out spanning signature or empty entry
  105. array_shift($aE);
  106. // Loop through the entries
  107. foreach ($aE as $vZ) {
  108. $aI = array();
  109. $aI['E'] = 0;
  110. $aI['EM'] = '';
  111. // Retrieving local file header information
  112. // $aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL', $vZ);
  113. $aP = unpack('v1VN/v1GPF/v1CM/v1FT/v1FD/V1CRC/V1CS/V1UCS/v1FNL/v1EFL', $vZ);
  114. // Check if data is encrypted
  115. // $bE = ($aP['GPF'] && 0x0001) ? TRUE : FALSE;
  116. $bE = false;
  117. $nF = $aP['FNL'];
  118. $mF = $aP['EFL'];
  119. // Special case : value block after the compressed data
  120. if ($aP['GPF'] & 0x0008) {
  121. $aP1 = unpack('V1CRC/V1CS/V1UCS', substr($vZ, -12));
  122. $aP['CRC'] = $aP1['CRC'];
  123. $aP['CS'] = $aP1['CS'];
  124. $aP['UCS'] = $aP1['UCS'];
  125. // 2013-08-10
  126. $vZ = substr($vZ, 0, -12);
  127. if (substr($vZ, -4) === "\x50\x4b\x07\x08")
  128. $vZ = substr($vZ, 0, -4);
  129. }
  130. // Getting stored filename
  131. $aI['N'] = substr($vZ, 26, $nF);
  132. if (substr($aI['N'], -1) == '/') {
  133. // is a directory entry - will be skipped
  134. continue;
  135. }
  136. // Truncate full filename in path and filename
  137. $aI['P'] = dirname($aI['N']);
  138. $aI['P'] = $aI['P'] == '.' ? '' : $aI['P'];
  139. $aI['N'] = basename($aI['N']);
  140. $vZ = substr($vZ, 26 + $nF + $mF);
  141. if (strlen($vZ) != $aP['CS']) { // check only if availabled
  142. $aI['E'] = 1;
  143. $aI['EM'] = 'Compressed size is not equal with the value in header information.';
  144. } else {
  145. if ($bE) {
  146. $aI['E'] = 5;
  147. $aI['EM'] = 'File is encrypted, which is not supported from this class.';
  148. } else {
  149. switch ($aP['CM']) {
  150. case 0: // Stored
  151. // Here is nothing to do, the file ist flat.
  152. break;
  153. case 8: // Deflated
  154. $vZ = gzinflate($vZ);
  155. break;
  156. case 12: // BZIP2
  157. if (!extension_loaded('bz2')) {
  158. if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
  159. @dl('php_bz2.dll');
  160. } else {
  161. @dl('bz2.so');
  162. }
  163. }
  164. if (extension_loaded('bz2')) {
  165. $vZ = bzdecompress($vZ);
  166. } else {
  167. $aI['E'] = 7;
  168. $aI['EM'] = "PHP BZIP2 extension not available.";
  169. }
  170. break;
  171. default:
  172. $aI['E'] = 6;
  173. $aI['EM'] = "De-/Compression method {$aP['CM']} is not supported.";
  174. }
  175. if (!$aI['E']) {
  176. if ($vZ === FALSE) {
  177. $aI['E'] = 2;
  178. $aI['EM'] = 'Decompression of data failed.';
  179. } else {
  180. if (strlen($vZ) != $aP['UCS']) {
  181. $aI['E'] = 3;
  182. $aI['EM'] = 'Uncompressed size is not equal with the value in header information.';
  183. } else {
  184. if (crc32($vZ) != $aP['CRC']) {
  185. $aI['E'] = 4;
  186. $aI['EM'] = 'CRC32 checksum is not equal with the value in header information.';
  187. }
  188. }
  189. }
  190. }
  191. }
  192. }
  193. $aI['D'] = $vZ;
  194. // DOS to UNIX timestamp
  195. $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);
  196. //$this->Entries[] = &new SimpleUnzipEntry($aI);
  197. $this->package['entries'][] = array(
  198. 'data' => $aI['D'],
  199. 'error' => $aI['E'],
  200. 'error_msg' => $aI['EM'],
  201. 'name' => $aI['N'],
  202. 'path' => $aI['P'],
  203. 'time' => $aI['T']
  204. );
  205. } // end for each entries
  206. }
  207. public function getPackage() {
  208. return $this->package;
  209. }
  210. public function entryExists($name) { // 0.6.6
  211. $dir = dirname($name);
  212. $name = basename($name);
  213. foreach ($this->package['entries'] as $entry)
  214. if ($entry['path'] == $dir && $entry['name'] == $name)
  215. return true;
  216. return false;
  217. }
  218. public function getEntryData($name) {
  219. $dir = dirname($name);
  220. $name = basename($name);
  221. foreach ($this->package['entries'] as $entry)
  222. if ($entry['path'] == $dir && $entry['name'] == $name)
  223. return $entry['data'];
  224. $this->error('Unknown format');
  225. return false;
  226. }
  227. public function setEntryData($name, $data) {
  228. $dir = dirname($name);
  229. $name = basename($name);
  230. foreach ($this->package['entries'] as $k => $entry)
  231. if ($entry['path'] == $dir && $entry['name'] == $name) {
  232. $this->package['entries'][$k]['data'] = $data;
  233. return;
  234. }
  235. return false;
  236. }
  237. private function _esc($s) {
  238. $s = str_replace(array('&', '"', "'", "<", ">"), array('&amp;', '&quot;', '&#039;', '&lt;', '&gt;'), $s);
  239. $s = str_replace(array("\r\n", "\r"), "\n", $s);
  240. $s = str_replace("\n", '</w:t><w:br/><w:t>', $s);
  241. return $s;
  242. }
  243. private function _zip($fh) {
  244. $zipSignature = "\x50\x4b\x03\x04"; // local file header signature
  245. $dirSignature = "\x50\x4b\x01\x02"; // central dir header signature
  246. $dirSignatureE = "\x50\x4b\x05\x06"; // end of central dir signature
  247. $zipComments = 'Generated by ' . __CLASS__ . ' PHP class, thanks Sergey Shuchkin';
  248. // $fh = fopen( $filename, 'wb' );
  249. if (!$fh)
  250. return false;
  251. $cdrec = '';
  252. foreach ($this->package['entries'] as $e) {
  253. $cfilename = ($e['path']) ? $e['path'] . '/' . $e['name'] : $e['name'];
  254. $e['uncsize'] = strlen($e['data']);
  255. // if data to compress is too small, just store it
  256. if ($e['uncsize'] < 256) {
  257. $e['comsize'] = $e['uncsize'];
  258. $e['vneeded'] = 10;
  259. $e['cmethod'] = 0;
  260. $zdata = $e['data'];
  261. } else { // otherwise, compress it
  262. $zdata = gzcompress($e['data']);
  263. $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug (thanks to Eric Mueller)
  264. $e['comsize'] = strlen($zdata);
  265. $e['vneeded'] = 10;
  266. $e['cmethod'] = 8;
  267. }
  268. $e['bitflag'] = 0;
  269. $e['crc_32'] = crc32($e['data']);
  270. // Convert date and time to DOS Format, and set then
  271. $lastmod_timeS = str_pad(decbin(date('s') >= 32 ? date('s') - 32 : date('s')), 5, '0', STR_PAD_LEFT);
  272. $lastmod_timeM = str_pad(decbin(date('i')), 6, '0', STR_PAD_LEFT);
  273. $lastmod_timeH = str_pad(decbin(date('H')), 5, '0', STR_PAD_LEFT);
  274. $lastmod_dateD = str_pad(decbin(date('d')), 5, '0', STR_PAD_LEFT);
  275. $lastmod_dateM = str_pad(decbin(date('m')), 4, '0', STR_PAD_LEFT);
  276. $lastmod_dateY = str_pad(decbin(date('Y') - 1980), 7, '0', STR_PAD_LEFT);
  277. # echo "ModTime: $lastmod_timeS-$lastmod_timeM-$lastmod_timeH (".date("s H H").")\n";
  278. # echo "ModDate: $lastmod_dateD-$lastmod_dateM-$lastmod_dateY (".date("d m Y").")\n";
  279. $e['modtime'] = bindec("$lastmod_timeH$lastmod_timeM$lastmod_timeS");
  280. $e['moddate'] = bindec("$lastmod_dateY$lastmod_dateM$lastmod_dateD");
  281. $e['offset'] = ftell($fh);
  282. fwrite($fh, $zipSignature);
  283. fwrite($fh, pack('s', $e['vneeded'])); // version_needed
  284. fwrite($fh, pack('s', $e['bitflag'])); // general_bit_flag
  285. fwrite($fh, pack('s', $e['cmethod'])); // compression_method
  286. fwrite($fh, pack('s', $e['modtime'])); // lastmod_time
  287. fwrite($fh, pack('s', $e['moddate'])); // lastmod_date
  288. fwrite($fh, pack('V', $e['crc_32'])); // crc-32
  289. fwrite($fh, pack('I', $e['comsize'])); // compressed_size
  290. fwrite($fh, pack('I', $e['uncsize'])); // uncompressed_size
  291. fwrite($fh, pack('s', strlen($cfilename))); // file_name_length
  292. fwrite($fh, pack('s', 0)); // extra_field_length
  293. fwrite($fh, $cfilename); // file_name
  294. // ignoring extra_field
  295. fwrite($fh, $zdata);
  296. // Append it to central dir
  297. $e['external_attributes'] = (substr($cfilename, -1) == '/' && !$zdata) ? 16 : 32; // Directory or file name
  298. $e['comments'] = '';
  299. $cdrec .= $dirSignature;
  300. $cdrec .= "\x0\x0"; // version made by
  301. $cdrec .= pack('v', $e['vneeded']); // version needed to extract
  302. $cdrec .= "\x0\x0"; // general bit flag
  303. $cdrec .= pack('v', $e['cmethod']); // compression method
  304. $cdrec .= pack('v', $e['modtime']); // lastmod time
  305. $cdrec .= pack('v', $e['moddate']); // lastmod date
  306. $cdrec .= pack('V', $e['crc_32']); // crc32
  307. $cdrec .= pack('V', $e['comsize']); // compressed filesize
  308. $cdrec .= pack('V', $e['uncsize']); // uncompressed filesize
  309. $cdrec .= pack('v', strlen($cfilename)); // file name length
  310. $cdrec .= pack('v', 0); // extra field length
  311. $cdrec .= pack('v', strlen($e['comments'])); // file comment length
  312. $cdrec .= pack('v', 0); // disk number start
  313. $cdrec .= pack('v', 0); // internal file attributes
  314. $cdrec .= pack('V', $e['external_attributes']); // internal file attributes
  315. $cdrec .= pack('V', $e['offset']); // relative offset of local header
  316. $cdrec .= $cfilename;
  317. $cdrec .= $e['comments'];
  318. }
  319. $before_cd = ftell($fh);
  320. fwrite($fh, $cdrec);
  321. // end of central dir
  322. fwrite($fh, $dirSignatureE);
  323. fwrite($fh, pack('v', 0)); // number of this disk
  324. fwrite($fh, pack('v', 0)); // number of the disk with the start of the central directory
  325. fwrite($fh, pack('v', count($this->package['entries']))); // total # of entries "on this disk"
  326. fwrite($fh, pack('v', count($this->package['entries']))); // total # of entries overall
  327. fwrite($fh, pack('V', strlen($cdrec))); // size of central dir
  328. fwrite($fh, pack('V', $before_cd)); // offset to start of central dir
  329. fwrite($fh, pack('v', strlen($zipComments))); // .zip file comment length
  330. fwrite($fh, $zipComments);
  331. return true;
  332. }
  333. function saveAs($filename) {
  334. if (!$this->_parse())
  335. return false;
  336. $fh = fopen($filename, 'wb');
  337. if (!$fh)
  338. return false;
  339. if (!$this->_zip($fh)) {
  340. fclose($fh);
  341. return false;
  342. }
  343. fclose($fh);
  344. return true;
  345. }
  346. function downloadAs($filename, $exit = true) {
  347. if (!$this->_parse())
  348. return false;
  349. //php://stdin
  350. $fh = tmpfile();
  351. if (!$fh)
  352. return false;
  353. if (!$this->_zip($fh)) {
  354. fclose($fh);
  355. return false;
  356. }
  357. $size = ftell($fh);
  358. $filename = ($filename) ? $filename : gmdate('Ymdhi');
  359. header('Content-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
  360. header('Content-Disposition: attachment; filename="' . $filename . '"');
  361. header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T', time()));
  362. header('Content-Length: ' . $size);
  363. fseek($fh, 0);
  364. echo fread($fh, $size);
  365. fclose($fh);
  366. if ($exit)
  367. exit();
  368. return true;
  369. }
  370. }
  371. ?>