Repository: PHPOffice/PhpSpreadsheet (php)

Issue:
Error in MS Excel after filling XLSX form with PhpOffice\PhpSpreadsheet
This is:

```
- [x] a bug report
- [ ] a feature request
- [ ] **not** a usage question (ask them on https://stackoverflow.com/questions/tagged/phpspreadsheet or https://gitter.im/PHPOffice/PhpSpreadsheet)
```

### What is the expected behavior?
 Using prefilled template fill some data and write into new file. New file created from template that opens using MS Excel without errors.

### What is the current behavior?
Using prefilled template fill some data and write into new file. New file created from template opens using MS Excel with error: "We found a problem with some content in 'my_file.xlsx'. Do you want us to try to recover as much as we can? If you trust the source of this workbook, click Yes."


### What are the steps to reproduce?

Please provide a [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) of code that exhibits the issue without relying on an external Excel file or a web server:

```php
<?php

require __DIR__ . '/vendor/autoload.php';

// Reading the file:
$reader = new PhpOffice\PhpSpreadsheet\Reader\Xlsx();
$reader->setReadDataOnly(false);
$spreadsheet = $reader->load('SEPA.xlsx');
$currentTab = $spreadsheet->getSheetByName('CarrierSchedule');

// Saving file:
$writer = new PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$writer->save('my_file.xlsx');

// Closing file:
$spreadsheet->disconnectWorksheets();
unset($spreadsheet);
unset($currentTab);
```
The filling cells data is skipped in the example because just opening template and saving into new file is enough to get the error.

If this is an issue with reading a specific spreadsheet file, then it may be appropriate to provide a sample file that demonstrates the problem; but please keep it as small as possible, and sanitize any confidential information before uploading.
[SEPA.xlsx](https://github.com/user-attachments/files/16686225/SEPA.xlsx)

### What features do you think are causing the issue

- [ ] Reader
- [x] Writer
- [ ] Styles
- [ ] Data Validations
- [ ] Formula Calculations
- [ ] Charts
- [ ] AutoFilter
- [ ] Form Elements

### Does an issue affect all spreadsheet file formats? If not, which formats are affected?
It possibly affects only xlsx files.


### Which versions of PhpSpreadsheet and PHP are affected?
PHP 8.2
phpoffice/phpspreadsheet: 1.29.0

Writer\Xlsx() produce broken files
This is:

```
- [X] a bug report
- [ ] a feature request
- [ ] **not** a usage question (ask them on https://stackoverflow.com/questions/tagged/phpspreadsheet or https://gitter.im/PHPOffice/PhpSpreadsheet)
```

I have an xlsx excel file template, open it, fill some cells and using Writer\Xlsx to save the output

Used template: [template.xlsx](https://github.com/user-attachments/files/17507283/template.xlsx)
Generated broken output: [output.xlsx](https://github.com/user-attachments/files/17507286/output.xlsx)

### What is the expected behavior?

To generate an excel compatible file.

### What is the current behavior?

It produces a broken file. However, LibreOffice opens it fine.
Xls writer, (without "x" at the end) works fine.

### What are the steps to reproduce?

Please provide a [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) of code that exhibits the issue without relying on an external Excel file or a web server:

```php
<?php

require __DIR__ . '/vendor/autoload.php';


# Read
$reader = new Spreadsheet\Reader\Xlsx();
$workbook = $reader->load('path/to/template.xlsx');
unset($reader);

# Write
$writer = new Spreadsheet\Writer\Xlsx($workbook);
ob_start();
$writer->save('php://output');
$content = ob_get_clean();
unset($writer);
header('Content-Disposition: attachment; filename="report.xlsx"');
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Length: ' . strlen($content));
header('Content-Transfer-Encoding: binary');
header('Cache-Control: must-revalidate');
echo $content;
unset($content);


```
If this is an issue with reading a specific spreadsheet file, then it may be appropriate to provide a sample file that demonstrates the problem; but please keep it as small as possible, and sanitize any confidential information before uploading.

### What features do you think are causing the issue

- [ ] Reader
- [X] Writer
- [ ] Styles
- [ ] Data Validations
- [ ] Formula Calculations
- [ ] Charts
- [ ] AutoFilter
- [ ] Form Elements

### Does an issue affect all spreadsheet file formats? If not, which formats are affected?
XLSX Writer is affected, XLS writer works fine.

### Which versions of PhpSpreadsheet and PHP are affected?
v3.3, v2.3

In earlier v1.x versions it worked fine. Not sure when it was broken.

Pull request description:
Write ignoredErrors Tag Before Drawings
Fix #4200. Fix #4145. Although the Xml is valid, Excel insists that worksheet.xml specifies `ignoredErrors` (introduced with 1.29.0) before `legacyDrawing` or `drawing`.

This is:

- [x] a bugfix
- [ ] a new feature
- [ ] refactoring
- [ ] additional unit tests

Checklist:

- [ ] Changes are covered by unit tests
  - [ ] Changes are covered by existing unit tests
  - [x] New unit tests have been added
- [x] Code style is respected
- [x] Commit message explains **why** the change is made (see https://github.com/erlang/otp/wiki/Writing-good-commit-messages)
- [ ] CHANGELOG.md contains a short summary of the change and a link to the pull request if applicable
- [ ] Documentation is updated as necessary

### Why this change is needed?

Provide an explanation of why this change is needed, with links to any Issues (if appropriate).
If this is a bugfix or a new feature, and there are no existing Issues, then please also create an issue that will make it easier to track progress with this PR.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 06a6e2de6..b57cfef45 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
 - Option to Write Hyperlink Rather Than Label to Csv. [Issue #1412](https://github.com/PHPOffice/PhpSpreadsheet/issues/1412) [PR #4151](https://github.com/PHPOffice/PhpSpreadsheet/pull/4151)
 - Invalid Html Due to Cached Filesize. [Issue #1107](https://github.com/PHPOffice/PhpSpreadsheet/issues/1107) [PR #4184](https://github.com/PHPOffice/PhpSpreadsheet/pull/4184)
 - Excel 2003 Allows Html Entities. [Issue #2157](https://github.com/PHPOffice/PhpSpreadsheet/issues/2157) [PR #4187](https://github.com/PHPOffice/PhpSpreadsheet/pull/4187)
+- Writer Xlsx ignoredErrors Before Drawings. [Issue #4200](https://github.com/PHPOffice/PhpSpreadsheet/issues/4200) [Issue #4145](https://github.com/PHPOffice/PhpSpreadsheet/issues/4145)  [PR #4212](https://github.com/PHPOffice/PhpSpreadsheet/pull/4212)
 - Allow ANCHORARRAY as Data Validation list. [Issue #4197](https://github.com/PHPOffice/PhpSpreadsheet/issues/4197) [PR #4203](https://github.com/PHPOffice/PhpSpreadsheet/pull/4203)
 
 ## 2024-09-29 - 3.3.0 (no 3.0.\*, 3.1.\*, 3.2.\*)
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
index 8df38ddce..28af25829 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
@@ -129,6 +129,9 @@ class Worksheet extends WriterPart
         // Breaks
         $this->writeBreaks($objWriter, $worksheet);
 
+        // IgnoredErrors
+        $this->writeIgnoredErrors($objWriter);
+
         // Drawings and/or Charts
         $this->writeDrawings($objWriter, $worksheet, $includeCharts);
 
@@ -141,9 +144,6 @@ class Worksheet extends WriterPart
         // AlternateContent
         $this->writeAlternateContent($objWriter, $worksheet);
 
-        // IgnoredErrors
-        $this->writeIgnoredErrors($objWriter);
-
         // BackgroundImage must come after ignored, before table
         $this->writeBackgroundImage($objWriter, $worksheet);
 


Test patch:
diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue4200Test.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue4200Test.php
new file mode 100644
index 000000000..4a64e8c0c
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue4200Test.php
@@ -0,0 +1,90 @@
+<?php
+
+declare(strict_types=1);
+
+namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
+
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\RichText\RichText;
+use PhpOffice\PhpSpreadsheet\Shared\File;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
+use PHPUnit\Framework\TestCase;
+use ZipArchive;
+
+class Issue4200Test extends TestCase
+{
+    private string $outputFile = '';
+
+    protected function tearDown(): void
+    {
+        if ($this->outputFile !== '') {
+            unlink($this->outputFile);
+            $this->outputFile = '';
+        }
+    }
+
+    public function testIssue4200(): void
+    {
+        // ignoredErrors came after legacyDrawing
+        $spreadsheet = new Spreadsheet();
+        $sheet = $spreadsheet->getActiveSheet();
+        $sheet->getCell('A1')->setValueExplicit('01', DataType::TYPE_STRING);
+        $sheet->getCell('A1')
+            ->getIgnoredErrors()
+            ->setNumberStoredAsText(true);
+        $richText = new RichText();
+        $richText->createText('hello');
+        $sheet->getComment('C1')
+            ->setText($richText);
+        $writer = new XlsxWriter($spreadsheet);
+        $this->outputFile = File::temporaryFilename();
+        $writer->save($this->outputFile);
+        $spreadsheet->disconnectWorksheets();
+
+        $zip = new ZipArchive();
+        $open = $zip->open($this->outputFile, ZipArchive::RDONLY);
+        if ($open !== true) {
+            self::fail("zip open failed for {$this->outputFile}");
+        } else {
+            $contents = (string) $zip->getFromName('xl/worksheets/sheet1.xml');
+            self::assertStringContainsString(
+                '<ignoredErrors><ignoredError sqref="A1" numberStoredAsText="1"/></ignoredErrors><legacyDrawing r:id="rId_comments_vml1"/>',
+                $contents
+            );
+        }
+    }
+
+    public function testIssue4145(): void
+    {
+        // ignoredErrors came after drawing
+        $spreadsheet = new Spreadsheet();
+        $sheet = $spreadsheet->getActiveSheet();
+        $sheet->getCell('A1')->setValueExplicit('01', DataType::TYPE_STRING);
+        $sheet->getCell('A1')
+            ->getIgnoredErrors()
+            ->setNumberStoredAsText(true);
+        $drawing = new Drawing();
+        $drawing->setName('Blue Square');
+        $drawing->setPath('tests/data/Writer/XLSX/blue_square.png');
+        $drawing->setCoordinates('C1');
+        $drawing->setWorksheet($sheet);
+        $writer = new XlsxWriter($spreadsheet);
+        $this->outputFile = File::temporaryFilename();
+        $writer->save($this->outputFile);
+        $spreadsheet->disconnectWorksheets();
+
+        $zip = new ZipArchive();
+        $open = $zip->open($this->outputFile, ZipArchive::RDONLY);
+        if ($open !== true) {
+            self::fail("zip open failed for {$this->outputFile}");
+        } else {
+            $contents = (string) $zip->getFromName('xl/worksheets/sheet1.xml');
+            self::assertStringContainsString(
+                '<ignoredErrors><ignoredError sqref="A1" numberStoredAsText="1"/></ignoredErrors><drawing r:id="rId1"/>',
+                $contents
+            );
+        }
+    }
+}