Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
= 10.47 (27th July, 2026) =

* Fix: SQL injection in the bulk "change email format" action for registered subscribers. The submitted format value was interpolated into an UPDATE statement instead of being passed through $wpdb->prepare().
* Fix: Block editor email preview always used the digest branch because the plugin options were read from an undefined property, which also caused an "Undefined array key" warning during preview.
* Fix: Subscription confirmation links no longer emit PHP warnings when the s2 parameter is missing or malformed.
* Fix: PHP 8.2 "creation of dynamic property" deprecation notices by declaring previously undeclared class properties.
* Fix: PHP 8.1+ "passing null" deprecation notices when building notification emails.

= 10.46 (27th July, 2026) =

* Fix: Reflected Cross-Site Scripting in the [subscribe2] subscription form. The email request parameter was reflected unescaped into the email field's inline onfocus/onblur JavaScript handlers. Thanks to Omar Elshopky and WPScan for the responsible disclosure.
Expand Down
17 changes: 14 additions & 3 deletions classes/class-s2-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -1203,10 +1203,21 @@ public function format_change( $emails, $format ) {
$useremails = explode( ",\r\n", $emails );
$useremails = implode( ', ', array_map( array( $this, 'prepare_in_data' ), $useremails ) );
$ids = $wpdb->get_col( "SELECT ID FROM $wpdb->users WHERE user_email IN ($useremails)" ); // phpcs:ignore WordPress.DB.PreparedSQL
$ids = implode( ',', array_map( array( $this, 'prepare_in_data' ), $ids ) );
$sql = "UPDATE $wpdb->usermeta SET meta_value='{$format}' WHERE meta_key='" . $this->get_usermeta_keyname( 's2_format' ) . "' AND user_id IN ($ids)";

$wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL
if ( empty( $ids ) ) {
return;
}

$ids = implode( ',', array_map( 'intval', $ids ) );

$wpdb->query( // phpcs:ignore WordPress.DB.PreparedSQL
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $ids is an integer-cast list built above.
"UPDATE $wpdb->usermeta SET meta_value = %s WHERE meta_key = %s AND user_id IN ($ids)",
$format,
$this->get_usermeta_keyname( 's2_format' )
)
);
}

/**
Expand Down
7 changes: 7 additions & 0 deletions classes/class-s2-ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
*/
class S2_Ajax {

/**
* Suffix used to load minified or development scripts.
*
* @var string
*/
public $script_debug = '';

/**
* Class constructor.
*/
Expand Down
17 changes: 10 additions & 7 deletions classes/class-s2-block-editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ public function register_preview_endpoint() {
},
),
),
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
'permission_callback' => function ( $request ) {
return current_user_can( 'edit_post', (int) $request['id'] );
},
)
);
Expand All @@ -83,8 +83,11 @@ public function register_resend_endpoint() {
},
),
),
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
// Resending mails the live subscriber list, so require the same
// capability as the Send Email screen, not merely edit_posts.
'permission_callback' => function ( $request ) {
return current_user_can( apply_filters( 's2_capability', 'manage_options', 'send' ) )
&& current_user_can( 'edit_post', (int) $request['id'] );
},
)
);
Expand All @@ -101,14 +104,14 @@ public function register_settings_endpoint() {
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'setting' ),
'args' => array(
'id' => array(
'setting' => array(
'validate_callback' => function( $param ) {
return preg_match( '/^[a-z0-9_]+$/', $param ) > 0;
},
),
),
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
return current_user_can( apply_filters( 's2_capability', 'manage_options', 'settings' ) );
},
)
);
Expand All @@ -126,7 +129,7 @@ public function preview( $data ) {
return false;
}

if ( 'never' !== $this->subscribe2_options['email_freq'] ) {
if ( 'never' !== $mysubscribe2->subscribe2_options['email_freq'] ) {
$mysubscribe2->subscribe2_cron( $current_user->user_email );
} else {
$mysubscribe2->publish( $post, $current_user->user_email );
Expand Down
58 changes: 43 additions & 15 deletions classes/class-s2-core.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,82 +36,110 @@ class S2_Core {
*/
public $filtered = 0;

/**
* True while sending a preview rather than a real notification.
*
* @var bool
*/
public $preview_email = false;

/**
* Subscriber email address being processed.
*
* @var string|null
*/
public $email;

/**
* IP address recorded against a subscription request.
*
* @var string|null
*/
public $ip;

/**
* Message returned to the visitor after a form action.
*
* @var string|null
*/
public $message;

/**
* State variable for affect processing.
*
* @var int|null
* @var int
*/
public $post_count;
public $post_count = 0;

/**
* Post title used for substitute() function.
*
* @var string|null
*/
public $post_title;
public $post_title = '';

/**
* Post title used for substitute() function.
*
* @var string|null
*/
public $post_title_text;
public $post_title_text = '';

/**
* Post permalink used for substitute() function.
*
* @var string|null
*/
public $permalink;
public $permalink = '';

/**
* Post date used for substitute() function.
*
* @var string|null
*/
public $post_date;
public $post_date = '';

/**
* Post time used for substitute() function.
*
* @var string|null
*/
public $post_time;
public $post_time = '';

/**
* State myname used for substitute() function.
*
* @var string|null
*/
public $myname;
public $myname = '';

/**
* State myemail used for substitute() function.
*
* @var string|null
*/
public $myemail;
public $myemail = '';

/**
* State author used for substitute() function.
*
* @var string|null
*/
public $authorname;
public $authorname = '';

/**
* Post category names used for substitute() function.
*
* @var array|null
* @var string
*/
public $post_cat_names;
public $post_cat_names = '';

/**
* Post tag names used for substitute() function.
*
* @var array|null
* @var string
*/
public $post_tag_names;
public $post_tag_names = '';
public $script_debug;
public $word_wrap;
public $excerpt_length;
Expand Down Expand Up @@ -436,7 +464,7 @@ public function plain_email() {
*/
public function get_tracking_link( $link ) {
if ( empty( $link ) ) {
return;
return '';
}

$delimiter = '';
Expand Down
4 changes: 2 additions & 2 deletions classes/class-s2-frontend.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public function query_filter() {
*/
public function title_filter( $title ) {
if ( in_the_loop() ) {
$code = $_GET['s2'];
$code = isset( $_GET['s2'] ) ? sanitize_text_field( wp_unslash( $_GET['s2'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$action = intval( substr( $code, 0, 1 ) );

if ( 1 === $action ) {
Expand Down Expand Up @@ -109,7 +109,7 @@ public function confirm( $content = '' ) {
return $content;
}

$code = $_GET['s2'];
$code = isset( $_GET['s2'] ) ? sanitize_text_field( wp_unslash( $_GET['s2'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$action = substr( $code, 0, 1 );
$hash = substr( $code, 1, 32 );
$id = intval( substr( $code, 33 ) );
Expand Down
12 changes: 10 additions & 2 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ Contributors: tareq1988, nizamuddinbabu, wemail
Donate link: https://getwemail.io
Tags: posts, subscription, email, subscribe, notify, notification, newsletter, post notification, email marketing, optin, form
Requires at least: 4.0
Tested up to: 6.9
Stable tag: 10.46
Tested up to: 7.0
Stable tag: 10.47
Requires PHP: 5.4
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
Expand Down Expand Up @@ -72,6 +72,14 @@ This token will automatically be replaced by dynamic subscription information an

== Changelog ==

= 10.47 (27th July, 2026) =

* Fix: SQL injection in the bulk "change email format" action for registered subscribers. The submitted format value was interpolated into an UPDATE statement instead of being passed through $wpdb->prepare().
* Fix: Block editor email preview always used the digest branch because the plugin options were read from an undefined property, which also caused an "Undefined array key" warning during preview.
* Fix: Subscription confirmation links no longer emit PHP warnings when the s2 parameter is missing or malformed.
* Fix: PHP 8.2 "creation of dynamic property" deprecation notices by declaring previously undeclared class properties.
* Fix: PHP 8.1+ "passing null" deprecation notices when building notification emails.

= 10.46 (27th July, 2026) =

* Fix: The email request parameter was reflected unescaped into the email field's inline onfocus/onblur JavaScript handlers.
Expand Down
4 changes: 2 additions & 2 deletions subscribe2.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Plugin Name: Subscribe2
Plugin URI: https://getwemail.io
Description: Notifies an email list when new entries are posted.
Version: 10.46
Version: 10.47
Author: weMail
Author URI: https://getwemail.io
Licence: GPLv3
Expand Down Expand Up @@ -55,7 +55,7 @@

// Our version number. Don't touch this or any line below.
// Unless you know exactly what you are doing.
define( 'S2VERSION', '10.46' );
define( 'S2VERSION', '10.47' );
define( 'S2PLUGIN', __FILE__ );
define( 'S2PATH', trailingslashit( dirname( __FILE__ ) ) );
define( 'S2DIR', trailingslashit( dirname( plugin_basename( __FILE__ ) ) ) );
Expand Down