Skip to content

ZIP export fails with fatal error #5885

@intrepidsilence

Description

@intrepidsilence

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions