-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
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
- Create a book with pages containing images
- Delete one or more pages that had gallery or drawio images attached (leaving orphaned images in database)
- Attempt to export the book as ZIP via:
- Web UI: Click "Export" → "Portable ZIP"
- API:
GET /api/books/{id}/export/zip
- Export fails with 500 error
Alternative reproduction:
- Have images uploaded but not properly attached to any page
- Attempt any book ZIP export containing these orphaned images
- 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()returnsnullfor orphaned images- Code immediately accesses
$page->idwithout 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
$pageis 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:clearBrowser Details
No response
Exact BookStack Version
Version: v25.11.1 (confirmed affected) Likely affected: v24.12+ (when ZIP export feature was added)