Support adding images in Templates (#1170)
* setImageValue() + fix adding files via ZipArchive * fix phpdoc variable name * Changed logic that determines extension image file extension for document to depend on MIME type. This same logic is used in Element/Image.php * support <w:t> tags with arguments * allow setup size of image into template variable * support of 'ratio' replace attribute + documentation
This commit is contained in:
parent
e6496bf411
commit
d5da80b56e
|
|
@ -24,8 +24,6 @@ matrix:
|
|||
- php: 5.3
|
||||
- php: 7.0
|
||||
- php: 7.3
|
||||
allow_failures:
|
||||
- php: 7.3
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ v0.16.0 (xx dec 2018)
|
|||
- Add setting Chart Title and Legend visibility @Tom-Magill #1433
|
||||
- Add ability to pass a Style object in Section constructor @ndench #1416
|
||||
- Add support for hidden text @Alexmg86 #1527
|
||||
- Add support for setting images in TemplateProcessor @SailorMax #1170
|
||||
|
||||
### Fixed
|
||||
- Fix regex in `cloneBlock` function @nicoder #1269
|
||||
|
|
|
|||
|
|
@ -7,14 +7,32 @@ You can create an OOXML document template with included search-patterns (macros)
|
|||
|
||||
To deal with a template file, use ``new TemplateProcessor`` statement. After TemplateProcessor instance creation the document template is copied into the temporary directory. Then you can use ``TemplateProcessor::setValue`` method to change the value of a search pattern. The search-pattern model is: ``${search-pattern}``.
|
||||
|
||||
The search-pattern model for images can be like:
|
||||
- ``${search-image-pattern}``
|
||||
- ``${search-image-pattern:[width]:[height]:[ratio]}``
|
||||
- ``${search-image-pattern:[width]x[height]}``
|
||||
- ``${search-image-pattern:size=[width]x[height]}``
|
||||
- ``${search-image-pattern:width=[width]:height=[height]:ratio=false}``
|
||||
Where:
|
||||
- [width] and [height] can be just numbers or numbers with measure, which supported by Word (cm|mm|in|pt|pc|px|%|em|ex)
|
||||
- [ratio] uses only for ``false``, ``-`` or ``f`` to turn off respect aspect ration of image. By default template image size uses as 'container' size.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: doc
|
||||
|
||||
${CompanyLogo}
|
||||
${UserLogo:50:50} ${Name} - ${City} - ${Street}
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$templateProcessor = new TemplateProcessor('Template.docx');
|
||||
$templateProcessor->setValue('Name', 'John Doe');
|
||||
$templateProcessor->setValue(array('City', 'Street'), array('Detroit', '12th Street'));
|
||||
|
||||
$templateProcessor->setImageValue('CompanyLogo', 'path/to/company/logo.png');
|
||||
$templateProcessor->setImageValue('UserLogo', array('path' => 'path/to/logo.png', 'width' => 100, 'height' => 100, 'ratio' => false));
|
||||
|
||||
It is not possible to directly add new OOXML elements to the template file being processed, but it is possible to transform headers, main document part, and footers of the template using XSLT (see ``TemplateProcessor::applyXslStyleSheet``).
|
||||
|
||||
See ``Sample_07_TemplateCloneRow.php`` for example on how to create
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ class ZipArchive
|
|||
{
|
||||
$result = true;
|
||||
$this->filename = $filename;
|
||||
$this->tempDir = Settings::getTempDir();
|
||||
|
||||
if (!$this->usePclzip) {
|
||||
$zip = new \ZipArchive();
|
||||
|
|
@ -139,7 +140,6 @@ class ZipArchive
|
|||
$this->numFiles = $zip->numFiles;
|
||||
} else {
|
||||
$zip = new \PclZip($this->filename);
|
||||
$this->tempDir = Settings::getTempDir();
|
||||
$zipContent = $zip->listContent();
|
||||
$this->numFiles = is_array($zipContent) ? count($zipContent) : 0;
|
||||
}
|
||||
|
|
@ -245,7 +245,13 @@ class ZipArchive
|
|||
$pathRemoved = $filenameParts['dirname'];
|
||||
$pathAdded = $localnameParts['dirname'];
|
||||
|
||||
$res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded);
|
||||
if (!$this->usePclzip) {
|
||||
$pathAdded = $pathAdded . '/' . ltrim(str_replace('\\', '/', substr($filename, strlen($pathRemoved))), '/');
|
||||
//$res = $zip->addFile($filename, $pathAdded);
|
||||
$res = $zip->addFromString($pathAdded, file_get_contents($filename)); // addFile can't use subfolders in some cases
|
||||
} else {
|
||||
$res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded);
|
||||
}
|
||||
|
||||
if ($tempFile) {
|
||||
// Remove temp file, if created
|
||||
|
|
|
|||
|
|
@ -62,6 +62,27 @@ class TemplateProcessor
|
|||
*/
|
||||
protected $tempDocumentFooters = array();
|
||||
|
||||
/**
|
||||
* Document relations (in XML format) of the temporary document.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $tempDocumentRelations = array();
|
||||
|
||||
/**
|
||||
* Document content types (in XML format) of the temporary document.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tempDocumentContentTypes = '';
|
||||
|
||||
/**
|
||||
* new inserted images list
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $tempDocumentNewImages = array();
|
||||
|
||||
/**
|
||||
* @since 0.12.0 Throws CreateTemporaryFileException and CopyFileException instead of Exception
|
||||
*
|
||||
|
|
@ -88,19 +109,33 @@ class TemplateProcessor
|
|||
$this->zipClass->open($this->tempDocumentFilename);
|
||||
$index = 1;
|
||||
while (false !== $this->zipClass->locateName($this->getHeaderName($index))) {
|
||||
$this->tempDocumentHeaders[$index] = $this->fixBrokenMacros(
|
||||
$this->zipClass->getFromName($this->getHeaderName($index))
|
||||
);
|
||||
$this->tempDocumentHeaders[$index] = $this->readPartWithRels($this->getHeaderName($index));
|
||||
$index++;
|
||||
}
|
||||
$index = 1;
|
||||
while (false !== $this->zipClass->locateName($this->getFooterName($index))) {
|
||||
$this->tempDocumentFooters[$index] = $this->fixBrokenMacros(
|
||||
$this->zipClass->getFromName($this->getFooterName($index))
|
||||
);
|
||||
$this->tempDocumentFooters[$index] = $this->readPartWithRels($this->getFooterName($index));
|
||||
$index++;
|
||||
}
|
||||
$this->tempDocumentMainPart = $this->fixBrokenMacros($this->zipClass->getFromName($this->getMainPartName()));
|
||||
|
||||
$this->tempDocumentMainPart = $this->readPartWithRels($this->getMainPartName());
|
||||
$this->tempDocumentContentTypes = $this->zipClass->getFromName($this->getDocumentContentTypesName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fileName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function readPartWithRels($fileName)
|
||||
{
|
||||
$relsFileName = $this->getRelationsName($fileName);
|
||||
$partRelations = $this->zipClass->getFromName($relsFileName);
|
||||
if ($partRelations !== false) {
|
||||
$this->tempDocumentRelations[$fileName] = $partRelations;
|
||||
}
|
||||
|
||||
return $this->fixBrokenMacros($this->zipClass->getFromName($fileName));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -233,6 +268,274 @@ class TemplateProcessor
|
|||
$this->tempDocumentFooters = $this->setValueForPart($search, $replace, $this->tempDocumentFooters, $limit);
|
||||
}
|
||||
|
||||
private function getImageArgs($varNameWithArgs)
|
||||
{
|
||||
$varElements = explode(':', $varNameWithArgs);
|
||||
array_shift($varElements); // first element is name of variable => remove it
|
||||
|
||||
$varInlineArgs = array();
|
||||
// size format documentation: https://msdn.microsoft.com/en-us/library/documentformat.openxml.vml.shape%28v=office.14%29.aspx?f=255&MSPPError=-2147217396
|
||||
foreach ($varElements as $argIdx => $varArg) {
|
||||
if (strpos($varArg, '=')) { // arg=value
|
||||
list($argName, $argValue) = explode('=', $varArg, 2);
|
||||
$argName = strtolower($argName);
|
||||
if ($argName == 'size') {
|
||||
list($varInlineArgs['width'], $varInlineArgs['height']) = explode('x', $argValue, 2);
|
||||
} else {
|
||||
$varInlineArgs[strtolower($argName)] = $argValue;
|
||||
}
|
||||
} elseif (preg_match('/^([0-9]*[a-z%]{0,2}|auto)x([0-9]*[a-z%]{0,2}|auto)$/i', $varArg)) { // 60x40
|
||||
list($varInlineArgs['width'], $varInlineArgs['height']) = explode('x', $varArg, 2);
|
||||
} else { // :60:40:f
|
||||
switch ($argIdx) {
|
||||
case 0:
|
||||
$varInlineArgs['width'] = $varArg;
|
||||
break;
|
||||
case 1:
|
||||
$varInlineArgs['height'] = $varArg;
|
||||
break;
|
||||
case 2:
|
||||
$varInlineArgs['ratio'] = $varArg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $varInlineArgs;
|
||||
}
|
||||
|
||||
private function chooseImageDimension($baseValue, $inlineValue, $defaultValue)
|
||||
{
|
||||
$value = $baseValue;
|
||||
if (is_null($value) && isset($inlineValue)) {
|
||||
$value = $inlineValue;
|
||||
}
|
||||
if (!preg_match('/^([0-9]*(cm|mm|in|pt|pc|px|%|em|ex|)|auto)$/i', $value)) {
|
||||
$value = null;
|
||||
}
|
||||
if (is_null($value)) {
|
||||
$value = $defaultValue;
|
||||
}
|
||||
if (is_numeric($value)) {
|
||||
$value .= 'px';
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function fixImageWidthHeightRatio(&$width, &$height, $actualWidth, $actualHeight)
|
||||
{
|
||||
$imageRatio = $actualWidth / $actualHeight;
|
||||
|
||||
if (($width === '') && ($height === '')) { // defined size are empty
|
||||
$width = $actualWidth . 'px';
|
||||
$height = $actualHeight . 'px';
|
||||
} elseif ($width === '') { // defined width is empty
|
||||
$heightFloat = (float) $height;
|
||||
$widthFloat = $heightFloat * $imageRatio;
|
||||
$matches = array();
|
||||
preg_match("/\d([a-z%]+)$/", $height, $matches);
|
||||
$width = $widthFloat . $matches[1];
|
||||
} elseif ($height === '') { // defined height is empty
|
||||
$widthFloat = (float) $width;
|
||||
$heightFloat = $widthFloat / $imageRatio;
|
||||
$matches = array();
|
||||
preg_match("/\d([a-z%]+)$/", $width, $matches);
|
||||
$height = $heightFloat . $matches[1];
|
||||
} else { // we have defined size, but we need also check it aspect ratio
|
||||
$widthMatches = array();
|
||||
preg_match("/\d([a-z%]+)$/", $width, $widthMatches);
|
||||
$heightMatches = array();
|
||||
preg_match("/\d([a-z%]+)$/", $height, $heightMatches);
|
||||
// try to fix only if dimensions are same
|
||||
if ($widthMatches[1] == $heightMatches[1]) {
|
||||
$dimention = $widthMatches[1];
|
||||
$widthFloat = (float) $width;
|
||||
$heightFloat = (float) $height;
|
||||
$definedRatio = $widthFloat / $heightFloat;
|
||||
|
||||
if ($imageRatio > $definedRatio) { // image wider than defined box
|
||||
$height = ($widthFloat / $imageRatio) . $dimention;
|
||||
} elseif ($imageRatio < $definedRatio) { // image higher than defined box
|
||||
$width = ($heightFloat * $imageRatio) . $dimention;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function prepareImageAttrs($replaceImage, $varInlineArgs)
|
||||
{
|
||||
// get image path and size
|
||||
$width = null;
|
||||
$height = null;
|
||||
$ratio = null;
|
||||
if (is_array($replaceImage) && isset($replaceImage['path'])) {
|
||||
$imgPath = $replaceImage['path'];
|
||||
if (isset($replaceImage['width'])) {
|
||||
$width = $replaceImage['width'];
|
||||
}
|
||||
if (isset($replaceImage['height'])) {
|
||||
$height = $replaceImage['height'];
|
||||
}
|
||||
if (isset($replaceImage['ratio'])) {
|
||||
$ratio = $replaceImage['ratio'];
|
||||
}
|
||||
} else {
|
||||
$imgPath = $replaceImage;
|
||||
}
|
||||
|
||||
$width = $this->chooseImageDimension($width, isset($varInlineArgs['width']) ? $varInlineArgs['width'] : null, 115);
|
||||
$height = $this->chooseImageDimension($height, isset($varInlineArgs['height']) ? $varInlineArgs['height'] : null, 70);
|
||||
|
||||
$imageData = @getimagesize($imgPath);
|
||||
if (!is_array($imageData)) {
|
||||
throw new Exception(sprintf('Invalid image: %s', $imgPath));
|
||||
}
|
||||
list($actualWidth, $actualHeight, $imageType) = $imageData;
|
||||
|
||||
// fix aspect ratio (by default)
|
||||
if (is_null($ratio) && isset($varInlineArgs['ratio'])) {
|
||||
$ratio = $varInlineArgs['ratio'];
|
||||
}
|
||||
if (is_null($ratio) || !in_array(strtolower($ratio), array('', '-', 'f', 'false'))) {
|
||||
$this->fixImageWidthHeightRatio($width, $height, $actualWidth, $actualHeight);
|
||||
}
|
||||
|
||||
$imageAttrs = array(
|
||||
'src' => $imgPath,
|
||||
'mime' => image_type_to_mime_type($imageType),
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
);
|
||||
|
||||
return $imageAttrs;
|
||||
}
|
||||
|
||||
private function addImageToRelations($partFileName, $rid, $imgPath, $imageMimeType)
|
||||
{
|
||||
// define templates
|
||||
$typeTpl = '<Override PartName="/word/media/{IMG}" ContentType="image/{EXT}"/>';
|
||||
$relationTpl = '<Relationship Id="{RID}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/{IMG}"/>';
|
||||
$newRelationsTpl = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n" . '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>';
|
||||
$newRelationsTypeTpl = '<Override PartName="/{RELS}" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
|
||||
$extTransform = array(
|
||||
'image/jpeg' => 'jpeg',
|
||||
'image/png' => 'png',
|
||||
'image/bmp' => 'bmp',
|
||||
'image/gif' => 'gif',
|
||||
);
|
||||
|
||||
// get image embed name
|
||||
if (isset($this->tempDocumentNewImages[$imgPath])) {
|
||||
$imgName = $this->tempDocumentNewImages[$imgPath];
|
||||
} else {
|
||||
// transform extension
|
||||
if (isset($extTransform[$imageMimeType])) {
|
||||
$imgExt = $extTransform[$imageMimeType];
|
||||
} else {
|
||||
throw new Exception("Unsupported image type $imageMimeType");
|
||||
}
|
||||
|
||||
// add image to document
|
||||
$imgName = 'image_' . $rid . '_' . pathinfo($partFileName, PATHINFO_FILENAME) . '.' . $imgExt;
|
||||
$this->zipClass->pclzipAddFile($imgPath, 'word/media/' . $imgName);
|
||||
$this->tempDocumentNewImages[$imgPath] = $imgName;
|
||||
|
||||
// setup type for image
|
||||
$xmlImageType = str_replace(array('{IMG}', '{EXT}'), array($imgName, $imgExt), $typeTpl);
|
||||
$this->tempDocumentContentTypes = str_replace('</Types>', $xmlImageType, $this->tempDocumentContentTypes) . '</Types>';
|
||||
}
|
||||
|
||||
$xmlImageRelation = str_replace(array('{RID}', '{IMG}'), array($rid, $imgName), $relationTpl);
|
||||
|
||||
if (!isset($this->tempDocumentRelations[$partFileName])) {
|
||||
// create new relations file
|
||||
$this->tempDocumentRelations[$partFileName] = $newRelationsTpl;
|
||||
// and add it to content types
|
||||
$xmlRelationsType = str_replace('{RELS}', $this->getRelationsName($partFileName), $newRelationsTypeTpl);
|
||||
$this->tempDocumentContentTypes = str_replace('</Types>', $xmlRelationsType, $this->tempDocumentContentTypes) . '</Types>';
|
||||
}
|
||||
|
||||
// add image to relations
|
||||
$this->tempDocumentRelations[$partFileName] = str_replace('</Relationships>', $xmlImageRelation, $this->tempDocumentRelations[$partFileName]) . '</Relationships>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $search
|
||||
* @param mixed $replace Path to image, or array("path" => xx, "width" => yy, "height" => zz)
|
||||
* @param int $limit
|
||||
*/
|
||||
public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_DEFAULT)
|
||||
{
|
||||
// prepare $search_replace
|
||||
if (!is_array($search)) {
|
||||
$search = array($search);
|
||||
}
|
||||
|
||||
$replacesList = array();
|
||||
if (!is_array($replace) || isset($replace['path'])) {
|
||||
$replacesList[] = $replace;
|
||||
} else {
|
||||
$replacesList = array_values($replace);
|
||||
}
|
||||
|
||||
$searchReplace = array();
|
||||
foreach ($search as $searchIdx => $searchString) {
|
||||
$searchReplace[$searchString] = isset($replacesList[$searchIdx]) ? $replacesList[$searchIdx] : $replacesList[0];
|
||||
}
|
||||
|
||||
// collect document parts
|
||||
$searchParts = array(
|
||||
$this->getMainPartName() => &$this->tempDocumentMainPart,
|
||||
);
|
||||
foreach (array_keys($this->tempDocumentHeaders) as $headerIndex) {
|
||||
$searchParts[$this->getHeaderName($headerIndex)] = &$this->tempDocumentHeaders[$headerIndex];
|
||||
}
|
||||
foreach (array_keys($this->tempDocumentFooters) as $headerIndex) {
|
||||
$searchParts[$this->getFooterName($headerIndex)] = &$this->tempDocumentFooters[$headerIndex];
|
||||
}
|
||||
|
||||
// define templates
|
||||
// result can be verified via "Open XML SDK 2.5 Productivity Tool" (http://www.microsoft.com/en-us/download/details.aspx?id=30425)
|
||||
$imgTpl = '<w:pict><v:shape type="#_x0000_t75" style="width:{WIDTH};height:{HEIGHT}"><v:imagedata r:id="{RID}" o:title=""/></v:shape></w:pict>';
|
||||
|
||||
foreach ($searchParts as $partFileName => &$partContent) {
|
||||
$partVariables = $this->getVariablesForPart($partContent);
|
||||
|
||||
foreach ($searchReplace as $searchString => $replaceImage) {
|
||||
$varsToReplace = array_filter($partVariables, function ($partVar) use ($searchString) {
|
||||
return ($partVar == $searchString) || preg_match('/^' . preg_quote($searchString) . ':/', $partVar);
|
||||
});
|
||||
|
||||
foreach ($varsToReplace as $varNameWithArgs) {
|
||||
$varInlineArgs = $this->getImageArgs($varNameWithArgs);
|
||||
$preparedImageAttrs = $this->prepareImageAttrs($replaceImage, $varInlineArgs);
|
||||
$imgPath = $preparedImageAttrs['src'];
|
||||
|
||||
// get image index
|
||||
$imgIndex = $this->getNextRelationsIndex($partFileName);
|
||||
$rid = 'rId' . $imgIndex;
|
||||
|
||||
// replace preparations
|
||||
$this->addImageToRelations($partFileName, $rid, $imgPath, $preparedImageAttrs['mime']);
|
||||
$xmlImage = str_replace(array('{RID}', '{WIDTH}', '{HEIGHT}'), array($rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']), $imgTpl);
|
||||
|
||||
// replace variable
|
||||
$varNameWithArgsFixed = self::ensureMacroCompleted($varNameWithArgs);
|
||||
$matches = array();
|
||||
if (preg_match('/(<[^<]+>)([^<]*)(' . preg_quote($varNameWithArgsFixed) . ')([^>]*)(<[^>]+>)/Uu', $partContent, $matches)) {
|
||||
$wholeTag = $matches[0];
|
||||
array_shift($matches);
|
||||
list($openTag, $prefix, , $postfix, $closeTag) = $matches;
|
||||
$replaceXml = $openTag . $prefix . $closeTag . $xmlImage . $openTag . $postfix . $closeTag;
|
||||
// replace on each iteration, because in one tag we can have 2+ inline variables => before proceed next variable we need to change $partContent
|
||||
$partContent = $this->setValueForPart($wholeTag, $replaceXml, $partContent, $limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns count of all variables in template.
|
||||
*
|
||||
|
|
@ -406,15 +709,17 @@ class TemplateProcessor
|
|||
public function save()
|
||||
{
|
||||
foreach ($this->tempDocumentHeaders as $index => $xml) {
|
||||
$this->zipClass->addFromString($this->getHeaderName($index), $xml);
|
||||
$this->savePartWithRels($this->getHeaderName($index), $xml);
|
||||
}
|
||||
|
||||
$this->zipClass->addFromString($this->getMainPartName(), $this->tempDocumentMainPart);
|
||||
$this->savePartWithRels($this->getMainPartName(), $this->tempDocumentMainPart);
|
||||
|
||||
foreach ($this->tempDocumentFooters as $index => $xml) {
|
||||
$this->zipClass->addFromString($this->getFooterName($index), $xml);
|
||||
$this->savePartWithRels($this->getFooterName($index), $xml);
|
||||
}
|
||||
|
||||
$this->zipClass->addFromString($this->getDocumentContentTypesName(), $this->tempDocumentContentTypes);
|
||||
|
||||
// Close zip file
|
||||
if (false === $this->zipClass->close()) {
|
||||
throw new Exception('Could not close zip file.');
|
||||
|
|
@ -423,6 +728,19 @@ class TemplateProcessor
|
|||
return $this->tempDocumentFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fileName
|
||||
* @param string $xml
|
||||
*/
|
||||
protected function savePartWithRels($fileName, $xml)
|
||||
{
|
||||
$this->zipClass->addFromString($fileName, $xml);
|
||||
if (isset($this->tempDocumentRelations[$fileName])) {
|
||||
$relsFileName = $this->getRelationsName($fileName);
|
||||
$this->zipClass->addFromString($relsFileName, $this->tempDocumentRelations[$fileName]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the result document to the user defined file.
|
||||
*
|
||||
|
|
@ -542,6 +860,35 @@ class TemplateProcessor
|
|||
return sprintf('word/footer%d.xml', $index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the relations file for document part.
|
||||
*
|
||||
* @param string $documentPartName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getRelationsName($documentPartName)
|
||||
{
|
||||
return 'word/_rels/' . pathinfo($documentPartName, PATHINFO_BASENAME) . '.rels';
|
||||
}
|
||||
|
||||
protected function getNextRelationsIndex($documentPartName)
|
||||
{
|
||||
if (isset($this->tempDocumentRelations[$documentPartName])) {
|
||||
return substr_count($this->tempDocumentRelations[$documentPartName], '<Relationship');
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getDocumentContentTypesName()
|
||||
{
|
||||
return '[Content_Types].xml';
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the start position of the nearest table row before $offset.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
|
|||
{
|
||||
$templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx');
|
||||
|
||||
$this->assertEquals(array('documentContent', 'headerValue', 'footerValue'), $templateProcessor->getVariables());
|
||||
$this->assertEquals(array('documentContent', 'headerValue:100:100', 'footerValue'), $templateProcessor->getVariables());
|
||||
|
||||
$macroNames = array('headerValue', 'documentContent', 'footerValue');
|
||||
$macroValues = array('Header Value', 'Document text.', 'Footer Value');
|
||||
|
|
@ -200,6 +200,83 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase
|
|||
$this->assertTrue($docFound);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setImageValue
|
||||
* @test
|
||||
*/
|
||||
public function testSetImageValue()
|
||||
{
|
||||
$templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/header-footer.docx');
|
||||
$imagePath = __DIR__ . '/_files/images/earth.jpg';
|
||||
|
||||
$variablesReplace = array(
|
||||
'headerValue' => $imagePath,
|
||||
'documentContent' => array('path' => $imagePath, 'width' => 500, 'height' => 500),
|
||||
'footerValue' => array('path' => $imagePath, 'width' => 100, 'height' => 50, 'ratio' => false),
|
||||
);
|
||||
$templateProcessor->setImageValue(array_keys($variablesReplace), $variablesReplace);
|
||||
|
||||
$docName = 'header-footer-images-test-result.docx';
|
||||
$templateProcessor->saveAs($docName);
|
||||
|
||||
$this->assertFileExists($docName, "Generated file '{$docName}' not found!");
|
||||
|
||||
$expectedDocumentZip = new \ZipArchive();
|
||||
$expectedDocumentZip->open($docName);
|
||||
$expectedContentTypesXml = $expectedDocumentZip->getFromName('[Content_Types].xml');
|
||||
$expectedDocumentRelationsXml = $expectedDocumentZip->getFromName('word/_rels/document.xml.rels');
|
||||
$expectedHeaderRelationsXml = $expectedDocumentZip->getFromName('word/_rels/header1.xml.rels');
|
||||
$expectedFooterRelationsXml = $expectedDocumentZip->getFromName('word/_rels/footer1.xml.rels');
|
||||
$expectedMainPartXml = $expectedDocumentZip->getFromName('word/document.xml');
|
||||
$expectedHeaderPartXml = $expectedDocumentZip->getFromName('word/header1.xml');
|
||||
$expectedFooterPartXml = $expectedDocumentZip->getFromName('word/footer1.xml');
|
||||
$expectedImage = $expectedDocumentZip->getFromName('word/media/image_rId11_document.jpeg');
|
||||
if (false === $expectedDocumentZip->close()) {
|
||||
throw new \Exception("Could not close zip file \"{$docName}\".");
|
||||
}
|
||||
|
||||
$this->assertNotEmpty($expectedImage, 'Embed image doesn\'t found.');
|
||||
$this->assertContains('/word/media/image_rId11_document.jpeg', $expectedContentTypesXml, '[Content_Types].xml missed "/word/media/image5_document.jpeg"');
|
||||
$this->assertContains('/word/_rels/header1.xml.rels', $expectedContentTypesXml, '[Content_Types].xml missed "/word/_rels/header1.xml.rels"');
|
||||
$this->assertContains('/word/_rels/footer1.xml.rels', $expectedContentTypesXml, '[Content_Types].xml missed "/word/_rels/footer1.xml.rels"');
|
||||
$this->assertNotContains('${documentContent}', $expectedMainPartXml, 'word/document.xml has no image.');
|
||||
$this->assertNotContains('${headerValue}', $expectedHeaderPartXml, 'word/header1.xml has no image.');
|
||||
$this->assertNotContains('${footerValue}', $expectedFooterPartXml, 'word/footer1.xml has no image.');
|
||||
$this->assertContains('media/image_rId11_document.jpeg', $expectedDocumentRelationsXml, 'word/_rels/document.xml.rels missed "media/image5_document.jpeg"');
|
||||
$this->assertContains('media/image_rId11_document.jpeg', $expectedHeaderRelationsXml, 'word/_rels/header1.xml.rels missed "media/image5_document.jpeg"');
|
||||
$this->assertContains('media/image_rId11_document.jpeg', $expectedFooterRelationsXml, 'word/_rels/footer1.xml.rels missed "media/image5_document.jpeg"');
|
||||
|
||||
unlink($docName);
|
||||
|
||||
// dynamic generated doc
|
||||
$testFileName = 'images-test-sample.docx';
|
||||
$phpWord = new \PhpOffice\PhpWord\PhpWord();
|
||||
$section = $phpWord->addSection();
|
||||
$section->addText('${Test:width=100:ratio=true}');
|
||||
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
|
||||
$objWriter->save($testFileName);
|
||||
$this->assertFileExists($testFileName, "Generated file '{$testFileName}' not found!");
|
||||
|
||||
$resultFileName = 'images-test-result.docx';
|
||||
$templateProcessor = new \PhpOffice\PhpWord\TemplateProcessor($testFileName);
|
||||
unlink($testFileName);
|
||||
$templateProcessor->setImageValue('Test', $imagePath);
|
||||
$templateProcessor->setImageValue('Test1', $imagePath);
|
||||
$templateProcessor->setImageValue('Test2', $imagePath);
|
||||
$templateProcessor->saveAs($resultFileName);
|
||||
$this->assertFileExists($resultFileName, "Generated file '{$resultFileName}' not found!");
|
||||
|
||||
$expectedDocumentZip = new \ZipArchive();
|
||||
$expectedDocumentZip->open($resultFileName);
|
||||
$expectedMainPartXml = $expectedDocumentZip->getFromName('word/document.xml');
|
||||
if (false === $expectedDocumentZip->close()) {
|
||||
throw new \Exception("Could not close zip file \"{$resultFileName}\".");
|
||||
}
|
||||
unlink($resultFileName);
|
||||
|
||||
$this->assertNotContains('${Test}', $expectedMainPartXml, 'word/document.xml has no image.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::cloneBlock
|
||||
* @covers ::deleteBlock
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue