PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/core/libraries/TinyButStrong/tinyDoc.class.php

https://github.com/aInternational/kimai
PHP | 1285 lines | 735 code | 203 blank | 347 comment | 70 complexity | 5937806c4c45901d7862a919f86d9efa MD5 | raw file
Possible License(s): GPL-3.0, LGPL-3.0, BSD-3-Clause
  1. <?php
  2. /*
  3. * This file is part of the tinyDoc package.
  4. * (c) Olivier Loynet
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. /**
  10. * tinyDoc class.
  11. *
  12. * This class extends TinyButStrong class to work with OpenDocument and Word 2007 documents.
  13. *
  14. * This class needs : PHP 5.2
  15. * This class needs : TinyButStrong class
  16. * This class needs optionally : ZipArchive from PECL
  17. *
  18. * For the full copyright and license information, please view the LICENSE
  19. * file that was distributed with this source code.
  20. *
  21. * @package tinyDoc
  22. * @subpackage tinyDoc
  23. * @author Olivier Loynet <tinydoc@googlegroups.com>
  24. * @version $Id$
  25. */
  26. class tinyDoc extends clsTinyButStrong
  27. {
  28. const VERSION = '1.0.3';
  29. const PICTURE_DPI = 96;
  30. const INCH_TO_CM = 2.54;
  31. const PIXEL_TO_CM = 0.0264583333; // 2.54 / 96
  32. private
  33. $zipMethod = 'shell',
  34. $zipBin = 'zip',
  35. $unzipBin = 'unzip',
  36. $sourcePathname = '',
  37. $processDir = '',
  38. $processBasename = '',
  39. $defaultExtension = 'odt',
  40. $xmlFilename = 'content.xml',
  41. $defaultCharset = 'UTF-8',
  42. $defaultIsEscape = true,
  43. $defaultCallback = '=~encodeData',
  44. $charset = 'UTF-8',
  45. $isEscape = true,
  46. $callback = '=~encodeData',
  47. $mimetype = array(
  48. 'sxw' => 'application/vnd.sun.xml.writer',
  49. 'stw' => 'application/vnd.sun.xml.writer.template',
  50. 'sxg' => 'application/vnd.sun.xml.writer.global',
  51. 'sxc' => 'application/vnd.sun.xml.calc',
  52. 'stc' => 'application/vnd.sun.xml.calc.template',
  53. 'sxi' => 'application/vnd.sun.xml.impress',
  54. 'sti' => 'application/vnd.sun.xml.impress.template',
  55. 'sxd' => 'application/vnd.sun.xml.draw',
  56. 'std' => 'application/vnd.sun.xml.draw.template',
  57. 'sxm' => 'application/vnd.sun.xml.math',
  58. 'odt' => 'application/vnd.oasis.OpenDocument.text',
  59. 'ott' => 'application/vnd.oasis.OpenDocument.text-template',
  60. 'oth' => 'application/vnd.oasis.OpenDocument.text-web',
  61. 'odm' => 'application/vnd.oasis.OpenDocument.text-master',
  62. 'odg' => 'application/vnd.oasis.OpenDocument.graphics',
  63. 'otg' => 'application/vnd.oasis.OpenDocument.graphics-template',
  64. 'odp' => 'application/vnd.oasis.OpenDocument.presentation',
  65. 'otp' => 'application/vnd.oasis.OpenDocument.presentation-template',
  66. 'ods' => 'application/vnd.oasis.OpenDocument.spreadsheet',
  67. 'ots' => 'application/vnd.oasis.OpenDocument.spreadsheet-template',
  68. 'odc' => 'application/vnd.oasis.OpenDocument.chart',
  69. 'odf' => 'application/vnd.oasis.OpenDocument.formula',
  70. 'odb' => 'application/vnd.oasis.OpenDocument.database',
  71. 'odi' => 'application/vnd.oasis.OpenDocument.image',
  72. 'docm' => 'application/vnd.ms-word.document.macroEnabled.12',
  73. 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  74. 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
  75. 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
  76. 'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
  77. 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
  78. 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
  79. 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
  80. 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
  81. 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
  82. 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  83. 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
  84. 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
  85. 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
  86. 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  87. 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
  88. 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
  89. );
  90. /**
  91. * Constructor.
  92. */
  93. public function __construct()
  94. {
  95. }
  96. /**
  97. * Create a new unique file from a source file
  98. *
  99. * @param mixed $options The pathname of source document (empty array by default)
  100. */
  101. public function createFrom($options = array())
  102. {
  103. $this->setSourcePathname($options);
  104. // create an unique basename to process the office document
  105. $this->newUniqueBasename();
  106. // create an unique directory like basename to zip/unzip files
  107. if (!mkdir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename(), 0777, true))
  108. {
  109. throw new tinyDocException(sprintf('Can\'t make directory "%s"', $this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename()));
  110. }
  111. if (!is_dir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename()))
  112. {
  113. throw new tinyDocException(sprintf('Directory not found "%s"', $this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename()));
  114. }
  115. // copy the file source into the process dir with an unique filename
  116. if (!copy($this->getSourcePathname(), $this->getPathname()))
  117. {
  118. if (!file_exists($this->getSourcePathname()))
  119. {
  120. throw new tinyDocException(sprintf('Can\'t copy file, source file not found "%s"', $this->getSourcePathname()));
  121. }
  122. else
  123. {
  124. throw new tinyDocException(sprintf('Can\'t copy file from "%s" to "%s"', $this->getSourcePathname(), $this->getPathname()));
  125. }
  126. }
  127. }
  128. /**
  129. * Load the XML file from the current process file as a TBS template
  130. *
  131. * @param string $xmlFilename The XML file (content.xml by default)
  132. */
  133. public function loadXml($xmlFilename = 'content.xml')
  134. {
  135. $this->setXmlFilename($xmlFilename);
  136. // unzip the XML file into the current basename process dir
  137. switch($this->getZipMethod())
  138. {
  139. case 'ziparchive':
  140. $zip = new ZipArchive();
  141. if ($zip->open($this->getPathname()) === true)
  142. {
  143. $zip->extractTo($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR, array($this->getXmlFilename()));
  144. $zip->close();
  145. }
  146. break;
  147. case 'pclzip':
  148. require_once('pclzip.lib.php');
  149. $zip = new PclZip($this->getPathname());
  150. $zip->extract(PCLZIP_OPT_PATH,$this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR,
  151. PCLZIP_OPT_BY_NAME,$this->getXmlFilename());
  152. break;
  153. case 'shell':
  154. default:
  155. $cmd = $this->getUnzipBinary();
  156. $cmd.= ' '.escapeshellarg($this->getPathname());
  157. $cmd.= ' -d';
  158. $cmd.= ' '.escapeshellarg($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename());
  159. $cmd.= ' '.escapeshellarg($this->getXmlFilename());
  160. exec($cmd);
  161. break;
  162. }
  163. // test if the XML file exist
  164. if (!file_exists($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$this->getXmlFilename()))
  165. {
  166. throw new tinyDocException(sprintf('Xml file not found "%s"', $this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$this->getXmlFilename()));
  167. }
  168. // load the XML file as a TBS template
  169. $this->ObjectRef = $this;
  170. $this->LoadTemplate($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$this->getXmlFilename(), $this->getCallback());
  171. // work around - convert apostrophe in XML file needed for TBS functions
  172. $this->Source = str_replace('&apos;', '\'', $this->Source);
  173. }
  174. /**
  175. * Merge data with the template file
  176. *
  177. * Available options:
  178. *
  179. * - name: The tag name in the template ('block' by default)
  180. * - type: The tag type in the template ('field' | 'block' - 'block' by default)
  181. * - data_type: The data type ('array' by default)
  182. * - charset: The data charset ('UTF-8' by default)
  183. * - is_escape: If data are escaped (true by default)
  184. * - callback: The callback to encode data ('=~encodeData' by default)
  185. *
  186. * @param array $options Options
  187. * @param mixed $data Data
  188. */
  189. public function mergeXml($options = array(), $data = array())
  190. {
  191. $name = isset($options['name']) && is_string($options['name']) ? $options['name'] : 'block';
  192. $type = isset($options['type']) && is_string($options['type']) ? $options['type'] : 'block';
  193. $data_type = isset($options['data_type']) && is_string($options['data_type']) ? $options['data_type'] : 'array';
  194. $this->setCharset (isset($options['charset']) && is_string($options['charset']) ? $options['charset'] : $this->getDefaultCharset());
  195. $this->setIsEscape(isset($options['is_escape']) && is_bool($options['is_escape']) ? $options['is_escape'] : $this->getDefaultIsEscape());
  196. $this->setCallback(isset($options['callback']) && is_string($options['callback']) ? $options['callback'] : $this->getDefaultCallback());
  197. switch($type)
  198. {
  199. case 'field':
  200. $this->MergeField($name, $data);
  201. break;
  202. case 'block':
  203. default:
  204. $this->MergeBlock($name, $data_type, $data);
  205. break;
  206. }
  207. }
  208. /**
  209. * Merge data with a TBS field in template
  210. *
  211. * @param string $name TBS field name in template
  212. * @param mixed $data Data to merge
  213. */
  214. public function mergeXmlField($name, $data)
  215. {
  216. $this->mergeXml(array('name' => $name, 'type' => 'field'), $data);
  217. }
  218. /**
  219. * Merge data with a TBS block in template
  220. *
  221. * @param string $name TBS block name in template
  222. * @param mixed $data Data to merge
  223. */
  224. public function mergeXmlBlock($name, $data)
  225. {
  226. $this->mergeXml(array('name' => $name, 'type' => 'block'), $data);
  227. }
  228. /**
  229. * Save the result of merged XML file into the current process file
  230. */
  231. public function saveXml()
  232. {
  233. // get the source result (TBS method)
  234. $this->Show(TBS_NOTHING);
  235. // update the result into the current process file
  236. switch($this->getZipMethod())
  237. {
  238. case 'ziparchive':
  239. $zip = new ZipArchive();
  240. if ($zip->open($this->getPathname()) === true)
  241. {
  242. $zip->addFromString($this->getXmlFilename(), $this->Source);
  243. $zip->close();
  244. }
  245. break;
  246. case 'pclzip':
  247. require_once('pclzip.lib.php');
  248. // save the merged result
  249. $fdw = fopen($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$this->getXmlFilename(), "w");
  250. fwrite($fdw, $this->Source, strlen($this->Source));
  251. fclose ($fdw);
  252. // change the current directory to basename in process dir
  253. $cwd = getcwd();
  254. chdir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR);
  255. $zip = new PclZip($this->getPathname());
  256. $zip->add($this->getXmlFilename());
  257. // get back current directory
  258. chdir($cwd);
  259. break;
  260. case 'shell':
  261. default:
  262. // save the merged result
  263. $fdw = fopen($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$this->getXmlFilename(), "w");
  264. fwrite($fdw, $this->Source, strlen($this->Source));
  265. fclose ($fdw);
  266. // change the current directory to basename in process dir
  267. $cwd = getcwd();
  268. chdir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR);
  269. // zip the XML file into the archive
  270. $cmd = $this->getZipBinary();
  271. $cmd.= ' -m';
  272. $cmd.= ' '.escapeshellarg($this->getPathname());
  273. $cmd.= ' '.escapeshellarg($this->getXmlFilename());
  274. exec($cmd);
  275. // get back current directory
  276. chdir($cwd);
  277. break;
  278. }
  279. @chmod($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR,0777);
  280. @chmod($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$this->getXmlFilename(),0777);
  281. }
  282. /**
  283. * Encode the data before merge
  284. *
  285. * @param string $encodeData The data to merge
  286. *
  287. * @return string The encoded data
  288. */
  289. public function encodeData($encodeData)
  290. {
  291. // XML charset of OpenOffice / OpenDocument / Word 2007 is utf-8
  292. switch($this->getCharset())
  293. {
  294. case 'ISO 8859-1':
  295. case 'ISO 8859-15':
  296. $encodeData = utf8_encode($encodeData);
  297. break;
  298. case 'UTF-8':
  299. case 'UTF8':
  300. default:
  301. break;
  302. }
  303. // convert special XML chars
  304. $encodeData = str_replace('&' ,'&amp;', $encodeData); // the '&' has to be the first to convert
  305. $encodeData = str_replace('\'' ,'&apos;', $encodeData);
  306. $encodeData = str_replace('"' ,'&quot;', $encodeData);
  307. if ($this->getIsEscape())
  308. {
  309. $encodeData = str_replace('<', '&lt;', $encodeData);
  310. $encodeData = str_replace('>', '&gt;', $encodeData);
  311. }
  312. // convert TAB & LF & CR to XML
  313. switch($this->getExtension())
  314. {
  315. case 'docm':
  316. case 'docx':
  317. case 'dotm':
  318. case 'dotx':
  319. $encodeData = str_replace("\r\n", '</w:t></w:r></w:p><w:p><w:r><w:t>', $encodeData);
  320. $encodeData = str_replace("\n", '</w:t></w:r></w:p><w:p><w:r><w:t>', $encodeData);
  321. $encodeData = str_replace("\r", '</w:t></w:r></w:p><w:p><w:r><w:t>', $encodeData);
  322. $encodeData = str_replace("\t", '</w:t></w:r><w:r><w:tab/><w:t>' , $encodeData);
  323. break;
  324. case 'ods':
  325. case 'sxc':
  326. $encodeData = str_replace("\r\n", '</text:p><text:p>' , $encodeData);
  327. $encodeData = str_replace("\n", '</text:p><text:p>' , $encodeData);
  328. $encodeData = str_replace("\r", '</text:p><text:p>' , $encodeData);
  329. $encodeData = str_replace("\t", '<text:tab/>' , $encodeData);
  330. // work-around for EURO caracter
  331. $encodeData = str_replace(chr(0xC2).chr(0x80) , chr(0xE2).chr(0x82).chr(0xAC), $encodeData);
  332. break;
  333. case 'odt':
  334. case 'sxw':
  335. default:
  336. $encodeData = str_replace("\r\n", '<text:line-break/>', $encodeData);
  337. $encodeData = str_replace("\n", '<text:line-break/>', $encodeData);
  338. $encodeData = str_replace("\r", '<text:line-break/>', $encodeData);
  339. $encodeData = str_replace("\t", '<text:tab/>' , $encodeData);
  340. // work-around for EURO caracter
  341. $encodeData = str_replace(chr(0xC2).chr(0x80) , chr(0xE2).chr(0x82).chr(0xAC), $encodeData);
  342. break;
  343. }
  344. // remove specials chars from 0x00 to 0x1F
  345. $encodeData = preg_replace('/[\x{00}-\x{1F}]+/u', '', $encodeData);
  346. return $encodeData;
  347. }
  348. /**
  349. * Create a unique id and set the basename of process file
  350. */
  351. public function newUniqueBasename()
  352. {
  353. $unique = md5(uniqid(rand(), true));
  354. $this->setBasename($unique);
  355. }
  356. /**
  357. * Close by remove the current process directory and subdirectory
  358. */
  359. public function close()
  360. {
  361. // remove the current process directory
  362. clearstatcache();
  363. if (is_dir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename()))
  364. {
  365. $this->deltree($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename());
  366. rmdir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename());
  367. }
  368. }
  369. /**
  370. * Remove a directory and his contents recursively
  371. *
  372. * @param string $dir The pathname the directory
  373. */
  374. private function deltree($dir)
  375. {
  376. // changed by kevin - makes no sense on shared hosting.
  377. // for example open basedir settings restrict access to /
  378. // if (realpath($dir) == realpath(DIRECTORY_SEPARATOR))
  379. if (realpath($dir) == DIRECTORY_SEPARATOR)
  380. {
  381. return false;
  382. }
  383. foreach(glob($dir.DIRECTORY_SEPARATOR.'*') as $filename)
  384. {
  385. if (is_dir($filename) && !is_link($filename))
  386. {
  387. $this->deltree($filename);
  388. if (is_writable($filename))
  389. {
  390. rmdir($filename);
  391. }
  392. }
  393. else
  394. {
  395. if (is_writable($filename))
  396. {
  397. unlink($filename);
  398. }
  399. }
  400. }
  401. }
  402. /**
  403. * Remove the current process file
  404. */
  405. public function remove()
  406. {
  407. unlink($this->getPathname());
  408. }
  409. /**
  410. * Add an external file into the current process file
  411. *
  412. * GIF images are not supported in WORD 2007
  413. *
  414. * @param string $sourcePathname The pathname of the source file to add
  415. * @param string $archivePathname The pathname in the archive
  416. */
  417. public function addFile($sourcePathname, $archivePathname)
  418. {
  419. if (file_exists($sourcePathname))
  420. {
  421. switch($this->getZipMethod())
  422. {
  423. case 'ziparchive':
  424. $zip = new ZipArchive();
  425. if ($zip->open($this->getPathname()) === true)
  426. {
  427. // don't work - archive corrupted
  428. // $zip->addFile($sourcePathname, $archivePathname);
  429. $zip->addFromString($archivePathname, file_get_contents($sourcePathname, false));
  430. $zip->close();
  431. }
  432. break;
  433. case 'pclzip':
  434. require_once('pclzip.lib.php');
  435. $zip = new PclZip($this->getPathname());
  436. $zip->add($sourcePathname,
  437. PCLZIP_OPT_ADD_PATH,$archivePathname,
  438. PCLZIP_OPT_REMOVE_ALL_PATH);
  439. break;
  440. case 'shell':
  441. default:
  442. $dir = dirname($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$archivePathname);
  443. if (!file_exists($dir))
  444. {
  445. mkdir($dir, 0777, true);
  446. }
  447. if (copy($sourcePathname, $this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR.$archivePathname))
  448. {
  449. // change the current directory to basename in process dir
  450. $cwd = getcwd();
  451. chdir($this->getProcessDir().DIRECTORY_SEPARATOR.$this->getBasename().DIRECTORY_SEPARATOR);
  452. // zip the file into the archive
  453. $cmd = $this->getZipBinary();
  454. $cmd.= ' -u';
  455. $cmd.= ' '.escapeshellarg($this->getPathname());
  456. $cmd.= ' '.escapeshellarg($archivePathname);
  457. exec($cmd);
  458. // get back current directory
  459. chdir($cwd);
  460. }
  461. break;
  462. }
  463. }
  464. }
  465. /**
  466. * This function is obsolete, use parameter 'image' to merge pictures.
  467. *
  468. * Hack to change in the XML source the tags for image. Only tags with [*.src] work.
  469. * The TBS tag need to set with OpenOffice into the title field of the image property.
  470. *
  471. * Experimental : only for OpenDocument/OpenOffice. Image in blocks don't work with spreadsheet.
  472. */
  473. public function tagXmlImage()
  474. {
  475. $doc = new DOMDocument();
  476. $doc->loadXML($this->Source);
  477. $draw_frames = $doc->getElementsByTagName('frame'); // draw:frame
  478. foreach ($draw_frames as $draw_frame)
  479. {
  480. $svg_titles = $draw_frame->getElementsByTagName('title'); //svg:title
  481. if ($svg_titles->length > 0)
  482. {
  483. $tag = $svg_titles->item(0)->nodeValue;
  484. if (preg_match('/\[.*src\]/', $tag))
  485. {
  486. $draw_images = $draw_frame->getElementsByTagName('image'); // draw:image
  487. if ($draw_images->length > 0)
  488. {
  489. $draw_image = $draw_images->item(0);
  490. $draw_image->removeAttribute('href'); // xlink:href
  491. $draw_image->setAttribute('xlink:href', $tag);
  492. }
  493. }
  494. }
  495. }
  496. $this->Source = $doc->saveXML();
  497. }
  498. /**
  499. * Send the response
  500. */
  501. public function sendResponse($options = array())
  502. {
  503. header('Content-Type: '.$this->getMimetype());
  504. header('Content-Disposition: attachment; filename="'.$this->getDownloadFilename().'"');
  505. header('Content-Length: '.$this->getSize());
  506. echo $this->getContent();
  507. }
  508. /**
  509. * Get the basename of the current process file
  510. *
  511. * @return string The basename
  512. */
  513. public function getBasename()
  514. {
  515. return $this->processBasename;
  516. }
  517. /**
  518. * Get the callback method to encode data
  519. *
  520. * @return string The callback method
  521. */
  522. public function getCallback()
  523. {
  524. return $this->callback;
  525. }
  526. /**
  527. * Get the charset of data to be merged
  528. *
  529. * @return string The charset
  530. */
  531. public function getCharset()
  532. {
  533. return $this->charset;
  534. }
  535. /**
  536. * Get the binary content of the current process file
  537. *
  538. * @return stream The binary content
  539. */
  540. public function getContent()
  541. {
  542. return file_get_contents($this->getPathname(), false);
  543. }
  544. /**
  545. * Get the default callback method to encode data
  546. *
  547. * @return string The default callback method
  548. */
  549. public function getDefaultCallback()
  550. {
  551. return $this->defaultCallback;
  552. }
  553. /**
  554. * Get the default charset of data
  555. *
  556. * @return string The default charset
  557. */
  558. public function getDefaultCharset()
  559. {
  560. return $this->defaultCharset;
  561. }
  562. /**
  563. * Get the default extension of source file
  564. *
  565. * @return string The default extension
  566. */
  567. public function getDefaultExtension()
  568. {
  569. return $this->defaultExtension;
  570. }
  571. /**
  572. * Get the default if data are escaped
  573. *
  574. * @return string The default
  575. */
  576. public function getDefaultIsEscape()
  577. {
  578. return $this->defaultIsEscape;
  579. }
  580. /**
  581. * Get the filename to download as the current template by default
  582. *
  583. * @return string The download filename
  584. */
  585. public function getDownloadFilename($options = array())
  586. {
  587. return basename($this->getSourcePathname());
  588. }
  589. /**
  590. * Get the extension of the current process file
  591. *
  592. * @return string The extension
  593. */
  594. public function getExtension()
  595. {
  596. $info = pathinfo($this->getSourcePathname());
  597. return $info['extension'];
  598. }
  599. /**
  600. * Get the filename of the current process file
  601. *
  602. * @return string The filename
  603. */
  604. public function getFilename()
  605. {
  606. return $this->getBasename().'.'.$this->getExtension();
  607. }
  608. /**
  609. * Get if data are escaped
  610. *
  611. * @return boolean
  612. */
  613. public function getIsEscape()
  614. {
  615. return $this->isEscape;
  616. }
  617. /**
  618. * Get the mimetype of the current process file
  619. *
  620. * @return string The mimetype
  621. */
  622. public function getMimetype()
  623. {
  624. return isset($this->mimetype[$this->getExtension()]) ? $this->mimetype[$this->getExtension()] : null;
  625. }
  626. /**
  627. * Get the pathname of the current process file
  628. *
  629. * @return string The pathname
  630. */
  631. public function getPathname()
  632. {
  633. return $this->getProcessDir().DIRECTORY_SEPARATOR.$this->getFilename();
  634. }
  635. /**
  636. * Get the process directory
  637. *
  638. * @return string The process dir pathname
  639. */
  640. public function getProcessDir()
  641. {
  642. return $this->processDir;
  643. }
  644. /**
  645. * Get the size of the current process file
  646. *
  647. * @return int The size
  648. */
  649. public function getSize()
  650. {
  651. return filesize($this->getPathname());
  652. }
  653. /**
  654. * Get the source file pathname
  655. *
  656. * @return string The source file pathname
  657. */
  658. public function getSourcePathname()
  659. {
  660. return $this->sourcePathname;
  661. }
  662. /**
  663. * Get the unzip binary pathfile
  664. *
  665. * @return string The unzip binary
  666. */
  667. public function getUnzipBinary()
  668. {
  669. return $this->unzipBin;
  670. }
  671. /**
  672. * Get the current XML filename
  673. *
  674. * @return string The XML filename
  675. */
  676. public function getXmlFilename()
  677. {
  678. return $this->xmlFilename;
  679. }
  680. /**
  681. * Get the zip binary pathfile
  682. *
  683. * @return string The zip binary
  684. */
  685. public function getZipBinary()
  686. {
  687. return $this->zipBin;
  688. }
  689. /**
  690. * Get the method name to zip/unzip
  691. *
  692. * @return string The zip method name
  693. */
  694. public function getZipMethod()
  695. {
  696. return $this->zipMethod;
  697. }
  698. /**
  699. * Set the basename of the current process file
  700. *
  701. * @param string $basename The basename
  702. */
  703. public function setBasename($basename)
  704. {
  705. return $this->processBasename = $basename;
  706. }
  707. /**
  708. * Set the callback method to encode merged data
  709. *
  710. * @param string $callback The callback method (TBS syntax)
  711. */
  712. public function setCallback($callback)
  713. {
  714. $this->callback = $callback;
  715. }
  716. /**
  717. * Set the charset
  718. *
  719. * @param string $charset The charset of the data to merge (utf-8 by default)
  720. */
  721. public function setCharset($charset = 'UTF-8')
  722. {
  723. $this->charset = strtoupper($charset);
  724. }
  725. /**
  726. * Set if '<' & '>' in merged data are escaped by '&lt;' & '&gt;'
  727. *
  728. * BE CAREFUL TO SET TO FALSE
  729. *
  730. * @param boolean $isEscape Escape data (true by default)
  731. */
  732. public function setIsEscape($isEscape = true)
  733. {
  734. $this->isEscape = $isEscape;
  735. }
  736. /**
  737. * Set the source file pathname
  738. *
  739. * @param mixed $options The sourcePathname
  740. */
  741. public function setSourcePathname($options = array())
  742. {
  743. $sourcePathname = $options;
  744. // test if file exist
  745. if (!file_exists($sourcePathname))
  746. {
  747. throw new tinyDocException(sprintf('File not found "%s"', $sourcePathname));
  748. }
  749. // test if file readable
  750. if (!is_readable($sourcePathname))
  751. {
  752. throw new tinyDocException(sprintf('File not readable "%s"', $sourcePathname));
  753. }
  754. $this->sourcePathname = $sourcePathname;
  755. }
  756. /**
  757. * Set the process directory
  758. *
  759. * @param string $processDir The pathname of the process dir
  760. */
  761. public function setProcessDir($processDir = 'tmp')
  762. {
  763. // changed by kevin - makes no sense on shared hosting.
  764. // for example open basedir settings restrict access to /
  765. // if (realpath($processDir) == realpath(DIRECTORY_SEPARATOR))
  766. if (realpath($processDir) == DIRECTORY_SEPARATOR)
  767. {
  768. throw new tinyDocException(sprintf('Could not use the root for the process directory "%s"', $processDir));
  769. }
  770. if (!is_dir(realpath($processDir)))
  771. {
  772. throw new tinyDocException(sprintf('Process directory not found "%s"', $processDir));
  773. }
  774. if (!is_writable(realpath($processDir)))
  775. {
  776. throw new tinyDocException(sprintf('Process directory not writable "%s"', $processDir));
  777. }
  778. $this->processDir = rtrim(realpath($processDir), DIRECTORY_SEPARATOR);
  779. }
  780. /**
  781. * Fix the unzip binary
  782. *
  783. * @param string $unzipBin The pathname of the unzip binary (unzip by default)
  784. */
  785. public function setUnzipBinary($unzipBinary = 'unzip')
  786. {
  787. if ($this->getZipMethod() == 'shell')
  788. {
  789. $unzipBinary = self::escapeShellCommand($unzipBinary);
  790. if (strlen(shell_exec($unzipBinary.' -h')) == 0)
  791. {
  792. throw new tinyDocException(sprintf('"%s" not executable', $unzipBinary));
  793. }
  794. $this->unzipBin = $unzipBinary;
  795. }
  796. }
  797. /**
  798. * Set the XML filename of the current template
  799. *
  800. * @param string The pathname of XML filename (content.xml by default)
  801. */
  802. public function setXmlFilename($xmlFilename = 'content.xml')
  803. {
  804. $this->xmlFilename = $xmlFilename;
  805. }
  806. /**
  807. * Fix the zip binary
  808. *
  809. * @param string $zipBin The pathname of the zip binary (zip by default)
  810. */
  811. public function setZipBinary($zipBinary = 'zip')
  812. {
  813. if ($this->getZipMethod() == 'shell')
  814. {
  815. $zipBinary = self::escapeShellCommand($zipBinary);
  816. if (strlen(shell_exec($zipBinary.' -h')) == 0)
  817. {
  818. throw new tinyDocException(sprintf('"%s" not executable', $zipBinary));
  819. }
  820. $this->zipBin = $zipBinary;
  821. }
  822. }
  823. /**
  824. * Fix the method to zip/unzip file.
  825. *
  826. * @param string $zipMethod The method are 'shell' or 'ziparchive' (shell by default)
  827. */
  828. public function setZipMethod($zipMethod = 'shell')
  829. {
  830. $method = strtolower($zipMethod);
  831. if (!in_array($method, array('shell', 'ziparchive', 'pclzip')))
  832. {
  833. throw new tinyDocException(sprintf('Zip method "%s" need to be \'shell\' or \'ziparchive\'', $method));
  834. }
  835. if ($method == 'ziparchive' && !class_exists('ZipArchive'))
  836. {
  837. throw new tinyDocException('Zip extension not loaded - check your php settings, PHP 5.2 minimum with zip extension is required');
  838. }
  839. $this->zipMethod = $method;
  840. }
  841. /**
  842. * Redefinition of TBS method to make an exception when an alert occured
  843. *
  844. * @param string $Src TBS subject ???
  845. * @param string $Msg TBS message ???
  846. * @param string $NoErrMsg TBS no error message ???
  847. * @param string $SrcType The TBS source type ???
  848. */
  849. public function meth_Misc_Alert($Src, $Msg, $NoErrMsg=false, $SrcType=false)
  850. {
  851. throw new tinyDocException(sprintf('%s', $Msg));
  852. }
  853. /**
  854. * Redefinition of TBS method to add parameter 'cell' to set value with native OpenDocument format in spreadsheet
  855. *
  856. * refactoring code from egroupware/perp_api/inc/report/ooo.inc.php
  857. *
  858. * @param string $Txt TBS source
  859. * @param string $Loc TBS locator
  860. * @param string $Value TBS data to merge
  861. * @param string $CheckSub TBS checksub ???
  862. */
  863. public function meth_Locator_Replace(&$Txt, &$Loc, &$Value, $CheckSub)
  864. {
  865. if (!isset($Loc->PrmLst['type']) && !isset($Loc->PrmLst['image']) && !isset($Loc->PrmLst['link']))
  866. {
  867. return parent::meth_Locator_Replace($Txt, $Loc, $Value, $CheckSub);
  868. }
  869. // keep 'Loc' position for the end
  870. $posBeg = $Loc->PosBeg;
  871. $posEnd = $Loc->PosEnd;
  872. // get data
  873. $data = isset($Value) ? $Value : null;
  874. foreach($Loc->SubLst as $sub)
  875. {
  876. $data = isset($Value[$sub]) ? $Value[$sub] : null ;
  877. }
  878. // ----- parameter = type
  879. if (isset($Loc->PrmLst['type']))
  880. {
  881. if ($data == '')
  882. {
  883. $Txt = substr_replace($Txt, '', $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1 );
  884. $Loc->PosBeg = $posBeg;
  885. $Loc->PosEnd = $posEnd;
  886. return $Loc->PosBeg;
  887. }
  888. // get container enlarged to table:table-cell
  889. $Loc->Enlarged = $this->f_Loc_EnlargeToStr($Txt, $Loc, '<table:table-cell' ,'/table:table-cell>');
  890. $container = substr($Txt, $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1);
  891. if ($container == '')
  892. {
  893. throw new tinyDocException(sprintf('<table:table-cell not found in document "%s"', $this->getXmlFilename()));
  894. }
  895. // OpenOffice attributes cell - see : http://books.evc-cit.info/odbook/ch05.html#table-cells-section
  896. switch($Loc->PrmLst['type'])
  897. {
  898. case 'datetime':
  899. case 'date':
  900. case 'dt':
  901. case 'd':
  902. $attribute = sprintf('office:value-type="date" office:date-value="%s"', str_replace(' ', 'T', $data));
  903. break;
  904. case 'time':
  905. case 't':
  906. list($h, $m, $s) = split(":", $data);
  907. $attribute = sprintf('office:value-type="time" office:time-value="PT%sH%sM%sS"', $h, $m, $s);
  908. break;
  909. case 'percentage':
  910. case 'percent':
  911. case 'p':
  912. $attribute = sprintf('office:value-type="percentage" office:value="%s"', $data);
  913. break;
  914. case 'currency':
  915. case 'cur':
  916. case 'c':
  917. //$attribute = sprintf('office:value-type="currency" office:currency="EUR" office:value="%s"', $data); // still not necessary to fix the currency
  918. $attribute = sprintf('office:value-type="currency" office:value="%s"', $data);
  919. break;
  920. case 'number':
  921. case 'n':
  922. case 'float':
  923. case 'f':
  924. $attribute = sprintf('office:value-type="float" office:value="%s"', $data);
  925. break;
  926. case 'int':
  927. case 'i':
  928. $attribute = sprintf('office:value-type="float" office:value="%d"', $data);
  929. break;
  930. default:
  931. case 'string':
  932. case 's':
  933. $attribute = sprintf('office:value-type="string"');
  934. break;
  935. }
  936. // new container
  937. $newContainer = preg_replace('/office:value-type="string"/', $attribute, $container);
  938. // replace the new cell containter in the main Txt
  939. $Txt = substr_replace($Txt, $newContainer, $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1 );
  940. // correct 'Loc' to include the change of the new cell container
  941. $delta = strlen($newContainer) - strlen($container);
  942. $Loc->PosBeg = $posBeg + $delta;
  943. $Loc->PosEnd = $posEnd + $delta;
  944. $Loc->Enlarged = null;
  945. }
  946. // ----- parameter = image
  947. if (isset($Loc->PrmLst['image']))
  948. {
  949. // get container enlarged to draw:frame
  950. $Loc->Enlarged = $this->f_Loc_EnlargeToStr($Txt, $Loc, '<draw:frame' ,'/draw:frame>');
  951. $container = substr($Txt, $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1);
  952. if ($container == '')
  953. {
  954. throw new tinyDocException(sprintf('<draw:frame not found in document "%s"', $this->getXmlFilename()));
  955. }
  956. // test if data is empty or if file not exists
  957. if ($data == '' || !file_exists($data))
  958. {
  959. $Txt = substr_replace($Txt, '', $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1 );
  960. $Loc->PosBeg = $posBeg;
  961. $Loc->PosEnd = $posEnd;
  962. $Loc->Enlarged = null;
  963. return $Loc->PosBeg;
  964. }
  965. $picture = 'Pictures/'.basename($data);
  966. // image size
  967. $size = @getimagesize($data);
  968. if ($size === false)
  969. {
  970. throw new tinyDocException(sprintf('Invalid image format "%"', $data));
  971. }
  972. else
  973. {
  974. list ($width, $height) = $size;
  975. }
  976. // image ratio
  977. $ratio = 1;
  978. switch(strtolower($Loc->PrmLst['image']))
  979. {
  980. case 'fit':
  981. if (preg_match('/svg:width="(.*?)(cm|in|mm|px)" svg:height="(.*?)(cm|in|mm|px)"/', $container, $matches))
  982. {
  983. $ratio_w = self::convertToCm($matches[1], $matches[2]) / self::convertToCm($width, 'px');
  984. $ratio_h = self::convertToCm($matches[3], $matches[4]) / self::convertToCm($height,'px');
  985. $ratio = min($ratio_w, $ratio_h);
  986. }
  987. break;
  988. case 'max':
  989. if (preg_match('/svg:width="(.*?)(cm|in|mm|px)" svg:height="(.*?)(cm|in|mm|px)"/', $container, $matches))
  990. {
  991. $ratio_w = self::convertToCm($matches[1], $matches[2]) / self::convertToCm($width, 'px');
  992. $ratio_h = self::convertToCm($matches[3], $matches[4]) / self::convertToCm($height,'px');
  993. $ratio = min(1, $ratio_w, $ratio_h);
  994. }
  995. break;
  996. default:
  997. if (preg_match('/([0-9\.]*)%/', $Loc->PrmLst['image'], $matches) > 0)
  998. {
  999. $ratio = $matches[1] / 100;
  1000. }
  1001. break;
  1002. }
  1003. // replace values
  1004. $newContainer = $container;
  1005. $newContainer = preg_replace('/svg:width="(.*?)"/' , sprintf('svg:width="%scm"' , self::convertToCm($width, 'px') * $ratio), $newContainer);
  1006. $newContainer = preg_replace('/svg:height="(.*?)"/', sprintf('svg:height="%scm"', self::convertToCm($height,'px') * $ratio), $newContainer);
  1007. $newContainer = preg_replace('/xlink:href="(.*?)"/', sprintf('xlink:href="%s"' , $picture), $newContainer);
  1008. // add file
  1009. $this->addFile($data, $picture);
  1010. // replace the new cell containter in the main Txt
  1011. $Txt = substr_replace($Txt, $newContainer, $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1);
  1012. $Loc->PosBeg = $posBeg;
  1013. $Loc->PosEnd = $posEnd;
  1014. $Loc->Enlarged = null;
  1015. }
  1016. // ----- parameter = link
  1017. if (isset($Loc->PrmLst['link']))
  1018. {
  1019. if ($data == '')
  1020. {
  1021. $Txt = substr_replace($Txt, '', $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1 );
  1022. $Loc->PosBeg = $posBeg;
  1023. $Loc->PosEnd = $posEnd;
  1024. return $Loc->PosBeg;
  1025. }
  1026. $container = substr($Txt, $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1);
  1027. $title = ($Loc->PrmLst['link'] != '1' ? $Loc->PrmLst['link'] : $data);
  1028. $newContainer = sprintf('<text:a xlink:type="simple" xlink:href="%s">%s</text:a>', $data, $title);
  1029. $Txt = substr_replace($Txt, $newContainer, $Loc->PosBeg, $Loc->PosEnd - $Loc->PosBeg + 1);
  1030. // before return, restore 'Loc' with beginning values (to work with block)
  1031. $Loc->PosBeg = $posBeg;
  1032. $Loc->PosEnd = $posEnd;
  1033. return $Loc->PosEnd;
  1034. }
  1035. // call the parent method to insert the value
  1036. $ret = parent::meth_Locator_Replace($Txt, $Loc, $Value, $CheckSub);
  1037. // before return, restore 'Loc' with beginning values (to work with block)
  1038. $Loc->PosBeg = $posBeg;
  1039. $Loc->PosEnd = $posEnd;
  1040. return $ret;
  1041. }
  1042. public static function convertToCm($value, $unit)
  1043. {
  1044. switch(strtolower($unit))
  1045. {
  1046. case 'px':
  1047. $returnValue = $value * self::INCH_TO_CM / self::PICTURE_DPI;
  1048. break;
  1049. case 'in':
  1050. $returnValue = $value * self::INCH_TO_CM;
  1051. break;
  1052. case 'mm':
  1053. $returnValue = $value / 10;
  1054. break;
  1055. default;
  1056. case 'cm':
  1057. $returnValue = $value;
  1058. break;
  1059. }
  1060. return $returnValue;
  1061. }
  1062. public static function escapeShellCommand($command)
  1063. {
  1064. if (strpos($command, ' ') !== false)
  1065. {
  1066. $command = (strpos($command, '"') === 0 ? '' : '"').$command;
  1067. $command = $command.((strrpos($command, '"') == strlen($command)-1) ? '' : '"');
  1068. }
  1069. return $command;
  1070. }
  1071. }
  1072. /**
  1073. * tinyDocException exception class
  1074. *
  1075. * @package tinyDoc
  1076. * @subpackage tinyDocException
  1077. * @author Olivier Loynet <tinydoc@googlegroups.com>
  1078. * @version $Id$
  1079. */
  1080. class tinyDocException extends Exception
  1081. {
  1082. }