For God so loved the world, that He gave His only begotten Son, that all who believe in Him should not perish but have everlasting life
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

904 lines
40 KiB

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