Segment.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. <?php
  2. require 'SegmentIterator.php';
  3. class SegmentException extends Exception
  4. {
  5. }
  6. /**
  7. * Class for handling templating segments with odt files
  8. * You need PHP 5.2 at least
  9. * You need Zip Extension or PclZip library
  10. *
  11. * @copyright 2008 - Julien Pauli - Cyril PIERRE de GEYER - Anaska (http://www.anaska.com)
  12. * @copyright 2012 - Stephen Larroque - lrq3000@gmail.com
  13. * @license https://www.gnu.org/copyleft/gpl.html GPL License
  14. * @version 1.4.5 (last update 2013-04-07)
  15. */
  16. class Segment implements IteratorAggregate, Countable
  17. {
  18. protected $xml;
  19. protected $xmlParsed = '';
  20. protected $name;
  21. protected $children = array();
  22. protected $vars = array();
  23. protected $images = array();
  24. protected $odf;
  25. protected $file;
  26. /**
  27. * Constructor
  28. *
  29. * @param string $name name of the segment to construct
  30. * @param string $xml XML tree of the segment
  31. * @param string $odf odf
  32. */
  33. public function __construct($name, $xml, $odf)
  34. {
  35. $this->name = (string) $name;
  36. $this->xml = (string) $xml;
  37. $this->odf = $odf;
  38. $zipHandler = $this->odf->getConfig('ZIP_PROXY');
  39. $this->file = new $zipHandler($this->odf->getConfig('PATH_TO_TMP'));
  40. $this->_analyseChildren($this->xml);
  41. }
  42. /**
  43. * Returns the name of the segment
  44. *
  45. * @return string
  46. */
  47. public function getName()
  48. {
  49. return $this->name;
  50. }
  51. /**
  52. * Does the segment have children ?
  53. *
  54. * @return bool
  55. */
  56. public function hasChildren()
  57. {
  58. return $this->getIterator()->hasChildren();
  59. }
  60. /**
  61. * Countable interface
  62. *
  63. * @return int
  64. */
  65. public function count()
  66. {
  67. return count($this->children);
  68. }
  69. /**
  70. * IteratorAggregate interface
  71. *
  72. * @return Iterator
  73. */
  74. public function getIterator()
  75. {
  76. return new RecursiveIteratorIterator(new SegmentIterator($this->children), 1);
  77. }
  78. /**
  79. * Replace variables of the template in the XML code
  80. * All the children are also called
  81. * Complete the current segment with new line
  82. *
  83. * @return string
  84. */
  85. public function merge()
  86. {
  87. // To provide debug information on line number processed
  88. global $count;
  89. if (empty($count)) $count=1;
  90. else $count++;
  91. if (empty($this->savxml)) $this->savxml = $this->xml; // Sav content of line at first line merged, so we will reuse original for next steps
  92. $this->xml = $this->savxml;
  93. $tmpvars = $this->vars; // Store into $tmpvars so we won't modify this->vars when completing data with empty values
  94. // Search all tags fou into condition to complete $tmpvars, so we will proceed all tests even if not defined
  95. $reg='@\[!--\sIF\s([{}a-zA-Z0-9\.\,_]+)\s--\]@smU';
  96. $matches = array();
  97. preg_match_all($reg, $this->xml, $matches, PREG_SET_ORDER);
  98. //var_dump($tmpvars);exit;
  99. foreach ($matches as $match) { // For each match, if there is no entry into this->vars, we add it
  100. if (! empty($match[1]) && ! isset($tmpvars[$match[1]])) {
  101. $tmpvars[$match[1]] = ''; // Not defined, so we set it to '', we just need entry into this->vars for next loop
  102. }
  103. }
  104. // Conditionals substitution
  105. // Note: must be done before static substitution, else the variable will be replaced by its value and the conditional won't work anymore
  106. foreach ($tmpvars as $key => $value) {
  107. // If value is true (not 0 nor false nor null nor empty string)
  108. if ($value) {
  109. // Remove the IF tag
  110. $this->xml = str_replace('[!-- IF '.$key.' --]', '', $this->xml);
  111. // Remove everything between the ELSE tag (if it exists) and the ENDIF tag
  112. $reg = '@(\[!--\sELSE\s' . $key . '\s--\](.*))?\[!--\sENDIF\s' . $key . '\s--\]@smU'; // U modifier = all quantifiers are non-greedy
  113. $this->xml = preg_replace($reg, '', $this->xml);
  114. }
  115. // 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
  116. else {
  117. // Find all conditional blocks for this variable: from IF to ELSE and to ENDIF
  118. $reg = '@\[!--\sIF\s' . $key . '\s--\](.*)(\[!--\sELSE\s' . $key . '\s--\](.*))?\[!--\sENDIF\s' . $key . '\s--\]@smU'; // U modifier = all quantifiers are non-greedy
  119. preg_match_all($reg, $this->xml, $matches, PREG_SET_ORDER);
  120. 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
  121. if (!empty($match[3])) $this->xml = str_replace($match[0], $match[3], $this->xml);
  122. }
  123. // Cleanup the other conditional blocks (all the others where there were no ELSE clause, we can just remove them altogether)
  124. $this->xml = preg_replace($reg, '', $this->xml);
  125. }
  126. }
  127. $this->xmlParsed .= str_replace(array_keys($tmpvars), array_values($tmpvars), $this->xml);
  128. if ($this->hasChildren()) {
  129. foreach ($this->children as $child) {
  130. $this->xmlParsed = str_replace($child->xml, ($child->xmlParsed=="")?$child->merge():$child->xmlParsed, $this->xmlParsed);
  131. $child->xmlParsed = '';
  132. }
  133. }
  134. $reg = "/\[!--\sBEGIN\s$this->name\s--\](.*)\[!--\sEND\s$this->name\s--\]/sm";
  135. $this->xmlParsed = preg_replace($reg, '$1', $this->xmlParsed);
  136. // Miguel Erill 09704/2017 - Add macro replacement to invoice lines
  137. $this->xmlParsed = $this->macroReplace($this->xmlParsed);
  138. $this->file->open($this->odf->getTmpfile());
  139. foreach ($this->images as $imageKey => $imageValue) {
  140. if ($this->file->getFromName('Pictures/' . $imageValue) === false) {
  141. // Add the image inside the ODT document
  142. $this->file->addFile($imageKey, 'Pictures/' . $imageValue);
  143. // 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)
  144. $this->odf->addImageToManifest($imageValue);
  145. }
  146. }
  147. $this->file->close();
  148. return $this->xmlParsed;
  149. }
  150. /**
  151. * Function to replace macros for invoice short and long month, invoice year
  152. *
  153. * Substitution occur when the invoice is generated, not considering the invoice date
  154. * so do not (re)generate in a diferent date than the one that the invoice belongs to
  155. * Perhaps it would be better to use the invoice issued date but I still do not know
  156. * how to get it here
  157. *
  158. * Miguel Erill 09/04/2017
  159. *
  160. * @param string $value String to convert
  161. */
  162. public function macroReplace($text)
  163. {
  164. include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
  165. global $langs;
  166. $hoy = dol_getdate(dol_now('tzuser'));
  167. $dateinonemontharray = dol_get_next_month($hoy['mon'], $hoy['year']);
  168. $nextMonth = $dateinonemontharray['month'];
  169. $patterns=array( '/__CURRENTDAY__/u','/__CURENTWEEKDAY__/u',
  170. '/__CURRENTMONTH__/u','/__CURRENTMONTHLONG__/u',
  171. '/__NEXTMONTH__/u','/__NEXTMONTHLONG__/u',
  172. '/__CURRENTYEAR__/u','/__NEXTYEAR__/u' );
  173. $values=array( $hoy['mday'], $langs->transnoentitiesnoconv($hoy['weekday']),
  174. $hoy['mon'], $langs->transnoentitiesnoconv($hoy['month']),
  175. $nextMonth, monthArray($langs)[$nextMonth],
  176. $hoy['year'], $hoy['year']+1 );
  177. $text=preg_replace($patterns, $values, $text);
  178. return $text;
  179. }
  180. /**
  181. * Analyse the XML code in order to find children
  182. *
  183. * @param string $xml Xml
  184. * @return Segment
  185. */
  186. protected function _analyseChildren($xml)
  187. {
  188. // $reg2 = "#\[!--\sBEGIN\s([\S]*)\s--\](?:<\/text:p>)?(.*)(?:<text:p\s.*>)?\[!--\sEND\s(\\1)\s--\]#sm";
  189. $reg2 = "#\[!--\sBEGIN\s([\S]*)\s--\](.*)\[!--\sEND\s(\\1)\s--\]#sm";
  190. preg_match_all($reg2, $xml, $matches);
  191. for ($i = 0, $size = count($matches[0]); $i < $size; $i++) {
  192. if ($matches[1][$i] != $this->name) {
  193. $this->children[$matches[1][$i]] = new self($matches[1][$i], $matches[0][$i], $this->odf);
  194. } else {
  195. $this->_analyseChildren($matches[2][$i]);
  196. }
  197. }
  198. return $this;
  199. }
  200. /**
  201. * Assign a template variable to replace
  202. *
  203. * @param string $key Key
  204. * @param string $value Value
  205. * @param string $encode Encode
  206. * @param string $charset Charset
  207. * @throws SegmentException
  208. * @return Segment
  209. */
  210. public function setVars($key, $value, $encode = true, $charset = 'ISO-8859')
  211. {
  212. $tag = $this->odf->getConfig('DELIMITER_LEFT') . $key . $this->odf->getConfig('DELIMITER_RIGHT');
  213. if (strpos($this->xml, $tag) === false) {
  214. //throw new SegmentException("var $key not found in {$this->getName()}");
  215. }
  216. $this->vars[$tag] = $this->odf->convertVarToOdf($value, $encode, $charset);
  217. return $this;
  218. }
  219. /**
  220. * Assign a template variable as a picture
  221. *
  222. * @param string $key name of the variable within the template
  223. * @param string $value path to the picture
  224. * @throws OdfException
  225. * @return Segment
  226. */
  227. public function setImage($key, $value)
  228. {
  229. $filename = strtok(strrchr($value, '/'), '/.');
  230. $file = substr(strrchr($value, '/'), 1);
  231. $size = @getimagesize($value);
  232. if ($size === false) {
  233. throw new OdfException("Invalid image");
  234. }
  235. // Set the width and height of the page
  236. list ($width, $height) = $size;
  237. $width *= Odf::PIXEL_TO_CM;
  238. $height *= Odf::PIXEL_TO_CM;
  239. // Fix local-aware issues (eg: 12,10 -> 12.10)
  240. $width = sprintf("%F", $width);
  241. $height = sprintf("%F", $height);
  242. $xml = <<<IMG
  243. <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>
  244. IMG;
  245. $this->images[$value] = $file;
  246. $this->setVars($key, $xml, false);
  247. return $this;
  248. }
  249. /**
  250. * Shortcut to retrieve a child
  251. *
  252. * @param string $prop Prop
  253. * @return Segment
  254. * @throws SegmentException
  255. */
  256. public function __get($prop)
  257. {
  258. if (array_key_exists($prop, $this->children)) {
  259. return $this->children[$prop];
  260. } else {
  261. throw new SegmentException('child ' . $prop . ' does not exist');
  262. }
  263. }
  264. /**
  265. * Proxy for setVars
  266. *
  267. * @param string $meth Meth
  268. * @param array $args Args
  269. * @return Segment
  270. */
  271. public function __call($meth, $args)
  272. {
  273. try {
  274. return $this->setVars($meth, $args[0]);
  275. } catch (SegmentException $e) {
  276. throw new SegmentException("method $meth nor var $meth exist");
  277. }
  278. }
  279. /**
  280. * Returns the parsed XML
  281. *
  282. * @return string
  283. */
  284. public function getXmlParsed()
  285. {
  286. return $this->xmlParsed;
  287. }
  288. }