@@ -62,6 +62,27 @@ class TemplateProcessor
62
62
*/
63
63
protected $ tempDocumentFooters = array ();
64
64
65
+ /**
66
+ * Document relations (in XML format) of the temporary document.
67
+ *
68
+ * @var string[]
69
+ */
70
+ protected $ tempDocumentRelations = array ();
71
+
72
+ /**
73
+ * Document content types (in XML format) of the temporary document.
74
+ *
75
+ * @var string
76
+ */
77
+ protected $ tempDocumentContentTypes = "" ;
78
+
79
+ /**
80
+ * new inserted images list
81
+ *
82
+ * @var string[]
83
+ */
84
+ protected $ tempDocumentNewImages = array ();
85
+
65
86
/**
66
87
* @since 0.12.0 Throws CreateTemporaryFileException and CopyFileException instead of Exception
67
88
*
@@ -88,19 +109,32 @@ public function __construct($documentTemplate)
88
109
$ this ->zipClass ->open ($ this ->tempDocumentFilename );
89
110
$ index = 1 ;
90
111
while (false !== $ this ->zipClass ->locateName ($ this ->getHeaderName ($ index ))) {
91
- $ this ->tempDocumentHeaders [$ index ] = $ this ->fixBrokenMacros (
92
- $ this ->zipClass ->getFromName ($ this ->getHeaderName ($ index ))
93
- );
112
+ $ this ->tempDocumentHeaders [$ index ] = $ this ->readPartWithRels ($ this ->getHeaderName ($ index ));
94
113
$ index ++;
95
114
}
96
115
$ index = 1 ;
97
116
while (false !== $ this ->zipClass ->locateName ($ this ->getFooterName ($ index ))) {
98
- $ this ->tempDocumentFooters [$ index ] = $ this ->fixBrokenMacros (
99
- $ this ->zipClass ->getFromName ($ this ->getFooterName ($ index ))
100
- );
117
+ $ this ->tempDocumentFooters [$ index ] = $ this ->readPartWithRels ($ this ->getFooterName ($ index ));
101
118
$ index ++;
102
119
}
103
- $ this ->tempDocumentMainPart = $ this ->fixBrokenMacros ($ this ->zipClass ->getFromName ($ this ->getMainPartName ()));
120
+
121
+ $ this ->tempDocumentMainPart = $ this ->readPartWithRels ($ this ->getMainPartName ());
122
+ $ this ->tempDocumentContentTypes = $ this ->zipClass ->getFromName ($ this ->getDocumentContentTypesName ());
123
+ }
124
+
125
+ /**
126
+ * @param string $fileName
127
+ *
128
+ * @return string
129
+ */
130
+ protected function readPartWithRels ($ fileName )
131
+ {
132
+ $ relsFileName = $ this ->getRelationsName ($ fileName );
133
+ $ partRelations = $ this ->zipClass ->getFromName ($ relsFileName );
134
+ if ($ partRelations !== false ) {
135
+ $ this ->tempDocumentRelations [$ fileName ] = $ partRelations ;
136
+ }
137
+ return $ this ->fixBrokenMacros ($ this ->zipClass ->getFromName ($ fileName ));
104
138
}
105
139
106
140
/**
@@ -232,6 +266,130 @@ public function setValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_
232
266
$ this ->tempDocumentFooters = $ this ->setValueForPart ($ search , $ replace , $ this ->tempDocumentFooters , $ limit );
233
267
}
234
268
269
+ /**
270
+ * @param mixed $search
271
+ * @param mixed $replace Path to image, or array("path" => xx, "width" => yy, "height" => zz)
272
+ * @param integer $limit
273
+ *
274
+ * @return void
275
+ */
276
+ public function setImageValue ($ search , $ replace , $ limit = self ::MAXIMUM_REPLACEMENTS_DEFAULT )
277
+ {
278
+ // prepare $search_replace
279
+ if (!is_array ($ search )) {
280
+ $ search = array ($ search );
281
+ }
282
+
283
+ $ replacesList = array ();
284
+ if (!is_array ($ replace ) || isset ($ replace ["path " ])) {
285
+ $ replacesList [] = $ replace ;
286
+ } else {
287
+ $ replacesList = array_values ($ replace );
288
+ }
289
+
290
+ $ searchReplace = array ();
291
+ foreach ($ search as $ searchIdx => $ searchString ) {
292
+ $ searchReplace [$ searchString ] = isset ($ replacesList [$ searchIdx ]) ? $ replacesList [$ searchIdx ] : $ replacesList [0 ];
293
+ }
294
+ //
295
+
296
+ // define templates
297
+ // result can be verified via "Open XML SDK 2.5 Productivity Tool" (http://www.microsoft.com/en-us/download/details.aspx?id=30425)
298
+ $ 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> ' ;
299
+ $ typeTpl = '<Override PartName="/word/media/{IMG}" ContentType="image/{EXT}"/> ' ;
300
+ $ relationTpl = '<Relationship Id="{RID}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/{IMG}"/> ' ;
301
+ $ newRelationsTpl = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?> ' ."\n" .'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships> ' ;
302
+ $ newRelationsTypeTpl = '<Override PartName="/{RELS}" ContentType="application/vnd.openxmlformats-package.relationships+xml"/> ' ;
303
+ $ extTransform = array (
304
+ "jpg " => "jpeg " ,
305
+ "JPG " => "jpeg " ,
306
+ "png " => "png " ,
307
+ "PNG " => "png " ,
308
+ );
309
+ //
310
+
311
+ $ searchParts = array (
312
+ $ this ->getMainPartName () => &$ this ->tempDocumentMainPart ,
313
+ );
314
+ foreach (array_keys ($ this ->tempDocumentHeaders ) as $ headerIndex ) {
315
+ $ searchParts [ $ this ->getHeaderName ($ headerIndex ) ] = &$ this ->tempDocumentHeaders [$ headerIndex ];
316
+ }
317
+ foreach (array_keys ($ this ->tempDocumentFooters ) as $ headerIndex ) {
318
+ $ searchParts [ $ this ->getFooterName ($ headerIndex ) ] = &$ this ->tempDocumentFooters [$ headerIndex ];
319
+ }
320
+
321
+ foreach ($ searchParts as $ partFileName => &$ partContent ) {
322
+ $ partVariables = $ this ->getVariablesForPart ($ partContent );
323
+
324
+ $ partSearchReplaces = array ();
325
+ foreach ($ searchReplace as $ search => $ replace ) {
326
+ if (!in_array ($ search , $ partVariables )) {
327
+ continue ;
328
+ }
329
+
330
+ // get image path and size
331
+ $ width = 115 ;
332
+ $ height = 70 ;
333
+ if (is_array ($ replace ) && isset ($ replace ["path " ])) {
334
+ $ imgPath = $ replace ["path " ];
335
+ if (isset ($ replace ["width " ])) {
336
+ $ width = $ replace ["width " ];
337
+ }
338
+ if (isset ($ replace ["height " ])) {
339
+ $ height = $ replace ["height " ];
340
+ }
341
+ } else {
342
+ $ imgPath = $ replace ;
343
+ }
344
+
345
+ // get image index
346
+ $ imgIndex = $ this ->getNextRelationsIndex ($ partFileName );
347
+ $ rid = 'rId ' . $ imgIndex ;
348
+
349
+ // get image embed name
350
+ if (isset ($ this ->tempDocumentNewImages [$ imgPath ])) {
351
+ $ imgName = $ this ->tempDocumentNewImages [$ imgPath ];
352
+ } else {
353
+ // transform extension
354
+ $ imgExt = pathinfo ($ imgPath , PATHINFO_EXTENSION );
355
+ if (isset ($ extTransform )) {
356
+ $ imgExt = $ extTransform [$ imgExt ];
357
+ }
358
+
359
+ // add image to document
360
+ $ imgName = 'image ' . $ imgIndex . '_ ' . pathinfo ($ partFileName , PATHINFO_FILENAME ) . '. ' . $ imgExt ;
361
+ $ this ->zipClass ->pclzipAddFile ($ imgPath , 'word/media/ ' . $ imgName );
362
+ $ this ->tempDocumentNewImages [$ imgPath ] = $ imgName ;
363
+
364
+ // setup type for image
365
+ $ xmlImageType = str_replace (array ('{IMG} ' , '{EXT} ' ), array ($ imgName , $ imgExt ), $ typeTpl ) ;
366
+ $ this ->tempDocumentContentTypes = str_replace ('</Types> ' , $ xmlImageType , $ this ->tempDocumentContentTypes ) . '</Types> ' ;
367
+ }
368
+
369
+ $ xmlImage = str_replace (array ('{RID} ' , '{WIDTH} ' , '{HEIGHT} ' ), array ($ rid , $ width , $ height ), $ imgTpl ) ;
370
+ $ xmlImageRelation = str_replace (array ('{RID} ' , '{IMG} ' ), array ($ rid , $ imgName ), $ relationTpl );
371
+
372
+ if (!isset ($ this ->tempDocumentRelations [$ partFileName ])) {
373
+ // create new relations file
374
+ $ this ->tempDocumentRelations [$ partFileName ] = $ newRelationsTpl ;
375
+ // and add it to content types
376
+ $ xmlRelationsType = str_replace ('{RELS} ' , $ this ->getRelationsName ($ partFileName ), $ newRelationsTypeTpl );
377
+ $ this ->tempDocumentContentTypes = str_replace ('</Types> ' , $ xmlRelationsType , $ this ->tempDocumentContentTypes ) . '</Types> ' ;
378
+ }
379
+
380
+ // add image to relations
381
+ $ this ->tempDocumentRelations [$ partFileName ] = str_replace ('</Relationships> ' , $ xmlImageRelation , $ this ->tempDocumentRelations [$ partFileName ]) . '</Relationships> ' ;
382
+
383
+ // collect prepared replaces
384
+ $ partSearchReplaces ["<w:t> " .self ::ensureMacroCompleted ($ search )."</w:t> " ] = $ xmlImage ;
385
+ }
386
+
387
+ if ($ partSearchReplaces ) {
388
+ $ partContent = $ this ->setValueForPart (array_keys ($ partSearchReplaces ), $ partSearchReplaces , $ partContent , $ limit );
389
+ }
390
+ }
391
+ }
392
+
235
393
/**
236
394
* Returns array of all variables in template.
237
395
*
@@ -389,15 +547,17 @@ public function deleteBlock($blockname)
389
547
public function save ()
390
548
{
391
549
foreach ($ this ->tempDocumentHeaders as $ index => $ xml ) {
392
- $ this ->zipClass -> addFromString ($ this ->getHeaderName ($ index ), $ xml );
550
+ $ this ->savePartWithRels ($ this ->getHeaderName ($ index ), $ xml );
393
551
}
394
552
395
- $ this ->zipClass -> addFromString ($ this ->getMainPartName (), $ this ->tempDocumentMainPart );
553
+ $ this ->savePartWithRels ($ this ->getMainPartName (), $ this ->tempDocumentMainPart );
396
554
397
555
foreach ($ this ->tempDocumentFooters as $ index => $ xml ) {
398
- $ this ->zipClass -> addFromString ($ this ->getFooterName ($ index ), $ xml );
556
+ $ this ->savePartWithRels ($ this ->getFooterName ($ index ), $ xml );
399
557
}
400
558
559
+ $ this ->zipClass ->addFromString ($ this ->getDocumentContentTypesName (), $ this ->tempDocumentContentTypes );
560
+
401
561
// Close zip file
402
562
if (false === $ this ->zipClass ->close ()) {
403
563
throw new Exception ('Could not close zip file. ' );
@@ -406,6 +566,21 @@ public function save()
406
566
return $ this ->tempDocumentFilename ;
407
567
}
408
568
569
+ /**
570
+ * @param string $fileName
571
+ * @param string $xml
572
+ *
573
+ * @return void
574
+ */
575
+ protected function savePartWithRels ($ fileName , $ xml )
576
+ {
577
+ $ this ->zipClass ->addFromString ($ fileName , $ xml );
578
+ if (isset ($ this ->tempDocumentRelations [$ fileName ])) {
579
+ $ relsFileName = $ this ->getRelationsName ($ fileName );
580
+ $ this ->zipClass ->addFromString ($ relsFileName , $ this ->tempDocumentRelations [$ fileName ]);
581
+ }
582
+ }
583
+
409
584
/**
410
585
* Saves the result document to the user defined file.
411
586
*
@@ -521,6 +696,34 @@ protected function getFooterName($index)
521
696
return sprintf ('word/footer%d.xml ' , $ index );
522
697
}
523
698
699
+ /**
700
+ * Get the name of the relations file for document part.
701
+ *
702
+ * @param string $docuemntPartName
703
+ *
704
+ * @return string
705
+ */
706
+ protected function getRelationsName ($ documentPartName )
707
+ {
708
+ return 'word/_rels/ ' .pathinfo ($ documentPartName , PATHINFO_BASENAME ).'.rels ' ;
709
+ }
710
+
711
+ protected function getNextRelationsIndex ($ documentPartName )
712
+ {
713
+ if (isset ($ this ->tempDocumentRelations [$ documentPartName ])) {
714
+ return substr_count ($ this ->tempDocumentRelations [$ documentPartName ], '<Relationship ' );
715
+ }
716
+ return 1 ;
717
+ }
718
+
719
+ /**
720
+ * @return string
721
+ */
722
+ protected function getDocumentContentTypesName ()
723
+ {
724
+ return '[Content_Types].xml ' ;
725
+ }
726
+
524
727
/**
525
728
* Find the start position of the nearest table row before $offset.
526
729
*
0 commit comments