Skip to content
Closed
Changes from all commits
Commits
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
138 changes: 133 additions & 5 deletions src/PhpWord/TemplateProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -283,17 +283,21 @@ public function setComplexValue($search, Element\AbstractElement $complexType):
$elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1);
$objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName;

$xmlWriter = new XMLWriter();
/** @var Writer\Word2007\Element\AbstractElement $elementWriter */
$elementWriter = new $objectClass($xmlWriter, $complexType, true);
$elementWriter->write();

$where = $this->findContainingXmlBlockForMacro($search, 'w:r');

if ($where === false) {
return;
}

// Process images in complex types BEFORE writing XML (to set relation IDs)
$partFileName = $this->getMainPartName();
$this->processComplexTypeImages($complexType, $partFileName);

$xmlWriter = new XMLWriter();
/** @var Writer\Word2007\Element\AbstractElement $elementWriter */
$elementWriter = new $objectClass($xmlWriter, $complexType, true);
$elementWriter->write();

$block = $this->getSlice($where['start'], $where['end']);
$textParts = $this->splitTextIntoTexts($block);
$this->replaceXmlBlock($search, $textParts, 'w:r');
Expand Down Expand Up @@ -636,6 +640,130 @@ private function addImageToRelations($partFileName, $rid, $imgPath, $imageMimeTy
$this->tempDocumentRelations[$partFileName] = str_replace('</Relationships>', $xmlImageRelation, $this->tempDocumentRelations[$partFileName]) . '</Relationships>';
}

/**
* Add image data (binary) to relations and ZIP package.
*
* @param string $partFileName
* @param string $rid
* @param string $imageData Binary image data
* @param string $imageMimeType
*/
private function addImageDataToRelations($partFileName, $rid, $imageData, $imageMimeType): void
{
// 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 = [
'image/jpeg' => 'jpeg',
'image/png' => 'png',
'image/bmp' => 'bmp',
'image/gif' => 'gif',
];

// transform extension
if (isset($extTransform[$imageMimeType])) {
$imgExt = $extTransform[$imageMimeType];
} else {
throw new Exception("Unsupported image type $imageMimeType");
}

// Generate unique image name
$imgName = 'image_' . $rid . '_' . pathinfo($partFileName, PATHINFO_FILENAME) . '.' . $imgExt;

// Add image data directly to ZIP
$this->zipClass->addFromString('word/media/' . $imgName, $imageData);

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

$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>';
}

/**
* Process images in complex types and add them to the document.
*
* @param Element\AbstractElement $element
* @param string $partFileName
*/
private function processComplexTypeImages(Element\AbstractElement $element, $partFileName): void
{
// Check if element is a container (like TextRun)
if ($element instanceof Element\AbstractContainer) {
$elements = $element->getElements();
foreach ($elements as $childElement) {
if ($childElement instanceof Element\Image) {
$this->addImageFromElement($childElement, $partFileName);
}
// Recursively process nested containers
$this->processComplexTypeImages($childElement, $partFileName);
}
}
}

/**
* Add an image element to the document.
*
* @param Element\Image $image
* @param string $partFileName
*/
private function addImageFromElement(Element\Image $image, $partFileName): void
{
$imageType = $image->getImageType();
$imageMimeType = $imageType;

// Get the next relation ID
$imgIndex = $this->getNextRelationsIndex($partFileName);
$rid = 'rId' . $imgIndex;

// CRITICAL: In TemplateProcessor, getNextRelationsIndex() returns the actual rId number (e.g., 7)
// But Image Writer will add 6 if isInSection() is true (which is the default).
// So we need to set relationId = actualRid - 6, so that (actualRid - 6) + 6 = actualRid
// This matches the normal PHPWord flow where Media assigns relationId=1,2,3...
// and Writer adds 6 to get rId7,rId8,rId9...
$relationIdForElement = $imgIndex - 6;
if ($relationIdForElement < 1) {
// If the template doesn't have the 6 system relationships, don't subtract
$relationIdForElement = $imgIndex;
// And make sure Image Writer doesn't add 6
$image->setDocPart('Document', 1);
}

// Set relation ID on the image element (without the 'rId' prefix, just the number)
$image->setRelationId($relationIdForElement);

// Handle different source types
$sourceType = $image->getSourceType();
$imgPath = $image->getSource();

// For memory images (GD, STRING, etc.), we need to get the binary data
// and add it directly to the ZIP, not as a file path
if ($image->isMemImage()) {
// Get image binary data
$imageData = $image->getImageString();
if ($imageData !== null) {
// Add image using binary data
$this->addImageDataToRelations($partFileName, $rid, $imageData, $imageMimeType);
}
} else {
// For local files and archives, use the file path
$this->addImageToRelations($partFileName, $rid, $imgPath, $imageMimeType);
}
}

/**
* @param mixed $search
* @param mixed $replace Path to image, or array("path" => xx, "width" => yy, "height" => zz)
Expand Down
Loading