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

Add/Insert/Replace image in templates #550

Open
ozilion opened this issue Jun 10, 2015 · 40 comments
Open

Add/Insert/Replace image in templates #550

ozilion opened this issue Jun 10, 2015 · 40 comments

Comments

@ozilion
Copy link
Contributor

ozilion commented Jun 10, 2015

Hi,

Can't we still Add/Insert/Replace an image using template files? Need this urgently... Does anyone has a solution?


Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

@OAFCROB
Copy link

OAFCROB commented Jun 30, 2015

I've come up with a hack solution see

#260

@emirpolo
Copy link

emirpolo commented Jul 7, 2015

class PHPWord_Template {
private $_objZip;
private $_tempFileName;
private $_documentXML;
private $_header1XML;
private $_footer1XML;
private $_rels;
private $_types;
private $_countRels;

/**
 * Create a new Template Object
 * @param string $strFilename
 */
public function __construct($strFilename) {
    $path = dirname($strFilename);

    $this->_tempFileName = $path . DIRECTORY_SEPARATOR . time() . '.docx'; // $path doesn't include the trailing slash - Custom code by Matt Bowden (blenderstyle) 04/12/2011

    copy($strFilename, $this->_tempFileName); // Copy the source File to the temp File

    $this->_objZip = new ZipArchive();
    $this->_objZip->open($this->_tempFileName);

    $this->_documentXML = $this->_objZip->getFromName('word/document.xml');
    $this->_header1XML  = $this->_objZip->getFromName('word/header1.xml'); // Custom code by Matt Bowden (blenderstyle) 04/12/2011
    $this->_footer1XML  = $this->_objZip->getFromName('word/footer1.xml'); // Custom code by Matt Bowden (blenderstyle) 04/12/2011
    $this->_rels        = $this->_objZip->getFromName('word/_rels/document.xml.rels'); #erap 07/07/2015
    $this->_types       = $this->_objZip->getFromName('[Content_Types].xml'); #erap 07/07/2015
    $this->_countRels   = substr_count($this->_rels, 'Relationship') - 1; #erap 07/07/2015
}

/**
 * Set a Template value
 * @param mixed $search
 * @param mixed $replace
 */
public function setValue($search, $replace) {
    if (substr($search, 0, 2) !== '${' && substr($search, -1) !== '}') {
        $search = '${' . $search . '}';
    }

    if (!is_array($replace)) {
        $replace = utf8_encode($replace);
    }

    $this->_documentXML = str_replace($search, $replace, $this->_documentXML);
    $this->_header1XML = str_replace($search, $replace, $this->_header1XML); // Custom code by Matt Bowden (blenderstyle) 04/12/2011
    $this->_footer1XML = str_replace($search, $replace, $this->_footer1XML); // Custom code by Matt Bowden (blenderstyle) 04/12/2011
}

/**
 * Save Template
 * @param string $strFilename
 */
public function save($strFilename) {
    if (file_exists($strFilename)) {
        unlink($strFilename);
    }

    $this->_objZip->addFromString('word/document.xml', $this->_documentXML);
    $this->_objZip->addFromString('word/header1.xml', $this->_header1XML); // Custom code by Matt Bowden (blenderstyle) 04/12/2011
    $this->_objZip->addFromString('word/footer1.xml', $this->_footer1XML); // Custom code by Matt Bowden (blenderstyle) 04/12/2011
    $this->_objZip->addFromString('word/_rels/document.xml.rels', $this->_rels); #erap 07/07/2015
    $this->_objZip->addFromString('[Content_Types].xml', $this->_types); #erap 07/07/2015
    // Close zip file
    if ($this->_objZip->close() === false) {
        throw new Exception('Could not close zip file.');
    }

    rename($this->_tempFileName, $strFilename);
}

public function replaceImage($path, $imageName) {
    $this->_objZip->deleteName('word/media/' . $imageName);
    $this->_objZip->addFile($path, 'word/media/' . $imageName);
}

public function replaceStrToImg( $strKey, $arrImgPath ){
    $strKey = '${'.$strKey.'}';
    if( !is_array($arrImgPath) )
        $arrImgPath = array($arrImgPath);

    $relationTmpl = '<Relationship Id="RID" Type="http:https://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/IMG"/>';
    $imgTmpl = '<w:pict><v:shape type="#_x0000_t75" style="width:289px;height:108px"><v:imagedata r:id="RID" o:title=""/></v:shape></w:pict>';
    $typeTmpl = ' <Override PartName="/word/media/IMG" ContentType="image/EXT"/>';
    $toAdd = $toAddImg = $toAddType = '';
    $aSearch = array('RID', 'IMG');
    $aSearchType = array('IMG', 'EXT');

    foreach($arrImgPath as $img){
        $imgExt = array_pop( explode('.', $img) );
        if( in_array($imgExt, array('jpg', 'JPG') ) )
            $imgExt = 'jpeg';
        $imgName = 'img' . $this->_countRels . '.' . $imgExt;
        $rid = 'rId' . $this->_countRels++;

        $this->_objZip->addFile($img, 'word/media/' . $imgName);

        $toAddImg .= str_replace('RID', $rid, $imgTmpl) ;

        $aReplace = array($imgName, $imgExt);
        $toAddType .= str_replace($aSearchType, $aReplace, $typeTmpl) ;

        $aReplace = array($rid, $imgName);
        $toAdd .= str_replace($aSearch, $aReplace, $relationTmpl);
    }

    $this->_documentXML = str_replace('<w:t>' . $strKey . '</w:t>', $toAddImg, $this->_documentXML);
    $this->_types       = str_replace('</Types>', $toAddType, $this->_types) . '</Types>';
    $this->_rels        = str_replace('</Relationships>', $toAdd, $this->_rels) . '</Relationships>';
}

}

use example:

$PHPWord = new PHPWord();
$document = $PHPWord->loadTemplate($template);
$arrImagenes = array(
'../../images/mc.png',
'../../images/logo.png',
'../../mineria/image/logo.jpg',
'../../images/draw-icon.png'

);
$document->replaceStrToImg('AreaImg', $arrImagenes);
$documentName = 'Concepto_Tecnico_' . date('Ymd_His') . '.docx';
$document->save( $documentName);

@emirpolo
Copy link

@mansonkibe
Copy link

I have an issue. When i get file content from Google Drive API and create the file. This function doesn't seem to be able to replace the image. Would be grateful if you could help out

@Rappi
Copy link

Rappi commented Dec 15, 2015

Hi emirpolo.

Do you have an example with the new version 0.12.1?
You are using a very old version in your example :-(

@labsev
Copy link

labsev commented Jan 20, 2016

I was looking for a solution to add image. I read a lot of articles, I only found suitable solutions for older versions. Changing and finalized the decision to get the code.
Let us proceed to change the file TemplateProcessor.php
public function __construct($documentTemplate)
{
//add to this function

$this->_countRels=100; //start id for relationship between image and document.xml

}

public function save()
{
//add to this function after $this->zipClass->addFromString('word/document.xml', $this->tempDocumentMainPart);

if($this->_rels!="")
{
    $this->zipClass->addFromString('word/_rels/document.xml.rels', $this->_rels);
}
if($this->_types!="")
{
    $this->zipClass->addFromString('[Content_Types].xml', $this->_types);
}

}

//add function

public function setImg( $strKey, $img){
        $strKey = '${'.$strKey.'}';
        $relationTmpl = '<Relationship Id="RID" Type="http:https://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/IMG"/>';

        $imgTmpl = '<w:pict><v:shape type="#_x0000_t75" style="width:WIDpx;height:HEIpx"><v:imagedata r:id="RID" o:title=""/></v:shape></w:pict>';

        $toAdd = $toAddImg = $toAddType = '';
        $aSearch = array('RID', 'IMG');
        $aSearchType = array('IMG', 'EXT');
        $countrels=$this->_countRels++;
        //I'm work for jpg files, if you are working with other images types -> Write conditions here
    $imgExt = 'jpg';
        $imgName = 'img' . $countrels . '.' . $imgExt;

            $this->zipClass->deleteName('word/media/' . $imgName);
            $this->zipClass->addFile($img['src'], 'word/media/' . $imgName);

            $typeTmpl = '<Override PartName="/word/media/'.$imgName.'" ContentType="image/EXT"/>';


            $rid = 'rId' . $countrels;
            $countrels++;
        list($w,$h) = getimagesize($img['src']);

 if(isset($img['swh'])) //Image proportionally larger side
 {
 if($w<=$h)
 {
    $ht=(int)$img['swh'];
    $ot=$w/$h;
    $wh=(int)$img['swh']*$ot;
    $wh=round($wh);
 }
 if($w>=$h)
 {
    $wh=(int)$img['swh'];
    $ot=$h/$w;
    $ht=(int)$img['swh']*$ot;
    $ht=round($ht);
 }
 $w=$wh;
 $h=$ht;
 }

if(isset($img['size']))
{
$w = $img['size'][0];
$h = $img['size'][1];           
}


            $toAddImg .= str_replace(array('RID', 'WID', 'HEI'), array($rid, $w, $h), $imgTmpl) ;
            if(isset($img['dataImg']))
            {
                $toAddImg.='<w:br/><w:t>'.$this->limpiarString($img['dataImg']).'</w:t><w:br/>';
            }

            $aReplace = array($imgName, $imgExt);
            $toAddType .= str_replace($aSearchType, $aReplace, $typeTmpl) ;

            $aReplace = array($rid, $imgName);
            $toAdd .= str_replace($aSearch, $aReplace, $relationTmpl);


        $this->tempDocumentMainPart=str_replace('<w:t>' . $strKey . '</w:t>', $toAddImg, $this->tempDocumentMainPart);
        //print $this->tempDocumentMainPart;



        if($this->_rels=="")
        {
            $this->_rels=$this->zipClass->getFromName('word/_rels/document.xml.rels');
            $this->_types=$this->zipClass->getFromName('[Content_Types].xml');
        }

        $this->_types       = str_replace('</Types>', $toAddType, $this->_types) . '</Types>';
                $this->_rels        = str_replace('</Relationships>', $toAdd, $this->_rels) . '</Relationships>';
}

//add function

function limpiarString($str) {
        return str_replace(
                array('&', '<', '>', "\n"), 
                array('&amp;', '&lt;', '&gt;', "\n" . '<w:br/>'), 
                $str
        );
}

//HOW TO USE???

$templateProcessor = new \PhpOffice\PhpWord\TemplateProcessor('templ.docx');

//static zone
$templateProcessor->setValue('date', htmlspecialchars(date('d.m.Y G:i:s')));    
//$templateProcessor->cloneRow('NAME_IN_TEMPLATE', NUMBER_OF_TABLE_RECORDS);
$templateProcessor->cloneRow('AVTOR', 3);

//variant 1
//dynamic zone
$templateProcessor->setValue('AVTOR#1', htmlspecialchars('Garry'));
$templateProcessor->setValue('NAME#1', htmlspecialchars('Black Horse'));
$templateProcessor->setValue('SIZES#1', htmlspecialchars('100x300'));

/*$img = array(
        'src' => 'image.jpg',//path
    'swh'=>'350',//Image proportionally larger side
        'size'=>array(580, 280)
);*/
$templateProcessor->setImg('IMGD#1',array('src' => 'image.jpg','swh'=>'250'));

$templateProcessor->setValue('AVTOR#2', htmlspecialchars('Barry'));
$templateProcessor->setValue('NAME#2', htmlspecialchars('White Horse'));
$templateProcessor->setValue('SIZES#2', htmlspecialchars('200x500'));
$templateProcessor->setImg('IMGD#2',array('src' => 'image2.jpg','swh'=>'250'));

$templateProcessor->setValue('AVTOR#3', htmlspecialchars('Backer'));
$templateProcessor->setValue('NAME#3', htmlspecialchars('Another Side'));
$templateProcessor->setValue('SIZES#3', htmlspecialchars('120x430'));
$templateProcessor->setImg('IMGD#3',array('src' => 'image3.jpg','swh'=>'250'));

//variant 2

$templateProcessor->cloneRow('AVTOR', count($output['ID'])); 
        for($i=0;$i<count($output['ID']);$i++)
        {
            $templateProcessor->setValue('AVTOR'.'#'.($i+1), htmlspecialchars($output['AVTOR'][$i]));
            $templateProcessor->setValue('NAME'.'#'.($i+1), htmlspecialchars($output['PNAM'][$i]));
//GetImg($output['ID'][$i]) my function return image path
            $templateProcessor->setImg('IMGD'.'#'.($i+1), array('src'=>GetImg($output['ID'][$i]),'swh'=>'250'));
        }

//Save

$templateProcessor->saveAs('testTemplate.docx');

@Rappi
Copy link

Rappi commented Mar 1, 2016

Great!
It work.
Many thanks.

@finguer
Copy link

finguer commented Apr 15, 2016

Bien Muchisimas Gracias

@hangzhoubison
Copy link

thinks labsev

@karupi
Copy link

karupi commented Jun 14, 2016

Hi,

I get this error:

Undefined property: PhpOffice\PhpWord\TemplateProcessor::$_rels

    if($this->_rels=="")
    {
        $this->_rels=$this->zipClass->getFromName('word/_rels/document.xml.rels');
        $this->_types=$this->zipClass->getFromName('[Content_Types].xml');
    }

@JPBetley
Copy link
Contributor

@karupi You should add them as properties. I had the same error and fixed it like this:

class TemplateProcessor
{
    const MAXIMUM_REPLACEMENTS_DEFAULT = -1;

    // add these two properties
    protected $_rels;
    protected $_types;
    [...]
}

@juanagu
Copy link

juanagu commented Jul 15, 2016

@labsev Thanks! It work! great job! 👍

@tanush1122
Copy link

@labsev Thanks a lot! It works.

@ozilion
Copy link
Contributor Author

ozilion commented Sep 9, 2016

Exactly on which version is working this solution?

@milosodalovic
Copy link

It's definitely working on v0.13.0

@labsev, what's exactly $_countRels is used for?

@david-quintanilla
Copy link

nice work, thanks labsev, its works for me :)

@TimRutte
Copy link

@labsev Yeah, finally it works for me now. Thx a lot!

@CodeJason
Copy link

I know this is over a year old, but @labsev thanks so much!

@ghazalaz
Copy link

Thanks @labsev .
How do you align the picture to center?

@vierkantemeter
Copy link

Hi all, thanks for all the work! I'm trying to use @labsev 's code, but I'm a little confused as to what placeholder text I should put in the template, something like ${IMAGE}? Would be great if someone could show me an example!

@ghazalaz
Copy link

Hi all, thanks for all the work! I'm trying to use @labsev 's code, but I'm a little confused as to what placeholder text I should put in the template, something like ${IMAGE}? Would be great if someone could show me an example!

If you are using ${IMAGE_index} in the template, you have to pass to setImg only this : 'IMAGE_index'
(as you see in the first line of the function, ${} is being added to the strkey)
$templateProcessor->setImg('IMAGE_index',array('src' => 'image.jpg','swh'=>'250'));

@vierkantemeter
Copy link

Oh, I see, thanks!!

@nformoso
Copy link

nformoso commented Dec 6, 2016

@labsev Thank you very much. Great solution for inserting images!

@tweecool
Copy link

tweecool commented Feb 2, 2017

Works great, thanks.

If you're inserting multiple images and get 'corrupted' warning message while opening the doc. Increase the $this->_countRels=100 to $this->_countRels=300 for instance. Changing the number cured it for me.

@levieraf
Copy link

levieraf commented Feb 5, 2017

Thanks!!!!!!!!!

@luanzhiyong
Copy link

我的神

@cesariverit
Copy link

cesariverit commented Aug 28, 2017

The @labsev solution is not working when I try to replace the image inside the header

@levieraf
Copy link

No, it does't you need to create the implementation for that

@calojad
Copy link

calojad commented Oct 27, 2017

how can you add the image in the document header? please

@FBnil
Copy link

FBnil commented Oct 27, 2017

If you're inserting multiple images and get 'corrupted' warning message while opening the doc. Increase the $this->_countRels=100 to $this->_countRels=300 for instance. Changing the number cured it for me.

Sounds like a bug that needs to be fixed before being a candidate to be merged into PHPWord...

@yuliantosb
Copy link

@labsev Hello sir, I have some issue, I follow the lead but still get error,
There's no error actually, but the image is not appear in the document, when I extract docx file, image from word/media is exist, but on [Content_Types].xml there's no image declared why that's happening?

Sorry my English bad

@andibastian
Copy link

thanks @labserv, its really work

@severocruz
Copy link

thanks @labsev very good

@andy-hammond
Copy link

This solutions looks great. How would you recommend including into our laravel project when the project hasn’t merge this? Currently we using composer and ideally didn’t want to have to store as a local dependancy.

@levieraf
Copy link

@andy-hammond what you can do is extend the class with the particular function. That could be a good practice until they choose merge it.

@andy-hammond
Copy link

@levieraf thanks for the tip. I hope they merge this soon! So essential.

@andy-hammond
Copy link

andy-hammond commented Feb 14, 2018

@labsev I am using this and it works great, however I would like to be able to pass in content rather than a locally saved file. Can anyone provide any suggestions? Thanks

@vriestimo
Copy link

vriestimo commented Jun 28, 2018

It's definitely working on v0.13.0

Labsev's solution still works using v0.14.0.
Personally, I like declaring the function limpiarString as private static function limpiarString($str), calling it usine self::limpiarString(...) instead of $this->limpiarString(...)

@pvkhanh178
Copy link

I was looking for a solution to add image. I read a lot of articles, I only found suitable solutions for older versions. Changing and finalized the decision to get the code.
Let us proceed to change the file TemplateProcessor.php
public function __construct($documentTemplate)
{
//add to this function

$this->_countRels=100; //start id for relationship between image and document.xml

}

public function save()
{
//add to this function after $this->zipClass->addFromString('word/document.xml', $this->tempDocumentMainPart);

if($this->_rels!="")
{
    $this->zipClass->addFromString('word/_rels/document.xml.rels', $this->_rels);
}
if($this->_types!="")
{
    $this->zipClass->addFromString('[Content_Types].xml', $this->_types);
}

}

//add function

public function setImg( $strKey, $img){
        $strKey = '${'.$strKey.'}';
        $relationTmpl = '<Relationship Id="RID" Type="http:https://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/IMG"/>';

        $imgTmpl = '<w:pict><v:shape type="#_x0000_t75" style="width:WIDpx;height:HEIpx"><v:imagedata r:id="RID" o:title=""/></v:shape></w:pict>';

        $toAdd = $toAddImg = $toAddType = '';
        $aSearch = array('RID', 'IMG');
        $aSearchType = array('IMG', 'EXT');
        $countrels=$this->_countRels++;
        //I'm work for jpg files, if you are working with other images types -> Write conditions here
    $imgExt = 'jpg';
        $imgName = 'img' . $countrels . '.' . $imgExt;

            $this->zipClass->deleteName('word/media/' . $imgName);
            $this->zipClass->addFile($img['src'], 'word/media/' . $imgName);

            $typeTmpl = '<Override PartName="/word/media/'.$imgName.'" ContentType="image/EXT"/>';


            $rid = 'rId' . $countrels;
            $countrels++;
        list($w,$h) = getimagesize($img['src']);

 if(isset($img['swh'])) //Image proportionally larger side
 {
 if($w<=$h)
 {
    $ht=(int)$img['swh'];
    $ot=$w/$h;
    $wh=(int)$img['swh']*$ot;
    $wh=round($wh);
 }
 if($w>=$h)
 {
    $wh=(int)$img['swh'];
    $ot=$h/$w;
    $ht=(int)$img['swh']*$ot;
    $ht=round($ht);
 }
 $w=$wh;
 $h=$ht;
 }

if(isset($img['size']))
{
$w = $img['size'][0];
$h = $img['size'][1];           
}


            $toAddImg .= str_replace(array('RID', 'WID', 'HEI'), array($rid, $w, $h), $imgTmpl) ;
            if(isset($img['dataImg']))
            {
                $toAddImg.='<w:br/><w:t>'.$this->limpiarString($img['dataImg']).'</w:t><w:br/>';
            }

            $aReplace = array($imgName, $imgExt);
            $toAddType .= str_replace($aSearchType, $aReplace, $typeTmpl) ;

            $aReplace = array($rid, $imgName);
            $toAdd .= str_replace($aSearch, $aReplace, $relationTmpl);


        $this->tempDocumentMainPart=str_replace('<w:t>' . $strKey . '</w:t>', $toAddImg, $this->tempDocumentMainPart);
        //print $this->tempDocumentMainPart;



        if($this->_rels=="")
        {
            $this->_rels=$this->zipClass->getFromName('word/_rels/document.xml.rels');
            $this->_types=$this->zipClass->getFromName('[Content_Types].xml');
        }

        $this->_types       = str_replace('</Types>', $toAddType, $this->_types) . '</Types>';
                $this->_rels        = str_replace('</Relationships>', $toAdd, $this->_rels) . '</Relationships>';
}

//add function

function limpiarString($str) {
        return str_replace(
                array('&', '<', '>', "\n"), 
                array('&amp;', '&lt;', '&gt;', "\n" . '<w:br/>'), 
                $str
        );
}

//HOW TO USE???

$templateProcessor = new \PhpOffice\PhpWord\TemplateProcessor('templ.docx');

//static zone
$templateProcessor->setValue('date', htmlspecialchars(date('d.m.Y G:i:s')));    
//$templateProcessor->cloneRow('NAME_IN_TEMPLATE', NUMBER_OF_TABLE_RECORDS);
$templateProcessor->cloneRow('AVTOR', 3);

//variant 1
//dynamic zone
$templateProcessor->setValue('AVTOR#1', htmlspecialchars('Garry'));
$templateProcessor->setValue('NAME#1', htmlspecialchars('Black Horse'));
$templateProcessor->setValue('SIZES#1', htmlspecialchars('100x300'));

/*$img = array(
        'src' => 'image.jpg',//path
    'swh'=>'350',//Image proportionally larger side
        'size'=>array(580, 280)
);*/
$templateProcessor->setImg('IMGD#1',array('src' => 'image.jpg','swh'=>'250'));

$templateProcessor->setValue('AVTOR#2', htmlspecialchars('Barry'));
$templateProcessor->setValue('NAME#2', htmlspecialchars('White Horse'));
$templateProcessor->setValue('SIZES#2', htmlspecialchars('200x500'));
$templateProcessor->setImg('IMGD#2',array('src' => 'image2.jpg','swh'=>'250'));

$templateProcessor->setValue('AVTOR#3', htmlspecialchars('Backer'));
$templateProcessor->setValue('NAME#3', htmlspecialchars('Another Side'));
$templateProcessor->setValue('SIZES#3', htmlspecialchars('120x430'));
$templateProcessor->setImg('IMGD#3',array('src' => 'image3.jpg','swh'=>'250'));

//variant 2

$templateProcessor->cloneRow('AVTOR', count($output['ID'])); 
        for($i=0;$i<count($output['ID']);$i++)
        {
            $templateProcessor->setValue('AVTOR'.'#'.($i+1), htmlspecialchars($output['AVTOR'][$i]));
            $templateProcessor->setValue('NAME'.'#'.($i+1), htmlspecialchars($output['PNAM'][$i]));
//GetImg($output['ID'][$i]) my function return image path
            $templateProcessor->setImg('IMGD'.'#'.($i+1), array('src'=>GetImg($output['ID'][$i]),'swh'=>'250'));
        }

//Save

$templateProcessor->saveAs('testTemplate.docx');

do this with laravel it work! thanks so much!

@l4nos
Copy link

l4nos commented Mar 21, 2024

I've forked and extended template processor to allow you to insert an image placeholder (any image) in MS Word, size it how you want, then set the alt text to bind it to your data as you would with setValues.

The distinct downside to using setimagevalue is that you can't really see how the size of the image will affect your document until you generate.

https://github.com/Lane-Ventures/PHPWord

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests