From 8b92444ebd5181c4b6b466a84793aac531867d29 Mon Sep 17 00:00:00 2001 From: Moudud Ahmed Date: Fri, 26 Jun 2026 11:45:57 +0600 Subject: [PATCH] fix: don't send an empty SMS when a comment is deleted before send MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WP\Comment::get_message() dereferenced the result of get_comment() with no null check. When the comment is deleted, trashed, or flagged as spam between the `comment_post` hook and the synchronous send (e.g. by an anti-spam plugin), get_comment() returns null. On PHP 8.x this is a warning rather than a fatal, but the notification still rendered an empty-bodied message and sent (and billed) it. - Guard Dispatcher::new_comment() so a missing comment bails before any send — no wasted, billed SMS for a comment that no longer exists. - Harden get_message(): null-guard the comment, and coerce every token value to string so a missing value never reaches str_replace() as null (deprecated since PHP 8.1). This also clears the deprecation that the typo'd `{ip}` token (comment_author_ip vs comment_author_IP) triggered on every comment. {post_title} is left unchanged — it already resolves correctly through WP_Comment's magic __get, which proxies post fields to the post. Co-Authored-By: Claude Opus 4.8 (1M context) --- includes/Dispatcher.php | 9 +++++++++ includes/Notifications/WP/Comment.php | 19 +++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/includes/Dispatcher.php b/includes/Dispatcher.php index 65b70b9..87620af 100644 --- a/includes/Dispatcher.php +++ b/includes/Dispatcher.php @@ -7,6 +7,7 @@ use Texty\Models\SmsStat; use WeDevs\WPKit\DataLayer\DataLayerFactory; use Texty\Gateways\GatewayInterface; +use WP_Comment; /** * Dispatcher Class @@ -76,6 +77,14 @@ public function user_register( $user_id ) { * @return void */ public function new_comment( $comment_id ) { + // The comment may have been deleted, trashed, or flagged as spam + // (e.g. by an anti-spam plugin hooked on `comment_post`) before this + // runs. Bail so we don't render and bill an SMS for a comment that no + // longer exists. + if ( ! get_comment( $comment_id ) instanceof WP_Comment ) { + return; + } + $class = texty()->notifications()->get( 'comment' ); $notifier = new $class(); diff --git a/includes/Notifications/WP/Comment.php b/includes/Notifications/WP/Comment.php index 8fcb91d..fc0fad6 100644 --- a/includes/Notifications/WP/Comment.php +++ b/includes/Notifications/WP/Comment.php @@ -3,6 +3,7 @@ namespace Texty\Notifications\WP; use Texty\Notifications\Notification; +use WP_Comment; class Comment extends Notification { @@ -52,9 +53,23 @@ public function get_message() { $comment = get_comment( $this->comment_id ); + // The comment can vanish between the `comment_post` hook and the send + // (deleted, trashed, or spam). Bail to the global-key pass instead of + // dereferencing null and rendering an empty-bodied message. + if ( ! $comment instanceof WP_Comment ) { + return $this->replace_global_keys( $message ); + } + foreach ( $this->replacement_keys() as $search => $value ) { - $value = ( $search === 'post_url' ) ? get_permalink( $comment->comment_post_ID ) : $comment->$value; - $message = str_replace( '{' . $search . '}', $value, $message ); + // `post_url` has no comment property; everything else reads off the + // comment via WP_Comment's magic getter (e.g. `post_title` proxies + // to the post). Coerce to string so a missing value never reaches + // str_replace as null (deprecated since PHP 8.1). + $replacement = ( 'post_url' === $search ) + ? (string) get_permalink( $comment->comment_post_ID ) + : (string) ( $comment->$value ?? '' ); + + $message = str_replace( '{' . $search . '}', $replacement, $message ); } $message = $this->replace_global_keys( $message );