Unable to load images in .docx
We have a document that we're running some tests against. However we can't get very far because its unable to load the document. I've dug into this a little, but I'm at a bit of a loss. Here's the issue, when running.
$phpWord = IOFactory::load('./default-from-exporter.docx');The get the following error:
PHP Fatal error: Uncaught PhpOffice\PhpWord\Exception\InvalidImageException: Invalid image: zip://./default-from-exporter.docx#word//word/media/mId1.jpg in /home/gnat/Projects/Web/PHPWord/src/PhpWord/Element/Image.php:473
Stack trace:
#0 /home/gnat/Projects/Web/PHPWord/src/PhpWord/Element/Image.php(166): PhpOffice\PhpWord\Element\Image->checkImage()
#1 [internal function]: PhpOffice\PhpWord\Element\Image->__construct()
#2 /home/gnat/Projects/Web/PHPWord/src/PhpWord/Element/AbstractContainer.php(155): ReflectionClass->newInstanceArgs()
#3 [internal function]: PhpOffice\PhpWord\Element\AbstractContainer->addElement()
#4 /home/gnat/Projects/Web/PHPWord/src/PhpWord/Element/AbstractContainer.php(121): call_user_func_array()
#5 /home/gnat/Projects/Web/PHPWord/src/PhpWord/Reader/Word2007/AbstractPart.php(541): PhpOffice\PhpWord\Element\AbstractContainer->__call()
#6 /home/gnat/Projects/Web/PHPWord/src/PhpWord/Reader/Word2007/AbstractPart.php(475): PhpOffice\PhpWord\Reader\Word2007\AbstractPart->readRunChild()
#7 /home/gnat/Projects/Web/PHPWord/src/PhpWord/Reader/Word2007/AbstractPart.php(325): PhpOffice\PhpWord\Reader\Word2007\AbstractPart->readRun()
#8 /home/gnat/Projects/Web/PHPWord/src/PhpWord/Reader/Word2007/Document.php(151): PhpOffice\PhpWord\Reader\Word2007\AbstractPart->readParagraph()
#9 /home/gnat/Projects/Web/PHPWord/src/PhpWord/Reader/Word2007/Document.php(58): PhpOffice\PhpWord\Reader\Word2007\Document->readWPNode()
#10 /home/gnat/Projects/Web/PHPWord/src/PhpWord/Reader/Word2007.php(116): PhpOffice\PhpWord\Reader\Word2007\Document->read()
#11 /home/gnat/Projects/Web/PHPWord/src/PhpWord/Reader/Word2007.php(90): PhpOffice\PhpWord\Reader\Word2007->readPart()
#12 /home/gnat/Projects/Web/PHPWord/src/PhpWord/IOFactory.php(92): PhpOffice\PhpWord\Reader\Word2007->load()
#13 /home/gnat/Projects/Web/PHPWord/poc/test.php(7): PhpOffice\PhpWord\IOFactory::load()
#14 {main}
thrown in /home/gnat/Projects/Web/PHPWord/src/PhpWord/Element/Image.php on line 473The image source path doesn't make a lot of sense. zip://./default-from-exporter.docx#word//word/media/mId1.jpg . I can see that this is used internally to know the image is in the document (aka archive). However, I'm confused by two parts. the word/ prefix added to the path. And the double /. Here's why.
In the word/_rels/document.xml.rels is:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="/word/media/mId1.jpg" />
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="/word/styles.xml" /
><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="/word/numbering.xml" />
<Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="/word/settings.xml" />
</Relationships>Which leads to my first question.
- In the
getRels()functions a prefix is passed. I don't really know why. What's he purpose of that prefix? I tried adding!str_contains($target, $targetPrefix)to
if ($type != 'hyperlink' && $mode != 'External' && !str_contains($target, $targetPrefix)) {
$target = $targetPrefix . $target;
}which results in the target being correct (as in it matches the data in the <Relationship Target="X"/> but still doesn't work. Which leads to the second.
- Why does
$target = $xmlReader->getAttribute('Target', $node);return the target as/word/media/mId1.jpg, but the following code returnsword/media/mId1.jpginstead?
<?php declare(strict_types=1);
require_once __DIR__.'/../vendor/autoload.php';
$zipFilename = './default-from-exporter.docx';
$zip = new ZipArchive();
if ($zip->open($zipFilename) !== false) {
for ($i = 0; $i < $zip->numFiles; $i++) {
// Get the file name by its index
$filename = $zip->getNameIndex($i);
echo $filename . PHP_EOL;
}
$zip->close();
}Here's the document.
I'm happy to fix this myself if possible and create a PR. However, I need a bit of guidance first.
Expected behavior
No errors.
Steps to reproduce
<?php declare(strict_types=1);
use PhpOffice\PhpWord\IOFactory;
require_once __DIR__ . '/../vendor/autoload.php';
$phpWord = IOFactory::load('./default-from-exporter.docx');
PHPWord version(s) where the bug happened
master
PHP version(s) where the bug happened
8.5
Priority
- I want to crowdfund the bug fix (with @algora-io) and fund a community developer.
- I want to pay the bug fix and fund a maintainer for that. (Contact @Progi1984)
Source: PHPOffice/PHPWord