Excel5.php 41 KB

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