Skip to content

Bug: Extension::delete_notification() deletes entries across all sources (source predicate commented out) #142

Description

@sadmansakibnadvi

Describe the bug

Extension::delete_notification() builds its WHERE clause with the source predicate commented out, so a call that passes only an $entry_key deletes matching entries across every source and every campaign — not just the calling extension's own.

https://github.com/WPDevelopers/notificationx/blob/master/includes/Extensions/Extension.php#L635-L649

public function delete_notification($entry_key = null, $nx_id = null) {
    $where = [
        // 'source' => $this->id      // <-- commented out
    ];
    if (!empty($entry_key)) {
        $where['entry_key'] = $entry_key;
    }
    if (!empty($nx_id)) {
        $where['nx_id'] = $nx_id;
    }
    if (!empty($where)) {
        return Entries::get_instance()->delete_entries($where);
    }
    return false;
}

This matters because entry_key is not namespaced per source and is frequently a small integer. Existing callers pass a bare upstream ID:

  • WPComments.php:256 — comment ID, on delete_comment
  • WOOReviews.php:375 — comment ID
  • Tutor.php:317{$order_id}-{$item_id}

A WooCommerce order with ID 42 and a blog comment with ID 42 therefore share an entry_key. Deleting comment 42 removes the WooCommerce order-42 notification too.

The nx_entries schema has no unique constraint that would prevent this — only PRIMARY KEY (entry_id) plus non-unique KEY source / KEY nx_id (includes/Core/Database.php#L62-L73).

Steps to reproduce

  1. Set up two campaigns on one site: a WooCommerce Sales notification and a WordPress Comments notification.
  2. Let both accumulate entries, ensuring a WooCommerce order and a comment happen to share the same numeric ID (on a fresh site this is close to guaranteed for low IDs).
  3. Delete that comment in WP Admin → Comments. This fires delete_commentWPComments::delete_notification($comment_id).
  4. Inspect {$prefix}nx_entries, or view the WooCommerce campaign's entries.

Expected: only the WP Comments entry with that entry_key is removed.
Actual: the WooCommerce entry with the same entry_key is removed as well, silently. The order notification stops displaying with no indication why.

Suggested fix

Uncomment the predicate:

$where = [
    'source' => $this->id,
];

Every current caller is a method on the extension that owns the entry, so scoping by $this->id should be safe. Worth confirming against the Pro plugin's callers too.

A defensive alternative — or an addition — would be to namespace entry_key per source at write time, but that needs a migration for existing rows.

Impact

Silent, cross-campaign data loss. No error, no log entry — the notification simply disappears. Sites running several integrations at once are the most exposed, and low-numbered IDs on newer sites make collisions more likely rather than less.

Environment

Found on master @ 55f62f10 while building a new integration whose entry_key is a booking ID from a custom table — i.e. exactly the collision-prone shape.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions