Skip to content

feat: show Eventyay description restore action as a block editor notice - #372

Open
TheVaibhaw wants to merge 8 commits into
fossasia:mainfrom
TheVaibhaw:enhancement/370-restore-action-notice
Open

TheVaibhaw wants to merge 8 commits into
fossasia:mainfrom
TheVaibhaw:enhancement/370-restore-action-notice

Conversation

@TheVaibhaw

@TheVaibhaw TheVaibhaw commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

Closes #370

Depends on #355 (still open). This branch is based on it, so #355's commits show here until it's merged; my change is the last commit.

What changed
In the block editor, the "Description edited on this site" note and the "Restore from Eventyay" button now appear as a WordPress editor notice at the top of the editing area, right above the description, so admins no longer have to look for it in the sidebar.

Why editor notices
As suggested in #355 (point 5), I first looked for an existing WordPress solution. Editor notices are what WordPress uses for its own messages and support an action button. Sidebar panels would keep the action in the sidebar.

Testing

  • phpcs, phpstan and ESLint pass; composer test passes (223 tests, 6 new in EventDescriptionNoticeTest.php).
  • Checked manually with the Astra theme (screencast below) and in the classic editor.

I used AI assistance for parts of this PR and reviewed the changes myself.

Screencast (Astra)

wpfaevent-370-astra-screencast.mp4
wpfaevent-370-astra-block-editor-notice wpfaevent-370-classic-editor-fallback

Summary by Sourcery

Preserve local event description edits across Eventyay syncs and surface a secure restore action wherever events are edited.

New Features:

  • Show locally edited Eventyay descriptions and a restore action as a native notice in the WordPress block editor.
  • Provide description restoration and sync-status feedback in the classic editor and frontend event-edit modal.

Bug Fixes:

  • Preserve locally edited event descriptions during subsequent Eventyay imports while continuing to synchronize other event fields.

Enhancements:

  • Track imported and locally modified descriptions, detect newer remote content, and allow restoring the latest Eventyay description with confirmation and capability protection.

Tests:

  • Add coverage for description synchronization, restoration, legacy events, editor notices, permissions, and nonce-protected restore URLs.

Summary by CodeRabbit

  • New Features

    • Event descriptions edited locally are preserved during imports, while other event details continue to sync.
    • Editors can review notices about newer imported descriptions and restore the latest version, with confirmation before replacing current content.
    • Description notices are available in both the block editor and classic editor.
  • Tests

    • Added coverage for description syncing, local edit preservation, restore actions, and editor notices.

SanketKokate712 and others added 8 commits October 9, 2026 16:55
Once an imported event's description is edited on the site (Edit Event
modal, WP admin or dashboard), manual and scheduled syncs no longer
overwrite it. Other imported fields keep syncing.

Each import that writes the description stores a fingerprint of the
saved content and excerpt; a mismatch means a local edit. Line-ending
changes from form round-trips do not count as edits.

The WP admin editor and the Edit Event modal show a note for such
events, mention when Eventyay has a newer description, and offer
"Restore from Eventyay", which applies the current Eventyay text and
makes the event follow Eventyay again.

Fixes fossasia#354
Slash the description at the post and meta write boundaries so a
backslash in an Eventyay description survives import, tracking and
"Restore from Eventyay".

Ask for confirmation before the Edit Event modal's restore link leaves
the page, since unsaved form changes would be lost.
Events imported before description tracking existed have no
fingerprint. On their first sync, compare the post with the incoming
Eventyay description: if it differs and the post was modified after its
last import, the difference was made on this site, so keep it and mark
it as a local edit. If the post was not modified since that import, the
difference came from Eventyay and the description follows it as before.
Use description-specific evidence for events imported before tracking:
imports write the same text to content and excerpt (or only content),
while local edit paths change just one of them. Two different non-empty
fields mean a local edit; title or status changes no longer lock the
description.

When marking such an edit, keep Eventyay's text as the baseline only if
the unedited field still matches it; otherwise leave the baseline empty
so the note reports that Eventyay has a newer description.
An empty excerpt next to content that differs from Eventyay can be a
content-only import whose text changed on Eventyay or a local content
edit. Keep it instead of overwriting, and let the note offer to restore
Eventyay's newer description. Follow Eventyay only when the content
still matches it.
WordPress rejects updates that leave the title, content and excerpt all
empty. Allow that for the restore update only, so an empty Eventyay
description can be restored on an event whose title was cleared.
@sourcery-ai

sourcery-ai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR introduces post-meta fingerprinting and synchronization logic to preserve locally edited Eventyay descriptions across imports, provides a secure restore path, and surfaces that action through a block-editor notice, classic-editor fallback, and frontend edit modal with confirmation handling and comprehensive tests.

Sequence diagram for restoring an Eventyay description from the block editor

sequenceDiagram
    actor Admin
    participant Editor as BlockEditor
    participant Notice as wp_notices
    participant Restore as admin_post
    participant Sync as Wpfaevent_Event_Description_Sync
    participant WordPress

    Editor->>Notice: createNotice
    Admin->>Notice: Restore from Eventyay
    Notice->>Editor: isEditedPostDirty
    Editor-->>Admin: window.confirm
    Admin->>Restore: GET restoreUrl
    Restore->>Sync: handle_restore_request
    Sync->>WordPress: check_admin_referer
    Sync->>WordPress: current_user_can
    Sync->>Sync: restore
    Sync->>WordPress: wp_update_post
    Sync->>Sync: remember_import
    Restore-->>Editor: wp_safe_redirect
Loading

Flow diagram for preserving locally edited Eventyay descriptions

flowchart TD
    A[Eventyay import] --> B[keep_local_description]
    B --> C{has_local_edit}
    C -->|No| D[wp_update_post]
    C -->|Yes| E[Keep post_content and post_excerpt]
    D --> F[remember_import]
    E --> F
    F --> G[Store REMOTE_META]
    F --> H[Store APPLIED_META and HASH_META when applied]
    H --> I[get_note]
    E --> I
    I --> J[Block editor notice or classic editor fallback]
Loading

File-Level Changes

Change Details Files
Add persistent synchronization tracking that preserves locally edited descriptions while continuing to update other imported event fields.
  • Store the latest remote description, applied baseline, and content/excerpt fingerprint in post meta.
  • Detect local edits, including legacy posts and edits to either content or excerpt, while normalizing line endings and preserving backslashes.
  • Skip description writes during imports when a local edit is detected, and resume normal syncing after restoration.
  • Restore the latest remote description through a nonce- and capability-protected admin-post handler, including empty descriptions.
admin/class-wpfaevent-eventyay-importer.php
includes/eventyay-importer/class-wpfaevent-event-repository.php
includes/helpers/class-wpfaevent-event-description-sync.php
includes/class-wpfaevent.php
Surface the description synchronization status and restore action in the block editor while retaining a classic-editor fallback.
  • Enqueue a WordPress notices-based warning only for editable wpfa_event posts with locally edited descriptions.
  • Localize the note, restore URL, and confirmation messages for the editor notice action.
  • Confirm restoration and warn about unsaved block-editor changes before navigating to the restore endpoint.
  • Render the existing-style sidebar meta box only when the classic editor is active.
admin/js/wpfaevent-description-notice.js
admin/partials/meta-boxes/class-wpfaevent-admin-event-metabox.php
Expose the same description status and restore flow in the frontend Edit Event modal.
  • Add description status and restore URL data to event cards for users who can edit the event.
  • Populate the modal note dynamically and confirm before discarding unsaved form changes.
  • Add localized restore messaging and styling for the frontend notice.
public/class-wpfaevent-public.php
public/css/wpfaevent-public.css
public/js/wpfaevent-events.js
public/partials/events/event-card.php
public/partials/events/event-modal.php
Add automated coverage for synchronization behavior, restoration security, editor notices, fallbacks, and legacy-event migration behavior.
  • Test local edits in post content and excerpt, remote updates, restoration, empty descriptions, escaping, and legacy metadata initialization.
  • Test notice enqueue conditions, localized nonce-protected URLs, capability checks, and classic-editor meta-box behavior.
tests/unit/EventDescriptionNoticeTest.php
tests/unit/EventDescriptionSyncTest.php

Assessment against linked issues

Issue Objective Addressed Explanation
#370 Move the description sync status and restore action from the block editor sidebar into a more discoverable, native editor UI near the event-editing workflow, while retaining a classic-editor fallback. ✅
#370 Preserve the explanation of local edits and newer Eventyay descriptions, and make restoring the Eventyay description clear and safe, including nonce and capability protection and confirmation when unsaved changes may be lost. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a591b29e-7170-4988-b2e8-5150a5f4cbdd
Commits

Reviewing files that changed from the base of the PR and between af771bf and 1f714ee.

Files selected for processing: 13
  • admin/class-wpfaevent-eventyay-importer.php
  • admin/js/wpfaevent-description-notice.js
  • admin/partials/meta-boxes/class-wpfaevent-admin-event-metabox.php
  • includes/class-wpfaevent.php
  • includes/eventyay-importer/class-wpfaevent-event-repository.php
  • includes/helpers/class-wpfaevent-event-description-sync.php
  • public/class-wpfaevent-public.php
  • public/css/wpfaevent-public.css
  • public/js/wpfaevent-events.js
  • public/partials/events/event-card.php
  • public/partials/events/event-modal.php
  • tests/unit/EventDescriptionNoticeTest.php
  • tests/unit/EventDescriptionSyncTest.php

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Walkthrough

Imported descriptions are now tracked so that detected local edits remain intact during later imports. The latest remote description can be restored from the admin editor or frontend event modal.

Changes: Event Description Synchronization

Layer / File(s) Summary
Description tracking and import preservation
includes/helpers/class-wpfaevent-event-description-sync.php, admin/class-wpfaevent-eventyay-importer.php, includes/eventyay-importer/class-wpfaevent-event-repository.php, tests/unit/EventDescriptionSyncTest.php
The synchronization helper detects local edits, records remote and applied descriptions, and normalizes text for fingerprint comparisons. Both import paths preserve detected local content and excerpt edits while continuing to sync other fields. Unit tests cover imports, legacy tracking, and description restoration.
Admin editor notice and restore
includes/class-wpfaevent.php, admin/partials/meta-boxes/class-wpfaevent-admin-event-metabox.php, admin/js/wpfaevent-description-notice.js, tests/unit/EventDescriptionNoticeTest.php
The block editor receives a dismissible notice and restore action when a local edit is detected. The classic editor uses a meta box. The restore request validates the nonce, event type, and edit permission.
Frontend event modal notice
public/class-wpfaevent-public.php, public/css/wpfaevent-public.css, public/js/wpfaevent-events.js, public/partials/events/event-card.php, public/partials/events/event-modal.php
Editable event cards include the description-sync note and restore URL when a note exists. The edit modal displays the note and asks for confirmation before following the restore link.

Priority: Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  actor EditorUser
  participant AdminEditor
  participant DescriptionNoticeScript
  participant AdminPost
  participant DescriptionSync
  participant WordPressPost
  EditorUser->>AdminEditor: Select restore action
  AdminEditor->>DescriptionNoticeScript: Run confirmation
  DescriptionNoticeScript->>AdminPost: Navigate to restore URL
  AdminPost->>DescriptionSync: Handle restore request
  DescriptionSync->>WordPressPost: Update content and excerpt
  AdminPost-->>AdminEditor: Redirect after restore
Loading

Possibly related PRs

Suggested reviewers: mateeb-haider, ubayed-bin-sufian

Merge Risk: Minimal · up to 1f714

The description notice and restore flows have no identified issue that needs to be fixed before merge.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Out of Scope Changes check Warning Issue [#370] limits scope to restore-action placement and usability. The whole PR also changes description synchronization in Wpfaevent_Event_Description_Sync, importer and repository behavior, and … Merge or rebase the #355 dependency before merging this PR so the #370 change contains only the block-editor notice, classic-editor fallback, related localization, and focused tests. Move unrelated synchronization and frontend restore chang…
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title names the main change: moving the Eventyay description restore action into a block editor notice. The diff adds a block editor notice script, a classic-editor fallback meta box, and the rest…
Linked Issues check Passed Issue [#370] requires a discoverable native editor UI, the existing status text, the restore action, nonce and capability checks, replacement and unsaved-change warnings, and editor compatibility. The…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 51 functions across 12 files. (1 skipped: …
Full details: Title check

Explanation

The title names the main change: moving the Eventyay description restore action into a block editor notice. The diff adds a block editor notice script, a classic-editor fallback meta box, and the restore handler. The title is specific and matches the PR objectives. It does not mention the dependency on PR #355 or the test changes, which is expected for a short title.

Full details: Linked Issues check

Explanation

Issue [#370] requires a discoverable native editor UI, the existing status text, the restore action, nonce and capability checks, replacement and unsaved-change warnings, and editor compatibility. The implementation uses a core/notices warning with an action in admin/js/wpfaevent-description-notice.js. enqueue_description_notice() supplies the note, restore URL, and confirmation messages. Wpfaevent_Event_Description_Sync::handle_restore_request() retains nonce and edit_post checks. The classic meta box remains when the block editor is not active. EventDescriptionNoticeTest covers placement conditions, permissions, nonce data, and the classic fallback.

Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 51 functions across 12 files. (1 skipped: 1 unsupported.)

Full details: Out of Scope Changes check

Explanation

Issue [#370] limits scope to restore-action placement and usability. The whole PR also changes description synchronization in Wpfaevent_Event_Description_Sync, importer and repository behavior, and adds frontend event-card and edit-modal restore UI with CSS and tests. These changes implement description preservation and frontend behavior, not the editor-notice placement. The issue explicitly excludes reopening that description behavior and related consistency work. The PR description identifies these commits as the #355 dependency, but they are still present in the merge-base-to-head changes.

Resolution

Merge or rebase the #355 dependency before merging this PR so the #370 change contains only the block-editor notice, classic-editor fallback, related localization, and focused tests. Move unrelated synchronization and frontend restore changes to their owning PR or link them to a separate active issue.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 6 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="includes/eventyay-importer/class-wpfaevent-event-repository.php" line_range="79-81" />
<code_context>
 		$created           = false;

+		// Keep a description that was edited on this site; other fields still sync.
+		$keep_local_description = $existing_id && Wpfaevent_Event_Description_Sync::keep_local_description( $existing_id, $description );
+		if ( $keep_local_description ) {
+			unset( $post_data['post_content'], $post_data['post_excerpt'] );
+		}
+
</code_context>
<issue_to_address>
**Concurrent local edits get overwritten**

When a local description save lands after `keep_local_description()` checks the fingerprint but before the importer writes the post, the importer uses its earlier check and writes the fetched description over the user's save; `remember_import()` then fingerprints the imported text as the baseline, so the user's edit is lost and future syncs treat the post as unedited.

Prevent the importer from overwriting a description saved after its local-edit check.

Also at `admin/class-wpfaevent-eventyay-importer.php:3353-3380`.
</issue_to_address>

### Comment 2
<location path="includes/helpers/class-wpfaevent-event-description-sync.php" line_range="153-167" />
<code_context>
+	 */
+	public static function restore( $post_id ) {
+		$post_id     = absint( $post_id );
+		$description = wp_kses_post( (string) get_post_meta( $post_id, self::REMOTE_META, true ) );
+
+		// WordPress rejects updates that leave title, content and excerpt all empty;
+		// an empty Eventyay description must still restore on an untitled event.
+		$allow_empty = static function ( $maybe_empty, $postarr ) use ( $post_id ) {
+			return isset( $postarr['ID'] ) && absint( $postarr['ID'] ) === $post_id ? false : $maybe_empty;
+		};
+		add_filter( 'wp_insert_post_empty_content', $allow_empty, 10, 2 );
+		$result = wp_update_post(
+			wp_slash(
+				array(
+					'ID'           => $post_id,
+					'post_content' => $description,
+					'post_excerpt' => $description,
+				)
+			),
+			true
</code_context>
<issue_to_address>
**Replayed restore overwrites later edits**

When a restore URL is retried or revisited after the first restore and an editor has since changed the description, the second request reads the same stored Eventyay description and `restore()` writes it over the post’s current content and excerpt, so a local edit made after the first restore is lost.

Make restore a one-time or state-checked operation, such as using a POST request with a per-action token and refusing to apply it if the post changed after the original restore.

Also at `includes/helpers/class-wpfaevent-event-description-sync.php:232-248`.
</issue_to_address>

### Comment 3
<location path="includes/helpers/class-wpfaevent-event-description-sync.php" line_range="135" />
<code_context>
+		$description = wp_kses_post( (string) $description );
+
+		// Meta writes unslash their input; slash it so backslashes in the text survive.
+		update_post_meta( $post_id, self::REMOTE_META, wp_slash( $description ) );
+
+		if ( $applied ) {
</code_context>
<issue_to_address>
**Failed metadata write clears descriptions**

When a database error rejects the `REMOTE_META` write during an import while the event post is saved, `remember_import()` ignores a failed write to `REMOTE_META`, then `restore()` reads the missing or stale value and writes it to both the content and excerpt; the local description is replaced with empty or outdated text.

Check that the remote-description metadata was stored successfully and refuse to restore when no valid stored description is available.
</issue_to_address>

### Comment 4
<location path="includes/eventyay-importer/class-wpfaevent-event-repository.php" line_range="105" />
<code_context>
 			);
 		}

+		Wpfaevent_Event_Description_Sync::remember_import( $saved_id, $description, ! $keep_local_description );
+
 		$start_datetime       = $this->eventyay_event_datetime( $event, 'start' );
</code_context>
<issue_to_address>
**Imported descriptions become locally locked**

When an import updates the post but the request fails before `remember_import()` stores the matching fingerprint, the next import sees a fingerprint mismatch and treats the imported text as a local edit, preserving stale text instead of applying Eventyay's description.

Keep the post update and its matching fingerprint consistent when the request fails between those steps.

Also at `admin/class-wpfaevent-eventyay-importer.php:3348-3380`, `includes/helpers/class-wpfaevent-event-description-sync.php:135-139`.
</issue_to_address>

### Comment 5
<location path="public/partials/events/event-card.php" line_range="143" />
<code_context>
 	data-all-day="<?php echo esc_attr( $event_all_day ? '1' : '0' ); ?>"
+	<?php if ( '' !== $description_note ) : ?>
+	data-description-note="<?php echo esc_attr( $description_note ); ?>"
+	data-description-restore-url="<?php echo esc_url( Wpfaevent_Event_Description_Sync::get_restore_url( $event_id ) ); ?>"
+	<?php endif; ?>
 	data-time="<?php echo esc_attr( $event_time_value ); ?>">
</code_context>
<issue_to_address>
**Cached restore link expires**

When a page containing the frontend restore link is cached past the WordPress nonce lifetime, the cached page keeps the expired nonce in `data-description-restore-url`; `handle_restore_request()` then fails at `check_admin_referer()` before restoring, so the frontend restore action is unavailable until the page cache is refreshed.

Avoid serving cached restore URLs, or fetch a fresh nonce-protected URL when the restore action is used.
</issue_to_address>

### Comment 6
<location path="admin/partials/meta-boxes/class-wpfaevent-admin-event-metabox.php" line_range="383" />
<code_context>
+			array(
+				'note'       => $note,
+				// wp_localize_script() decodes the "&amp;" that wp_nonce_url() adds, so the script gets a usable URL.
+				'restoreUrl' => Wpfaevent_Event_Description_Sync::get_restore_url( $current_post->ID ),
+				'i18n'       => array(
+					'restore'        => __( 'Restore from Eventyay', 'wpfaevent' ),
</code_context>
<issue_to_address>
**Block editor restore URL breaks**

When the block-editor notice's restore action is clicked, `get_restore_url()` returns a display-escaped nonce URL, and `wp_localize_script()` serializes that value into JavaScript without decoding HTML entities. The `&#038;` in the URL starts a fragment instead of separating query parameters, so the restore request lacks its post ID and nonce and fails.

Pass an unescaped URL to the localized script, for example by building it with `add_query_arg()` and adding the nonce without the display-escaping step.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 6 findings to address first, and using “Restore from Eventyay” replaces the event’s locally edited content and excerpt with the remote description; reverting the code would not recover that local text. The overwrite is limited to one event and requires an explicit confirmation, but the lost description may not be reproducible.

Blocking findings: includes/eventyay-importer/class-wpfaevent-event-repository.php:81, includes/helpers/class-wpfaevent-event-description-sync.php:167, includes/helpers/class-wpfaevent-event-description-sync.php:135, includes/eventyay-importer/class-wpfaevent-event-repository.php:105, public/partials/events/event-card.php:143, and 1 more


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines +79 to +81
$keep_local_description = $existing_id && Wpfaevent_Event_Description_Sync::keep_local_description( $existing_id, $description );
if ( $keep_local_description ) {
unset( $post_data['post_content'], $post_data['post_excerpt'] );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High · Concurrent local edits get overwritten

When a local description save lands after keep_local_description() checks the fingerprint but before the importer writes the post, the importer uses its earlier check and writes the fetched description over the user's save; remember_import() then fingerprints the imported text as the baseline, so the user's edit is lost and future syncs treat the post as unedited.

Prevent the importer from overwriting a description saved after its local-edit check.

Also at admin/class-wpfaevent-eventyay-importer.php:3353-3380.

Prompt for AI agents
In `includes/eventyay-importer/class-wpfaevent-event-repository.php` at lines 79-81:

**Concurrent local edits get overwritten**

When a local description save lands after `keep_local_description()` checks the fingerprint but before the importer writes the post, the importer uses its earlier check and writes the fetched description over the user's save; `remember_import()` then fingerprints the imported text as the baseline, so the user's edit is lost and future syncs treat the post as unedited.

Prevent the importer from overwriting a description saved after its local-edit check.

Also at `admin/class-wpfaevent-eventyay-importer.php:3353-3380`.

Comment on lines +153 to +167
$description = wp_kses_post( (string) get_post_meta( $post_id, self::REMOTE_META, true ) );

// WordPress rejects updates that leave title, content and excerpt all empty;
// an empty Eventyay description must still restore on an untitled event.
$allow_empty = static function ( $maybe_empty, $postarr ) use ( $post_id ) {
return isset( $postarr['ID'] ) && absint( $postarr['ID'] ) === $post_id ? false : $maybe_empty;
};
add_filter( 'wp_insert_post_empty_content', $allow_empty, 10, 2 );
$result = wp_update_post(
wp_slash(
array(
'ID' => $post_id,
'post_content' => $description,
'post_excerpt' => $description,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Replayed restore overwrites later edits

When a restore URL is retried or revisited after the first restore and an editor has since changed the description, the second request reads the same stored Eventyay description and restore() writes it over the post’s current content and excerpt, so a local edit made after the first restore is lost.

Make restore a one-time or state-checked operation, such as using a POST request with a per-action token and refusing to apply it if the post changed after the original restore.

Also at includes/helpers/class-wpfaevent-event-description-sync.php:232-248.

Prompt for AI agents
In `includes/helpers/class-wpfaevent-event-description-sync.php` at lines 153-167:

**Replayed restore overwrites later edits**

When a restore URL is retried or revisited after the first restore and an editor has since changed the description, the second request reads the same stored Eventyay description and `restore()` writes it over the post’s current content and excerpt, so a local edit made after the first restore is lost.

Make restore a one-time or state-checked operation, such as using a POST request with a per-action token and refusing to apply it if the post changed after the original restore.

Also at `includes/helpers/class-wpfaevent-event-description-sync.php:232-248`.

$description = wp_kses_post( (string) $description );

// Meta writes unslash their input; slash it so backslashes in the text survive.
update_post_meta( $post_id, self::REMOTE_META, wp_slash( $description ) );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Failed metadata write clears descriptions

When a database error rejects the REMOTE_META write during an import while the event post is saved, remember_import() ignores a failed write to REMOTE_META, then restore() reads the missing or stale value and writes it to both the content and excerpt; the local description is replaced with empty or outdated text.

Check that the remote-description metadata was stored successfully and refuse to restore when no valid stored description is available.

Prompt for AI agents
In `includes/helpers/class-wpfaevent-event-description-sync.php` at line 135:

**Failed metadata write clears descriptions**

When a database error rejects the `REMOTE_META` write during an import while the event post is saved, `remember_import()` ignores a failed write to `REMOTE_META`, then `restore()` reads the missing or stale value and writes it to both the content and excerpt; the local description is replaced with empty or outdated text.

Check that the remote-description metadata was stored successfully and refuse to restore when no valid stored description is available.

);
}

Wpfaevent_Event_Description_Sync::remember_import( $saved_id, $description, ! $keep_local_description );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Imported descriptions become locally locked

When an import updates the post but the request fails before remember_import() stores the matching fingerprint, the next import sees a fingerprint mismatch and treats the imported text as a local edit, preserving stale text instead of applying Eventyay's description.

Keep the post update and its matching fingerprint consistent when the request fails between those steps.

Also at admin/class-wpfaevent-eventyay-importer.php:3348-3380, includes/helpers/class-wpfaevent-event-description-sync.php:135-139.

Prompt for AI agents
In `includes/eventyay-importer/class-wpfaevent-event-repository.php` at line 105:

**Imported descriptions become locally locked**

When an import updates the post but the request fails before `remember_import()` stores the matching fingerprint, the next import sees a fingerprint mismatch and treats the imported text as a local edit, preserving stale text instead of applying Eventyay's description.

Keep the post update and its matching fingerprint consistent when the request fails between those steps.

Also at `admin/class-wpfaevent-eventyay-importer.php:3348-3380`, `includes/helpers/class-wpfaevent-event-description-sync.php:135-139`.

data-all-day="<?php echo esc_attr( $event_all_day ? '1' : '0' ); ?>"
<?php if ( '' !== $description_note ) : ?>
data-description-note="<?php echo esc_attr( $description_note ); ?>"
data-description-restore-url="<?php echo esc_url( Wpfaevent_Event_Description_Sync::get_restore_url( $event_id ) ); ?>"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Cached restore link expires

When a page containing the frontend restore link is cached past the WordPress nonce lifetime, the cached page keeps the expired nonce in data-description-restore-url; handle_restore_request() then fails at check_admin_referer() before restoring, so the frontend restore action is unavailable until the page cache is refreshed.

Avoid serving cached restore URLs, or fetch a fresh nonce-protected URL when the restore action is used.

Prompt for AI agents
In `public/partials/events/event-card.php` at line 143:

**Cached restore link expires**

When a page containing the frontend restore link is cached past the WordPress nonce lifetime, the cached page keeps the expired nonce in `data-description-restore-url`; `handle_restore_request()` then fails at `check_admin_referer()` before restoring, so the frontend restore action is unavailable until the page cache is refreshed.

Avoid serving cached restore URLs, or fetch a fresh nonce-protected URL when the restore action is used.

array(
'note' => $note,
// wp_localize_script() decodes the "&amp;" that wp_nonce_url() adds, so the script gets a usable URL.
'restoreUrl' => Wpfaevent_Event_Description_Sync::get_restore_url( $current_post->ID ),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High · Block editor restore URL breaks

When the block-editor notice's restore action is clicked, get_restore_url() returns a display-escaped nonce URL, and wp_localize_script() serializes that value into JavaScript without decoding HTML entities. The &#038; in the URL starts a fragment instead of separating query parameters, so the restore request lacks its post ID and nonce and fails.

Pass an unescaped URL to the localized script, for example by building it with add_query_arg() and adding the nonce without the display-escaping step.

Prompt for AI agents
In `admin/partials/meta-boxes/class-wpfaevent-admin-event-metabox.php` at line 383:

**Block editor restore URL breaks**

When the block-editor notice's restore action is clicked, `get_restore_url()` returns a display-escaped nonce URL, and `wp_localize_script()` serializes that value into JavaScript without decoding HTML entities. The `&#038;` in the URL starts a fragment instead of separating query parameters, so the restore request lacks its post ID and nonce and fails.

Pass an unescaped URL to the localized script, for example by building it with `add_query_arg()` and adding the nonce without the display-escaping step.

@SanketKokate712

Copy link
Copy Markdown
Member

@TheVaibhaw, please address the issues flagged by Sourcery-AI, particularly the high-priority concerns regarding race conditions in concurrent local edits and the URL encoding error that is currently breaking the restore action in the block editor.

This branch has not been deployed

No deployments
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.

[Enhancement]: Make the Eventyay description restore action easier to find

2 participants