tempFileName = tempnam(sys_get_temp_dir(), '');
if ($this->tempFileName === false) {
throw new Exception('Could not create temporary file with unique name in the default temporary directory.');
}
// Copy the source File to the temp File
if (!copy($strFilename, $this->tempFileName)) {
throw new Exception("Could not copy the template from {$strFilename} to {$this->tempFileName}.");
}
$zipClass = Settings::getZipClass();
$this->zipClass = new $zipClass();
$this->zipClass->open($this->tempFileName);
// Find and load headers and footers
$i = 1;
while ($this->zipClass->locateName($this->getHeaderName($i)) !== false) {
$this->headerXMLs[$i] = $this->zipClass->getFromName($this->getHeaderName($i));
$i++;
}
$i = 1;
while ($this->zipClass->locateName($this->getFooterName($i)) !== false) {
$this->footerXMLs[$i] = $this->zipClass->getFromName($this->getFooterName($i));
$i++;
}
$this->documentXML = $this->zipClass->getFromName('word/document.xml');
}
/**
* Applies XSL style sheet to template's parts
*
* @param \DOMDocument $xslDOMDocument
* @param array $xslOptions
* @param string $xslOptionsURI
* @throws Exception
*/
public function applyXslStyleSheet(&$xslDOMDocument, $xslOptions = array(), $xslOptionsURI = '')
{
$processor = new \XSLTProcessor();
$processor->importStylesheet($xslDOMDocument);
if ($processor->setParameter($xslOptionsURI, $xslOptions) === false) {
throw new Exception('Could not set values for the given XSL style sheet parameters.');
}
$xmlDOMDocument = new \DOMDocument();
if ($xmlDOMDocument->loadXML($this->documentXML) === false) {
throw new Exception('Could not load XML from the given template.');
}
$xmlTransformed = $processor->transformToXml($xmlDOMDocument);
if ($xmlTransformed === false) {
throw new Exception('Could not transform the given XML document.');
}
$this->documentXML = $xmlTransformed;
}
/**
* Set a Template value
*
* @param mixed $search
* @param mixed $replace
* @param integer $limit
*/
public function setValue($search, $replace, $limit = -1)
{
foreach ($this->headerXMLs as $index => $headerXML) {
$this->headerXMLs[$index] = $this->setValueForPart($this->headerXMLs[$index], $search, $replace, $limit);
}
$this->documentXML = $this->setValueForPart($this->documentXML, $search, $replace, $limit);
foreach ($this->footerXMLs as $index => $headerXML) {
$this->footerXMLs[$index] = $this->setValueForPart($this->footerXMLs[$index], $search, $replace, $limit);
}
}
/**
* Returns array of all variables in template
* @return string[]
*/
public function getVariables()
{
$variables = $this->getVariablesForPart($this->documentXML);
foreach ($this->headerXMLs as $headerXML) {
$variables = array_merge($variables, $this->getVariablesForPart($headerXML));
}
foreach ($this->footerXMLs as $footerXML) {
$variables = array_merge($variables, $this->getVariablesForPart($footerXML));
}
return array_unique($variables);
}
/**
* Clone a table row in a template document
*
* @param string $search
* @param integer $numberOfClones
* @throws Exception
*/
public function cloneRow($search, $numberOfClones)
{
if (substr($search, 0, 2) !== '${' && substr($search, -1) !== '}') {
$search = '${' . $search . '}';
}
$tagPos = strpos($this->documentXML, $search);
if (!$tagPos) {
throw new Exception("Can not clone row, template variable not found or variable contains markup.");
}
$rowStart = $this->findRowStart($tagPos);
$rowEnd = $this->findRowEnd($tagPos);
$xmlRow = $this->getSlice($rowStart, $rowEnd);
// Check if there's a cell spanning multiple rows.
if (preg_match('##', $xmlRow)) {
$extraRowStart = $rowEnd;
$extraRowEnd = $rowEnd;
while (true) {
$extraRowStart = $this->findRowStart($extraRowEnd + 1);
$extraRowEnd = $this->findRowEnd($extraRowEnd + 1);
// If extraRowEnd is lower then 7, there was no next row found.
if ($extraRowEnd < 7) {
break;
}
// If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row.
$tmpXmlRow = $this->getSlice($extraRowStart, $extraRowEnd);
if (!preg_match('##', $tmpXmlRow) && !preg_match('##', $tmpXmlRow)) {
break;
}
// This row was a spanned row, update $rowEnd and search for the next row.
$rowEnd = $extraRowEnd;
}
$xmlRow = $this->getSlice($rowStart, $rowEnd);
}
$result = $this->getSlice(0, $rowStart);
for ($i = 1; $i <= $numberOfClones; $i++) {
$result .= preg_replace('/\$\{(.*?)\}/', '\${\\1#' . $i . '}', $xmlRow);
}
$result .= $this->getSlice($rowEnd);
$this->documentXML = $result;
}
/**
* Clone a block
*
* @param string $blockname
* @param integer $clones
* @param boolean $replace
* @return null
*/
public function cloneBlock($blockname, $clones = 1, $replace = true)
{
$xmlBlock = null;
preg_match('/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', $this->documentXML, $matches);
if (isset($matches[3])) {
$xmlBlock = $matches[3];
$cloned = array();
for ($i = 1; $i <= $clones; $i++) {
$cloned[] = $xmlBlock;
}
if ($replace) {
$this->documentXML = str_replace($matches[2] . $matches[3] . $matches[4], implode('', $cloned), $this->documentXML);
}
}
return $xmlBlock;
}
/**
* Replace a block
*
* @param string $blockname
* @param string $replacement
*/
public function replaceBlock($blockname, $replacement)
{
preg_match('/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', $this->documentXML, $matches);
if (isset($matches[3])) {
$this->documentXML = str_replace($matches[2] . $matches[3] . $matches[4], $replacement, $this->documentXML);
}
}
/**
* Delete a block of text
*
* @param string $blockname
* @param string $replacement
*/
public function deleteBlock($blockname)
{
$this->replaceBlock($blockname, '');
}
/**
* Save XML to temporary file
*
* @return string
* @throws Exception
*/
public function save()
{
foreach ($this->headerXMLs as $index => $headerXML) {
$this->zipClass->addFromString($this->getHeaderName($index), $this->headerXMLs[$index]);
}
$this->zipClass->addFromString('word/document.xml', $this->documentXML);
foreach ($this->footerXMLs as $index => $headerXML) {
$this->zipClass->addFromString($this->getFooterName($index), $this->footerXMLs[$index]);
}
// Close zip file
if ($this->zipClass->close() === false) {
throw new Exception('Could not close zip file.');
}
return $this->tempFileName;
}
/**
* Save XML to defined name
*
* @param string $strFilename
*/
public function saveAs($strFilename)
{
$tempFilename = $this->save();
if (file_exists($strFilename)) {
unlink($strFilename);
}
rename($tempFilename, $strFilename);
}
/**
* Find and replace placeholders in the given XML section.
*
* @param string $documentPartXML
* @param string $search
* @param mixed $replace
* @param integer $limit
* @return string
*/
protected function setValueForPart($documentPartXML, $search, $replace, $limit)
{
$pattern = '|\$\{([^\}]+)\}|U';
preg_match_all($pattern, $documentPartXML, $matches);
foreach ($matches[0] as $value) {
$valueCleaned = preg_replace('/<[^>]+>/', '', $value);
$valueCleaned = preg_replace('/<\/[^>]+>/', '', $valueCleaned);
$documentPartXML = str_replace($value, $valueCleaned, $documentPartXML);
}
if (substr($search, 0, 2) !== '${' && substr($search, -1) !== '}') {
$search = '${' . $search . '}';
}
if (!is_array($replace)) {
if (!String::isUTF8($replace)) {
$replace = utf8_encode($replace);
}
$replace = htmlspecialchars($replace);
} else {
foreach ($replace as $key => $value) {
$replace[$key] = htmlspecialchars($value);
}
}
$regExpDelim = '/';
$escapedSearch = preg_quote($search, $regExpDelim);
return preg_replace("{$regExpDelim}{$escapedSearch}{$regExpDelim}u", $replace, $documentPartXML, $limit);
}
/**
* Find all variables in $documentPartXML
* @param string $documentPartXML
* @return string[]
*/
protected function getVariablesForPart($documentPartXML)
{
preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches);
return $matches[1];
}
/**
* Get the name of the footer file for $index
* @param integer $index
* @return string
*/
private function getFooterName($index)
{
return sprintf('word/footer%d.xml', $index);
}
/**
* Get the name of the header file for $index
* @param integer $index
* @return string
*/
private function getHeaderName($index)
{
return sprintf('word/header%d.xml', $index);
}
/**
* Find the start position of the nearest table row before $offset
*
* @param integer $offset
* @return integer
* @throws Exception
*/
private function findRowStart($offset)
{
$rowStart = strrpos($this->documentXML, "documentXML) - $offset) * -1));
if (!$rowStart) {
$rowStart = strrpos($this->documentXML, "", ((strlen($this->documentXML) - $offset) * -1));
}
if (!$rowStart) {
throw new Exception("Can not find the start position of the row to clone.");
}
return $rowStart;
}
/**
* Find the end position of the nearest table row after $offset
*
* @param integer $offset
* @return integer
*/
private function findRowEnd($offset)
{
$rowEnd = strpos($this->documentXML, "", $offset) + 7;
return $rowEnd;
}
/**
* Get a slice of a string
*
* @param integer $startPosition
* @param integer $endPosition
* @return string
*/
private function getSlice($startPosition, $endPosition = 0)
{
if (!$endPosition) {
$endPosition = strlen($this->documentXML);
}
return substr($this->documentXML, $startPosition, ($endPosition - $startPosition));
}
/**
* Delete a block of text
*
* @param string $blockname
* @param string $replacement
* @deprecated
* @codeCoverageIgnore
*/
public function deleteTemplateBlock($blockname, $replacement = '')
{
$this->deleteBlock($blockname, $replacement);
}
}