PageRenderTime 68ms CodeModel.GetById 9ms app.highlight 48ms RepoModel.GetById 1ms app.codeStats 1ms

/Classes/PHPExcel/Writer/Excel5.php

https://github.com/iGroup/PHPExcel
PHP | 936 lines | 538 code | 138 blank | 260 comment | 50 complexity | 583507b7f8ac725427b0c447c0077c3b MD5 | raw file
  1<?php
  2/**
  3 * PHPExcel
  4 *
  5 * Copyright (c) 2006 - 2012 PHPExcel
  6 *
  7 * This library is free software; you can redistribute it and/or
  8 * modify it under the terms of the GNU Lesser General Public
  9 * License as published by the Free Software Foundation; either
 10 * version 2.1 of the License, or (at your option) any later version.
 11 *
 12 * This library is distributed in the hope that it will be useful,
 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 15 * Lesser General Public License for more details.
 16 *
 17 * You should have received a copy of the GNU Lesser General Public
 18 * License along with this library; if not, write to the Free Software
 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 20 *
 21 * @category   PHPExcel
 22 * @package    PHPExcel_Writer_Excel5
 23 * @copyright  Copyright (c) 2006 - 2012 PHPExcel (http://www.codeplex.com/PHPExcel)
 24 * @license	http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt	LGPL
 25 * @version	##VERSION##, ##DATE##
 26 */
 27
 28
 29/**
 30 * PHPExcel_Writer_Excel5
 31 *
 32 * @category   PHPExcel
 33 * @package    PHPExcel_Writer_Excel5
 34 * @copyright  Copyright (c) 2006 - 2012 PHPExcel (http://www.codeplex.com/PHPExcel)
 35 */
 36class PHPExcel_Writer_Excel5 extends PHPExcel_Writer_Abstract implements PHPExcel_Writer_IWriter
 37{
 38	/**
 39	 * PHPExcel object
 40	 *
 41	 * @var PHPExcel
 42	 */
 43	private $_phpExcel;
 44
 45	/**
 46	 * Total number of shared strings in workbook
 47	 *
 48	 * @var int
 49	 */
 50	private $_str_total		= 0;
 51
 52	/**
 53	 * Number of unique shared strings in workbook
 54	 *
 55	 * @var int
 56	 */
 57	private $_str_unique	= 0;
 58
 59	/**
 60	 * Array of unique shared strings in workbook
 61	 *
 62	 * @var array
 63	 */
 64	private $_str_table		= array();
 65
 66	/**
 67	 * Color cache. Mapping between RGB value and color index.
 68	 *
 69	 * @var array
 70	 */
 71	private $_colors;
 72
 73	/**
 74	 * Formula parser
 75	 *
 76	 * @var PHPExcel_Writer_Excel5_Parser
 77	 */
 78	private $_parser;
 79
 80	/**
 81	 * Identifier clusters for drawings. Used in MSODRAWINGGROUP record.
 82	 *
 83	 * @var array
 84	 */
 85	private $_IDCLs;
 86
 87	/**
 88	 * Basic OLE object summary information
 89	 *
 90	 * @var array
 91	 */
 92	private $_summaryInformation;
 93
 94	/**
 95	 * Extended OLE object document summary information
 96	 *
 97	 * @var array
 98	 */
 99	private $_documentSummaryInformation;
100
101	/**
102	 * Create a new PHPExcel_Writer_Excel5
103	 *
104	 * @param	PHPExcel	$phpExcel	PHPExcel object
105	 */
106	public function __construct(PHPExcel $phpExcel) {
107		$this->_phpExcel	= $phpExcel;
108
109		$this->_parser		= new PHPExcel_Writer_Excel5_Parser();
110	}
111
112	/**
113	 * Save PHPExcel to file
114	 *
115	 * @param	string		$pFilename
116	 * @throws	PHPExcel_Writer_Exception
117	 */
118	public function save($pFilename = null) {
119
120		// garbage collect
121		$this->_phpExcel->garbageCollect();
122
123		$saveDebugLog = PHPExcel_Calculation::getInstance()->writeDebugLog;
124		PHPExcel_Calculation::getInstance()->writeDebugLog = false;
125		$saveDateReturnType = PHPExcel_Calculation_Functions::getReturnDateType();
126		PHPExcel_Calculation_Functions::setReturnDateType(PHPExcel_Calculation_Functions::RETURNDATE_EXCEL);
127
128		// initialize colors array
129		$this->_colors          = array();
130
131		// Initialise workbook writer
132		$this->_writerWorkbook = new PHPExcel_Writer_Excel5_Workbook($this->_phpExcel,
133																	 $this->_str_total, $this->_str_unique, $this->_str_table,
134																	 $this->_colors, $this->_parser);
135
136		// Initialise worksheet writers
137		$countSheets = $this->_phpExcel->getSheetCount();
138		for ($i = 0; $i < $countSheets; ++$i) {
139			$this->_writerWorksheets[$i] = new PHPExcel_Writer_Excel5_Worksheet($this->_str_total, $this->_str_unique,
140																			   $this->_str_table, $this->_colors,
141																			   $this->_parser,
142																			   $this->_preCalculateFormulas,
143																			   $this->_phpExcel->getSheet($i));
144		}
145
146		// build Escher objects. Escher objects for workbooks needs to be build before Escher object for workbook.
147		$this->_buildWorksheetEschers();
148		$this->_buildWorkbookEscher();
149
150		// add 15 identical cell style Xfs
151		// for now, we use the first cellXf instead of cellStyleXf
152		$cellXfCollection = $this->_phpExcel->getCellXfCollection();
153		for ($i = 0; $i < 15; ++$i) {
154			$this->_writerWorkbook->addXfWriter($cellXfCollection[0], true);
155		}
156
157		// add all the cell Xfs
158		foreach ($this->_phpExcel->getCellXfCollection() as $style) {
159			$this->_writerWorkbook->addXfWriter($style, false);
160		}
161
162		// add fonts from rich text eleemnts
163		for ($i = 0; $i < $countSheets; ++$i) {
164			foreach ($this->_writerWorksheets[$i]->_phpSheet->getCellCollection() as $cellID) {
165				$cell = $this->_writerWorksheets[$i]->_phpSheet->getCell($cellID);
166				$cVal = $cell->getValue();
167				if ($cVal instanceof PHPExcel_RichText) {
168					$elements = $cVal->getRichTextElements();
169					foreach ($elements as $element) {
170						if ($element instanceof PHPExcel_RichText_Run) {
171							$font = $element->getFont();
172							$this->_writerWorksheets[$i]->_fntHashIndex[$font->getHashCode()] = $this->_writerWorkbook->_addFont($font);
173						}
174					}
175				}
176			}
177		}
178
179		// initialize OLE file
180		$workbookStreamName = 'Workbook';
181		$OLE = new PHPExcel_Shared_OLE_PPS_File(PHPExcel_Shared_OLE::Asc2Ucs($workbookStreamName));
182
183		// Write the worksheet streams before the global workbook stream,
184		// because the byte sizes of these are needed in the global workbook stream
185		$worksheetSizes = array();
186		for ($i = 0; $i < $countSheets; ++$i) {
187			$this->_writerWorksheets[$i]->close();
188			$worksheetSizes[] = $this->_writerWorksheets[$i]->_datasize;
189		}
190
191		// add binary data for global workbook stream
192		$OLE->append( $this->_writerWorkbook->writeWorkbook($worksheetSizes) );
193
194		// add binary data for sheet streams
195		for ($i = 0; $i < $countSheets; ++$i) {
196			$OLE->append($this->_writerWorksheets[$i]->getData());
197		}
198
199		$this->_documentSummaryInformation = $this->_writeDocumentSummaryInformation();
200		// initialize OLE Document Summary Information
201		if(isset($this->_documentSummaryInformation) && !empty($this->_documentSummaryInformation)){
202			$OLE_DocumentSummaryInformation = new PHPExcel_Shared_OLE_PPS_File(PHPExcel_Shared_OLE::Asc2Ucs(chr(5) . 'DocumentSummaryInformation'));
203			$OLE_DocumentSummaryInformation->append($this->_documentSummaryInformation);
204		}
205
206		$this->_summaryInformation = $this->_writeSummaryInformation();
207		// initialize OLE Summary Information
208		if(isset($this->_summaryInformation) && !empty($this->_summaryInformation)){
209		  $OLE_SummaryInformation = new PHPExcel_Shared_OLE_PPS_File(PHPExcel_Shared_OLE::Asc2Ucs(chr(5) . 'SummaryInformation'));
210		  $OLE_SummaryInformation->append($this->_summaryInformation);
211		}
212
213		// define OLE Parts
214		$arrRootData = array($OLE);
215		// initialize OLE Properties file
216		if(isset($OLE_SummaryInformation)){
217			$arrRootData[] = $OLE_SummaryInformation;
218		}
219		// initialize OLE Extended Properties file
220		if(isset($OLE_DocumentSummaryInformation)){
221			$arrRootData[] = $OLE_DocumentSummaryInformation;
222		}
223
224		$root = new PHPExcel_Shared_OLE_PPS_Root(time(), time(), $arrRootData);
225		// save the OLE file
226		$res = $root->save($pFilename);
227
228		PHPExcel_Calculation_Functions::setReturnDateType($saveDateReturnType);
229		PHPExcel_Calculation::getInstance()->writeDebugLog = $saveDebugLog;
230	}
231
232	/**
233	 * Set temporary storage directory
234	 *
235	 * @deprecated
236	 * @param	string	$pValue		Temporary storage directory
237	 * @throws	PHPExcel_Writer_Exception	Exception when directory does not exist
238	 * @return PHPExcel_Writer_Excel5
239	 */
240	public function setTempDir($pValue = '') {
241		return $this;
242	}
243
244	/**
245	 * Build the Worksheet Escher objects
246	 *
247	 */
248	private function _buildWorksheetEschers()
249	{
250		// 1-based index to BstoreContainer
251		$blipIndex = 0;
252		$lastReducedSpId = 0;
253		$lastSpId = 0;
254
255		foreach ($this->_phpExcel->getAllsheets() as $sheet) {
256			// sheet index
257			$sheetIndex = $sheet->getParent()->getIndex($sheet);
258
259			$escher = null;
260
261			// check if there are any shapes for this sheet
262			$filterRange = $sheet->getAutoFilter()->getRange();
263			if (count($sheet->getDrawingCollection()) == 0 && empty($filterRange)) {
264				continue;
265			}
266
267			// create intermediate Escher object
268			$escher = new PHPExcel_Shared_Escher();
269
270			// dgContainer
271			$dgContainer = new PHPExcel_Shared_Escher_DgContainer();
272
273			// set the drawing index (we use sheet index + 1)
274			$dgId = $sheet->getParent()->getIndex($sheet) + 1;
275			$dgContainer->setDgId($dgId);
276			$escher->setDgContainer($dgContainer);
277
278			// spgrContainer
279			$spgrContainer = new PHPExcel_Shared_Escher_DgContainer_SpgrContainer();
280			$dgContainer->setSpgrContainer($spgrContainer);
281
282			// add one shape which is the group shape
283			$spContainer = new PHPExcel_Shared_Escher_DgContainer_SpgrContainer_SpContainer();
284			$spContainer->setSpgr(true);
285			$spContainer->setSpType(0);
286			$spContainer->setSpId(($sheet->getParent()->getIndex($sheet) + 1) << 10);
287			$spgrContainer->addChild($spContainer);
288
289			// add the shapes
290
291			$countShapes[$sheetIndex] = 0; // count number of shapes (minus group shape), in sheet
292
293			foreach ($sheet->getDrawingCollection() as $drawing) {
294				++$blipIndex;
295
296				++$countShapes[$sheetIndex];
297
298				// add the shape
299				$spContainer = new PHPExcel_Shared_Escher_DgContainer_SpgrContainer_SpContainer();
300
301				// set the shape type
302				$spContainer->setSpType(0x004B);
303				// set the shape flag
304				$spContainer->setSpFlag(0x02);
305
306				// set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
307				$reducedSpId = $countShapes[$sheetIndex];
308				$spId = $reducedSpId
309					| ($sheet->getParent()->getIndex($sheet) + 1) << 10;
310				$spContainer->setSpId($spId);
311
312				// keep track of last reducedSpId
313				$lastReducedSpId = $reducedSpId;
314
315				// keep track of last spId
316				$lastSpId = $spId;
317
318				// set the BLIP index
319				$spContainer->setOPT(0x4104, $blipIndex);
320
321				// set coordinates and offsets, client anchor
322				$coordinates = $drawing->getCoordinates();
323				$offsetX = $drawing->getOffsetX();
324				$offsetY = $drawing->getOffsetY();
325				$width = $drawing->getWidth();
326				$height = $drawing->getHeight();
327
328				$twoAnchor = PHPExcel_Shared_Excel5::oneAnchor2twoAnchor($sheet, $coordinates, $offsetX, $offsetY, $width, $height);
329
330				$spContainer->setStartCoordinates($twoAnchor['startCoordinates']);
331				$spContainer->setStartOffsetX($twoAnchor['startOffsetX']);
332				$spContainer->setStartOffsetY($twoAnchor['startOffsetY']);
333				$spContainer->setEndCoordinates($twoAnchor['endCoordinates']);
334				$spContainer->setEndOffsetX($twoAnchor['endOffsetX']);
335				$spContainer->setEndOffsetY($twoAnchor['endOffsetY']);
336
337				$spgrContainer->addChild($spContainer);
338			}
339
340			// AutoFilters
341			if(!empty($filterRange)){
342				$rangeBounds = PHPExcel_Cell::rangeBoundaries($filterRange);
343				$iNumColStart = $rangeBounds[0][0];
344				$iNumColEnd = $rangeBounds[1][0];
345
346				$iInc = $iNumColStart;
347				while($iInc <= $iNumColEnd){
348					++$countShapes[$sheetIndex];
349
350					// create an Drawing Object for the dropdown
351					$oDrawing  = new PHPExcel_Worksheet_BaseDrawing();
352					// get the coordinates of drawing
353					$cDrawing   = PHPExcel_Cell::stringFromColumnIndex($iInc - 1) . $rangeBounds[0][1];
354					$oDrawing->setCoordinates($cDrawing);
355					$oDrawing->setWorksheet($sheet);
356
357					// add the shape
358					$spContainer = new PHPExcel_Shared_Escher_DgContainer_SpgrContainer_SpContainer();
359					// set the shape type
360					$spContainer->setSpType(0x00C9);
361					// set the shape flag
362					$spContainer->setSpFlag(0x01);
363
364					// set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
365					$reducedSpId = $countShapes[$sheetIndex];
366					$spId = $reducedSpId
367						| ($sheet->getParent()->getIndex($sheet) + 1) << 10;
368					$spContainer->setSpId($spId);
369
370					// keep track of last reducedSpId
371					$lastReducedSpId = $reducedSpId;
372
373					// keep track of last spId
374					$lastSpId = $spId;
375
376					$spContainer->setOPT(0x007F, 0x01040104); // Protection -> fLockAgainstGrouping
377					$spContainer->setOPT(0x00BF, 0x00080008); // Text -> fFitTextToShape
378					$spContainer->setOPT(0x01BF, 0x00010000); // Fill Style -> fNoFillHitTest
379					$spContainer->setOPT(0x01FF, 0x00080000); // Line Style -> fNoLineDrawDash
380					$spContainer->setOPT(0x03BF, 0x000A0000); // Group Shape -> fPrint
381
382					// set coordinates and offsets, client anchor
383					$endCoordinates = PHPExcel_Cell::stringFromColumnIndex(PHPExcel_Cell::stringFromColumnIndex($iInc - 1));
384					$endCoordinates .= $rangeBounds[0][1] + 1;
385
386					$spContainer->setStartCoordinates($cDrawing);
387					$spContainer->setStartOffsetX(0);
388					$spContainer->setStartOffsetY(0);
389					$spContainer->setEndCoordinates($endCoordinates);
390					$spContainer->setEndOffsetX(0);
391					$spContainer->setEndOffsetY(0);
392
393					$spgrContainer->addChild($spContainer);
394					$iInc++;
395				}
396			}
397
398			// identifier clusters, used for workbook Escher object
399			$this->_IDCLs[$dgId] = $lastReducedSpId;
400
401			// set last shape index
402			$dgContainer->setLastSpId($lastSpId);
403
404			// set the Escher object
405			$this->_writerWorksheets[$sheetIndex]->setEscher($escher);
406		}
407	}
408
409	/**
410	 * Build the Escher object corresponding to the MSODRAWINGGROUP record
411	 */
412	private function _buildWorkbookEscher()
413	{
414		$escher = null;
415
416		// any drawings in this workbook?
417		$found = false;
418		foreach ($this->_phpExcel->getAllSheets() as $sheet) {
419			if (count($sheet->getDrawingCollection()) > 0) {
420				$found = true;
421				break;
422			}
423		}
424
425		// nothing to do if there are no drawings
426		if (!$found) {
427			return;
428		}
429
430		// if we reach here, then there are drawings in the workbook
431		$escher = new PHPExcel_Shared_Escher();
432
433		// dggContainer
434		$dggContainer = new PHPExcel_Shared_Escher_DggContainer();
435		$escher->setDggContainer($dggContainer);
436
437		// set IDCLs (identifier clusters)
438		$dggContainer->setIDCLs($this->_IDCLs);
439
440		// this loop is for determining maximum shape identifier of all drawing
441		$spIdMax = 0;
442		$totalCountShapes = 0;
443		$countDrawings = 0;
444
445		foreach ($this->_phpExcel->getAllsheets() as $sheet) {
446			$sheetCountShapes = 0; // count number of shapes (minus group shape), in sheet
447
448			if (count($sheet->getDrawingCollection()) > 0) {
449				++$countDrawings;
450
451				foreach ($sheet->getDrawingCollection() as $drawing) {
452					++$sheetCountShapes;
453					++$totalCountShapes;
454
455					$spId = $sheetCountShapes
456						| ($this->_phpExcel->getIndex($sheet) + 1) << 10;
457					$spIdMax = max($spId, $spIdMax);
458				}
459			}
460		}
461
462		$dggContainer->setSpIdMax($spIdMax + 1);
463		$dggContainer->setCDgSaved($countDrawings);
464		$dggContainer->setCSpSaved($totalCountShapes + $countDrawings); // total number of shapes incl. one group shapes per drawing
465
466		// bstoreContainer
467		$bstoreContainer = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer();
468		$dggContainer->setBstoreContainer($bstoreContainer);
469
470		// the BSE's (all the images)
471		foreach ($this->_phpExcel->getAllsheets() as $sheet) {
472			foreach ($sheet->getDrawingCollection() as $drawing) {
473				if ($drawing instanceof PHPExcel_Worksheet_Drawing) {
474
475					$filename = $drawing->getPath();
476
477					list($imagesx, $imagesy, $imageFormat) = getimagesize($filename);
478
479					switch ($imageFormat) {
480
481					case 1: // GIF, not supported by BIFF8, we convert to PNG
482						$blipType = PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_PNG;
483						ob_start();
484						imagepng(imagecreatefromgif($filename));
485						$blipData = ob_get_contents();
486						ob_end_clean();
487						break;
488
489					case 2: // JPEG
490						$blipType = PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_JPEG;
491						$blipData = file_get_contents($filename);
492						break;
493
494					case 3: // PNG
495						$blipType = PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_PNG;
496						$blipData = file_get_contents($filename);
497						break;
498
499					case 6: // Windows DIB (BMP), we convert to PNG
500						$blipType = PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_PNG;
501						ob_start();
502						imagepng(PHPExcel_Shared_Drawing::imagecreatefrombmp($filename));
503						$blipData = ob_get_contents();
504						ob_end_clean();
505						break;
506
507					default: continue 2;
508
509					}
510
511					$blip = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE_Blip();
512					$blip->setData($blipData);
513
514					$BSE = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE();
515					$BSE->setBlipType($blipType);
516					$BSE->setBlip($blip);
517
518					$bstoreContainer->addBSE($BSE);
519
520				} else if ($drawing instanceof PHPExcel_Worksheet_MemoryDrawing) {
521
522					switch ($drawing->getRenderingFunction()) {
523
524					case PHPExcel_Worksheet_MemoryDrawing::RENDERING_JPEG:
525						$blipType = PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_JPEG;
526						$renderingFunction = 'imagejpeg';
527						break;
528
529					case PHPExcel_Worksheet_MemoryDrawing::RENDERING_GIF:
530					case PHPExcel_Worksheet_MemoryDrawing::RENDERING_PNG:
531					case PHPExcel_Worksheet_MemoryDrawing::RENDERING_DEFAULT:
532						$blipType = PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_PNG;
533						$renderingFunction = 'imagepng';
534						break;
535
536					}
537
538					ob_start();
539					call_user_func($renderingFunction, $drawing->getImageResource());
540					$blipData = ob_get_contents();
541					ob_end_clean();
542
543					$blip = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE_Blip();
544					$blip->setData($blipData);
545
546					$BSE = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE();
547					$BSE->setBlipType($blipType);
548					$BSE->setBlip($blip);
549
550					$bstoreContainer->addBSE($BSE);
551				}
552			}
553		}
554
555		// Set the Escher object
556		$this->_writerWorkbook->setEscher($escher);
557	}
558
559	/**
560	 * Build the OLE Part for DocumentSummary Information
561	 * @return string
562	 */
563	private function _writeDocumentSummaryInformation(){
564
565		// offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
566		$data = pack('v', 0xFFFE);
567		// offset: 2; size: 2;
568		$data .= pack('v', 0x0000);
569		// offset: 4; size: 2; OS version
570		$data .= pack('v', 0x0106);
571		// offset: 6; size: 2; OS indicator
572		$data .= pack('v', 0x0002);
573		// offset: 8; size: 16
574		$data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00);
575		// offset: 24; size: 4; section count
576		$data .= pack('V', 0x0001);
577
578		// offset: 28; size: 16; first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae
579		$data .= pack('vvvvvvvv', 0xD502, 0xD5CD, 0x2E9C, 0x101B, 0x9793, 0x0008, 0x2C2B, 0xAEF9);
580		// offset: 44; size: 4; offset of the start
581		$data .= pack('V', 0x30);
582
583		// SECTION
584		$dataSection = array();
585		$dataSection_NumProps = 0;
586		$dataSection_Summary = '';
587		$dataSection_Content = '';
588
589		// GKPIDDSI_CODEPAGE: CodePage
590		$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x01),
591							   'offset' => array('pack' => 'V'),
592							   'type' 	=> array('pack' => 'V', 'data' => 0x02), // 2 byte signed integer
593							   'data'	=> array('data' => 1252));
594		$dataSection_NumProps++;
595
596		// GKPIDDSI_CATEGORY : Category
597		if($this->_phpExcel->getProperties()->getCategory()){
598			$dataProp = $this->_phpExcel->getProperties()->getCategory();
599			$dataProp = 'Test result file';
600			$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x02),
601								   'offset' => array('pack' => 'V'),
602								   'type' 	=> array('pack' => 'V', 'data' => 0x1E),
603								   'data'	=> array('data' => $dataProp, 'length' => strlen($dataProp)));
604			$dataSection_NumProps++;
605		}
606		// GKPIDDSI_VERSION :Version of the application that wrote the property storage
607		$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x17),
608							   'offset' => array('pack' => 'V'),
609							   'type' 	=> array('pack' => 'V', 'data' => 0x03),
610							   'data'	=> array('pack' => 'V', 'data' => 0x000C0000));
611		$dataSection_NumProps++;
612		// GKPIDDSI_SCALE : FALSE
613		$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x0B),
614							   'offset' => array('pack' => 'V'),
615							   'type' 	=> array('pack' => 'V', 'data' => 0x0B),
616							   'data'	=> array('data' => false));
617		$dataSection_NumProps++;
618		// GKPIDDSI_LINKSDIRTY : True if any of the values for the linked properties have changed outside of the application
619		$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x10),
620							   'offset' => array('pack' => 'V'),
621							   'type' 	=> array('pack' => 'V', 'data' => 0x0B),
622							   'data'	=> array('data' => false));
623		$dataSection_NumProps++;
624		// GKPIDDSI_SHAREDOC : FALSE
625		$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x13),
626							   'offset' => array('pack' => 'V'),
627							   'type' 	=> array('pack' => 'V', 'data' => 0x0B),
628							   'data'	=> array('data' => false));
629		$dataSection_NumProps++;
630		// GKPIDDSI_HYPERLINKSCHANGED : True if any of the values for the _PID_LINKS (hyperlink text) have changed outside of the application
631		$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x16),
632							   'offset' => array('pack' => 'V'),
633							   'type' 	=> array('pack' => 'V', 'data' => 0x0B),
634							   'data'	=> array('data' => false));
635		$dataSection_NumProps++;
636
637		// GKPIDDSI_DOCSPARTS
638		// MS-OSHARED p75 (2.3.3.2.2.1)
639		// Structure is VtVecUnalignedLpstrValue (2.3.3.1.9)
640		// cElements
641		$dataProp = pack('v', 0x0001);
642		$dataProp .= pack('v', 0x0000);
643		// array of UnalignedLpstr
644		  // cch
645		  $dataProp .= pack('v', 0x000A);
646		  $dataProp .= pack('v', 0x0000);
647		  // value
648		  $dataProp .= 'Worksheet'.chr(0);
649
650		$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x0D),
651							   'offset' => array('pack' => 'V'),
652							   'type' 	=> array('pack' => 'V', 'data' => 0x101E),
653							   'data'	=> array('data' => $dataProp, 'length' => strlen($dataProp)));
654		$dataSection_NumProps++;
655
656		// GKPIDDSI_HEADINGPAIR
657		// VtVecHeadingPairValue
658		  // cElements
659		  $dataProp = pack('v', 0x0002);
660		  $dataProp .= pack('v', 0x0000);
661		  // Array of vtHeadingPair
662		    // vtUnalignedString - headingString
663		      // stringType
664		      $dataProp .= pack('v', 0x001E);
665		      // padding
666		      $dataProp .= pack('v', 0x0000);
667		      // UnalignedLpstr
668		        // cch
669		        $dataProp .= pack('v', 0x0013);
670		        $dataProp .= pack('v', 0x0000);
671		        // value
672		        $dataProp .= 'Feuilles de calcul';
673		    // vtUnalignedString - headingParts
674		      // wType : 0x0003 = 32 bit signed integer
675		      $dataProp .= pack('v', 0x0300);
676		      // padding
677		      $dataProp .= pack('v', 0x0000);
678		      // value
679		      $dataProp .= pack('v', 0x0100);
680		      $dataProp .= pack('v', 0x0000);
681			  $dataProp .= pack('v', 0x0000);
682		      $dataProp .= pack('v', 0x0000);
683
684        $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x0C),
685        					   'offset' => array('pack' => 'V'),
686        					   'type' 	=> array('pack' => 'V', 'data' => 0x100C),
687        					   'data'	=> array('data' => $dataProp, 'length' => strlen($dataProp)));
688        $dataSection_NumProps++;
689
690		// 		4 	Section Length
691		//		4 	Property count
692		//		8 * $dataSection_NumProps (8 =  ID (4) + OffSet(4))
693		$dataSection_Content_Offset = 8 + $dataSection_NumProps * 8;
694		foreach ($dataSection as $dataProp){
695			// Summary
696			$dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']);
697			// Offset
698			$dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset);
699			// DataType
700			$dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']);
701			// Data
702			if($dataProp['type']['data'] == 0x02){ // 2 byte signed integer
703				$dataSection_Content .= pack('V', $dataProp['data']['data']);
704
705				$dataSection_Content_Offset += 4 + 4;
706			}
707			elseif($dataProp['type']['data'] == 0x03){ // 4 byte signed integer
708				$dataSection_Content .= pack('V', $dataProp['data']['data']);
709
710				$dataSection_Content_Offset += 4 + 4;
711			}
712			elseif($dataProp['type']['data'] == 0x0B){ // Boolean
713				if($dataProp['data']['data'] == false){
714					$dataSection_Content .= pack('V', 0x0000);
715				} else {
716					$dataSection_Content .= pack('V', 0x0001);
717				}
718				$dataSection_Content_Offset += 4 + 4;
719			}
720			elseif($dataProp['type']['data'] == 0x1E){ // null-terminated string prepended by dword string length
721				// Null-terminated string
722				$dataProp['data']['data'] .= chr(0);
723				$dataProp['data']['length'] += 1;
724				// Complete the string with null string for being a %4
725				$dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4)==4 ? 0 : (4 - $dataProp['data']['length'] % 4));
726				$dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT);
727
728				$dataSection_Content .= pack('V', $dataProp['data']['length']);
729				$dataSection_Content .= $dataProp['data']['data'];
730
731				$dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']);
732			}
733			elseif($dataProp['type']['data'] == 0x40){ // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
734				$dataSection_Content .= $dataProp['data']['data'];
735
736				$dataSection_Content_Offset += 4 + 8;
737			}
738			else {
739				// Data Type Not Used at the moment
740				$dataSection_Content .= $dataProp['data']['data'];
741
742				$dataSection_Content_Offset += 4 + $dataProp['data']['length'];
743			}
744		}
745		// Now $dataSection_Content_Offset contains the size of the content
746
747		// section header
748		// offset: $secOffset; size: 4; section length
749		// 		+ x  Size of the content (summary + content)
750		$data .= pack('V', $dataSection_Content_Offset);
751		// offset: $secOffset+4; size: 4; property count
752		$data .= pack('V', $dataSection_NumProps);
753		// Section Summary
754		$data .= $dataSection_Summary;
755		// Section Content
756		$data .= $dataSection_Content;
757
758		return $data;
759	}
760
761	/**
762	 * Build the OLE Part for Summary Information
763	 * @return string
764	 */
765	private function _writeSummaryInformation(){
766		// offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
767		$data = pack('v', 0xFFFE);
768		// offset: 2; size: 2;
769		$data .= pack('v', 0x0000);
770		// offset: 4; size: 2; OS version
771		$data .= pack('v', 0x0106);
772		// offset: 6; size: 2; OS indicator
773		$data .= pack('v', 0x0002);
774		// offset: 8; size: 16
775		$data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00);
776		// offset: 24; size: 4; section count
777		$data .= pack('V', 0x0001);
778
779		// offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9
780		$data .= pack('vvvvvvvv', 0x85E0, 0xF29F, 0x4FF9, 0x1068, 0x91AB, 0x0008, 0x272B, 0xD9B3);
781		// offset: 44; size: 4; offset of the start
782		$data .= pack('V', 0x30);
783
784		// SECTION
785		$dataSection = array();
786		$dataSection_NumProps = 0;
787		$dataSection_Summary = '';
788		$dataSection_Content = '';
789
790		// CodePage : CP-1252
791		$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x01),
792							   'offset' => array('pack' => 'V'),
793							   'type' 	=> array('pack' => 'V', 'data' => 0x02), // 2 byte signed integer
794							   'data'	=> array('data' => 1252));
795		$dataSection_NumProps++;
796
797		//	Title
798		if($this->_phpExcel->getProperties()->getTitle()){
799			$dataProp = $this->_phpExcel->getProperties()->getTitle();
800			$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x02),
801								   'offset' => array('pack' => 'V'),
802								   'type' 	=> array('pack' => 'V', 'data' => 0x1E), // null-terminated string prepended by dword string length
803								   'data'	=> array('data' => $dataProp, 'length' => strlen($dataProp)));
804			$dataSection_NumProps++;
805		}
806		//	Subject
807		if($this->_phpExcel->getProperties()->getSubject()){
808			$dataProp = $this->_phpExcel->getProperties()->getSubject();
809			$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x03),
810								   'offset' => array('pack' => 'V'),
811								   'type' 	=> array('pack' => 'V', 'data' => 0x1E), // null-terminated string prepended by dword string length
812								   'data'	=> array('data' => $dataProp, 'length' => strlen($dataProp)));
813			$dataSection_NumProps++;
814		}
815		//	Author (Creator)
816		if($this->_phpExcel->getProperties()->getCreator()){
817			$dataProp = $this->_phpExcel->getProperties()->getCreator();
818			$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x04),
819								   'offset' => array('pack' => 'V'),
820								   'type' 	=> array('pack' => 'V', 'data' => 0x1E), // null-terminated string prepended by dword string length
821								   'data'	=> array('data' => $dataProp, 'length' => strlen($dataProp)));
822			$dataSection_NumProps++;
823		}
824		//	Keywords
825		if($this->_phpExcel->getProperties()->getKeywords()){
826			$dataProp = $this->_phpExcel->getProperties()->getKeywords();
827			$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x05),
828								   'offset' => array('pack' => 'V'),
829								   'type' 	=> array('pack' => 'V', 'data' => 0x1E), // null-terminated string prepended by dword string length
830								   'data'	=> array('data' => $dataProp, 'length' => strlen($dataProp)));
831			$dataSection_NumProps++;
832		}
833		//	Comments (Description)
834		if($this->_phpExcel->getProperties()->getDescription()){
835			$dataProp = $this->_phpExcel->getProperties()->getDescription();
836			$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x06),
837								   'offset' => array('pack' => 'V'),
838								   'type' 	=> array('pack' => 'V', 'data' => 0x1E), // null-terminated string prepended by dword string length
839								   'data'	=> array('data' => $dataProp, 'length' => strlen($dataProp)));
840			$dataSection_NumProps++;
841		}
842		//	Last Saved By (LastModifiedBy)
843		if($this->_phpExcel->getProperties()->getLastModifiedBy()){
844			$dataProp = $this->_phpExcel->getProperties()->getLastModifiedBy();
845			$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x08),
846								   'offset' => array('pack' => 'V'),
847								   'type' 	=> array('pack' => 'V', 'data' => 0x1E), // null-terminated string prepended by dword string length
848								   'data'	=> array('data' => $dataProp, 'length' => strlen($dataProp)));
849			$dataSection_NumProps++;
850		}
851		//	Created Date/Time
852		if($this->_phpExcel->getProperties()->getCreated()){
853			$dataProp = $this->_phpExcel->getProperties()->getCreated();
854			$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x0C),
855								   'offset' => array('pack' => 'V'),
856								   'type' 	=> array('pack' => 'V', 'data' => 0x40), // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
857								   'data'	=> array('data' => PHPExcel_Shared_OLE::LocalDate2OLE($dataProp)));
858			$dataSection_NumProps++;
859		}
860		//	Modified Date/Time
861		if($this->_phpExcel->getProperties()->getModified()){
862			$dataProp = $this->_phpExcel->getProperties()->getModified();
863			$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x0D),
864								   'offset' => array('pack' => 'V'),
865								   'type' 	=> array('pack' => 'V', 'data' => 0x40), // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
866								   'data'	=> array('data' => PHPExcel_Shared_OLE::LocalDate2OLE($dataProp)));
867			$dataSection_NumProps++;
868		}
869		//	Security
870		$dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x13),
871							   'offset' => array('pack' => 'V'),
872							   'type' 	=> array('pack' => 'V', 'data' => 0x03), // 4 byte signed integer
873							   'data'	=> array('data' => 0x00));
874		$dataSection_NumProps++;
875
876
877		// 		4 	Section Length
878		//		4 	Property count
879		//		8 * $dataSection_NumProps (8 =  ID (4) + OffSet(4))
880		$dataSection_Content_Offset = 8 + $dataSection_NumProps * 8;
881		foreach ($dataSection as $dataProp){
882			// Summary
883			$dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']);
884			// Offset
885			$dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset);
886			// DataType
887			$dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']);
888			// Data
889			if($dataProp['type']['data'] == 0x02){ // 2 byte signed integer
890				$dataSection_Content .= pack('V', $dataProp['data']['data']);
891
892				$dataSection_Content_Offset += 4 + 4;
893			}
894			elseif($dataProp['type']['data'] == 0x03){ // 4 byte signed integer
895				$dataSection_Content .= pack('V', $dataProp['data']['data']);
896
897				$dataSection_Content_Offset += 4 + 4;
898			}
899			elseif($dataProp['type']['data'] == 0x1E){ // null-terminated string prepended by dword string length
900				// Null-terminated string
901				$dataProp['data']['data'] .= chr(0);
902				$dataProp['data']['length'] += 1;
903				// Complete the string with null string for being a %4
904				$dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4)==4 ? 0 : (4 - $dataProp['data']['length'] % 4));
905				$dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT);
906
907				$dataSection_Content .= pack('V', $dataProp['data']['length']);
908				$dataSection_Content .= $dataProp['data']['data'];
909
910				$dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']);
911			}
912			elseif($dataProp['type']['data'] == 0x40){ // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
913				$dataSection_Content .= $dataProp['data']['data'];
914
915				$dataSection_Content_Offset += 4 + 8;
916			}
917			else {
918				// Data Type Not Used at the moment
919			}
920		}
921		// Now $dataSection_Content_Offset contains the size of the content
922
923		// section header
924		// offset: $secOffset; size: 4; section length
925		// 		+ x  Size of the content (summary + content)
926		$data .= pack('V', $dataSection_Content_Offset);
927		// offset: $secOffset+4; size: 4; property count
928		$data .= pack('V', $dataSection_NumProps);
929		// Section Summary
930		$data .= $dataSection_Summary;
931		// Section Content
932		$data .= $dataSection_Content;
933
934		return $data;
935	}
936}