This commit is contained in:
Laurent Destailleur 2022-01-26 13:50:49 +01:00
parent 416543a5f5
commit 6d04bac740

View File

@ -16,296 +16,291 @@ class SegmentException extends Exception
*/ */
class Segment implements IteratorAggregate, Countable class Segment implements IteratorAggregate, Countable
{ {
protected $xml; protected $xml;
protected $xmlParsed = ''; protected $xmlParsed = '';
protected $name; protected $name;
protected $children = array(); protected $children = array();
protected $vars = array(); protected $vars = array();
protected $images = array(); protected $images = array();
protected $odf; protected $odf;
protected $file; protected $file;
/** /**
* Constructor * Constructor
* *
* @param string $name name of the segment to construct * @param string $name name of the segment to construct
* @param string $xml XML tree of the segment * @param string $xml XML tree of the segment
* @param string $odf odf * @param string $odf odf
*/ */
public function __construct($name, $xml, $odf) public function __construct($name, $xml, $odf)
{ {
$this->name = (string) $name; $this->name = (string) $name;
$this->xml = (string) $xml; $this->xml = (string) $xml;
$this->odf = $odf; $this->odf = $odf;
$zipHandler = $this->odf->getConfig('ZIP_PROXY'); $zipHandler = $this->odf->getConfig('ZIP_PROXY');
$this->file = new $zipHandler($this->odf->getConfig('PATH_TO_TMP')); $this->file = new $zipHandler($this->odf->getConfig('PATH_TO_TMP'));
$this->_analyseChildren($this->xml); $this->_analyseChildren($this->xml);
} }
/** /**
* Returns the name of the segment * Returns the name of the segment
* *
* @return string * @return string
*/ */
public function getName() public function getName()
{ {
return $this->name; return $this->name;
} }
/** /**
* Does the segment have children ? * Does the segment have children ?
* *
* @return bool * @return bool
*/ */
public function hasChildren() public function hasChildren()
{ {
return $this->getIterator()->hasChildren(); return $this->getIterator()->hasChildren();
} }
/** /**
* Countable interface * Countable interface
* *
* @return int * @return int
*/ */
public function count() public function count()
{ {
return count($this->children); return count($this->children);
} }
/** /**
* IteratorAggregate interface * IteratorAggregate interface
* *
* @return Iterator * @return Iterator
*/ */
public function getIterator() public function getIterator()
{ {
return new RecursiveIteratorIterator(new SegmentIterator($this->children), 1); return new RecursiveIteratorIterator(new SegmentIterator($this->children), 1);
} }
/** /**
* Replace variables of the template in the XML code * Replace variables of the template in the XML code
* All the children are also called * All the children are also called
* Complete the current segment with new line * Complete the current segment with new line
* *
* @return string * @return string
*/ */
public function merge() public function merge()
{ {
// To provide debug information on line number processed // To provide debug information on line number processed
global $count; global $count;
if (empty($count)) $count=1; if (empty($count)) $count=1;
else $count++; else $count++;
if (empty($this->savxml)) $this->savxml = $this->xml; // Sav content of line at first line merged, so we will reuse original for next steps if (empty($this->savxml)) $this->savxml = $this->xml; // Sav content of line at first line merged, so we will reuse original for next steps
$this->xml = $this->savxml; $this->xml = $this->savxml;
$tmpvars = $this->vars; // Store into $tmpvars so we won't modify this->vars when completing data with empty values $tmpvars = $this->vars; // Store into $tmpvars so we won't modify this->vars when completing data with empty values
// Search all tags fou into condition to complete $tmpvars, so we will proceed all tests even if not defined // Search all tags fou into condition to complete $tmpvars, so we will proceed all tests even if not defined
$reg='@\[!--\sIF\s([{}a-zA-Z0-9\.\,_]+)\s--\]@smU'; $reg='@\[!--\sIF\s([{}a-zA-Z0-9\.\,_]+)\s--\]@smU';
preg_match_all($reg, $this->xml, $matches, PREG_SET_ORDER); $matches = array();
//var_dump($tmpvars);exit; preg_match_all($reg, $this->xml, $matches, PREG_SET_ORDER);
foreach($matches as $match) // For each match, if there is no entry into this->vars, we add it //var_dump($tmpvars);exit;
{ foreach ($matches as $match) { // For each match, if there is no entry into this->vars, we add it
if (! empty($match[1]) && ! isset($tmpvars[$match[1]])) if (! empty($match[1]) && ! isset($tmpvars[$match[1]])) {
{ $tmpvars[$match[1]] = ''; // Not defined, so we set it to '', we just need entry into this->vars for next loop
$tmpvars[$match[1]] = ''; // Not defined, so we set it to '', we just need entry into this->vars for next loop }
} }
}
// Conditionals substitution // Conditionals substitution
// Note: must be done before static substitution, else the variable will be replaced by its value and the conditional won't work anymore // Note: must be done before static substitution, else the variable will be replaced by its value and the conditional won't work anymore
foreach($tmpvars as $key => $value) foreach ($tmpvars as $key => $value) {
{ // If value is true (not 0 nor false nor null nor empty string)
// If value is true (not 0 nor false nor null nor empty string) if ($value) {
if ($value) // Remove the IF tag
{ $this->xml = str_replace('[!-- IF '.$key.' --]', '', $this->xml);
// Remove the IF tag // Remove everything between the ELSE tag (if it exists) and the ENDIF tag
$this->xml = str_replace('[!-- IF '.$key.' --]', '', $this->xml); $reg = '@(\[!--\sELSE\s' . $key . '\s--\](.*))?\[!--\sENDIF\s' . $key . '\s--\]@smU'; // U modifier = all quantifiers are non-greedy
// Remove everything between the ELSE tag (if it exists) and the ENDIF tag $this->xml = preg_replace($reg, '', $this->xml);
$reg = '@(\[!--\sELSE\s' . $key . '\s--\](.*))?\[!--\sENDIF\s' . $key . '\s--\]@smU'; // U modifier = all quantifiers are non-greedy }
$this->xml = preg_replace($reg, '', $this->xml); // 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
} else {
// 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 // Find all conditional blocks for this variable: from IF to ELSE and to ENDIF
else $reg = '@\[!--\sIF\s' . $key . '\s--\](.*)(\[!--\sELSE\s' . $key . '\s--\](.*))?\[!--\sENDIF\s' . $key . '\s--\]@smU'; // U modifier = all quantifiers are non-greedy
{ preg_match_all($reg, $this->xml, $matches, PREG_SET_ORDER);
// Find all conditional blocks for this variable: from IF to ELSE and to ENDIF 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
$reg = '@\[!--\sIF\s' . $key . '\s--\](.*)(\[!--\sELSE\s' . $key . '\s--\](.*))?\[!--\sENDIF\s' . $key . '\s--\]@smU'; // U modifier = all quantifiers are non-greedy if (!empty($match[3])) $this->xml = str_replace($match[0], $match[3], $this->xml);
preg_match_all($reg, $this->xml, $matches, PREG_SET_ORDER); }
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 // Cleanup the other conditional blocks (all the others where there were no ELSE clause, we can just remove them altogether)
if (!empty($match[3])) $this->xml = str_replace($match[0], $match[3], $this->xml); $this->xml = preg_replace($reg, '', $this->xml);
} }
// Cleanup the other conditional blocks (all the others where there were no ELSE clause, we can just remove them altogether) }
$this->xml = preg_replace($reg, '', $this->xml);
}
}
$this->xmlParsed .= str_replace(array_keys($tmpvars), array_values($tmpvars), $this->xml); $this->xmlParsed .= str_replace(array_keys($tmpvars), array_values($tmpvars), $this->xml);
if ($this->hasChildren()) { if ($this->hasChildren()) {
foreach ($this->children as $child) { foreach ($this->children as $child) {
$this->xmlParsed = str_replace($child->xml, ($child->xmlParsed=="")?$child->merge():$child->xmlParsed, $this->xmlParsed); $this->xmlParsed = str_replace($child->xml, ($child->xmlParsed=="")?$child->merge():$child->xmlParsed, $this->xmlParsed);
$child->xmlParsed = ''; $child->xmlParsed = '';
} }
} }
$reg = "/\[!--\sBEGIN\s$this->name\s--\](.*)\[!--\sEND\s$this->name\s--\]/sm"; $reg = "/\[!--\sBEGIN\s$this->name\s--\](.*)\[!--\sEND\s$this->name\s--\]/sm";
$this->xmlParsed = preg_replace($reg, '$1', $this->xmlParsed); $this->xmlParsed = preg_replace($reg, '$1', $this->xmlParsed);
// Miguel Erill 09704/2017 - Add macro replacement to invoice lines // Miguel Erill 09704/2017 - Add macro replacement to invoice lines
$this->xmlParsed = $this->macroReplace($this->xmlParsed); $this->xmlParsed = $this->macroReplace($this->xmlParsed);
$this->file->open($this->odf->getTmpfile()); $this->file->open($this->odf->getTmpfile());
foreach ($this->images as $imageKey => $imageValue) { foreach ($this->images as $imageKey => $imageValue) {
if ($this->file->getFromName('Pictures/' . $imageValue) === false) { if ($this->file->getFromName('Pictures/' . $imageValue) === false) {
// Add the image inside the ODT document // Add the image inside the ODT document
$this->file->addFile($imageKey, 'Pictures/' . $imageValue); $this->file->addFile($imageKey, 'Pictures/' . $imageValue);
// 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) // 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)
$this->odf->addImageToManifest($imageValue); $this->odf->addImageToManifest($imageValue);
} }
} }
$this->file->close(); $this->file->close();
return $this->xmlParsed; return $this->xmlParsed;
} }
/** /**
* Function to replace macros for invoice short and long month, invoice year * Function to replace macros for invoice short and long month, invoice year
* *
* Substitution occur when the invoice is generated, not considering the invoice date * Substitution occur when the invoice is generated, not considering the invoice date
* so do not (re)generate in a diferent date than the one that the invoice belongs to * so do not (re)generate in a diferent date than the one that the invoice belongs to
* Perhaps it would be better to use the invoice issued date but I still do not know * Perhaps it would be better to use the invoice issued date but I still do not know
* how to get it here * how to get it here
* *
* Miguel Erill 09/04/2017 * Miguel Erill 09/04/2017
* *
* @param string $value String to convert * @param string $value String to convert
*/ */
public function macroReplace($text) public function macroReplace($text)
{ {
include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
global $langs; global $langs;
$hoy = dol_getdate(dol_now('tzuser')); $hoy = dol_getdate(dol_now('tzuser'));
$dateinonemontharray = dol_get_next_month($hoy['mon'], $hoy['year']); $dateinonemontharray = dol_get_next_month($hoy['mon'], $hoy['year']);
$nextMonth = $dateinonemontharray['month']; $nextMonth = $dateinonemontharray['month'];
$patterns=array( '/__CURRENTDAY__/u','/__CURENTWEEKDAY__/u', $patterns=array( '/__CURRENTDAY__/u','/__CURENTWEEKDAY__/u',
'/__CURRENTMONTH__/u','/__CURRENTMONTHLONG__/u', '/__CURRENTMONTH__/u','/__CURRENTMONTHLONG__/u',
'/__NEXTMONTH__/u','/__NEXTMONTHLONG__/u', '/__NEXTMONTH__/u','/__NEXTMONTHLONG__/u',
'/__CURRENTYEAR__/u','/__NEXTYEAR__/u' ); '/__CURRENTYEAR__/u','/__NEXTYEAR__/u' );
$values=array( $hoy['mday'], $langs->transnoentitiesnoconv($hoy['weekday']), $values=array( $hoy['mday'], $langs->transnoentitiesnoconv($hoy['weekday']),
$hoy['mon'], $langs->transnoentitiesnoconv($hoy['month']), $hoy['mon'], $langs->transnoentitiesnoconv($hoy['month']),
$nextMonth, monthArray($langs)[$nextMonth], $nextMonth, monthArray($langs)[$nextMonth],
$hoy['year'], $hoy['year']+1 ); $hoy['year'], $hoy['year']+1 );
$text=preg_replace($patterns, $values, $text); $text=preg_replace($patterns, $values, $text);
return $text; return $text;
} }
/** /**
* Analyse the XML code in order to find children * Analyse the XML code in order to find children
* *
* @param string $xml Xml * @param string $xml Xml
* @return Segment * @return Segment
*/ */
protected function _analyseChildren($xml) protected function _analyseChildren($xml)
{ {
// $reg2 = "#\[!--\sBEGIN\s([\S]*)\s--\](?:<\/text:p>)?(.*)(?:<text:p\s.*>)?\[!--\sEND\s(\\1)\s--\]#sm"; // $reg2 = "#\[!--\sBEGIN\s([\S]*)\s--\](?:<\/text:p>)?(.*)(?:<text:p\s.*>)?\[!--\sEND\s(\\1)\s--\]#sm";
$reg2 = "#\[!--\sBEGIN\s([\S]*)\s--\](.*)\[!--\sEND\s(\\1)\s--\]#sm"; $reg2 = "#\[!--\sBEGIN\s([\S]*)\s--\](.*)\[!--\sEND\s(\\1)\s--\]#sm";
preg_match_all($reg2, $xml, $matches); preg_match_all($reg2, $xml, $matches);
for ($i = 0, $size = count($matches[0]); $i < $size; $i++) { for ($i = 0, $size = count($matches[0]); $i < $size; $i++) {
if ($matches[1][$i] != $this->name) { if ($matches[1][$i] != $this->name) {
$this->children[$matches[1][$i]] = new self($matches[1][$i], $matches[0][$i], $this->odf); $this->children[$matches[1][$i]] = new self($matches[1][$i], $matches[0][$i], $this->odf);
} else { } else {
$this->_analyseChildren($matches[2][$i]); $this->_analyseChildren($matches[2][$i]);
} }
} }
return $this; return $this;
} }
/** /**
* Assign a template variable to replace * Assign a template variable to replace
* *
* @param string $key Key * @param string $key Key
* @param string $value Value * @param string $value Value
* @param string $encode Encode * @param string $encode Encode
* @param string $charset Charset * @param string $charset Charset
* @throws SegmentException * @throws SegmentException
* @return Segment * @return Segment
*/ */
public function setVars($key, $value, $encode = true, $charset = 'ISO-8859') public function setVars($key, $value, $encode = true, $charset = 'ISO-8859')
{ {
if (strpos($this->xml, $this->odf->getConfig('DELIMITER_LEFT') . $key . $this->odf->getConfig('DELIMITER_RIGHT')) === false) { if (strpos($this->xml, $this->odf->getConfig('DELIMITER_LEFT') . $key . $this->odf->getConfig('DELIMITER_RIGHT')) === false) {
//throw new SegmentException("var $key not found in {$this->getName()}"); //throw new SegmentException("var $key not found in {$this->getName()}");
} }
$tag = $this->odf->getConfig('DELIMITER_LEFT') . $key . $this->odf->getConfig('DELIMITER_RIGHT'); $tag = $this->odf->getConfig('DELIMITER_LEFT') . $key . $this->odf->getConfig('DELIMITER_RIGHT');
$this->vars[$tag] = $this->odf->convertVarToOdf($value, $encode, $charset); $this->vars[$tag] = $this->odf->convertVarToOdf($value, $encode, $charset);
return $this; return $this;
} }
/** /**
* Assign a template variable as a picture * Assign a template variable as a picture
* *
* @param string $key name of the variable within the template * @param string $key name of the variable within the template
* @param string $value path to the picture * @param string $value path to the picture
* @throws OdfException * @throws OdfException
* @return Segment * @return Segment
*/ */
public function setImage($key, $value) public function setImage($key, $value)
{ {
$filename = strtok(strrchr($value, '/'), '/.'); $filename = strtok(strrchr($value, '/'), '/.');
$file = substr(strrchr($value, '/'), 1); $file = substr(strrchr($value, '/'), 1);
$size = @getimagesize($value); $size = @getimagesize($value);
if ($size === false) { if ($size === false) {
throw new OdfException("Invalid image"); throw new OdfException("Invalid image");
} }
// Set the width and height of the page // Set the width and height of the page
list ($width, $height) = $size; list ($width, $height) = $size;
$width *= Odf::PIXEL_TO_CM; $width *= Odf::PIXEL_TO_CM;
$height *= Odf::PIXEL_TO_CM; $height *= Odf::PIXEL_TO_CM;
// Fix local-aware issues (eg: 12,10 -> 12.10) // Fix local-aware issues (eg: 12,10 -> 12.10)
$width = sprintf("%F", $width); $width = sprintf("%F", $width);
$height = sprintf("%F", $height); $height = sprintf("%F", $height);
$xml = <<<IMG $xml = <<<IMG
<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> <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>
IMG; IMG;
$this->images[$value] = $file; $this->images[$value] = $file;
$this->setVars($key, $xml, false); $this->setVars($key, $xml, false);
return $this; return $this;
} }
/** /**
* Shortcut to retrieve a child * Shortcut to retrieve a child
* *
* @param string $prop Prop * @param string $prop Prop
* @return Segment * @return Segment
* @throws SegmentException * @throws SegmentException
*/ */
public function __get($prop) public function __get($prop)
{ {
if (array_key_exists($prop, $this->children)) { if (array_key_exists($prop, $this->children)) {
return $this->children[$prop]; return $this->children[$prop];
} else { } else {
throw new SegmentException('child ' . $prop . ' does not exist'); throw new SegmentException('child ' . $prop . ' does not exist');
} }
} }
/** /**
* Proxy for setVars * Proxy for setVars
* *
* @param string $meth Meth * @param string $meth Meth
* @param array $args Args * @param array $args Args
* @return Segment * @return Segment
*/ */
public function __call($meth, $args) public function __call($meth, $args)
{ {
try { try {
return $this->setVars($meth, $args[0]); return $this->setVars($meth, $args[0]);
} catch (SegmentException $e) { } catch (SegmentException $e) {
throw new SegmentException("method $meth nor var $meth exist"); throw new SegmentException("method $meth nor var $meth exist");
} }
} }
/** /**
* Returns the parsed XML * Returns the parsed XML
* *
* @return string * @return string
*/ */
public function getXmlParsed() public function getXmlParsed()
{ {
return $this->xmlParsed; return $this->xmlParsed;
} }
} }