PHPWord icon indicating copy to clipboard operation
PHPWord copied to clipboard

Add/Insert/Replace image in templates

Open ozilion opened this issue 10 years ago • 40 comments

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.

ozilion avatar Jun 10 '15 14:06 ozilion

I've come up with a hack solution see

https://github.com/PHPOffice/PHPWord/issues/260

OAFCROB avatar Jun 30 '15 09:06 OAFCROB

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://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 avatar Jul 07 '15 17:07 emirpolo

This is a full example: https://drive.google.com/open?id=0B01uU2P1KMbBakkycTEyMW54M0U

emirpolo avatar Jul 23 '15 15:07 emirpolo

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

mansonkibe avatar Nov 06 '15 09:11 mansonkibe

Hi emirpolo.

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

Rappi avatar Dec 15 '15 12:12 Rappi

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://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');

labsev avatar Jan 20 '16 08:01 labsev

Great! It work. Many thanks.

Rappi avatar Mar 01 '16 12:03 Rappi

Bien Muchisimas Gracias

finguer avatar Apr 15 '16 20:04 finguer

thinks labsev

hangzhoubison avatar Apr 20 '16 11:04 hangzhoubison

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

karupi avatar Jun 14 '16 10:06 karupi

@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;
    [...]
}

JPBetley avatar Jun 17 '16 13:06 JPBetley

@labsev Thanks! It work! great job! 👍

juanagu avatar Jul 15 '16 17:07 juanagu

@labsev Thanks a lot! It works.

tanush1122 avatar Sep 08 '16 14:09 tanush1122

Exactly on which version is working this solution?

ozilion avatar Sep 09 '16 06:09 ozilion

It's definitely working on v0.13.0

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

milosodalovic avatar Sep 12 '16 14:09 milosodalovic

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

david-quintanilla avatar Oct 04 '16 19:10 david-quintanilla

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

timrutte avatar Oct 21 '16 19:10 timrutte

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

CodeJason avatar Nov 14 '16 10:11 CodeJason

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

ghazalaz avatar Nov 14 '16 13:11 ghazalaz

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!

vierkantemeter avatar Nov 24 '16 12:11 vierkantemeter

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'));

ghazalaz avatar Nov 25 '16 09:11 ghazalaz

Oh, I see, thanks!!

vierkantemeter avatar Nov 30 '16 13:11 vierkantemeter

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

nformoso avatar Dec 06 '16 19:12 nformoso

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.

tweecool avatar Feb 02 '17 10:02 tweecool

Thanks!!!!!!!!!

levieraf avatar Feb 05 '17 17:02 levieraf

我的神

luanzhiyong avatar Jun 09 '17 07:06 luanzhiyong

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

cesariverit avatar Aug 28 '17 19:08 cesariverit

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

levieraf avatar Aug 28 '17 23:08 levieraf

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

calojad avatar Oct 27 '17 05:10 calojad

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...

FBnil avatar Oct 27 '17 08:10 FBnil