From fcf78204670fe5c7ab298d9c2cdd9b1c15000c31 Mon Sep 17 00:00:00 2001 From: Adrien Crivelli Date: Sat, 16 May 2020 20:12:28 +0900 Subject: [PATCH] All writers can write to stream --- src/PhpSpreadsheet/Shared/OLE/PPS/Root.php | 44 ++---------------- src/PhpSpreadsheet/Writer/BaseWriter.php | 45 +++++++++++++++++++ src/PhpSpreadsheet/Writer/Csv.php | 5 --- src/PhpSpreadsheet/Writer/Html.php | 21 +++------ src/PhpSpreadsheet/Writer/Ods.php | 43 +----------------- src/PhpSpreadsheet/Writer/Pdf.php | 10 ++--- src/PhpSpreadsheet/Writer/Pdf/Mpdf.php | 2 +- src/PhpSpreadsheet/Writer/Xls.php | 4 +- src/PhpSpreadsheet/Writer/Xlsx.php | 43 +----------------- .../Functional/StreamTest.php | 45 +++++++++++++++++++ 10 files changed, 111 insertions(+), 151 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Functional/StreamTest.php diff --git a/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php b/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php index c52cea23..472483eb 100644 --- a/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php +++ b/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php @@ -22,7 +22,6 @@ namespace PhpOffice\PhpSpreadsheet\Shared\OLE\PPS; // use PhpOffice\PhpSpreadsheet\Shared\OLE; use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS; -use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; /** * Class for creating Root PPS's for OLE containers. @@ -33,23 +32,11 @@ use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; */ class Root extends PPS { - /** - * Directory for temporary files. - * - * @var string - */ - protected $tempDirectory; - /** * @var resource */ private $fileHandle; - /** - * @var string - */ - private $tempFilename; - /** * @var int */ @@ -67,8 +54,6 @@ class Root extends PPS */ public function __construct($time_1st, $time_2nd, $raChild) { - $this->tempDirectory = \PhpOffice\PhpSpreadsheet\Shared\File::sysGetTempDir(); - parent::__construct(null, OLE::ascToUcs('Root Entry'), OLE::OLE_PPS_TYPE_ROOT, null, null, null, $time_1st, $time_2nd, null, $raChild); } @@ -79,14 +64,14 @@ class Root extends PPS * If a resource pointer to a stream created by fopen() is passed * it will be used, but you have to close such stream by yourself. * - * @param resource|string $filename the name of the file or stream where to save the OLE container - * - * @throws WriterException + * @param resource $fileHandle the name of the file or stream where to save the OLE container * * @return bool true on success */ - public function save($filename) + public function save($fileHandle) { + $this->fileHandle = $fileHandle; + // Initial Setting for saving $this->bigBlockSize = pow( 2, @@ -97,23 +82,6 @@ class Root extends PPS (isset($this->smallBlockSize)) ? self::adjust2($this->smallBlockSize) : 6 ); - if (is_resource($filename)) { - $this->fileHandle = $filename; - } elseif ($filename == '-' || $filename == '') { - if ($this->tempDirectory === null) { - $this->tempDirectory = \PhpOffice\PhpSpreadsheet\Shared\File::sysGetTempDir(); - } - $this->tempFilename = tempnam($this->tempDirectory, 'OLE_PPS_Root'); - $this->fileHandle = fopen($this->tempFilename, 'w+b'); - if ($this->fileHandle == false) { - throw new WriterException("Can't create temporary file."); - } - } else { - $this->fileHandle = fopen($filename, 'wb'); - } - if ($this->fileHandle == false) { - throw new WriterException("Can't open $filename. It may be in use or protected."); - } // Make an array of PPS's (for Save) $aList = []; PPS::_savePpsSetPnt($aList, [$this]); @@ -132,10 +100,6 @@ class Root extends PPS // Write Big Block Depot and BDList and Adding Header informations $this->_saveBbd($iSBDcnt, $iBBcnt, $iPPScnt); - if (!is_resource($filename)) { - fclose($this->fileHandle); - } - return true; } diff --git a/src/PhpSpreadsheet/Writer/BaseWriter.php b/src/PhpSpreadsheet/Writer/BaseWriter.php index f13150d7..dc7a0431 100644 --- a/src/PhpSpreadsheet/Writer/BaseWriter.php +++ b/src/PhpSpreadsheet/Writer/BaseWriter.php @@ -35,6 +35,16 @@ abstract class BaseWriter implements IWriter */ private $diskCachingDirectory = './'; + /** + * @var resource + */ + protected $fileHandle; + + /** + * @var bool + */ + private $shouldCloseFile; + public function getIncludeCharts() { return $this->includeCharts; @@ -83,4 +93,39 @@ abstract class BaseWriter implements IWriter { return $this->diskCachingDirectory; } + + /** + * Open file handle. + * + * @param resource|string $filename + */ + public function openFileHandle($filename): void + { + if (is_resource($filename)) { + $this->fileHandle = $filename; + $this->shouldCloseFile = false; + + return; + } + + $fileHandle = fopen($filename, 'wb+'); + if ($fileHandle === false) { + throw new Exception('Could not open file ' . $filename . ' for writing.'); + } + + $this->fileHandle = $fileHandle; + $this->shouldCloseFile = true; + } + + /** + * Close file handle only we opened it ourselves. + */ + protected function maybeCloseFileHandle(): void + { + if ($this->shouldCloseFile) { + if (!fclose($this->fileHandle)) { + throw new Exception('Could not close file after writing.'); + } + } + } } diff --git a/src/PhpSpreadsheet/Writer/Csv.php b/src/PhpSpreadsheet/Writer/Csv.php index 1cf64634..e5c08c13 100644 --- a/src/PhpSpreadsheet/Writer/Csv.php +++ b/src/PhpSpreadsheet/Writer/Csv.php @@ -131,11 +131,6 @@ class Csv extends BaseWriter $this->writeLine($fileHandle, $cellsArray[0]); } - // Close file - rewind($fileHandle); - - fclose($fileHandle); - Calculation::setArrayReturnType($saveArrayReturnType); Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); } diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index 91beedb7..ed6db1b7 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -164,32 +164,23 @@ class Html extends BaseWriter $this->buildCSS(!$this->useInlineCss); // Open file - if (is_resource($pFilename)) { - $fileHandle = $pFilename; - } else { - $fileHandle = fopen($pFilename, 'wb+'); - } - - if ($fileHandle === false) { - throw new WriterException("Could not open file $pFilename for writing."); - } + $this->openFileHandle($pFilename); // Write headers - fwrite($fileHandle, $this->generateHTMLHeader(!$this->useInlineCss)); + fwrite($this->fileHandle, $this->generateHTMLHeader(!$this->useInlineCss)); // Write navigation (tabs) if ((!$this->isPdf) && ($this->generateSheetNavigationBlock)) { - fwrite($fileHandle, $this->generateNavigation()); + fwrite($this->fileHandle, $this->generateNavigation()); } // Write data - fwrite($fileHandle, $this->generateSheetData()); + fwrite($this->fileHandle, $this->generateSheetData()); // Write footer - fwrite($fileHandle, $this->generateHTMLFooter()); + fwrite($this->fileHandle, $this->generateHTMLFooter()); - // Close file - fclose($fileHandle); + $this->maybeCloseFileHandle(); Calculation::setArrayReturnType($saveArrayReturnType); Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); diff --git a/src/PhpSpreadsheet/Writer/Ods.php b/src/PhpSpreadsheet/Writer/Ods.php index 9de19eb4..bb981d35 100644 --- a/src/PhpSpreadsheet/Writer/Ods.php +++ b/src/PhpSpreadsheet/Writer/Ods.php @@ -32,11 +32,6 @@ class Ods extends BaseWriter */ private $spreadSheet; - /** - * @var resource - */ - private $fileHandle; - /** * Create a new Ods. * @@ -93,25 +88,7 @@ class Ods extends BaseWriter // garbage collect $this->spreadSheet->garbageCollect(); - $originalFilename = $pFilename; - if (is_resource($pFilename)) { - $this->fileHandle = $pFilename; - } else { - // If $pFilename is php://output or php://stdout, make it a temporary file... - if (in_array(strtolower($pFilename), ['php://output', 'php://stdout'], true)) { - $pFilename = @tempnam(File::sysGetTempDir(), 'phpxltmp'); - if ($pFilename === '') { - $pFilename = $originalFilename; - } - } - - $fileHandle = fopen($pFilename, 'wb+'); - if ($fileHandle === false) { - throw new WriterException('Could not open file ' . $pFilename . ' for writing.'); - } - - $this->fileHandle = $fileHandle; - } + $this->openFileHandle($pFilename); $zip = $this->createZip(); @@ -130,23 +107,7 @@ class Ods extends BaseWriter throw new WriterException('Could not close resource.'); } - rewind($this->fileHandle); - - // If a temporary file was used, copy it to the correct file stream - if ($originalFilename !== $pFilename) { - $destinationFileHandle = fopen($originalFilename, 'wb+'); - if (!is_resource($destinationFileHandle)) { - throw new WriterException("Could not open resource $originalFilename for writing."); - } - - if (stream_copy_to_stream($this->fileHandle, $destinationFileHandle) === false) { - throw new WriterException("Could not copy temporary zip file $pFilename to $originalFilename."); - } - - if (is_string($pFilename) && !unlink($pFilename)) { - throw new WriterException('Could not unlink temporary zip file.'); - } - } + $this->maybeCloseFileHandle(); } /** diff --git a/src/PhpSpreadsheet/Writer/Pdf.php b/src/PhpSpreadsheet/Writer/Pdf.php index d9184560..52f95749 100644 --- a/src/PhpSpreadsheet/Writer/Pdf.php +++ b/src/PhpSpreadsheet/Writer/Pdf.php @@ -255,17 +255,14 @@ abstract class Pdf extends Html Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE); // Open file - $fileHandle = fopen($pFilename, 'w'); - if ($fileHandle === false) { - throw new WriterException("Could not open file $pFilename for writing."); - } + $this->openFileHandle($pFilename); // Set PDF $this->isPdf = true; // Build CSS $this->buildCSS(true); - return $fileHandle; + return $this->fileHandle; } /** @@ -275,8 +272,7 @@ abstract class Pdf extends Html */ protected function restoreStateAfterSave($fileHandle) { - // Close file - fclose($fileHandle); + $this->maybeCloseFileHandle(); Calculation::setArrayReturnType($this->saveArrayReturnType); } diff --git a/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php b/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php index fd2664a8..5fdc4ea8 100644 --- a/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php +++ b/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php @@ -65,7 +65,7 @@ class Mpdf extends Pdf } // Create PDF - $config = ['tempDir' => $this->tempDir]; + $config = ['tempDir' => $this->tempDir . '/mpdf']; $pdf = $this->createExternalWriterInstance($config); $ortmp = $orientation; $pdf->_setPageSize(strtoupper($paperSize), $ortmp); diff --git a/src/PhpSpreadsheet/Writer/Xls.php b/src/PhpSpreadsheet/Writer/Xls.php index 95693dde..fb42bf9e 100644 --- a/src/PhpSpreadsheet/Writer/Xls.php +++ b/src/PhpSpreadsheet/Writer/Xls.php @@ -221,7 +221,9 @@ class Xls extends BaseWriter $root = new Root(time(), time(), $arrRootData); // save the OLE file - $root->save($pFilename); + $this->openFileHandle($pFilename); + $root->save($this->fileHandle); + $this->maybeCloseFileHandle(); Functions::setReturnDateType($saveDateReturnType); Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index 3b4bdfd9..aea6a26c 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -107,11 +107,6 @@ class Xlsx extends BaseWriter */ private $drawingHashTable; - /** - * @var resource - */ - private $fileHandle; - /** * Create a new Xlsx Writer. * @@ -184,25 +179,7 @@ class Xlsx extends BaseWriter // garbage collect $this->spreadSheet->garbageCollect(); - $originalFilename = $pFilename; - if (is_resource($pFilename)) { - $this->fileHandle = $pFilename; - } else { - // If $pFilename is php://output or php://stdout, make it a temporary file... - if (in_array(strtolower($pFilename), ['php://output', 'php://stdout'], true)) { - $pFilename = @tempnam(File::sysGetTempDir(), 'phpxltmp'); - if ($pFilename === '') { - $pFilename = $originalFilename; - } - } - - $fileHandle = fopen($pFilename, 'wb+'); - if ($fileHandle === false) { - throw new WriterException('Could not open file ' . $pFilename . ' for writing.'); - } - - $this->fileHandle = $fileHandle; - } + $this->openFileHandle($pFilename); $saveDebugLog = Calculation::getInstance($this->spreadSheet)->getDebugLog()->getWriteDebugLog(); Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog(false); @@ -425,23 +402,7 @@ class Xlsx extends BaseWriter throw new WriterException('Could not close resource.'); } - rewind($this->fileHandle); - - // If a temporary file was used, copy it to the correct file stream - if ($originalFilename !== $pFilename) { - $destinationFileHandle = fopen($originalFilename, 'wb+'); - if (!is_resource($destinationFileHandle)) { - throw new WriterException("Could not open resource $originalFilename for writing."); - } - - if (stream_copy_to_stream($this->fileHandle, $destinationFileHandle) === false) { - throw new WriterException("Could not copy temporary zip file $pFilename to $originalFilename."); - } - - if (is_string($pFilename) && !unlink($pFilename)) { - throw new WriterException('Could not unlink temporary zip file.'); - } - } + $this->maybeCloseFileHandle(); } else { throw new WriterException('PhpSpreadsheet object unassigned.'); } diff --git a/tests/PhpSpreadsheetTests/Functional/StreamTest.php b/tests/PhpSpreadsheetTests/Functional/StreamTest.php new file mode 100644 index 00000000..20e588bb --- /dev/null +++ b/tests/PhpSpreadsheetTests/Functional/StreamTest.php @@ -0,0 +1,45 @@ +getActiveSheet()->setCellValue('A1', 'foo'); + $writer = IOFactory::createWriter($spreadsheet, $format); + + $stream = fopen('php://memory', 'wb+'); + self::assertSame(0, fstat($stream)['size']); + + $writer->save($stream); + + self::assertIsResource($stream, 'should not close the stream for further usage out of PhpSpreadsheet'); + self::assertGreaterThan(0, fstat($stream)['size'], 'something should have been written to the stream'); + self::assertGreaterThan(0, ftell($stream), 'should not be rewinded, because not all streams support it'); + } +}