odf.php 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030
  1. <?php
  2. require 'Segment.php';
  3. /**
  4. * Class of ODT Exception
  5. */
  6. class OdfException extends Exception
  7. {
  8. }
  9. /**
  10. * Class of ODT Exception
  11. */
  12. class OdfExceptionSegmentNotFound extends Exception
  13. {
  14. }
  15. /**
  16. * Templating class for odt file
  17. * You need PHP 5.2 at least
  18. * You need Zip Extension or PclZip library
  19. *
  20. * @copyright 2008 - Julien Pauli - Cyril PIERRE de GEYER - Anaska (http://www.anaska.com)
  21. * @copyright 2010-2015 - Laurent Destailleur - eldy@users.sourceforge.net
  22. * @copyright 2010 - Vikas Mahajan - http://vikasmahajan.wordpress.com
  23. * @copyright 2012 - Stephen Larroque - lrq3000@gmail.com
  24. * @license https://www.gnu.org/copyleft/gpl.html GPL License
  25. * @version 1.5.0
  26. */
  27. class Odf
  28. {
  29. protected $config = array(
  30. 'ZIP_PROXY' => 'PclZipProxy', // PclZipProxy, PhpZipProxy
  31. 'DELIMITER_LEFT' => '{',
  32. 'DELIMITER_RIGHT' => '}',
  33. 'PATH_TO_TMP' => '/tmp'
  34. );
  35. protected $file;
  36. protected $contentXml; // To store content of content.xml file
  37. protected $metaXml; // To store content of meta.xml file
  38. protected $stylesXml; // To store content of styles.xml file
  39. protected $manifestXml; // To store content of META-INF/manifest.xml file
  40. protected $tmpfile;
  41. protected $tmpdir='';
  42. protected $images = array();
  43. protected $vars = array();
  44. protected $segments = array();
  45. public $creator;
  46. public $title;
  47. public $subject;
  48. public $userdefined=array();
  49. const PIXEL_TO_CM = 0.026458333;
  50. const FIND_TAGS_REGEX = '/<([A-Za-z0-9]+)(?:\s([A-Za-z]+(?:\-[A-Za-z]+)?(?:=(?:".*?")|(?:[0-9]+))))*(?:(?:\s\/>)|(?:>(.*)<\/\1>))/s';
  51. const FIND_ENCODED_TAGS_REGEX = '/&lt;([A-Za-z]+)(?:\s([A-Za-z]+(?:\-[A-Za-z]+)?(?:=(?:".*?")|(?:[0-9]+))))*(?:(?:\s\/&gt;)|(?:&gt;(.*)&lt;\/\1&gt;))/';
  52. /**
  53. * Class constructor
  54. *
  55. * @param string $filename The name of the odt file
  56. * @param string $config Array of config data
  57. * @throws OdfException
  58. */
  59. public function __construct($filename, $config = array())
  60. {
  61. clearstatcache();
  62. if (! is_array($config)) {
  63. throw new OdfException('Configuration data must be provided as array');
  64. }
  65. foreach ($config as $configKey => $configValue) {
  66. if (array_key_exists($configKey, $this->config)) {
  67. $this->config[$configKey] = $configValue;
  68. }
  69. }
  70. $md5uniqid = md5(uniqid());
  71. if ($this->config['PATH_TO_TMP']) $this->tmpdir = preg_replace('|[\/]$|', '', $this->config['PATH_TO_TMP']); // Remove last \ or /
  72. $this->tmpdir .= ($this->tmpdir?'/':'').$md5uniqid;
  73. $this->tmpfile = $this->tmpdir.'/'.$md5uniqid.'.odt'; // We keep .odt extension to allow OpenOffice usage during debug.
  74. // A working directory is required for some zip proxy like PclZipProxy
  75. if (in_array($this->config['ZIP_PROXY'], array('PclZipProxy')) && ! is_dir($this->config['PATH_TO_TMP'])) {
  76. throw new OdfException('Temporary directory '.$this->config['PATH_TO_TMP'].' must exists');
  77. }
  78. // Create tmp direcoty (will be deleted in destructor)
  79. if (!file_exists($this->tmpdir)) {
  80. $result = mkdir($this->tmpdir);
  81. }
  82. // Load zip proxy
  83. $zipHandler = $this->config['ZIP_PROXY'];
  84. if (!defined('PCLZIP_TEMPORARY_DIR')) define('PCLZIP_TEMPORARY_DIR', $this->tmpdir);
  85. include_once 'zip/'.$zipHandler.'.php';
  86. if (! class_exists($this->config['ZIP_PROXY'])) {
  87. throw new OdfException($this->config['ZIP_PROXY'] . ' class not found - check your php settings');
  88. }
  89. $this->file = new $zipHandler($this->tmpdir);
  90. if ($this->file->open($filename) !== true) { // This also create the tmpdir directory
  91. throw new OdfException("Error while Opening the file '$filename' - Check your odt filename");
  92. }
  93. if (($this->contentXml = $this->file->getFromName('content.xml')) === false) {
  94. throw new OdfException("Nothing to parse - Check that the content.xml file is correctly formed in source file '$filename'");
  95. }
  96. if (($this->manifestXml = $this->file->getFromName('META-INF/manifest.xml')) === false) {
  97. throw new OdfException("Something is wrong with META-INF/manifest.xml in source file '$filename'");
  98. }
  99. if (($this->metaXml = $this->file->getFromName('meta.xml')) === false) {
  100. throw new OdfException("Nothing to parse - Check that the meta.xml file is correctly formed in source file '$filename'");
  101. }
  102. if (($this->stylesXml = $this->file->getFromName('styles.xml')) === false) {
  103. throw new OdfException("Nothing to parse - Check that the styles.xml file is correctly formed in source file '$filename'");
  104. }
  105. $this->file->close();
  106. //print "tmpdir=".$tmpdir;
  107. //print "filename=".$filename;
  108. //print "tmpfile=".$tmpfile;
  109. copy($filename, $this->tmpfile);
  110. // Now file has been loaded, we must move the [!-- BEGIN and [!-- END tags outside the
  111. // <table:table-row tag and clean bad lines tags.
  112. $this->_moveRowSegments();
  113. }
  114. /**
  115. * Assing a template variable into ->vars.
  116. * For example, key is {object_date} and value is '2021-01-01'
  117. *
  118. * @param string $key Name of the variable within the template
  119. * @param string $value Replacement value
  120. * @param bool $encode If true, special XML characters are encoded
  121. * @param string $charset Charset
  122. * @throws OdfException
  123. * @return odf
  124. */
  125. public function setVars($key, $value, $encode = true, $charset = 'ISO-8859')
  126. {
  127. $tag = $this->config['DELIMITER_LEFT'] . $key . $this->config['DELIMITER_RIGHT'];
  128. // TODO Warning string may be:
  129. // <text:span text:style-name="T13">{</text:span><text:span text:style-name="T12">aaa</text:span><text:span text:style-name="T13">}</text:span>
  130. // instead of {aaa} so we should enhance this function.
  131. //print $key.'-'.$value.'-'.strpos($this->contentXml, $this->config['DELIMITER_LEFT'] . $key . $this->config['DELIMITER_RIGHT']).'<br>';
  132. if (strpos($this->contentXml, $tag) === false && strpos($this->stylesXml, $tag) === false) {
  133. // Add the throw only for development. In most cases, it is normal to not having the key into the document (only few keys are presents).
  134. //throw new OdfException("var $key not found in the document");
  135. return $this;
  136. }
  137. $this->vars[$tag] = $this->convertVarToOdf($value, $encode, $charset);
  138. return $this;
  139. }
  140. /**
  141. * Replaces html tags found into the $value with ODT compatible tags and return the converted compatible string
  142. *
  143. * @param string $value Replacement value
  144. * @param bool $encode If true, special XML characters are encoded
  145. * @param string $charset Charset
  146. * @return string String in ODTsyntax format
  147. */
  148. public function convertVarToOdf($value, $encode = true, $charset = 'ISO-8859')
  149. {
  150. $value = html_entity_decode($value, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401);
  151. // fix breaklines.
  152. $value = preg_replace('/<br\s*\/?>/', "<br />", $value);
  153. $convertedValue = $value;
  154. // Check if the value includes html tags
  155. if ($this->_hasHtmlTag($value) === true) {
  156. $value = strip_tags($value, '<br><strong><b><i><em><u><s><sub><sup><span>'); // remove html tags except the one into the list in second parameter
  157. // Default styles for strong/b, i/em, u, s, sub & sup
  158. $automaticStyles = array(
  159. '<style:style style:name="boldText" style:family="text"><style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold" /></style:style>',
  160. '<style:style style:name="italicText" style:family="text"><style:text-properties fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic" /></style:style>',
  161. '<style:style style:name="underlineText" style:family="text"><style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" /></style:style>',
  162. '<style:style style:name="strikethroughText" style:family="text"><style:text-properties style:text-line-through-style="solid" style:text-line-through-type="single" /></style:style>',
  163. '<style:style style:name="subText" style:family="text"><style:text-properties style:text-position="sub 58%" /></style:style>',
  164. '<style:style style:name="supText" style:family="text"><style:text-properties style:text-position="super 58%" /></style:style>'
  165. );
  166. $customStyles = array();
  167. $fontDeclarations = array();
  168. $convertedValue = $this->_replaceHtmlWithOdtTag($this->_getDataFromHtml($value), $customStyles, $fontDeclarations, $encode, $charset);
  169. foreach ($customStyles as $key => $val) {
  170. array_push($automaticStyles, '<style:style style:name="customStyle' . $key . '" style:family="text">' . $val . '</style:style>');
  171. }
  172. // Join the styles and add them to the content xml
  173. $styles = '';
  174. foreach ($automaticStyles as $style) {
  175. if (strpos($this->contentXml, $style) === false) {
  176. $styles .= $style;
  177. }
  178. }
  179. $this->contentXml = str_replace('</office:automatic-styles>', $styles . '</office:automatic-styles>', $this->contentXml);
  180. // Join the font declarations and add them to the content xml
  181. $fonts = '';
  182. foreach ($fontDeclarations as $font) {
  183. if (strpos($this->contentXml, 'style:name="' . $font . '"') === false) {
  184. $fonts .= '<style:font-face style:name="' . $font . '" svg:font-family="\'' . $font . '\'" />';
  185. }
  186. }
  187. $this->contentXml = str_replace('</office:font-face-decls>', $fonts . '</office:font-face-decls>', $this->contentXml);
  188. } else {
  189. $convertedValue = $this->encode_chars($convertedValue, $encode, $charset);
  190. $convertedValue = preg_replace('/(\r\n|\r|\n)/i', "<text:line-break/>", $convertedValue);
  191. }
  192. return $convertedValue;
  193. }
  194. /**
  195. * Replaces html tags in with odt tags and returns an odt string. Encodes and converts inner text.
  196. * @param array $tags An array with html tags generated by the getDataFromHtml() function
  197. * @param array $customStyles An array of style defenitions that should be included inside the odt file
  198. * @param array $fontDeclarations An array of font declarations that should be included inside the odt file
  199. * @param bool $encode If true, special XML characters are encoded
  200. * @param string $charset Charset. See encode_chars()
  201. * @return string
  202. */
  203. private function _replaceHtmlWithOdtTag($tags, &$customStyles, &$fontDeclarations, $encode = false, $charset = '')
  204. {
  205. if ($customStyles == null) $customStyles = array();
  206. if ($fontDeclarations == null) $fontDeclarations = array();
  207. $odtResult = '';
  208. foreach ((array) $tags as $tag) {
  209. // Check if the current item is a tag or just plain text
  210. if (isset($tag['text'])) {
  211. $text = $this->encode_chars($tag['text'], $encode, $charset);
  212. $odtResult .= $text;
  213. } elseif (isset($tag['name'])) {
  214. switch ($tag['name']) {
  215. case 'br':
  216. $odtResult .= '<text:line-break/>';
  217. break;
  218. case 'strong':
  219. case 'b':
  220. $odtResult .= '<text:span text:style-name="boldText">' . ($tag['children'] != null ? $this->_replaceHtmlWithOdtTag($tag['children'], $customStyles, $fontDeclarations, $encode) : $this->encode_chars($tag['innerText'], $encode, $charset)) . '</text:span>';
  221. break;
  222. case 'i':
  223. case 'em':
  224. $odtResult .= '<text:span text:style-name="italicText">' . ($tag['children'] != null ? $this->_replaceHtmlWithOdtTag($tag['children'], $customStyles, $fontDeclarations, $encode) : $this->encode_chars($tag['innerText'], $encode, $charset)) . '</text:span>';
  225. break;
  226. case 'u':
  227. $odtResult .= '<text:span text:style-name="underlineText">' . ($tag['children'] != null ? $this->_replaceHtmlWithOdtTag($tag['children'], $customStyles, $fontDeclarations, $encode) : $this->encode_chars($tag['innerText'], $encode, $charset)) . '</text:span>';
  228. break;
  229. case 's':
  230. $odtResult .= '<text:span text:style-name="strikethroughText">' . ($tag['children'] != null ? $this->_replaceHtmlWithOdtTag($tag['children'], $customStyles, $fontDeclarations, $encode) : $this->encode_chars($tag['innerText'], $encode, $charset)) . '</text:span>';
  231. break;
  232. case 'sub':
  233. $odtResult .= '<text:span text:style-name="subText">' . ($tag['children'] != null ? $this->_replaceHtmlWithOdtTag($tag['children'], $customStyles, $fontDeclarations, $encode) : $this->encode_chars($tag['innerText'], $encode, $charset)) . '</text:span>';
  234. break;
  235. case 'sup':
  236. $odtResult .= '<text:span text:style-name="supText">' . ($tag['children'] != null ? $this->_replaceHtmlWithOdtTag($tag['children'], $customStyles, $fontDeclarations, $encode) : $this->encode_chars($tag['innerText'], $encode, $charset)) . '</text:span>';
  237. break;
  238. case 'span':
  239. if (isset($tag['attributes']['style'])) {
  240. $odtStyles = '';
  241. foreach ($tag['attributes']['style'] as $styleName => $styleValue) {
  242. switch ($styleName) {
  243. case 'font-family':
  244. $fontName = $styleValue;
  245. if (strpos($fontName, ',') !== false) {
  246. $fontName = explode(',', $fontName)[0];
  247. }
  248. if (!in_array($fontName, $fontDeclarations)) {
  249. array_push($fontDeclarations, $fontName);
  250. }
  251. $odtStyles .= '<style:text-properties style:font-name="' . $fontName . '" />';
  252. break;
  253. case 'font-size':
  254. if (preg_match('/([0-9]+)\s?(px|pt)/', $styleValue, $matches)) {
  255. $fontSize = intval($matches[1]);
  256. if ($matches[2] == 'px') {
  257. $fontSize = round($fontSize * 0.75);
  258. }
  259. $odtStyles .= '<style:text-properties fo:font-size="' . $fontSize . 'pt" style:font-size-asian="' . $fontSize . 'pt" style:font-size-complex="' . $fontSize . 'pt" />';
  260. }
  261. break;
  262. case 'color':
  263. if (preg_match('/#[0-9A-Fa-f]{3}(?:[0-9A-Fa-f]{3})?/', $styleValue)) {
  264. $odtStyles .= '<style:text-properties fo:color="' . $styleValue . '" />';
  265. }
  266. break;
  267. }
  268. }
  269. if (strlen($odtStyles) > 0) {
  270. // Generate a unique id for the style (using microtime and random because some CPUs are really fast...)
  271. $key = floatval(str_replace('.', '', microtime(true))) + rand(0, 10);
  272. $customStyles[$key] = $odtStyles;
  273. $odtResult .= '<text:span text:style-name="customStyle' . $key . '">' . ($tag['children'] != null ? $this->_replaceHtmlWithOdtTag($tag['children'], $customStyles, $fontDeclarations, $encode) : $this->encode_chars($tag['innerText'], $encode, $charset)) . '</text:span>';
  274. }
  275. }
  276. break;
  277. default:
  278. $odtResult .= $this->_replaceHtmlWithOdtTag($tag['children'], $customStyles, $fontDeclarations, $encode);
  279. break;
  280. }
  281. }
  282. }
  283. return $odtResult;
  284. }
  285. /**
  286. * Correctly encode chars
  287. * @param string $text The text to encode or not
  288. * @param bool $encode If true, special XML characters are encoded
  289. * @param string $charset Charset
  290. * @return string The converted text
  291. * @see self::convertVarToOdf()
  292. */
  293. private function encode_chars($text, $encode = false, $charset = '')
  294. {
  295. $newtext = $encode ? htmlspecialchars($text, ENT_QUOTES | ENT_XML1) : $text;
  296. $newtext = ($charset == 'ISO-8859') ? mb_convert_encoding($newtext, 'UTF-8', 'ISO-8859-1') : $newtext;
  297. return $newtext;
  298. }
  299. /**
  300. * Checks if the given text is a html string
  301. * @param string $text The text to check
  302. * @return bool
  303. */
  304. private function _isHtmlTag($text)
  305. {
  306. return preg_match(self::FIND_TAGS_REGEX, $text);
  307. }
  308. /**
  309. * Checks if the given text includes a html string
  310. * @param string $text The text to check
  311. * @return bool
  312. */
  313. private function _hasHtmlTag($text)
  314. {
  315. $result = preg_match_all(self::FIND_TAGS_REGEX, $text);
  316. return is_numeric($result) && $result > 0;
  317. }
  318. /**
  319. * Returns an array of html elements
  320. * @param string $html A string with html tags
  321. * @return array
  322. */
  323. private function _getDataFromHtml($html)
  324. {
  325. $tags = array();
  326. $tempHtml = $html;
  327. while (strlen($tempHtml) > 0) {
  328. // Check if the string includes a html tag
  329. if (preg_match_all(self::FIND_TAGS_REGEX, $tempHtml, $matches)) {
  330. $tagOffset = strpos($tempHtml, $matches[0][0]);
  331. // Check if the string starts with the html tag
  332. if ($tagOffset > 0) {
  333. // Push the text infront of the html tag to the result array
  334. array_push($tags, array(
  335. 'text' => substr($tempHtml, 0, $tagOffset)
  336. ));
  337. // Remove the text from the string
  338. $tempHtml = substr($tempHtml, $tagOffset);
  339. }
  340. // Extract the attribute data from the html tag
  341. preg_match_all('/([0-9A-Za-z]+(?:="[0-9A-Za-z\:\-\s\,\;\#]*")?)+/', $matches[2][0], $explodedAttributes);
  342. $explodedAttributes = array_filter($explodedAttributes[0]);
  343. $attributes = array();
  344. // Store each attribute with its name in the $attributes array
  345. $explodedAttributesCount = count($explodedAttributes);
  346. for ($i = 0; $i < $explodedAttributesCount; $i++) {
  347. $attribute = trim($explodedAttributes[$i]);
  348. // Check if the attribute has a value (like style="") or has no value (like required)
  349. if (strpos($attribute, '=') !== false) {
  350. $splitAttribute = explode('=', $attribute);
  351. $attrName = trim($splitAttribute[0]);
  352. $attrValue = trim(str_replace('"', '', $splitAttribute[1]));
  353. // check if the current attribute is a style attribute
  354. if (strtolower($attrName) == 'style') {
  355. $attributes[$attrName] = array();
  356. if (strpos($attrValue, ';') !== false) {
  357. // Split the style properties and store them in an array
  358. $explodedStyles = explode(';', $attrValue);
  359. $explodedStylesCount = count($explodedStyles);
  360. for ($n = 0; $n < $explodedStylesCount; $n++) {
  361. $splitStyle = explode(':', $explodedStyles[$n]);
  362. $attributes[$attrName][trim($splitStyle[0])] = trim($splitStyle[1]);
  363. }
  364. } else {
  365. $splitStyle = explode(':', $attrValue);
  366. $attributes[$attrName][trim($splitStyle[0])] = trim($splitStyle[1]);
  367. }
  368. } else {
  369. // Store the value directly in the $attributes array if this is not the style attribute
  370. $attributes[$attrName] = $attrValue;
  371. }
  372. } else {
  373. $attributes[trim($attribute)] = true;
  374. }
  375. }
  376. // Push the html tag data to the result array
  377. array_push($tags, array(
  378. 'name' => $matches[1][0],
  379. 'attributes' => $attributes,
  380. 'innerText' => strip_tags($matches[3][0]),
  381. 'children' => $this->_hasHtmlTag($matches[3][0]) ? $this->_getDataFromHtml($matches[3][0]) : null
  382. ));
  383. // Remove the processed html tag from the html string
  384. $tempHtml = substr($tempHtml, strlen($matches[0][0]));
  385. } else {
  386. array_push($tags, array(
  387. 'text' => $tempHtml
  388. ));
  389. $tempHtml = '';
  390. }
  391. }
  392. return $tags;
  393. }
  394. /**
  395. * Function to convert a HTML string into an ODT string
  396. *
  397. * @param string $value String to convert
  398. * @return string String converted
  399. */
  400. public function htmlToUTFAndPreOdf($value)
  401. {
  402. // We decode into utf8, entities
  403. $value=dol_html_entity_decode($value, ENT_QUOTES|ENT_HTML5);
  404. // We convert html tags
  405. $ishtml=dol_textishtml($value);
  406. if ($ishtml) {
  407. // If string is "MYPODUCT - Desc <strong>bold</strong> with &eacute; accent<br />\n<br />\nUn texto en espa&ntilde;ol ?"
  408. // Result after clean must be "MYPODUCT - Desc bold with é accent\n\nUn texto en espa&ntilde;ol ?"
  409. // We want to ignore \n and we want all <br> to be \n
  410. $value=preg_replace('/(\r\n|\r|\n)/i', '', $value);
  411. $value=preg_replace('/<br>/i', "\n", $value);
  412. $value=preg_replace('/<br\s+[^<>\/]*>/i', "\n", $value);
  413. $value=preg_replace('/<br\s+[^<>\/]*\/>/i', "\n", $value);
  414. //$value=preg_replace('/<strong>/','__lt__text:p text:style-name=__quot__bold__quot____gt__',$value);
  415. //$value=preg_replace('/<\/strong>/','__lt__/text:p__gt__',$value);
  416. $value=dol_string_nohtmltag($value, 0);
  417. }
  418. return $value;
  419. }
  420. /**
  421. * Function to convert a HTML string into an ODT string
  422. *
  423. * @param string $value String to convert
  424. * @return string String converted
  425. */
  426. public function preOdfToOdf($value)
  427. {
  428. $value = str_replace("\n", "<text:line-break/>", $value);
  429. //$value = str_replace("__lt__", "<", $value);
  430. //$value = str_replace("__gt__", ">", $value);
  431. //$value = str_replace("__quot__", '"', $value);
  432. return $value;
  433. }
  434. /**
  435. * Assign a template variable as a picture
  436. *
  437. * @param string $key name of the variable within the template
  438. * @param string $value path to the picture
  439. * @throws OdfException
  440. * @return odf
  441. */
  442. public function setImage($key, $value)
  443. {
  444. $filename = strtok(strrchr($value, '/'), '/.');
  445. $file = substr(strrchr($value, '/'), 1);
  446. $size = @getimagesize($value);
  447. if ($size === false) {
  448. throw new OdfException("Invalid image");
  449. }
  450. list ($width, $height) = $size;
  451. $width *= self::PIXEL_TO_CM;
  452. $height *= self::PIXEL_TO_CM;
  453. $xml = <<<IMG
  454. <draw:frame draw:style-name="fr1" draw:name="$filename" text:anchor-type="aschar" svg:width="{$width}cm" svg:height="{$height}cm" draw:z-index="3"><draw:image xlink:href="Pictures/$file" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/></draw:frame>
  455. IMG;
  456. $this->images[$value] = $file;
  457. $this->setVars($key, $xml, false);
  458. return $this;
  459. }
  460. /**
  461. * Move segment tags for lines of tables
  462. * This function is called automatically within the constructor, so this->contentXml is clean before any other thing
  463. *
  464. * @return void
  465. */
  466. private function _moveRowSegments()
  467. {
  468. // Replace BEGIN<text:s/>xxx into BEGIN xxx
  469. $this->contentXml = preg_replace('/\[!--\sBEGIN<text:s[^>]>(row.[\S]*)\s--\]/sm', '[!-- BEGIN \\1 --]', $this->contentXml);
  470. // Replace END<text:s/>xxx into END xxx
  471. $this->contentXml = preg_replace('/\[!--\sEND<text:s[^>]>(row.[\S]*)\s--\]/sm', '[!-- END \\1 --]', $this->contentXml);
  472. // Search all possible rows in the document
  473. $reg1 = "#<table:table-row[^>]*>(.*)</table:table-row>#smU";
  474. $matches = array();
  475. preg_match_all($reg1, $this->contentXml, $matches);
  476. for ($i = 0, $size = count($matches[0]); $i < $size; $i++) {
  477. // Check if the current row contains a segment row.*
  478. $reg2 = '#\[!--\sBEGIN\s(row.[\S]*)\s--\](.*)\[!--\sEND\s\\1\s--\]#sm';
  479. $matches2 = array();
  480. if (preg_match($reg2, $matches[0][$i], $matches2)) {
  481. $balise = str_replace('row.', '', $matches2[1]);
  482. // Move segment tags around the row
  483. $replace = array(
  484. '[!-- BEGIN ' . $matches2[1] . ' --]' => '',
  485. '[!-- END ' . $matches2[1] . ' --]' => '',
  486. '<table:table-row' => '[!-- BEGIN ' . $balise . ' --]<table:table-row',
  487. '</table:table-row>' => '</table:table-row>[!-- END ' . $balise . ' --]'
  488. );
  489. $replacedXML = str_replace(array_keys($replace), array_values($replace), $matches[0][$i]);
  490. $this->contentXml = str_replace($matches[0][$i], $replacedXML, $this->contentXml);
  491. }
  492. }
  493. }
  494. /**
  495. * Merge template variables
  496. * Called at the beginning of the _save function
  497. *
  498. * @param string $type 'content', 'styles' or 'meta'
  499. * @return void
  500. */
  501. private function _parse($type = 'content')
  502. {
  503. // Search all tags found into condition to complete $this->vars, so we will proceed all tests even if not defined
  504. $reg='@\[!--\sIF\s([{}a-zA-Z0-9\.\,_]+)\s--\]@smU';
  505. $matches = array();
  506. preg_match_all($reg, $this->contentXml, $matches, PREG_SET_ORDER);
  507. //var_dump($this->vars);exit;
  508. foreach ($matches as $match) { // For each match, if there is no entry into this->vars, we add it
  509. if (! empty($match[1]) && ! isset($this->vars[$match[1]])) {
  510. $this->vars[$match[1]] = ''; // Not defined, so we set it to '', we just need entry into this->vars for next loop
  511. }
  512. }
  513. //var_dump($this->vars);exit;
  514. // Conditionals substitution
  515. // Note: must be done before static substitution, else the variable will be replaced by its value and the conditional won't work anymore
  516. foreach ($this->vars as $key => $value) {
  517. // If value is true (not 0 nor false nor null nor empty string)
  518. if ($value) {
  519. //dol_syslog("Var ".$key." is defined, we remove the IF, ELSE and ENDIF ");
  520. //$sav=$this->contentXml;
  521. // Remove the IF tag
  522. $this->contentXml = str_replace('[!-- IF '.$key.' --]', '', $this->contentXml);
  523. // Remove everything between the ELSE tag (if it exists) and the ENDIF tag
  524. $reg = '@(\[!--\sELSE\s' . $key . '\s--\](.*))?\[!--\sENDIF\s' . $key . '\s--\]@smU'; // U modifier = all quantifiers are non-greedy
  525. $this->contentXml = preg_replace($reg, '', $this->contentXml);
  526. /*if ($sav != $this->contentXml)
  527. {
  528. dol_syslog("We found a IF and it was processed");
  529. //var_dump($sav);exit;
  530. }*/
  531. } else {
  532. // Else the value is false, then two cases: no ELSE and we're done, or there is at least one place where there is an ELSE clause, then we replace it
  533. //dol_syslog("Var ".$key." is not defined, we remove the IF, ELSE and ENDIF ");
  534. //$sav=$this->contentXml;
  535. // Find all conditional blocks for this variable: from IF to ELSE and to ENDIF
  536. $reg = '@\[!--\sIF\s' . $key . '\s--\](.*)(\[!--\sELSE\s' . $key . '\s--\](.*))?\[!--\sENDIF\s' . $key . '\s--\]@smU'; // U modifier = all quantifiers are non-greedy
  537. preg_match_all($reg, $this->contentXml, $matches, PREG_SET_ORDER);
  538. foreach ($matches as $match) { // For each match, if there is an ELSE clause, we replace the whole block by the value in the ELSE clause
  539. if (!empty($match[3])) $this->contentXml = str_replace($match[0], $match[3], $this->contentXml);
  540. }
  541. // Cleanup the other conditional blocks (all the others where there were no ELSE clause, we can just remove them altogether)
  542. $this->contentXml = preg_replace($reg, '', $this->contentXml);
  543. /*if ($sav != $this->contentXml)
  544. {
  545. dol_syslog("We found a IF and it was processed");
  546. //var_dump($sav);exit;
  547. }*/
  548. }
  549. }
  550. // Static substitution
  551. if ($type == 'content') $this->contentXml = str_replace(array_keys($this->vars), array_values($this->vars), $this->contentXml);
  552. if ($type == 'styles') $this->stylesXml = str_replace(array_keys($this->vars), array_values($this->vars), $this->stylesXml);
  553. if ($type == 'meta') $this->metaXml = str_replace(array_keys($this->vars), array_values($this->vars), $this->metaXml);
  554. }
  555. /**
  556. * Add the merged segment to the document
  557. *
  558. * @param Segment $segment Segment
  559. * @throws OdfException
  560. * @return odf
  561. */
  562. public function mergeSegment(Segment $segment)
  563. {
  564. if (! array_key_exists($segment->getName(), $this->segments)) {
  565. throw new OdfException($segment->getName() . 'cannot be parsed, has it been set yet ?');
  566. }
  567. $string = $segment->getName();
  568. // $reg = '@<text:p[^>]*>\[!--\sBEGIN\s' . $string . '\s--\](.*)\[!--.+END\s' . $string . '\s--\]<\/text:p>@smU';
  569. $reg = '@\[!--\sBEGIN\s' . $string . '\s--\](.*)\[!--.+END\s' . $string . '\s--\]@smU';
  570. $this->contentXml = preg_replace($reg, $segment->getXmlParsed(), $this->contentXml);
  571. return $this;
  572. }
  573. /**
  574. * Display all the current template variables
  575. *
  576. * @return string
  577. */
  578. public function printVars()
  579. {
  580. return print_r('<pre>' . print_r($this->vars, true) . '</pre>', true);
  581. }
  582. /**
  583. * Display the XML content of the file from odt document
  584. * as it is at the moment
  585. *
  586. * @return string
  587. */
  588. public function __toString()
  589. {
  590. return $this->contentXml;
  591. }
  592. /**
  593. * Display loop segments declared with setSegment()
  594. *
  595. * @return string
  596. */
  597. public function printDeclaredSegments()
  598. {
  599. return '<pre>' . print_r(implode(' ', array_keys($this->segments)), true) . '</pre>';
  600. }
  601. /**
  602. * Declare a segment in order to use it in a loop.
  603. * Extract the segment and store it into $this->segments[]. Return it for next call.
  604. *
  605. * @param string $segment Segment
  606. * @throws OdfExceptionSegmentNotFound
  607. * @return Segment
  608. */
  609. public function setSegment($segment)
  610. {
  611. if (array_key_exists($segment, $this->segments)) {
  612. return $this->segments[$segment];
  613. }
  614. // $reg = "#\[!--\sBEGIN\s$segment\s--\]<\/text:p>(.*)<text:p\s.*>\[!--\sEND\s$segment\s--\]#sm";
  615. $reg = "#\[!--\sBEGIN\s$segment\s--\](.*)\[!--\sEND\s$segment\s--\]#sm";
  616. $m = array();
  617. if (preg_match($reg, html_entity_decode($this->contentXml), $m) == 0) {
  618. throw new OdfExceptionSegmentNotFound("'".$segment."' segment not found in the document. The tag [!-- BEGIN xxx --] or [!-- END xxx --] is not present into content file.");
  619. }
  620. $this->segments[$segment] = new Segment($segment, $m[1], $this);
  621. return $this->segments[$segment];
  622. }
  623. /**
  624. * Save the odt file on the disk
  625. *
  626. * @param string $file name of the desired file
  627. * @throws OdfException
  628. * @return void
  629. */
  630. public function saveToDisk($file = null)
  631. {
  632. if ($file !== null && is_string($file)) {
  633. if (file_exists($file) && !(is_file($file) && is_writable($file))) {
  634. throw new OdfException('Permission denied : can\'t create ' . $file);
  635. }
  636. $this->_save();
  637. copy($this->tmpfile, $file);
  638. } else {
  639. $this->_save();
  640. }
  641. }
  642. /**
  643. * Write output file onto disk
  644. *
  645. * @throws OdfException
  646. * @return void
  647. */
  648. private function _save()
  649. {
  650. $res=$this->file->open($this->tmpfile); // tmpfile is odt template
  651. $this->_parse('content');
  652. $this->_parse('styles');
  653. $this->_parse('meta');
  654. $this->setMetaData();
  655. //print $this->metaXml;exit;
  656. if (! $this->file->addFromString('content.xml', $this->contentXml)) {
  657. throw new OdfException('Error during file export addFromString content');
  658. }
  659. if (! $this->file->addFromString('meta.xml', $this->metaXml)) {
  660. throw new OdfException('Error during file export addFromString meta');
  661. }
  662. if (! $this->file->addFromString('styles.xml', $this->stylesXml)) {
  663. throw new OdfException('Error during file export addFromString styles');
  664. }
  665. foreach ($this->images as $imageKey => $imageValue) {
  666. // Add the image inside the ODT document
  667. $this->file->addFile($imageKey, 'Pictures/' . $imageValue);
  668. // Add the image to the Manifest (which maintains a list of images, necessary to avoid "Corrupt ODT file. Repair?" when opening the file with LibreOffice)
  669. $this->addImageToManifest($imageValue);
  670. }
  671. if (! $this->file->addFromString('./META-INF/manifest.xml', $this->manifestXml)) {
  672. throw new OdfException('Error during file export: manifest.xml');
  673. }
  674. $this->file->close();
  675. }
  676. /**
  677. * Update Meta information
  678. * <dc:date>2013-03-16T14:06:25</dc:date>
  679. *
  680. * @return void
  681. */
  682. public function setMetaData()
  683. {
  684. if (empty($this->creator)) $this->creator='';
  685. $this->metaXml = preg_replace('/<dc:date>.*<\/dc:date>/', '<dc:date>'.gmdate("Y-m-d\TH:i:s").'</dc:date>', $this->metaXml);
  686. $this->metaXml = preg_replace('/<dc:creator>.*<\/dc:creator>/', '<dc:creator>'.htmlspecialchars($this->creator).'</dc:creator>', $this->metaXml);
  687. $this->metaXml = preg_replace('/<dc:title>.*<\/dc:title>/', '<dc:title>'.htmlspecialchars($this->title).'</dc:title>', $this->metaXml);
  688. $this->metaXml = preg_replace('/<dc:subject>.*<\/dc:subject>/', '<dc:subject>'.htmlspecialchars($this->subject).'</dc:subject>', $this->metaXml);
  689. if (count($this->userdefined)) {
  690. foreach ($this->userdefined as $key => $val) {
  691. $this->metaXml = preg_replace('<meta:user-defined meta:name="'.$key.'"/>', '', $this->metaXml);
  692. $this->metaXml = preg_replace('/<meta:user-defined meta:name="'.$key.'">.*<\/meta:user-defined>/', '', $this->metaXml);
  693. $this->metaXml = str_replace('</office:meta>', '<meta:user-defined meta:name="'.$key.'">'.htmlspecialchars($val).'</meta:user-defined></office:meta>', $this->metaXml);
  694. }
  695. }
  696. }
  697. /**
  698. * Update Manifest file according to added image files
  699. *
  700. * @param string $file Image file to add into manifest content
  701. * @return void
  702. */
  703. public function addImageToManifest($file)
  704. {
  705. // Get the file extension
  706. $ext = substr(strrchr($file, '.'), 1);
  707. // Create the correct image XML entry to add to the manifest (this is necessary because ODT format requires that we keep a list of the images in the manifest.xml)
  708. $add = ' <manifest:file-entry manifest:media-type="image/'.$ext.'" manifest:full-path="Pictures/'.$file.'"/>'."\n";
  709. // Append the image to the manifest
  710. $this->manifestXml = str_replace('</manifest:manifest>', $add.'</manifest:manifest>', $this->manifestXml); // we replace the manifest closing tag by the image XML entry + manifest closing tag (this results in appending the data, we do not overwrite anything)
  711. }
  712. /**
  713. * Export the file as attached file by HTTP
  714. *
  715. * @param string $name (optional)
  716. * @throws OdfException
  717. * @return void
  718. */
  719. public function exportAsAttachedFile($name = "")
  720. {
  721. $this->_save();
  722. if (headers_sent($filename, $linenum)) {
  723. throw new OdfException("headers already sent ($filename at $linenum)");
  724. }
  725. if ( $name == "" ) {
  726. $name = md5(uniqid()) . ".odt";
  727. }
  728. header('Content-type: application/vnd.oasis.opendocument.text');
  729. header('Content-Disposition: attachment; filename="'.$name.'"');
  730. header('Content-Length: '.filesize($this->tmpfile));
  731. readfile($this->tmpfile);
  732. }
  733. /**
  734. * Convert the ODT file to PDF and export the file as attached file by HTTP
  735. * Note: you need to have JODConverter and OpenOffice or LibreOffice installed and executable on the same system as where this php script will be executed. You also need to chmod +x odt2pdf.sh
  736. *
  737. * @param string $name Name of ODT file to generate before generating PDF
  738. * @throws OdfException
  739. * @return void
  740. */
  741. public function exportAsAttachedPDF($name = "")
  742. {
  743. global $conf;
  744. if ( $name == "" ) $name = "temp".md5(uniqid());
  745. dol_syslog(get_class($this).'::exportAsAttachedPDF $name='.$name, LOG_DEBUG);
  746. $this->saveToDisk($name);
  747. $execmethod=(empty($conf->global->MAIN_EXEC_USE_POPEN)?1:2); // 1 or 2
  748. // Method 1 sometimes hang the server.
  749. // Export to PDF using LibreOffice
  750. if (getDolGlobalString('MAIN_ODT_AS_PDF') == 'libreoffice') {
  751. dol_mkdir($conf->user->dir_temp); // We must be sure the directory exists and is writable
  752. // We delete and recreate a subdir because the soffice may have change pemrissions on it
  753. dol_delete_dir_recursive($conf->user->dir_temp.'/odtaspdf');
  754. dol_mkdir($conf->user->dir_temp.'/odtaspdf');
  755. // Install prerequisites: apt install soffice libreoffice-common libreoffice-writer
  756. // using windows libreoffice that must be in path
  757. // using linux/mac libreoffice that must be in path
  758. // Note PHP Config "fastcgi.impersonate=0" must set to 0 - Default is 1
  759. $command ='soffice --headless -env:UserInstallation=file:\''.$conf->user->dir_temp.'/odtaspdf\' --convert-to pdf --outdir '. escapeshellarg(dirname($name)). " ".escapeshellarg($name);
  760. } elseif (preg_match('/unoconv/', getDolGlobalString('MAIN_ODT_AS_PDF'))) {
  761. // If issue with unoconv, see https://github.com/dagwieers/unoconv/issues/87
  762. // MAIN_ODT_AS_PDF should be "sudo -u unoconv /usr/bin/unoconv" and userunoconv must have sudo to be root by adding file /etc/sudoers.d/unoconv with content www-data ALL=(unoconv) NOPASSWD: /usr/bin/unoconv .
  763. // Try this with www-data user: /usr/bin/unoconv -vvvv -f pdf /tmp/document-example.odt
  764. // It must return:
  765. //Verbosity set to level 4
  766. //Using office base path: /usr/lib/libreoffice
  767. //Using office binary path: /usr/lib/libreoffice/program
  768. //DEBUG: Connection type: socket,host=127.0.0.1,port=2002;urp;StarOffice.ComponentContext
  769. //DEBUG: Existing listener not found.
  770. //DEBUG: Launching our own listener using /usr/lib/libreoffice/program/soffice.bin.
  771. //LibreOffice listener successfully started. (pid=9287)
  772. //Input file: /tmp/document-example.odt
  773. //unoconv: file `/tmp/document-example.odt' does not exist.
  774. //unoconv: RuntimeException during import phase:
  775. //Office probably died. Unsupported URL <file:///tmp/document-example.odt>: "type detection failed"
  776. //DEBUG: Terminating LibreOffice instance.
  777. //DEBUG: Waiting for LibreOffice instance to exit
  778. // If it fails:
  779. // - set shell of user to bash instead of nologin.
  780. // - set permission to read/write to user on home directory /var/www so user can create the libreoffice , dconf and .cache dir and files then set permission back
  781. $command = getDolGlobalString('MAIN_ODT_AS_PDF').' '.escapeshellcmd($name);
  782. //$command = '/usr/bin/unoconv -vvv '.escapeshellcmd($name);
  783. } else {
  784. // deprecated old method using odt2pdf.sh (native, jodconverter, ...)
  785. $tmpname=preg_replace('/\.odt/i', '', $name);
  786. if (getDolGlobalString('MAIN_DOL_SCRIPTS_ROOT')) {
  787. $command = getDolGlobalString('MAIN_DOL_SCRIPTS_ROOT').'/scripts/odt2pdf/odt2pdf.sh '.escapeshellcmd($tmpname).' '.(is_numeric(getDolGlobalString('MAIN_ODT_AS_PDF'))?'jodconverter':getDolGlobalString('MAIN_ODT_AS_PDF'));
  788. } else {
  789. dol_syslog(get_class($this).'::exportAsAttachedPDF is used but the constant MAIN_DOL_SCRIPTS_ROOT with path to script directory was not defined.', LOG_WARNING);
  790. $command = '../../scripts/odt2pdf/odt2pdf.sh '.escapeshellcmd($tmpname).' '.(is_numeric(getDolGlobalString('MAIN_ODT_AS_PDF'))?'jodconverter':getDolGlobalString('MAIN_ODT_AS_PDF'));
  791. }
  792. }
  793. //$dirname=dirname($name);
  794. //$command = DOL_DOCUMENT_ROOT.'/includes/odtphp/odt2pdf.sh '.$name.' '.$dirname;
  795. dol_syslog(get_class($this).'::exportAsAttachedPDF $execmethod='.$execmethod.' Run command='.$command, LOG_DEBUG);
  796. // TODO Use:
  797. // $outputfile = DOL_DATA_ROOT.'/odt2pdf.log';
  798. // $result = $utils->executeCLI($command, $outputfile); and replace test on $execmethod.
  799. // $retval will be $result['result']
  800. // $errorstring will be $result['output']
  801. $retval=0; $output_arr=array();
  802. if ($execmethod == 1) {
  803. exec($command, $output_arr, $retval);
  804. }
  805. if ($execmethod == 2) {
  806. $outputfile = DOL_DATA_ROOT.'/odt2pdf.log';
  807. $ok=0;
  808. $handle = fopen($outputfile, 'w');
  809. if ($handle) {
  810. dol_syslog(get_class($this)."Run command ".$command, LOG_DEBUG);
  811. fwrite($handle, $command."\n");
  812. $handlein = popen($command, 'r');
  813. while (!feof($handlein)) {
  814. $read = fgets($handlein);
  815. fwrite($handle, $read);
  816. $output_arr[]=$read;
  817. }
  818. pclose($handlein);
  819. fclose($handle);
  820. }
  821. dolChmod($outputfile);
  822. }
  823. if ($retval == 0) {
  824. dol_syslog(get_class($this).'::exportAsAttachedPDF $ret_val='.$retval, LOG_DEBUG);
  825. $filename=''; $linenum=0;
  826. if (php_sapi_name() != 'cli') { // If we are in a web context (not into CLI context)
  827. if (headers_sent($filename, $linenum)) {
  828. throw new OdfException("headers already sent ($filename at $linenum)");
  829. }
  830. if (!empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
  831. $name=preg_replace('/\.od(x|t)/i', '', $name);
  832. header('Content-type: application/pdf');
  833. header('Content-Disposition: attachment; filename="'.$name.'.pdf"');
  834. readfile($name.".pdf");
  835. }
  836. }
  837. if (!empty($conf->global->MAIN_ODT_AS_PDF_DEL_SOURCE)) {
  838. unlink($name);
  839. }
  840. } else {
  841. dol_syslog(get_class($this).'::exportAsAttachedPDF $ret_val='.$retval, LOG_DEBUG);
  842. dol_syslog(get_class($this).'::exportAsAttachedPDF $output_arr='.var_export($output_arr, true), LOG_DEBUG);
  843. if ($retval == 126) {
  844. throw new OdfException('Permission execute convert script : ' . $command);
  845. } else {
  846. $errorstring='';
  847. foreach ($output_arr as $line) {
  848. $errorstring.= $line."<br>";
  849. }
  850. throw new OdfException('ODT to PDF convert fail (option MAIN_ODT_AS_PDF is '.$conf->global->MAIN_ODT_AS_PDF.', command was '.$command.', retval='.$retval.') : ' . $errorstring);
  851. }
  852. }
  853. }
  854. /**
  855. * Returns a variable of configuration
  856. *
  857. * @param string $configKey Config key
  858. * @return string The requested variable of configuration
  859. */
  860. public function getConfig($configKey)
  861. {
  862. if (array_key_exists($configKey, $this->config)) {
  863. return $this->config[$configKey];
  864. }
  865. return false;
  866. }
  867. /**
  868. * Returns the temporary working file
  869. *
  870. * @return string le chemin vers le fichier temporaire de travail
  871. */
  872. public function getTmpfile()
  873. {
  874. return $this->tmpfile;
  875. }
  876. /**
  877. * Delete the temporary file when the object is destroyed
  878. */
  879. public function __destruct()
  880. {
  881. if (file_exists($this->tmpfile)) {
  882. unlink($this->tmpfile);
  883. }
  884. if (file_exists($this->tmpdir)) {
  885. $this->_rrmdir($this->tmpdir);
  886. rmdir($this->tmpdir);
  887. }
  888. }
  889. /**
  890. * Empty the temporary working directory recursively
  891. *
  892. * @param string $dir The temporary working directory
  893. * @return void
  894. */
  895. private function _rrmdir($dir)
  896. {
  897. if ($handle = opendir($dir)) {
  898. while (($file = readdir($handle)) !== false) {
  899. if ($file != '.' && $file != '..') {
  900. if (is_dir($dir . '/' . $file)) {
  901. $this->_rrmdir($dir . '/' . $file);
  902. rmdir($dir . '/' . $file);
  903. } else {
  904. unlink($dir . '/' . $file);
  905. }
  906. }
  907. }
  908. closedir($handle);
  909. }
  910. }
  911. /**
  912. * return the value present on odt in [valuename][/valuename]
  913. *
  914. * @param string $valuename Balise in the template
  915. * @return string The value inside the balise
  916. */
  917. public function getvalue($valuename)
  918. {
  919. $searchreg="/\\[".$valuename."\\](.*)\\[\\/".$valuename."\\]/";
  920. $matches = array();
  921. preg_match($searchreg, $this->contentXml, $matches);
  922. $this->contentXml = preg_replace($searchreg, "", $this->contentXml);
  923. if ($matches) {
  924. return $matches[1];
  925. }
  926. return "";
  927. }
  928. }