From 728dd3b39984d64b826999c5252900e519871e07 Mon Sep 17 00:00:00 2001 From: Adrien Loison Date: Sat, 5 Dec 2015 00:13:56 -0800 Subject: [PATCH] Proper mime type detection for XLSX files Heuristics to detect proper mime type for XLSX files expect to see certain files at the beginning of the XLSX archive. The order in which the XML files are added therefore matters. Specifically, "[Content_Types].xml" should be added first, followed by the files located in the "xl" folder (at least 1 file). --- src/Spout/Writer/Common/Helper/ZipHelper.php | 31 +++++++++++++++++++- tests/Spout/Writer/XLSX/WriterTest.php | 15 ++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/Spout/Writer/Common/Helper/ZipHelper.php b/src/Spout/Writer/Common/Helper/ZipHelper.php index d2ef416..15e5ff6 100644 --- a/src/Spout/Writer/Common/Helper/ZipHelper.php +++ b/src/Spout/Writer/Common/Helper/ZipHelper.php @@ -11,6 +11,7 @@ namespace Box\Spout\Writer\Common\Helper; class ZipHelper { const ZIP_EXTENSION = '.zip'; + const CONTENT_TYPES_XML_FILE_NAME = '[Content_Types].xml'; /** * Zips the root folder and streams the contents of the zip into the given stream @@ -61,7 +62,12 @@ class ZipHelper $folderRealPath = $this->getNormalizedRealPath($folderPath) . '/'; $itemIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST); - foreach ($itemIterator as $itemInfo) { + // In order to have the file's mime type detected properly, items need to be + // sorted in a particular order... + $itemsInfo = iterator_to_array($itemIterator); + usort($itemsInfo, [$this, 'sortItemsForCorrectMimeTypeDetection']); + + foreach ($itemsInfo as $itemInfo) { $itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname()); $itemLocalPath = str_replace($folderRealPath, '', $itemRealPath); @@ -73,6 +79,29 @@ class ZipHelper } } + /** + * On order to have the file's mime type detected properly, files need to be added + * to the zip file in a particular order. + * [Content_Types].xml and files located in "xl" folder should be zipped first. + * + * @param \SplFileInfo $itemInfo1 First item to compare + * @param \SplFileInfo $itemInfo2 Second item to compare + * @return int + */ + protected function sortItemsForCorrectMimeTypeDetection($itemInfo1, $itemInfo2) + { + // Have the "[Content_Types].xml" file be first + if ($itemInfo1->getFilename() === self::CONTENT_TYPES_XML_FILE_NAME) { + return -1; + } else if ($itemInfo2->getFilename() === self::CONTENT_TYPES_XML_FILE_NAME) { + return 1; + } else { + // Then make sure the files in the "xl" folder will go next + // by sorting items in reverse alphabetical order + return strcmp($itemInfo2->getRealPath(), $itemInfo1->getRealPath()); + } + } + /** * Returns canonicalized absolute pathname, containing only forward slashes. * diff --git a/tests/Spout/Writer/XLSX/WriterTest.php b/tests/Spout/Writer/XLSX/WriterTest.php index 4d97549..7e10498 100644 --- a/tests/Spout/Writer/XLSX/WriterTest.php +++ b/tests/Spout/Writer/XLSX/WriterTest.php @@ -385,6 +385,21 @@ class WriterTest extends \PHPUnit_Framework_TestCase $this->assertInlineDataWasWrittenToSheet($fileName, 1, 'control's _x0015_ "character"'); } + /** + * @return void + */ + public function testGeneratedFileShouldHaveTheCorrectMimeType() + { + $fileName = 'test_mime_type.xlsx'; + $resourcePath = $this->getGeneratedResourcePath($fileName); + $dataRows = [['foo']]; + + $this->writeToXLSXFile($dataRows, $fileName); + + $finfo = new \finfo(FILEINFO_MIME_TYPE); + $this->assertEquals('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', $finfo->file($resourcePath)); + } + /** * @param array $allRows * @param string $fileName