ZIP export fails with fatal error #5496

Closed
opened 2026-02-05 10:07:02 +03:00 by OVERLORD · 1 comment
Owner

Originally created by @intrepidsilence on GitHub (Nov 12, 2025).

Describe the Bug

ZIP export fails with fatal error "Attempt to read property 'id' on null" when exporting books that contain orphaned gallery or drawio images (images that exist in the database but have no associated page).

Error Message:

[2025-11-12 14:47:01] production.ERROR: Attempt to read property "id" on null {"userId":170,"exception":"[object] (ErrorException(code: 0):
Attempt to read property \"id\" on null at /var/www/bookstack/app/Exports/ZipExports/ZipExportReferences.php:138)

Impact:

  • ZIP exports fail completely (500 Internal Server Error)
  • Both UI and API exports are affected
  • HTML/PDF exports work fine (not affected)

Steps to Reproduce

  1. Create a book with pages containing images
  2. Delete one or more pages that had gallery or drawio images attached (leaving orphaned images in database)
  3. Attempt to export the book as ZIP via:
    • Web UI: Click "Export" → "Portable ZIP"
    • API: GET /api/books/{id}/export/zip
  4. Export fails with 500 error

Alternative reproduction:

  1. Have images uploaded but not properly attached to any page
  2. Attempt any book ZIP export containing these orphaned images
  3. Export fails

Expected Behaviour

ZIP export should:

  • Handle orphaned images gracefully (skip them or include them)
  • Complete successfully even when images lack page associations
  • Log a warning about orphaned images (optional)
  • Return a valid ZIP file

Screenshots or Additional Context

Root Cause:

File: app/Exports/ZipExports/ZipExportReferences.php
Line: 138
Function: handleModelReference()

Problematic Code:

// Line 137
$page = $model->getPage();
// Line 138 - BUG: $page can be null, but we access $page->id without checking
$pageExportModel = $this->pages[$page->id] ?? ($exportModel instanceof ZipExportPage ? $exportModel : null);

Issue:

  • $model->getPage() returns null for orphaned images
  • Code immediately accesses $page->id without null check
  • PHP 8+ throws "Attempt to read property 'id' on null" error

How Orphaned Images Occur:

  • Pages deleted but images remain in database
  • Images uploaded but never properly attached to pages
  • Database migrations or cleanup operations
  • Bulk deletions that don't cascade to images

Suggested Fix

Add null check before accessing $page->id:

File: app/Exports/ZipExports/ZipExportReferences.php
Line: 138

Change FROM:

$pageExportModel = $this->pages[$page->id] ?? ($exportModel instanceof ZipExportPage ? $exportModel : null);

Change TO:

$pageExportModel = ($page && isset($this->pages[$page->id])) ? $this->pages[$page->id] : ($exportModel instanceof ZipExportPage ? $exportModel : null);

Explanation:

  • Check $page is not null before accessing $page->id
  • Use isset() to safely check array key existence
  • Maintains same fallback logic for null cases
  • Orphaned images are skipped (won't be included in export)

Tested: Fix confirmed working on v25.11.1 - ZIP exports now succeed with orphaned images present


Alternative Solutions

Option 1: Database Cleanup (Admin workaround)
Identify and delete orphaned images:

DELETE FROM images
WHERE (type = 'gallery' OR type = 'drawio')
  AND id NOT IN (
    SELECT image_id FROM page_image WHERE image_id IS NOT NULL
  );

Option 2: Comprehensive Fix (Better long-term)

  • Add database constraint to cascade delete images when pages are deleted
  • Add null check (as suggested above)
  • Log warning when orphaned images are encountered
  • Include orphaned images in a separate "unattached" folder in ZIP

Additional Notes

  • Bug affects ZIP exports only (HTML/PDF exports unaffected)
  • Occurs with both API and UI exports
  • Error is silent to API consumers (just returns 500)
  • Can block entire migration workflows that depend on ZIP exports
  • Issue discovered during BookStack → Confluence migration

Environment:

  • PHP: 8.x (required for this specific error message)
  • Database: MySQL/MariaDB
  • Installation: Standard BookStack deployment

Patch File

A working patch file is available if needed:

--- a/app/Exports/ZipExports/ZipExportReferences.php
+++ b/app/Exports/ZipExports/ZipExportReferences.php
@@ -135,7 +135,7 @@ class ZipExportReferences
             if ($model instanceof Image && ($model->type === 'gallery' || $model->type === 'drawio')) {
                 $page = $model->getPage();
-                $pageExportModel = $this->pages[$page->id] ?? ($exportModel instanceof ZipExportPage ? $exportModel : null);
+                $pageExportModel = ($page && isset($this->pages[$page->id])) ? $this->pages[$page->id] : ($exportModel instanceof ZipExportPage ? $exportModel : null);
                 if ($page && $this->pageIsInExport($page, $pageExportModel, $exportModel, $linkOnly)) {
                     $this->addContentImages[] = $model;
                 }

Apply with:

cd /var/www/bookstack
patch -p1 < fix.patch
php artisan cache:clear

Browser Details

No response

Exact BookStack Version

Version: v25.11.1 (confirmed affected) Likely affected: v24.12+ (when ZIP export feature was added)

Originally created by @intrepidsilence on GitHub (Nov 12, 2025). ### Describe the Bug ZIP export fails with fatal error "Attempt to read property 'id' on null" when exporting books that contain orphaned gallery or drawio images (images that exist in the database but have no associated page). **Error Message:** ``` [2025-11-12 14:47:01] production.ERROR: Attempt to read property "id" on null {"userId":170,"exception":"[object] (ErrorException(code: 0): Attempt to read property \"id\" on null at /var/www/bookstack/app/Exports/ZipExports/ZipExportReferences.php:138) ``` **Impact:** - ZIP exports fail completely (500 Internal Server Error) - Both UI and API exports are affected - HTML/PDF exports work fine (not affected) ### Steps to Reproduce 1. Create a book with pages containing images 2. Delete one or more pages that had gallery or drawio images attached (leaving orphaned images in database) 3. Attempt to export the book as ZIP via: - Web UI: Click "Export" → "Portable ZIP" - API: `GET /api/books/{id}/export/zip` 4. Export fails with 500 error **Alternative reproduction:** 1. Have images uploaded but not properly attached to any page 2. Attempt any book ZIP export containing these orphaned images 3. Export fails ### Expected Behaviour ZIP export should: - Handle orphaned images gracefully (skip them or include them) - Complete successfully even when images lack page associations - Log a warning about orphaned images (optional) - Return a valid ZIP file ### Screenshots or Additional Context **Root Cause:** File: `app/Exports/ZipExports/ZipExportReferences.php` Line: 138 Function: `handleModelReference()` **Problematic Code:** ```php // Line 137 $page = $model->getPage(); // Line 138 - BUG: $page can be null, but we access $page->id without checking $pageExportModel = $this->pages[$page->id] ?? ($exportModel instanceof ZipExportPage ? $exportModel : null); ``` **Issue:** - `$model->getPage()` returns `null` for orphaned images - Code immediately accesses `$page->id` without null check - PHP 8+ throws "Attempt to read property 'id' on null" error **How Orphaned Images Occur:** - Pages deleted but images remain in database - Images uploaded but never properly attached to pages - Database migrations or cleanup operations - Bulk deletions that don't cascade to images --- ## Suggested Fix Add null check before accessing `$page->id`: **File:** `app/Exports/ZipExports/ZipExportReferences.php` **Line:** 138 **Change FROM:** ```php $pageExportModel = $this->pages[$page->id] ?? ($exportModel instanceof ZipExportPage ? $exportModel : null); ``` **Change TO:** ```php $pageExportModel = ($page && isset($this->pages[$page->id])) ? $this->pages[$page->id] : ($exportModel instanceof ZipExportPage ? $exportModel : null); ``` **Explanation:** - Check `$page` is not null before accessing `$page->id` - Use `isset()` to safely check array key existence - Maintains same fallback logic for null cases - Orphaned images are skipped (won't be included in export) **Tested:** Fix confirmed working on v25.11.1 - ZIP exports now succeed with orphaned images present --- ## Alternative Solutions **Option 1: Database Cleanup (Admin workaround)** Identify and delete orphaned images: ```sql DELETE FROM images WHERE (type = 'gallery' OR type = 'drawio') AND id NOT IN ( SELECT image_id FROM page_image WHERE image_id IS NOT NULL ); ``` **Option 2: Comprehensive Fix (Better long-term)** - Add database constraint to cascade delete images when pages are deleted - Add null check (as suggested above) - Log warning when orphaned images are encountered - Include orphaned images in a separate "unattached" folder in ZIP --- ## Additional Notes - Bug affects **ZIP exports only** (HTML/PDF exports unaffected) - Occurs with both API and UI exports - Error is silent to API consumers (just returns 500) - Can block entire migration workflows that depend on ZIP exports - Issue discovered during BookStack → Confluence migration **Environment:** - PHP: 8.x (required for this specific error message) - Database: MySQL/MariaDB - Installation: Standard BookStack deployment --- ## Patch File A working patch file is available if needed: ```diff --- a/app/Exports/ZipExports/ZipExportReferences.php +++ b/app/Exports/ZipExports/ZipExportReferences.php @@ -135,7 +135,7 @@ class ZipExportReferences if ($model instanceof Image && ($model->type === 'gallery' || $model->type === 'drawio')) { $page = $model->getPage(); - $pageExportModel = $this->pages[$page->id] ?? ($exportModel instanceof ZipExportPage ? $exportModel : null); + $pageExportModel = ($page && isset($this->pages[$page->id])) ? $this->pages[$page->id] : ($exportModel instanceof ZipExportPage ? $exportModel : null); if ($page && $this->pageIsInExport($page, $pageExportModel, $exportModel, $linkOnly)) { $this->addContentImages[] = $model; } ``` **Apply with:** ```bash cd /var/www/bookstack patch -p1 < fix.patch php artisan cache:clear ``` ### Browser Details _No response_ ### Exact BookStack Version **Version:** v25.11.1 (confirmed affected) **Likely affected:** v24.12+ (when ZIP export feature was added)
OVERLORD added the 🐛 Bug label 2026-02-05 10:07:02 +03:00
Author
Owner

@ssddanbrown commented on GitHub (Nov 18, 2025):

Thanks for reporting this issue @intrepidsilence, I have addressed this within #5899, to be part of the next patch release.

@ssddanbrown commented on GitHub (Nov 18, 2025): Thanks for reporting this issue @intrepidsilence, I have addressed this within #5899, to be part of the next patch release.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/BookStack#5496