Skip to content

fix(imp): invalidate search cache when mailboxes change - #63

Merged
ralflang merged 1 commit into
FRAMEWORK_6_0from
fix/invalidate_search_cache
Jun 12, 2026
Merged

ralflang merged 1 commit into
FRAMEWORK_6_0from
fix/invalidate_search_cache

Conversation

@TDannhauer

Copy link
Copy Markdown
Contributor

Fix stale IMP search results after delete/move/expunge

Summary

  • Invalidate virtual search mailboxes when underlying IMAP folders change (delete, move, expunge).
  • Tie search viewport cache IDs to a per-query generation counter so changed() detects stale search results.
  • Handle unresolved browser UIDs gracefully when opening a message from outdated search results.

Problem

When messages were deleted from the inbox (moved to Trash or expunged), IMP search could still list them. Clicking such a stale result caused fatal errors in horde.log:

PHP ERROR: Attempt to read property "imp_imap" on null
Error: Call to a member function fetch() on null in Contents.php

Root cause: search results are stored as a virtual mailbox (impsearch…) in the session. That list was cached with a static cache ID ('1') that never changed when underlying folders were modified. Additionally, IMP_Ajax_Application::changed() skipped automatic refresh for search mailboxes unless forceUpdate was set.

Stale browser BUIDs could no longer be resolved to mailbox/UID pairs (fromBuids() returned empty indices), leaving getMailbox() as null and crashing IMP_Contents::_fetchData().

Solution

Search cache invalidation

Add IMP_Search::invalidateMailboxes() to rebuild affected search/virtual-folder lists when folder contents change. Called from:

  • IMP_Indices::delete() — after successful delete or move-to-trash
  • IMP_Indices::copy() — on move
  • IMP_Mailbox::expunge()

Only mailboxes actually modified are tracked; invalidation runs only after successful IMAP operations.

Per-query cache generation

Add a _cacheGen counter on IMP_Search_Query, bumped by invalidateCache(). Search mailbox _getCacheID() now includes this generation alongside sort/hide-deleted preferences, so the viewport cache ID changes when results are invalidated.

Remove the search-only exception in IMP_Ajax_Application::changed(). Re-check changed() in deleteMsgs() after deletion so the viewport refreshes when the search cache ID has moved.

Defensive message load

In IMP_Contents::_fetchData(), throw IMP_Exception when indices resolve to no mailbox (before the IMAP try block). showMessage() already catches this and refreshes the viewport instead of fatalling.

Files changed

  • lib/Search/Query.php — cache generation counter, serialization
  • lib/Search.php — invalidateMailboxes(), _queryAffected()
  • lib/Mailbox.php — dynamic search _getCacheID(), expunge hook
  • lib/Indices.php — invalidate on delete/move
  • lib/Ajax/Application.php — remove search changed() bypass, re-check after delete
  • lib/Contents.php — guard against unresolved BUIDs

Test plan

  • Run a quick search in INBOX; delete a result from INBOX (move to Trash). Re-open the search: deleted message must no longer appear.
  • Repeat with advanced search scoped to INBOX only and with “all mailboxes”.
  • Delete from Trash (expunge). Confirm expunged messages disappear from any active search that included Trash.
  • Move messages between folders via drag-and-drop; confirm searches covering source or target folders refresh.
  • Click a search result immediately after delete (race/stale BUID): expect “message does not exist” notice, not a PHP fatal in horde.log.
  • Confirm no $sortpref warnings in horde.log when opening or refreshing search mailboxes.
  • Sort order / hide-deleted toggle on a search mailbox still triggers a viewport refresh.

Refresh virtual search mailboxes after delete, move, and expunge so
stale session results and BUIDs no longer appear after messages leave
the inbox. Tie search viewport cache IDs to per-query generation
counters and let changed() detect search updates like normal mailboxes.
Handle unresolved message indices in IMP_Contents when a browser BUID
no longer maps to a mailbox.
@TDannhauer
TDannhauer requested a review from ralflang June 11, 2026 20:22
@ralflang
ralflang merged commit 37ef4ee into FRAMEWORK_6_0 Jun 12, 2026
0 of 6 checks passed
@TDannhauer
TDannhauer deleted the fix/invalidate_search_cache branch June 12, 2026 10:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants