@@ -677,4 +677,167 @@ public function testDrawingPassThroughPreservesShapesAndSvg(): void
677677 $ zip ->close ();
678678 unlink ($ tempFile );
679679 }
680+
681+ /**
682+ * Test that pass-through preserves grouped images.
683+ * Grouped images are not parsed into the drawing collection, so they would be lost
684+ * without proper pass-through of drawing XML, relationships, and media files.
685+ *
686+ * File structure of grouped_images.xlsx:
687+ * - Contains a group (xdr:grpSp) with 2 images inside
688+ * - xl/media/image1.png and xl/media/image2.png
689+ * - The images are referenced from within the group element
690+ */
691+ public function testDrawingPassThroughPreservesGroupedImages (): void
692+ {
693+ $ template = self ::DIRECTORY . 'grouped_images.xlsx ' ;
694+
695+ // Verify that the original file contains a group with images
696+ $ originalZip = new ZipArchive ();
697+ $ originalZip ->open ($ template );
698+ $ originalDrawingXml = $ originalZip ->getFromName ('xl/drawings/drawing1.xml ' );
699+ $ originalRels = $ originalZip ->getFromName ('xl/drawings/_rels/drawing1.xml.rels ' );
700+ $ originalZip ->close ();
701+
702+ self ::assertNotFalse ($ originalDrawingXml , 'Original file should have drawing XML ' );
703+ self ::assertStringContainsString ('<xdr:grpSp> ' , $ originalDrawingXml , 'Original file should contain a group ' );
704+ self ::assertStringContainsString ('<xdr:pic> ' , $ originalDrawingXml , 'Original file should contain images inside group ' );
705+ self ::assertNotFalse ($ originalRels , 'Original file should have drawing relationships ' );
706+ self ::assertStringContainsString ('image1.png ' , $ originalRels , 'Original rels should reference image1.png ' );
707+ self ::assertStringContainsString ('image2.bmp ' , $ originalRels , 'Original rels should reference image2.bmp ' );
708+ self ::assertStringContainsString ('image4.gif ' , $ originalRels , 'Original rels should reference image4.gif ' );
709+ self ::assertStringContainsString ('image6.svg ' , $ originalRels , 'Original rels should reference image6.svg ' );
710+
711+ // Load with pass-through enabled
712+ $ reader = new XlsxReader ();
713+ $ reader ->setEnableDrawingPassThrough (true );
714+ $ spreadsheet = $ reader ->load ($ template );
715+
716+ $ sheet = $ spreadsheet ->getActiveSheet ();
717+
718+ // Verify that drawing collection is empty (grouped images are not parsed)
719+ $ drawings = $ sheet ->getDrawingCollection ();
720+ self ::assertCount (0 , $ drawings , 'Drawing collection should be empty (grouped images are not parsed) ' );
721+
722+ // Verify that pass-through data is stored
723+ $ unparsedData = $ spreadsheet ->getUnparsedLoadedData ();
724+ $ codeName = $ sheet ->getCodeName ();
725+ self ::assertArrayHasKey ('sheets ' , $ unparsedData );
726+ self ::assertIsArray ($ unparsedData ['sheets ' ]);
727+ self ::assertArrayHasKey ($ codeName , $ unparsedData ['sheets ' ]);
728+ self ::assertIsArray ($ unparsedData ['sheets ' ][$ codeName ]);
729+ self ::assertTrue ($ unparsedData ['sheets ' ][$ codeName ]['drawingPassThroughEnabled ' ] ?? false , 'Pass-through should be enabled ' );
730+
731+ // Verify that drawing relationships are stored
732+ self ::assertArrayHasKey ('drawingRelationships ' , $ unparsedData ['sheets ' ][$ codeName ], 'Drawing relationships should be stored ' );
733+
734+ // Verify that media files paths are stored
735+ self ::assertArrayHasKey ('drawingMediaFiles ' , $ unparsedData ['sheets ' ][$ codeName ], 'Media files should be stored ' );
736+ $ mediaFiles = $ unparsedData ['sheets ' ][$ codeName ]['drawingMediaFiles ' ];
737+ self ::assertIsArray ($ mediaFiles );
738+ self ::assertGreaterThanOrEqual (2 , count ($ mediaFiles ), 'Should have at least 2 media files ' );
739+
740+ // Save to file
741+ $ tempFile = File::temporaryFilename ();
742+ $ writer = new XlsxWriter ($ spreadsheet );
743+ $ writer ->save ($ tempFile );
744+ $ spreadsheet ->disconnectWorksheets ();
745+
746+ // Verify that the saved file preserves the group and images
747+ $ zip = new ZipArchive ();
748+ $ zip ->open ($ tempFile );
749+
750+ // Check that drawing XML contains the group
751+ $ drawingXml = $ zip ->getFromName ('xl/drawings/drawing1.xml ' );
752+ self ::assertNotFalse ($ drawingXml , 'Drawing XML should exist in saved file ' );
753+ self ::assertStringContainsString ('<xdr:grpSp> ' , $ drawingXml , 'Group should be preserved ' );
754+ self ::assertStringContainsString ('<xdr:pic> ' , $ drawingXml , 'Images inside group should be preserved ' );
755+
756+ // Check that drawing relationships are preserved
757+ $ drawingRels = $ zip ->getFromName ('xl/drawings/_rels/drawing1.xml.rels ' );
758+ self ::assertNotFalse ($ drawingRels , 'Drawing relationships file should exist ' );
759+ self ::assertStringContainsString ('image1.png ' , $ drawingRels , 'Drawing rels should reference image1.png ' );
760+ self ::assertStringContainsString ('image2.bmp ' , $ drawingRels , 'Drawing rels should reference image2.bmp ' );
761+ self ::assertStringContainsString ('image4.gif ' , $ drawingRels , 'Drawing rels should reference image4.gif ' );
762+ self ::assertStringContainsString ('image6.svg ' , $ drawingRels , 'Drawing rels should reference image6.svg ' );
763+
764+ // Check that media files are present
765+ $ image1Content = $ zip ->getFromName ('xl/media/image1.png ' );
766+ $ image2Content = $ zip ->getFromName ('xl/media/image2.bmp ' );
767+ $ image4Content = $ zip ->getFromName ('xl/media/image4.gif ' );
768+ $ image6Content = $ zip ->getFromName ('xl/media/image6.svg ' );
769+ self ::assertNotFalse ($ image1Content , 'image1.png should be preserved ' );
770+ self ::assertNotFalse ($ image2Content , 'image2.bmp should be preserved ' );
771+ self ::assertNotFalse ($ image4Content , 'image4.gif should be preserved ' );
772+ self ::assertNotFalse ($ image6Content , 'image6.svg should be preserved ' );
773+
774+ // Check that Content_Types.xml contains all image extensions
775+ $ contentTypes = $ zip ->getFromName ('[Content_Types].xml ' );
776+ self ::assertNotFalse ($ contentTypes , 'Content_Types.xml should exist ' );
777+ self ::assertStringContainsString ('image/png ' , $ contentTypes , 'Content_Types should declare PNG mime type ' );
778+ self ::assertStringContainsString ('image/bmp ' , $ contentTypes , 'Content_Types should declare BMP mime type ' );
779+ self ::assertStringContainsString ('image/gif ' , $ contentTypes , 'Content_Types should declare GIF mime type ' );
780+ self ::assertStringContainsString ('image/svg+xml ' , $ contentTypes , 'Content_Types should declare SVG mime type ' );
781+
782+ $ zip ->close ();
783+ unlink ($ tempFile );
784+ }
785+
786+ /**
787+ * Test that WITHOUT pass-through, grouped images are lost.
788+ * This documents the expected behavior without the fix.
789+ */
790+ public function testWithoutPassThroughGroupedImagesAreLost (): void
791+ {
792+ $ template = self ::DIRECTORY . 'grouped_images.xlsx ' ;
793+
794+ // Verify that the original file contains grouped images
795+ $ originalZip = new ZipArchive ();
796+ $ originalZip ->open ($ template );
797+ $ originalDrawingXml = $ originalZip ->getFromName ('xl/drawings/drawing1.xml ' );
798+ $ originalZip ->close ();
799+ self ::assertStringContainsString ('<xdr:grpSp> ' , $ originalDrawingXml , 'Original file should contain a group ' );
800+
801+ // Load WITHOUT pass-through
802+ $ reader = new XlsxReader ();
803+ // Don't enable pass-through
804+ $ spreadsheet = $ reader ->load ($ template );
805+
806+ $ sheet = $ spreadsheet ->getActiveSheet ();
807+
808+ // Verify that drawing collection is empty (grouped images are not parsed)
809+ $ drawings = $ sheet ->getDrawingCollection ();
810+ self ::assertCount (0 , $ drawings , 'Drawing collection should be empty without pass-through too ' );
811+
812+ // Save to file
813+ $ tempFile = File::temporaryFilename ();
814+ $ writer = new XlsxWriter ($ spreadsheet );
815+ $ writer ->save ($ tempFile );
816+ $ spreadsheet ->disconnectWorksheets ();
817+
818+ // Verify that the group and images are lost
819+ $ zip = new ZipArchive ();
820+ $ zip ->open ($ tempFile );
821+
822+ // Drawing XML should not exist or not contain the group
823+ $ drawingXml = $ zip ->getFromName ('xl/drawings/drawing1.xml ' );
824+
825+ if ($ drawingXml !== false ) {
826+ // If drawing XML exists, it should NOT contain the group
827+ self ::assertStringNotContainsString ('<xdr:grpSp> ' , $ drawingXml , 'Group should be lost without pass-through ' );
828+ }
829+
830+ // Media files should NOT be present (nothing was in the drawing collection)
831+ $ image1Content = $ zip ->getFromName ('xl/media/image1.png ' );
832+ $ image2Content = $ zip ->getFromName ('xl/media/image2.bmp ' );
833+ $ image4Content = $ zip ->getFromName ('xl/media/image4.gif ' );
834+ $ image6Content = $ zip ->getFromName ('xl/media/image6.svg ' );
835+ self ::assertFalse ($ image1Content , 'image1.png should NOT be present without pass-through ' );
836+ self ::assertFalse ($ image2Content , 'image2.bmp should NOT be present without pass-through ' );
837+ self ::assertFalse ($ image4Content , 'image4.gif should NOT be present without pass-through ' );
838+ self ::assertFalse ($ image6Content , 'image6.svg should NOT be present without pass-through ' );
839+
840+ $ zip ->close ();
841+ unlink ($ tempFile );
842+ }
680843}
0 commit comments