From b1b400fe4520737b55f115049b34ff4c99a0706f Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Wed, 27 May 2020 23:55:16 -0700 Subject: [PATCH 1/6] Xls Writer - Correct Timestamp Bug, Improve Coverage I believe that Xls Writer is 100% covered now. The Xls Writer sets its timestamp incorrectly. The problem is actually in Shared/Ole::localDateToOLE, which converts its timestamp using gmmktime; mktime is correct. If I save a file at 3:00 p.m. in San Francisco, this bug means the time is actually recorded as 3:00 p.m. UTC. A consequence of this is that if you use Phpspreadsheet to read the file and save it as a new Xls, the creation timestamp goes further and further back in time with each generation (or further forward if east of Greenwich). One of the tests added confirms that the creation timestamp is consistent with the start and end times of the test. The major change in coverage is adding tests to save GIF and BMP images, which aren't supported in Xls, but are converted to PNG in the PhpSpreadsheet code. --- samples/images/bmp.bmp | Bin 0 -> 30186 bytes samples/images/gif.gif | Bin 0 -> 1578 bytes src/PhpSpreadsheet/Shared/OLE.php | 9 +- src/PhpSpreadsheet/Writer/Xls.php | 349 ++++++++---------- .../Writer/Xls/XlsGifBmpTest.php | 124 +++++++ 5 files changed, 288 insertions(+), 194 deletions(-) create mode 100644 samples/images/bmp.bmp create mode 100644 samples/images/gif.gif create mode 100644 tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php diff --git a/samples/images/bmp.bmp b/samples/images/bmp.bmp new file mode 100644 index 0000000000000000000000000000000000000000..01fee85ebcd7ca3dda1783f0d0b2c59494e34ac6 GIT binary patch literal 30186 zcmeHQEpHu35cILYKtsboqr=gmkdu5Y(9kgC&;djLguuW+lamY$1N}219WgY_mECcv z)HBmNA3GcSX`hsr*_rO@>e`;!*?BqLAAkP$_tDemcYMFYpMUV@FZ}uK=osHeNB{mD zf4cbj^<)2Rs{nN0HsBuZ$FBMIMX+nWYree#+eh5%eAj$?1-6g4*ZHpb_6lqtaj)}T z^X(PbKH}lk`PJ1GlDfFK*j~)ah=-^7n>TNe)Jx9mC3CAR8)x>U_pZ59W$xwiu^piv zU0+|%jns>Ncy<2i(&lRp$B~p61`be>crvY-%r-*hr~S^M{9r5u8cB2dO!i z^p77uHtDv_KJy?o|NQwgOT9_+ZT6W5sX27+)4b2C>9!iTR+m>{-=|)mSF0e1((3Xm z?EBQ~^J*1j*IX6bHCHuS+kNWwd3A7Wb$J!`ed^I`>airEyb29lNm9KgM|w?_Wh!`6 zzUvxOy(U>8`}JTOh6PjfvS8|RR+vYtS5Gq}9GB!6wJ zzJvYx^{dK0SZ1u))?eu8xTs7?(#8H7RIR_zX{lGnYBj6T z9GG9ee9;Qu$`)h?6X#NbRWXB72O6uwA&*~<>^%^@+g(d8%>uA# zXH!o}bAV9Hz(&oQ;#L}yL#4el>vpH3o8wlBXJ=<4+lD|-#tm$cBh6gCt6UC(ce~Tk z&2jgV()WA^cu5?_4Qz0`X;#7+=^q5|cBdm6%3qfqvvCtajZ1BZsb;0PYQyIgwwKz! zR+}990Qod*Rfdir;Fb~hO5X9}HljJma3_z~s6JU@FXrAVn!&Op+!7H7#i-VE_U&Wj z#jRU&2o5qH!?gX?f6gtT^Ys1l!`&#{RAe0sj z(gu`gX9pkpTBC%Z3p*?q7a7=;pfsYh(M;XY>X)REMdk`%&vCbrqi>LA7S=k|9qnN0 z(1m?5R$!njl_Q3K4gp7e=IvM*(E{O{J=);HqR;WH^6FLDRs}#H-kcz4OqD@Pxxj!W zg1 z5f*uNx`96p0P^kIw^oL?x3`RLjTnLRBAyX|3UC;aHn@XK1O?Cjc?~Aw-Us!Wf_a@K zfVbDyO@+f@^cv0eVH%WrljgwL;N8<@t%+#;aOukyIYCP9G>98|*J7;+WO>c1tKc+#pPVm|9o+(>0;ZWMc zA=jWK8g8iOQlD!zk1&>c>0BzM>v_ZC<$iGBz5F-;K3F%xH%gV7D{Ur=#^QS} z6CZ3asNt}d;s>Z1<4A<=Xw<9^DO<>{An}dURXvp*I4M4R7)3$YX^l8Kp#~caYB-#C ziw{t>3!Y3skGa<@ESk<>TQ@M^Q`(kjl&tKPnrF=v z5C&5V9{Bb_My(5riZIyL<)ySOaUoPCTal26v(d}vL?8dwezx6$Gf#FhL1=B0$u^Dj z)Qe;)8^%9it2lv~-bz#gh~ac7u zQ%&VyZ8AxRDtnqVN4|@j6G-6}J*+mBWCAm0Y7LvDf&IK8K1OTyEm;~(npf^@xtBA2 zD!sDwtW($UwJ?Kl@es31_s_8-M=LeQqJgD@Ue-X(^M_HB!hL7G97xS2*Kotfv!ljq zl8qjx^o58u=R9&^)?BVQ{olQN{C|qsXGPdP#o&eA%lr55ySE2OBna(ZHlOoe{+rL} z0K9&PZvpGV))QFQGS(cim~U3-wN{UHlX~H3i<-Z1{;!eVuKD(NZP$F)e0v49kBB<| EA4~Nmpa1{> literal 0 HcmV?d00001 diff --git a/samples/images/gif.gif b/samples/images/gif.gif new file mode 100644 index 0000000000000000000000000000000000000000..4cf06035b8a0e830eafd83733c77eee7bcb06d0b GIT binary patch literal 1578 zcmXYwdr;8F5yzJpThh!p0w!rhQSCJ1q)yiEP-XP0#?BRI0Q%F7@UAp@Bkjc z6L%G&w15`T5?V$p zXcZlxLv)0W(Fr<559kp+p=b1hUNHa$#6TDrgJ4ipKt)tSWmG|pbVOq#0TD2PM6$>L z86-nwn2eB7vOpHe5?LlIWR)C{Lvlop$q6|n59E2OWAVpFl zWl|xH5Jbe0ff$%UA|h;n4YDCN%tqKKTVRW9i7m4gw#p9JAv%3OqGA*U18PtW zsbMvuM%98^R7+}Et*BLXpbpiMI#ws@R6VFi^`xHFi+a@n8c+jiU=5<-ezPcOk=qgS zXzu?u|9|eIZQz{;V*u{Lp!-+%{{+M;bU*ZD=HcCqC9w%9Q$9Jo=fv8+iQ}p=kG$Qq z;m0_y?UN(br#3x3di|4G@4eGpnKaQ)X?}0-={JVH(o&UmwC2oP!7~{89!TQ+x6@KnKYVGy#>!>$Rr;SIo-=<4{e(~msGhgPu zu=9fn-!E$4cWqkLt3`K`|5knW)6K6fzKeJEZFY}m+es^GV~ReX25qf7R=DJX&u7B; zmAeb#x1W4^ReD_M=C!@)LVW2DDvB!Bb^PYvJ4zq&MG5PBHNN@O2GiWIX=D7>+<^tp z))Y;ArO(aTiRJNeg>hxuzq1LpH8$(3(JxP}Kll33>jeYH%v+J$UfE~qob!drFa87eN^uyt9_byvOUc)^%y54fa^ozJIK?mhHs z{&2f%Kz;GdioFA7n@wd`9_y(1Q_rROyXW4#@b&1V&X$B-@o~5{=C5NEwV)k`U z@f`^zt=mr3)U7Ej{(H`aEj=AIjgQt%-t)qpH4EPF&9U<= 0.0 ? PHP_INT_MAX : PHP_INT_MIN; + // Overflow conditions can't happen on 64-bit system + return ($iTimestamp == $unixTimestamp) ? $iTimestamp : ($unixTimestamp >= 0.0 ? PHP_INT_MAX : PHP_INT_MIN); } } diff --git a/src/PhpSpreadsheet/Writer/Xls.php b/src/PhpSpreadsheet/Writer/Xls.php index cf87d5beee..c8edee01f8 100644 --- a/src/PhpSpreadsheet/Writer/Xls.php +++ b/src/PhpSpreadsheet/Writer/Xls.php @@ -389,25 +389,129 @@ private function buildWorksheetEschers(): void } } + private function processMemoryDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing, string $renderingFunctionx): void + { + switch ($renderingFunctionx) { + case MemoryDrawing::RENDERING_JPEG: + $blipType = BSE::BLIPTYPE_JPEG; + $renderingFunction = 'imagejpeg'; + + break; + default: + $blipType = BSE::BLIPTYPE_PNG; + $renderingFunction = 'imagepng'; + + break; + } + + ob_start(); + call_user_func($renderingFunction, $drawing->getImageResource()); + $blipData = ob_get_contents(); + ob_end_clean(); + + $blip = new Blip(); + $blip->setData($blipData); + + $BSE = new BSE(); + $BSE->setBlipType($blipType); + $BSE->setBlip($blip); + + $bstoreContainer->addBSE($BSE); + } + + private function processDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing): void + { + $blipData = ''; + $filename = $drawing->getPath(); + + [$imagesx, $imagesy, $imageFormat] = getimagesize($filename); + + switch ($imageFormat) { + case 1: // GIF, not supported by BIFF8, we convert to PNG + $blipType = BSE::BLIPTYPE_PNG; + ob_start(); + imagepng(imagecreatefromgif($filename)); + $blipData = ob_get_contents(); + ob_end_clean(); + + break; + case 2: // JPEG + $blipType = BSE::BLIPTYPE_JPEG; + $blipData = file_get_contents($filename); + + break; + case 3: // PNG + $blipType = BSE::BLIPTYPE_PNG; + $blipData = file_get_contents($filename); + + break; + case 6: // Windows DIB (BMP), we convert to PNG + $blipType = BSE::BLIPTYPE_PNG; + ob_start(); + imagepng(SharedDrawing::imagecreatefrombmp($filename)); + $blipData = ob_get_contents(); + ob_end_clean(); + + break; + } + if ($blipData) { + $blip = new Blip(); + $blip->setData($blipData); + + $BSE = new BSE(); + $BSE->setBlipType($blipType); + $BSE->setBlip($blip); + + $bstoreContainer->addBSE($BSE); + } + } + + private function processBaseDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing): void + { + if ($drawing instanceof Drawing) { + $this->processDrawing($bstoreContainer, $drawing); + } elseif ($drawing instanceof MemoryDrawing) { + $this->processMemoryDrawing($bstoreContainer, $drawing, $drawing->getRenderingFunction()); + } + } + /** - * Build the Escher object corresponding to the MSODRAWINGGROUP record. + * Total number of shared strings in workbook. + * + * @var bool */ - private function buildWorkbookEscher(): void + private $simulateNoGd = false; + + public function setSimulateNoGd(bool $arg): void { - $escher = null; + $this->simulateNoGd = $arg; + } + private function checkForDrawings(): bool + { // any drawings in this workbook? $found = false; foreach ($this->spreadsheet->getAllSheets() as $sheet) { if (count($sheet->getDrawingCollection()) > 0) { $found = true; + if ($this->simulateNoGd || !extension_loaded('gd')) { + throw new RuntimeException('Saving images in xls requires gd extension'); + } break; } } + return $found; + } + + /** + * Build the Escher object corresponding to the MSODRAWINGGROUP record. + */ + private function buildWorkbookEscher(): void + { // nothing to do if there are no drawings - if (!$found) { + if (!$this->checkForDrawings()) { return; } @@ -429,17 +533,16 @@ private function buildWorkbookEscher(): void foreach ($this->spreadsheet->getAllsheets() as $sheet) { $sheetCountShapes = 0; // count number of shapes (minus group shape), in sheet - if (count($sheet->getDrawingCollection()) > 0) { - ++$countDrawings; - - foreach ($sheet->getDrawingCollection() as $drawing) { - ++$sheetCountShapes; - ++$totalCountShapes; + $addCount = 0; + foreach ($sheet->getDrawingCollection() as $drawing) { + $addCount = 1; + ++$sheetCountShapes; + ++$totalCountShapes; - $spId = $sheetCountShapes | ($this->spreadsheet->getIndex($sheet) + 1) << 10; - $spIdMax = max($spId, $spIdMax); - } + $spId = $sheetCountShapes | ($this->spreadsheet->getIndex($sheet) + 1) << 10; + $spIdMax = max($spId, $spIdMax); } + $countDrawings += $addCount; } $dggContainer->setSpIdMax($spIdMax + 1); @@ -453,83 +556,7 @@ private function buildWorkbookEscher(): void // the BSE's (all the images) foreach ($this->spreadsheet->getAllsheets() as $sheet) { foreach ($sheet->getDrawingCollection() as $drawing) { - if (!extension_loaded('gd')) { - throw new RuntimeException('Saving images in xls requires gd extension'); - } - if ($drawing instanceof Drawing) { - $filename = $drawing->getPath(); - - [$imagesx, $imagesy, $imageFormat] = getimagesize($filename); - - switch ($imageFormat) { - case 1: // GIF, not supported by BIFF8, we convert to PNG - $blipType = BSE::BLIPTYPE_PNG; - ob_start(); - imagepng(imagecreatefromgif($filename)); - $blipData = ob_get_contents(); - ob_end_clean(); - - break; - case 2: // JPEG - $blipType = BSE::BLIPTYPE_JPEG; - $blipData = file_get_contents($filename); - - break; - case 3: // PNG - $blipType = BSE::BLIPTYPE_PNG; - $blipData = file_get_contents($filename); - - break; - case 6: // Windows DIB (BMP), we convert to PNG - $blipType = BSE::BLIPTYPE_PNG; - ob_start(); - imagepng(SharedDrawing::imagecreatefrombmp($filename)); - $blipData = ob_get_contents(); - ob_end_clean(); - - break; - default: - continue 2; - } - - $blip = new Blip(); - $blip->setData($blipData); - - $BSE = new BSE(); - $BSE->setBlipType($blipType); - $BSE->setBlip($blip); - - $bstoreContainer->addBSE($BSE); - } elseif ($drawing instanceof MemoryDrawing) { - switch ($drawing->getRenderingFunction()) { - case MemoryDrawing::RENDERING_JPEG: - $blipType = BSE::BLIPTYPE_JPEG; - $renderingFunction = 'imagejpeg'; - - break; - case MemoryDrawing::RENDERING_GIF: - case MemoryDrawing::RENDERING_PNG: - case MemoryDrawing::RENDERING_DEFAULT: - $blipType = BSE::BLIPTYPE_PNG; - $renderingFunction = 'imagepng'; - - break; - } - - ob_start(); - call_user_func($renderingFunction, $drawing->getImageResource()); - $blipData = ob_get_contents(); - ob_end_clean(); - - $blip = new Blip(); - $blip->setData($blipData); - - $BSE = new BSE(); - $BSE->setBlipType($blipType); - $BSE->setBlip($blip); - - $bstoreContainer->addBSE($BSE); - } + $this->processBaseDrawing($bstoreContainer, $drawing); } } @@ -578,8 +605,8 @@ private function writeDocumentSummaryInformation() ++$dataSection_NumProps; // GKPIDDSI_CATEGORY : Category - if ($this->spreadsheet->getProperties()->getCategory()) { - $dataProp = $this->spreadsheet->getProperties()->getCategory(); + $dataProp = $this->spreadsheet->getProperties()->getCategory(); + if ($dataProp) { $dataSection[] = [ 'summary' => ['pack' => 'V', 'data' => 0x02], 'offset' => ['pack' => 'V'], @@ -707,11 +734,7 @@ private function writeDocumentSummaryInformation() $dataSection_Content_Offset += 4 + 4; } elseif ($dataProp['type']['data'] == 0x0B) { // Boolean - if ($dataProp['data']['data'] == false) { - $dataSection_Content .= pack('V', 0x0000); - } else { - $dataSection_Content .= pack('V', 0x0001); - } + $dataSection_Content .= pack('V', (int) $dataProp['data']['data']); $dataSection_Content_Offset += 4 + 4; } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length // Null-terminated string @@ -725,12 +748,12 @@ private function writeDocumentSummaryInformation() $dataSection_Content .= $dataProp['data']['data']; $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']); - } elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) - $dataSection_Content .= $dataProp['data']['data']; + // Condition below can never be true + //} elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) + // $dataSection_Content .= $dataProp['data']['data']; - $dataSection_Content_Offset += 4 + 8; + // $dataSection_Content_Offset += 4 + 8; } else { - // Data Type Not Used at the moment $dataSection_Content .= $dataProp['data']['data']; $dataSection_Content_Offset += 4 + $dataProp['data']['length']; @@ -752,6 +775,32 @@ private function writeDocumentSummaryInformation() return $data; } + private function writeSummaryPropOle(int $dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata): void + { + if ($dataProp) { + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => $sumdata], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => $typdata], // null-terminated string prepended by dword string length + 'data' => ['data' => OLE::localDateToOLE($dataProp)], + ]; + ++$dataSection_NumProps; + } + } + + private function writeSummaryProp(string $dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata): void + { + if ($dataProp) { + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => $sumdata], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => $typdata], // null-terminated string prepended by dword string length + 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], + ]; + ++$dataSection_NumProps; + } + } + /** * Build the OLE Part for Summary Information. * @@ -792,94 +841,16 @@ private function writeSummaryInformation() ]; ++$dataSection_NumProps; - // Title - if ($this->spreadsheet->getProperties()->getTitle()) { - $dataProp = $this->spreadsheet->getProperties()->getTitle(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x02], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length - 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Subject - if ($this->spreadsheet->getProperties()->getSubject()) { - $dataProp = $this->spreadsheet->getProperties()->getSubject(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x03], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length - 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Author (Creator) - if ($this->spreadsheet->getProperties()->getCreator()) { - $dataProp = $this->spreadsheet->getProperties()->getCreator(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x04], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length - 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Keywords - if ($this->spreadsheet->getProperties()->getKeywords()) { - $dataProp = $this->spreadsheet->getProperties()->getKeywords(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x05], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length - 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Comments (Description) - if ($this->spreadsheet->getProperties()->getDescription()) { - $dataProp = $this->spreadsheet->getProperties()->getDescription(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x06], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length - 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Last Saved By (LastModifiedBy) - if ($this->spreadsheet->getProperties()->getLastModifiedBy()) { - $dataProp = $this->spreadsheet->getProperties()->getLastModifiedBy(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x08], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length - 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Created Date/Time - if ($this->spreadsheet->getProperties()->getCreated()) { - $dataProp = $this->spreadsheet->getProperties()->getCreated(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x0C], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x40], // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) - 'data' => ['data' => OLE::localDateToOLE($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Modified Date/Time - if ($this->spreadsheet->getProperties()->getModified()) { - $dataProp = $this->spreadsheet->getProperties()->getModified(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x0D], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x40], // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) - 'data' => ['data' => OLE::localDateToOLE($dataProp)], - ]; - ++$dataSection_NumProps; - } + $props = $this->spreadsheet->getProperties(); + $this->writeSummaryProp($props->getTitle(), $dataSection_NumProps, $dataSection, 0x02, 0x1e); + $this->writeSummaryProp($props->getSubject(), $dataSection_NumProps, $dataSection, 0x03, 0x1e); + $this->writeSummaryProp($props->getCreator(), $dataSection_NumProps, $dataSection, 0x04, 0x1e); + $this->writeSummaryProp($props->getKeywords(), $dataSection_NumProps, $dataSection, 0x05, 0x1e); + $this->writeSummaryProp($props->getDescription(), $dataSection_NumProps, $dataSection, 0x06, 0x1e); + $this->writeSummaryProp($props->getLastModifiedBy(), $dataSection_NumProps, $dataSection, 0x08, 0x1e); + $this->writeSummaryPropOle($props->getCreated(), $dataSection_NumProps, $dataSection, 0x0c, 0x40); + $this->writeSummaryPropOle($props->getModified(), $dataSection_NumProps, $dataSection, 0x0d, 0x40); + // Security $dataSection[] = [ 'summary' => ['pack' => 'V', 'data' => 0x13], diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php new file mode 100644 index 0000000000..2b89c1c99d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php @@ -0,0 +1,124 @@ +filename) { + unlink($this->filename); + } + $this->filename = ''; + } + + public function testBmp(): void + { + $pgmstart = time(); + $spreadsheet = new Spreadsheet(); + $filstart = $spreadsheet->getProperties()->getModified(); + self::assertLessThanOrEqual($filstart, $pgmstart); + + // Add a drawing to the worksheet + $drawing = new Drawing(); + $drawing->setName('Letters B, M, and P'); + $drawing->setDescription('Handwritten B, M, and P'); + $drawing->setPath(__DIR__ . '../../../../../samples/images/bmp.bmp'); + $drawing->setHeight(36); + $drawing->setWorksheet($spreadsheet->getActiveSheet()); + $drawing->setCoordinates('A1'); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xls'); + $creationDatestamp = $reloadedSpreadsheet->getProperties()->getCreated(); + $filstart = $creationDatestamp; + $pSheet = $reloadedSpreadsheet->getActiveSheet(); + $drawings = $pSheet->getDrawingCollection(); + self::assertCount(1, $drawings); + foreach ($pSheet->getDrawingCollection() as $drawing) { + self::assertTrue($drawing instanceof MemoryDrawing); + self::assertEquals('image/png', $drawing->getMimeType()); + } + $pgmend = time(); + + self::assertLessThanOrEqual($pgmend, $pgmstart); + self::assertLessThanOrEqual($pgmend, $filstart); + self::assertLessThanOrEqual($filstart, $pgmstart); + } + + public function testGif(): void + { + $spreadsheet = new Spreadsheet(); + + // Add a drawing to the worksheet + $drawing = new Drawing(); + $drawing->setName('Letters G, I, and G'); + $drawing->setDescription('Handwritten G, I, and F'); + $drawing->setPath(__DIR__ . '../../../../../samples/images/gif.gif'); + $drawing->setHeight(36); + $drawing->setWorksheet($spreadsheet->getActiveSheet()); + $drawing->setCoordinates('A1'); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xls'); + $pSheet = $reloadedSpreadsheet->getActiveSheet(); + $drawings = $pSheet->getDrawingCollection(); + self::assertCount(1, $drawings); + foreach ($pSheet->getDrawingCollection() as $drawing) { + self::assertTrue($drawing instanceof MemoryDrawing); + self::assertEquals('image/png', $drawing->getMimeType()); + } + } + + public function testGifNoGd(): void + { + $this->expectException(RuntimeException::class); + $spreadsheet = new Spreadsheet(); + + // Add a drawing to the worksheet + $drawing = new Drawing(); + $drawing->setName('Letters G, I, and G'); + $drawing->setDescription('Handwritten G, I, and F'); + $drawing->setPath(__DIR__ . '../../../../../samples/images/gif.gif'); + $drawing->setHeight(36); + $drawing->setWorksheet($spreadsheet->getActiveSheet()); + $drawing->setCoordinates('A1'); + + $writer = new XlsWriter($spreadsheet); + $writer->setSimulateNoGd(true); + $this->filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $writer->save($this->filename); + } + + public function testNoImagesNoGd(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A1', 1); + + $writer = new XlsWriter($spreadsheet); + $writer->setSimulateNoGd(true); + $oufil = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); + $writer->save($oufil); + $reader = new XlsReader(); + $rdobj = $reader->load($oufil); + unlink($oufil); + $rdsheet = $rdobj->getActiveSheet(); + self::assertEquals(1, $rdsheet->getCell('A1')->getValue()); + } + + public function testInvalidTimestamp(): void + { + $this->expectException(\PhpOffice\PhpSpreadsheet\Reader\Exception::class); + \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE2LocalDate(' '); + } +} From 164a07ca7bf9f79a9d5313a68b39d56b37610c30 Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Thu, 28 May 2020 00:11:33 -0700 Subject: [PATCH 2/6] Altered File Location The location of the new BMP and GIF files was mis-coded in such a way that Windows allowed the error, but Unix didn't. --- tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php index 2b89c1c99d..4f501f4aec 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php @@ -34,7 +34,7 @@ public function testBmp(): void $drawing = new Drawing(); $drawing->setName('Letters B, M, and P'); $drawing->setDescription('Handwritten B, M, and P'); - $drawing->setPath(__DIR__ . '../../../../../samples/images/bmp.bmp'); + $drawing->setPath(__DIR__ . '/../../../../samples/images/bmp.bmp'); $drawing->setHeight(36); $drawing->setWorksheet($spreadsheet->getActiveSheet()); $drawing->setCoordinates('A1'); @@ -64,7 +64,7 @@ public function testGif(): void $drawing = new Drawing(); $drawing->setName('Letters G, I, and G'); $drawing->setDescription('Handwritten G, I, and F'); - $drawing->setPath(__DIR__ . '../../../../../samples/images/gif.gif'); + $drawing->setPath(__DIR__ . '/../../../../samples/images/gif.gif'); $drawing->setHeight(36); $drawing->setWorksheet($spreadsheet->getActiveSheet()); $drawing->setCoordinates('A1'); @@ -88,7 +88,7 @@ public function testGifNoGd(): void $drawing = new Drawing(); $drawing->setName('Letters G, I, and G'); $drawing->setDescription('Handwritten G, I, and F'); - $drawing->setPath(__DIR__ . '../../../../../samples/images/gif.gif'); + $drawing->setPath(__DIR__ . '/../../../../samples/images/gif.gif'); $drawing->setHeight(36); $drawing->setWorksheet($spreadsheet->getActiveSheet()); $drawing->setCoordinates('A1'); From 2c925104da64a661466037f192ce7de96885ed1f Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Thu, 28 May 2020 00:29:52 -0700 Subject: [PATCH 3/6] Scrutinizer Recommendation Mktime uses int arguments rather than string. --- src/PhpSpreadsheet/Shared/OLE.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpSpreadsheet/Shared/OLE.php b/src/PhpSpreadsheet/Shared/OLE.php index 2be7727f73..25d698a537 100644 --- a/src/PhpSpreadsheet/Shared/OLE.php +++ b/src/PhpSpreadsheet/Shared/OLE.php @@ -505,7 +505,7 @@ public static function localDateToOLE($date) // days from 1-1-1601 until the beggining of UNIX era $days = 134774; // calculate seconds - $big_date = $days * 24 * 3600 + mktime(date('H', $date), date('i', $date), date('s', $date), date('m', $date), date('d', $date), date('Y', $date)); + $big_date = $days * 24 * 3600 + mktime((int) date('H', $date), (int) date('i', $date), (int) date('s', $date), (int) date('m', $date), (int) date('d', $date), (int) date('Y', $date)); // multiply just to make MS happy $big_date *= 10000000; From 985f82e26628e4d9ca1d47b548363c66f86d27c3 Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Sun, 31 May 2020 11:06:02 -0700 Subject: [PATCH 4/6] GD Now Required - no need to test for it Recommendation from PowerKiki --- src/PhpSpreadsheet/Writer/Xls.php | 16 -------- .../Writer/Xls/XlsGifBmpTest.php | 41 ------------------- 2 files changed, 57 deletions(-) diff --git a/src/PhpSpreadsheet/Writer/Xls.php b/src/PhpSpreadsheet/Writer/Xls.php index c8edee01f8..4f4b256a34 100644 --- a/src/PhpSpreadsheet/Writer/Xls.php +++ b/src/PhpSpreadsheet/Writer/Xls.php @@ -23,7 +23,6 @@ use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; -use RuntimeException; class Xls extends BaseWriter { @@ -475,18 +474,6 @@ private function processBaseDrawing(BstoreContainer &$bstoreContainer, BaseDrawi } } - /** - * Total number of shared strings in workbook. - * - * @var bool - */ - private $simulateNoGd = false; - - public function setSimulateNoGd(bool $arg): void - { - $this->simulateNoGd = $arg; - } - private function checkForDrawings(): bool { // any drawings in this workbook? @@ -494,9 +481,6 @@ private function checkForDrawings(): bool foreach ($this->spreadsheet->getAllSheets() as $sheet) { if (count($sheet->getDrawingCollection()) > 0) { $found = true; - if ($this->simulateNoGd || !extension_loaded('gd')) { - throw new RuntimeException('Saving images in xls requires gd extension'); - } break; } diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php index 4f501f4aec..13547645d0 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php @@ -2,14 +2,10 @@ namespace PhpOffice\PhpSpreadsheetTests\Writer\Xls; -use PhpOffice\PhpSpreadsheet\Reader\Xls as XlsReader; -use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; -use PhpOffice\PhpSpreadsheet\Writer\Xls as XlsWriter; use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional; -use RuntimeException; class XlsGifBmpTest extends AbstractFunctional { @@ -79,43 +75,6 @@ public function testGif(): void } } - public function testGifNoGd(): void - { - $this->expectException(RuntimeException::class); - $spreadsheet = new Spreadsheet(); - - // Add a drawing to the worksheet - $drawing = new Drawing(); - $drawing->setName('Letters G, I, and G'); - $drawing->setDescription('Handwritten G, I, and F'); - $drawing->setPath(__DIR__ . '/../../../../samples/images/gif.gif'); - $drawing->setHeight(36); - $drawing->setWorksheet($spreadsheet->getActiveSheet()); - $drawing->setCoordinates('A1'); - - $writer = new XlsWriter($spreadsheet); - $writer->setSimulateNoGd(true); - $this->filename = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); - $writer->save($this->filename); - } - - public function testNoImagesNoGd(): void - { - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); - $sheet->setCellValue('A1', 1); - - $writer = new XlsWriter($spreadsheet); - $writer->setSimulateNoGd(true); - $oufil = tempnam(File::sysGetTempDir(), 'phpspreadsheet-test'); - $writer->save($oufil); - $reader = new XlsReader(); - $rdobj = $reader->load($oufil); - unlink($oufil); - $rdsheet = $rdobj->getActiveSheet(); - self::assertEquals(1, $rdsheet->getCell('A1')->getValue()); - } - public function testInvalidTimestamp(): void { $this->expectException(\PhpOffice\PhpSpreadsheet\Reader\Exception::class); From b8780c8b5b83b3e238ca59873c780f03fda34931 Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Sun, 31 May 2020 12:26:32 -0700 Subject: [PATCH 5/6] Wrestling with Scrutinizer Asserted that drawing was a MemoryDrawing, but Scrutinizer doesn't think it will always have MemoryDrawing properties. Seeing if I can influence it. --- tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php index 13547645d0..0967d89225 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php @@ -42,8 +42,8 @@ public function testBmp(): void $drawings = $pSheet->getDrawingCollection(); self::assertCount(1, $drawings); foreach ($pSheet->getDrawingCollection() as $drawing) { - self::assertTrue($drawing instanceof MemoryDrawing); - self::assertEquals('image/png', $drawing->getMimeType()); + $mimeType = ($drawing instanceof MemoryDrawing) ? $drawing->getMimeType() : 'notmemorydrawing'; + self::assertEquals('image/png', $mimeType); } $pgmend = time(); @@ -70,8 +70,8 @@ public function testGif(): void $drawings = $pSheet->getDrawingCollection(); self::assertCount(1, $drawings); foreach ($pSheet->getDrawingCollection() as $drawing) { - self::assertTrue($drawing instanceof MemoryDrawing); - self::assertEquals('image/png', $drawing->getMimeType()); + $mimeType = ($drawing instanceof MemoryDrawing) ? $drawing->getMimeType() : 'notmemorydrawing'; + self::assertEquals('image/png', $mimeType); } } From b0bd163c788e7e5175f4d25a798734d79f57685c Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Sun, 31 May 2020 12:59:04 -0700 Subject: [PATCH 6/6] Scrutinizer Error'ed It didn't find errors in my package - it just didn't start. Trying again half an hour later to see if timing was the issue. --- tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php index 0967d89225..ceba7e54a1 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php @@ -42,6 +42,7 @@ public function testBmp(): void $drawings = $pSheet->getDrawingCollection(); self::assertCount(1, $drawings); foreach ($pSheet->getDrawingCollection() as $drawing) { + // See if Scrutinizer approves this $mimeType = ($drawing instanceof MemoryDrawing) ? $drawing->getMimeType() : 'notmemorydrawing'; self::assertEquals('image/png', $mimeType); }