From b3d30f4cbc30c45eb83111ac9d23b05afe10cab3 Mon Sep 17 00:00:00 2001 From: oleibman Date: Fri, 19 Jun 2020 12:08:36 -0700 Subject: [PATCH] Xls Writer - Correct Timestamp Bug, Improve Coverage (#1493) * 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 | 339 ++++++++---------- .../Writer/Xls/XlsGifBmpTest.php | 84 +++++ 5 files changed, 235 insertions(+), 197 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 cf87d5be..4f4b256a 100644 --- a/src/PhpSpreadsheet/Writer/Xls.php +++ b/src/PhpSpreadsheet/Writer/Xls.php @@ -23,7 +23,6 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; -use RuntimeException; class Xls extends BaseWriter { @@ -389,13 +388,94 @@ class Xls extends BaseWriter } } - /** - * Build the Escher object corresponding to the MSODRAWINGGROUP record. - */ - private function buildWorkbookEscher(): void + private function processMemoryDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing, string $renderingFunctionx): void { - $escher = null; + 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()); + } + } + + private function checkForDrawings(): bool + { // any drawings in this workbook? $found = false; foreach ($this->spreadsheet->getAllSheets() as $sheet) { @@ -406,8 +486,16 @@ class Xls extends BaseWriter } } + 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 +517,16 @@ class Xls extends BaseWriter foreach ($this->spreadsheet->getAllsheets() as $sheet) { $sheetCountShapes = 0; // count number of shapes (minus group shape), in sheet - if (count($sheet->getDrawingCollection()) > 0) { - ++$countDrawings; + $addCount = 0; + foreach ($sheet->getDrawingCollection() as $drawing) { + $addCount = 1; + ++$sheetCountShapes; + ++$totalCountShapes; - foreach ($sheet->getDrawingCollection() as $drawing) { - ++$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 +540,7 @@ class Xls extends BaseWriter // 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 +589,8 @@ class Xls extends BaseWriter ++$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 +718,7 @@ class Xls extends BaseWriter $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 +732,12 @@ class Xls extends BaseWriter $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 +759,32 @@ class Xls extends BaseWriter 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 +825,16 @@ class Xls extends BaseWriter ]; ++$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 00000000..ceba7e54 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php @@ -0,0 +1,84 @@ +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) { + // See if Scrutinizer approves this + $mimeType = ($drawing instanceof MemoryDrawing) ? $drawing->getMimeType() : 'notmemorydrawing'; + self::assertEquals('image/png', $mimeType); + } + $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) { + $mimeType = ($drawing instanceof MemoryDrawing) ? $drawing->getMimeType() : 'notmemorydrawing'; + self::assertEquals('image/png', $mimeType); + } + } + + public function testInvalidTimestamp(): void + { + $this->expectException(\PhpOffice\PhpSpreadsheet\Reader\Exception::class); + \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE2LocalDate(' '); + } +}