Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TemplateProcessor->setImageValue() #1170

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7553b4c
setImageValue() + fix adding files via ZipArchive
SailorMax Oct 25, 2017
76bc755
tab to spaces
SailorMax Oct 25, 2017
8e8fd8e
tabs to spaces
SailorMax Oct 25, 2017
25f24d1
syntax fixes
SailorMax Oct 25, 2017
879a9b4
syntax fixes
SailorMax Oct 25, 2017
8a9293a
tabs to spaces
SailorMax Oct 25, 2017
869a363
syntax fixes
SailorMax Oct 25, 2017
5179074
php 5.3 compatibility fix
SailorMax Oct 25, 2017
aaf59ac
syntax fixes
SailorMax Oct 25, 2017
a37f60f
php 5.3 compatibility fix
SailorMax Oct 25, 2017
8e28879
Merge branch 'develop' into template_processor__set_image_value
SailorMax Nov 10, 2017
9137251
Merge branch 'develop' into template_processor__set_image_value
SailorMax Nov 10, 2017
1e2ef76
syntax fix
SailorMax Nov 11, 2017
dcca596
Merge branch 'template_processor__set_image_value' of https://github.…
SailorMax Nov 11, 2017
3109939
fix phpdoc variable name
SailorMax Nov 11, 2017
ae27499
Merge branch 'develop' into template_processor__set_image_value
SailorMax Dec 7, 2017
6f63982
Changed logic that determines extension image file extension for docu…
gatis-ozols Jan 5, 2018
ff512f4
Merge pull request #1 from gatis-ozols/template_processor__set_image_…
SailorMax Jan 18, 2018
04dd86f
syntax fixes
SailorMax Jan 18, 2018
dbcbbeb
support <w:t> tags with arguments
SailorMax Feb 16, 2018
ef50aeb
simpler replace inline variables + test
SailorMax Mar 16, 2018
65ebf4a
spaces fix
SailorMax Mar 16, 2018
9fcf064
allow setup size of image into template variable
SailorMax May 25, 2018
de25054
restore using mime types
SailorMax May 25, 2018
a6b2600
better variable names
SailorMax May 25, 2018
2031ccc
fix variable name
SailorMax May 25, 2018
28fd8a0
reduce size of methods
SailorMax May 25, 2018
a4f8153
fix broken xml structure after replace variables
SailorMax May 25, 2018
3f87352
more reduce functions size
SailorMax May 25, 2018
5038b3b
more reduce methods size
SailorMax May 25, 2018
7959651
support of 'ratio' replace attribute + documentation
SailorMax May 25, 2018
c6515db
update unit test
SailorMax May 25, 2018
58f52e3
Update CHANGELOG.md
troosan Dec 23, 2018
d8dae9e
Merge branch 'develop' into template_processor__set_image_value
troosan Dec 23, 2018
495227c
fix check style error
troosan Dec 23, 2018
6bedbd8
php 7.3 should not fail anymore
troosan Dec 23, 2018
3b31a65
Revert "php 7.3 should not fail anymore"
troosan Dec 23, 2018
3673dd5
ignore dependencies for php 7.3
troosan Dec 23, 2018
ef40717
rewrite test to use phpunit assertions
troosan Dec 24, 2018
8ba5bfd
improve code coverage
troosan Dec 24, 2018
d771461
use old phpunit compatible assertions
troosan Dec 26, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
setImageValue() + fix adding files via ZipArchive
  • Loading branch information
SailorMax committed Oct 25, 2017
commit 7553b4cc796294eca3ec4067b6c1b0396e12ab54
14 changes: 10 additions & 4 deletions src/PhpWord/Shared/ZipArchive.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ public function open($filename, $flags = null)
{
$result = true;
$this->filename = $filename;
$this->tempDir = Settings::getTempDir();

if (!$this->usePclzip) {
$zip = new \ZipArchive();
Expand All @@ -139,7 +140,6 @@ public function open($filename, $flags = null)
$this->numFiles = $zip->numFiles;
} else {
$zip = new \PclZip($this->filename);
$this->tempDir = Settings::getTempDir();
$this->numFiles = count($zip->listContent());
}
$this->zip = $zip;
Expand Down Expand Up @@ -232,10 +232,10 @@ public function pclzipAddFile($filename, $localname = null)

// To Rename the file while adding it to the zip we
// need to create a temp file with the correct name
$tempFile = false;
$tempFile = false;
if ($filenameParts['basename'] != $localnameParts['basename']) {
$tempFile = true; // temp file created
$temppath = $this->tempDir . DIRECTORY_SEPARATOR . $localnameParts['basename'];
$temppath = $this->tempDir . DIRECTORY_SEPARATOR . $localnameParts['basename'];
copy($filename, $temppath);
$filename = $temppath;
$filenameParts = pathinfo($temppath);
Expand All @@ -244,7 +244,13 @@ public function pclzipAddFile($filename, $localname = null)
$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
Expand Down
220 changes: 208 additions & 12 deletions src/PhpWord/TemplateProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -88,21 +109,34 @@ public function __construct($documentTemplate)
$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) );
}

/**
* @param string $xml
* @param \XSLTProcessor $xsltProcessor
Expand Down Expand Up @@ -236,6 +270,123 @@ public function setValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_
$this->tempDocumentFooters = $this->setValueForPart($search, $replace, $this->tempDocumentFooters, $limit);
}

/**
* @param mixed $search
* @param mixed $replace Path to image, or array("path" => xx, "width" => yy, "height" => zz)
* @param integer $limit
*
* @return void
*/
public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_DEFAULT)
{
// prepare $search_replace
if (!is_array($search))
$search = array($search);

$replaces_list = array();
if (!is_array($replace) || isset($replace["path"])) {
$replaces_list[] = $replace;
} else {
$replaces_list = array_values($replace);
}

$search_replace = array();
foreach ($search as $searchIdx => $searchString)
$search_replace[$searchString] = isset($replaces_list[$searchIdx]) ? $replaces_list[$searchIdx] : $replaces_list[0];
//

// define templates
// result can be verified via "Open XML SDK 2.5 Productivity Tool" (https://www.microsoft.com/en-us/download/details.aspx?id=30425)
$imgTpl = '<w:pict><v:shape type="#_x0000_t75" style="width:{WIDTH}px;height:{HEIGHT}px"><v:imagedata r:id="{RID}" o:title=""/></v:shape></w:pict>';
$typeTpl = '<Override PartName="/word/media/{IMG}" ContentType="image/{EXT}"/>';
$relationTpl = '<Relationship Id="{RID}" Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/{IMG}"/>';
$newRelationsTpl = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n".'<Relationships xmlns="https://schemas.openxmlformats.org/package/2006/relationships"></Relationships>';
$newRelationsTypeTpl = '<Override PartName="/{RELS}" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>';
$extTransform = array(
"jpg" => "jpeg",
"JPG" => "jpeg",
"png" => "png",
"PNG" => "png",
);
//

$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];

foreach ($searchParts as $partFileName => &$partContent) {
$partVariables = $this->getVariablesForPart($partContent);

$partSearchReplaces = array();
foreach ($search_replace as $search => $replace) {
if (!in_array($search, $partVariables)) {
continue;
}

// get image path and size
$width = 115;
$height = 70;
if (is_array($replace) && isset($replace["path"])) {
$imgPath = $replace["path"];
if (isset($replace["width"]))
$width = $replace["width"];
if (isset($replace["height"]))
$height = $replace["height"];
}
else
$imgPath = $replace;

// get image index
$imgIndex = $this->getNextRelationsIndex($partFileName);
$rid = 'rId' . $imgIndex;

// get image embed name
if (isset($this->tempDocumentNewImages[$imgPath])) {
$imgName = $this->tempDocumentNewImages[$imgPath];
} else {
// transform extension
$imgExt = pathinfo($imgPath, PATHINFO_EXTENSION);
if (isset($extTransform))
$imgExt = $extTransform[$imgExt];

// add image to document
$imgName = 'image' . $imgIndex . '_' . pathinfo($partFileName, PATHINFO_FILENAME) . '.' . $imgExt;
$this->zipClass->pclzipAddFile($imgPath, 'word/media/' . $imgName);
$this->tempDocumentNewImages[$imgPath] = $imgName;

// setup type for image
$xmlImageType = str_replace(['{IMG}', '{EXT}'], [$imgName, $imgExt], $typeTpl) ;
$this->tempDocumentContentTypes = str_replace('</Types>', $xmlImageType, $this->tempDocumentContentTypes) . '</Types>';
}

$xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}'], [$rid, $width, $height], $imgTpl) ;
$xmlImageRelation = str_replace(['{RID}', '{IMG}'], [$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>';

// collect prepared replaces
$partSearchReplaces["<w:t>".self::ensureMacroCompleted($search)."</w:t>"] = $xmlImage;
}

if ($partSearchReplaces)
$partContent = $this->setValueForPart(array_keys($partSearchReplaces), $partSearchReplaces, $partContent, $limit);
}
}

/**
* Returns array of all variables in template.
*
Expand Down Expand Up @@ -399,22 +550,39 @@ public function deleteBlock($blockname)
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.');
}

return $this->tempDocumentFilename;
}
}

/**
* @param string $fileName
* @param string $xml
*
* @return void
*/
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.
Expand Down Expand Up @@ -533,6 +701,34 @@ protected function getFooterName($index)
return sprintf('word/footer%d.xml', $index);
}

/**
* Get the name of the relations file for document part.
*
* @param string $docuemntPartName
*
* @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.
*
Expand Down
50 changes: 50 additions & 0 deletions tests/PhpWord/TemplateProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,56 @@ public function testMacrosCanBeReplacedInHeaderAndFooter()
$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' => ["path" => $imagePath, "width" => 500, "height" => 500],
'footerValue' => ["path" => $imagePath, "width" => 50, "height" => 50],
);
$templateProcessor->setImageValue(array_keys($variablesReplace), $variablesReplace);

$docName = 'header-footer-images-test-result.docx';
$templateProcessor->saveAs($docName);
$docFound = file_exists($docName);

if ($docFound) {
$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/image5_document.jpeg');
if (false === $expectedDocumentZip->close()) {
throw new \Exception("Could not close zip file \"{$docName}\".");
}

$this->assertTrue(!empty($expectedImage), 'Embed image doesn\'t found.');
$this->assertTrue(strpos($expectedContentTypesXml, '/word/media/image5_document.jpeg') > 0, '[Content_Types].xml missed "/word/media/image5_document.jpeg"');
$this->assertTrue(strpos($expectedContentTypesXml, '/word/_rels/header1.xml.rels') > 0, '[Content_Types].xml missed "/word/_rels/header1.xml.rels"');
$this->assertTrue(strpos($expectedContentTypesXml, '/word/_rels/footer1.xml.rels') > 0, '[Content_Types].xml missed "/word/_rels/footer1.xml.rels"');
$this->assertTrue(strpos($expectedMainPartXml, '${documentContent}') === false, 'word/document.xml has no image.');
$this->assertTrue(strpos($expectedHeaderPartXml, '${headerValue}') === false, 'word/header1.xml has no image.');
$this->assertTrue(strpos($expectedFooterPartXml, '${footerValue}') === false, 'word/footer1.xml has no image.');
$this->assertTrue(strpos($expectedDocumentRelationsXml, 'media/image5_document.jpeg') > 0, 'word/_rels/document.xml.rels missed "media/image5_document.jpeg"');
$this->assertTrue(strpos($expectedHeaderRelationsXml, 'media/image5_document.jpeg') > 0, 'word/_rels/header1.xml.rels missed "media/image5_document.jpeg"');
$this->assertTrue(strpos($expectedFooterRelationsXml, 'media/image5_document.jpeg') > 0, 'word/_rels/footer1.xml.rels missed "media/image5_document.jpeg"');

unlink($docName);
}
}

/**
* @covers ::cloneBlock
* @covers ::deleteBlock
Expand Down