--- a/popup-builder/com/boot.php
+++ b/popup-builder/com/boot.php
@@ -1,4 +1,6 @@
<?php
+defined( 'ABSPATH' ) || exit;
+
require_once(dirname(__FILE__).'/config/config.php');
require_once(dirname(__FILE__).'/config/configPackage.php');
--- a/popup-builder/com/classes/Actions.php
+++ b/popup-builder/com/classes/Actions.php
@@ -1,5 +1,8 @@
<?php
namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
use WP_Query;
use SgpbPopupConfig;
use SgpbDataConfig;
@@ -34,6 +37,7 @@
add_action('admin_post_csv_file', array($this, 'getSubscribersCsvFile'));
add_action('admin_post_sgpb_system_info', array($this, 'getSystemInfoFile'));
add_action('admin_post_sgpbSaveSettings', array($this, 'saveSettings'), 10, 1);
+ add_action('admin_post_sgpb_request_new_unsubscribe_link', array($this, 'requestNewUnsubscribeLink'));
add_action('admin_init', array($this, 'userRolesCaps'));
add_action('admin_notices', array($this, 'pluginNotices'));
add_action('admin_init', array($this, 'pluginLoaded'));
@@ -500,6 +504,13 @@
update_option('sgpbUnsubscribeColumnFixed', 1);
delete_option('sgpbUnsubscribeColumn');
}
+
+ // Add unsubscribe_token column for secure unsubscribe tokens
+ $unsubscribeTokenColumnFixed = get_option('sgpbUnsubscribeTokenColumnFixed');
+ if (!$unsubscribeTokenColumnFixed) {
+ AdminHelper::addUnsubscribeTokenColumn();
+ update_option('sgpbUnsubscribeTokenColumnFixed', 1);
+ }
if ($versionPopup && !$convert) {
update_option('sgpbConvertToNewVersion', 1);
@@ -785,11 +796,23 @@
wp_clear_scheduled_hook('sgpb_send_newsletter');
return false;
}
- $table_subscription = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
- $selectionQuery = "SELECT id FROM $table_subscription WHERE";
- $selectionQuery = apply_filters('sgpbUserSelectionQuery', $selectionQuery);
-
- $result = $wpdb->get_row( $wpdb->prepare("$selectionQuery and subscriptionType = %d limit 1", $subscriptionFormId), ARRAY_A);//db call ok
+
+ $table_subscription = $wpdb->prefix . SGPB_SUBSCRIBERS_TABLE_NAME;
+ $result = $wpdb->get_row(
+ $wpdb->prepare(
+ "SELECT id
+ FROM $table_subscription
+ WHERE status = %d
+ AND unsubscribed = %d
+ AND subscriptionType = %d
+ LIMIT 1",
+ 0,
+ 0,
+ $subscriptionFormId
+ ),
+ ARRAY_A
+ );
+
$currentStateEmailId = isset($result['id']) ? (int)$result['id'] : 0;
$table_subscription = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
$totalSubscribers = $wpdb->get_var( $wpdb->prepare("SELECT count(*) FROM $table_subscription WHERE unsubscribed = 0 and subscriptionType = %d", $subscriptionFormId) );
@@ -817,9 +840,25 @@
return;
}
- $getAllDataSql = 'SELECT id, firstName, lastName, email FROM '.$wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME.' WHERE';
- $getAllDataSql = apply_filters('sgpbUserSelectionQuery', $getAllDataSql);
- $subscribers = $wpdb->get_results( $wpdb->prepare( "$getAllDataSql and id >= %d and subscriptionType = %s limit %d", $currentStateEmailId, $subscriptionFormId, $emailsInFlow), ARRAY_A);
+ $table_subscription = $wpdb->prefix . SGPB_SUBSCRIBERS_TABLE_NAME;
+
+ $subscribers = $wpdb->get_results(
+ $wpdb->prepare(
+ "
+ SELECT id, firstName, lastName, email
+ FROM $table_subscription
+ WHERE status = 0
+ AND unsubscribed = 0
+ AND id >= %d
+ AND subscriptionType = %s
+ LIMIT %d
+ ",
+ $currentStateEmailId,
+ $subscriptionFormId,
+ $emailsInFlow
+ ),
+ ARRAY_A
+ ); // db call ok
$subscribers = apply_filters('sgpNewsletterSendingSubscribers', $subscribers);
@@ -839,10 +878,25 @@
$replacementUserName = $newsletterOptions['username'];
$replacementEmail = $subscriber['email'];
$replacementUnsubscribe = get_home_url();
- $replacementUnsubscribe .= '?sgpbUnsubscribe='.md5($replacementId.$replacementEmail);
- $replacementUnsubscribe .= '&email='.$subscriber['email'];
+
+ // Get or generate secure unsubscribe token
+ global $wpdb;
+ $subscribersTableName = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
+ $subscriberTokenData = $wpdb->get_row( $wpdb->prepare( "SELECT unsubscribe_token FROM $subscribersTableName WHERE id = %d", $replacementId ), ARRAY_A );
+
+ $unsubscribeToken = '';
+ if (!empty($subscriberTokenData['unsubscribe_token'])) {
+ $unsubscribeToken = $subscriberTokenData['unsubscribe_token'];
+ } else {
+ // Generate token if it doesn't exist (for backward compatibility)
+ $unsubscribeToken = AdminHelper::generateUnsubscribeToken();
+ $wpdb->query( $wpdb->prepare( "UPDATE $subscribersTableName SET unsubscribe_token = %s WHERE id = %d", $unsubscribeToken, $replacementId ) );
+ }
+
+ $replacementUnsubscribe .= '?sgpbUnsubscribe='.urlencode($unsubscribeToken);
+ $replacementUnsubscribe .= '&email='.urlencode($subscriber['email']);
$replacementUnsubscribe .= '&popup='.$subscriptionFormId;
- $replacementUnsubscribe = '<br><a href="'.$replacementUnsubscribe.'">'.$title.'</a>';
+ $replacementUnsubscribe = '<br><a href="'.esc_url($replacementUnsubscribe).'">'.$title.'</a>';
// Replace First name and Last name from email message
$emailMessageCustom = preg_replace($allAvailableShortcodes['patternFirstName'], $replacementFirstName, $emailMessage);
@@ -1000,6 +1054,13 @@
*/
$unsubscribeArgs = $this->collectUnsubscriberArgs();
+ // Check if this is a request to show the form (expired link)
+ if (isset($_GET['sgpbUnsubscribe']) && sanitize_text_field(wp_unslash($_GET['sgpbUnsubscribe'])) === 'expired') {
+ $popup = isset($_GET['popup']) ? sanitize_text_field(wp_unslash($_GET['popup'])) : '';
+ AdminHelper::displayUnsubscribeLinkRequestForm($popup);
+ wp_die();
+ }
+
if (!empty($unsubscribeArgs)) {
$this->unsubscribe($unsubscribeArgs);
}
@@ -1808,4 +1869,51 @@
return $file;
}
+ /**
+ * Handle request for new unsubscribe link when old link is expired
+ */
+ public function requestNewUnsubscribeLink()
+ {
+ // Verify nonce
+ $nonce = isset($_POST['sgpb_unsubscribe_nonce']) ? sanitize_text_field(wp_unslash($_POST['sgpb_unsubscribe_nonce'])) : '';
+ if (!wp_verify_nonce($nonce, 'sgpb_request_unsubscribe_link')) {
+ wp_redirect(add_query_arg('sgpb_unsubscribe_status', 'error', wp_get_referer()));
+ exit;
+ }
+
+ // Get and validate email
+ $email = isset($_POST['email']) ? sanitize_email(wp_unslash($_POST['email'])) : '';
+ $popup = isset($_POST['popup']) ? sanitize_text_field(wp_unslash($_POST['popup'])) : '';
+
+ if (empty($email) || !is_email($email)) {
+ $redirectUrl = add_query_arg(array(
+ 'sgpbUnsubscribe' => 'expired',
+ 'popup' => $popup,
+ 'sgpb_unsubscribe_status' => 'error'
+ ), home_url());
+ wp_redirect($redirectUrl);
+ exit;
+ }
+
+ // Send new unsubscribe link
+ $result = AdminHelper::sendNewUnsubscribeLink($email, $popup);
+
+ if ($result) {
+ $redirectUrl = add_query_arg(array(
+ 'sgpbUnsubscribe' => 'expired',
+ 'popup' => $popup,
+ 'sgpb_unsubscribe_status' => 'success'
+ ), home_url());
+ } else {
+ $redirectUrl = add_query_arg(array(
+ 'sgpbUnsubscribe' => 'expired',
+ 'popup' => $popup,
+ 'sgpb_unsubscribe_status' => 'not_found'
+ ), home_url());
+ }
+
+ wp_redirect($redirectUrl);
+ exit;
+ }
+
}
--- a/popup-builder/com/classes/Ajax.php
+++ b/popup-builder/com/classes/Ajax.php
@@ -1,5 +1,8 @@
<?php
namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
use SGPBConfigDataHelper;
class Ajax
@@ -308,8 +311,10 @@
if(isset($allPopupsCount[$popupId])) {
$allPopupsCount[$popupId] = 0;
}
-
- $popupAnalyticsData = $wpdb->get_var( $wpdb->prepare(' DELETE FROM '.$wpdb->prefix.'sgpb_analytics WHERE target_id = %d AND event_id NOT IN (7, 12, 13)', $popupId));
+ if($wpdb->get_var("SHOW TABLES LIKE '$tableName'") == $tableName) {
+ $popupAnalyticsData = $wpdb->get_var( $wpdb->prepare(' DELETE FROM '.$wpdb->prefix.'sgpb_analytics WHERE target_id = %d AND event_id NOT IN (7, 12, 13)', $popupId));
+ }
+
update_option('SgpbCounter', $allPopupsCount);
@@ -524,12 +529,22 @@
foreach($subscriptionPopupsId as $subscriptionPopupId) {
$res = $wpdb->get_row( $wpdb->prepare("SELECT id FROM $table_sgpb_subscribers WHERE email = %s AND subscriptionType = %d", $email, $subscriptionPopupId), ARRAY_A);
+
+ // Generate secure unsubscribe token
+ $unsubscribeToken = AdminHelper::generateUnsubscribeToken();
+
// add new subscriber
if(empty($res)) {
- $res = $wpdb->query( $wpdb->prepare("INSERT INTO $table_sgpb_subscribers (firstName, lastName, email, cDate, subscriptionType) VALUES (%s, %s, %s, %s, %d) ", $firstName, $lastName, $email, $date, $subscriptionPopupId) );
+ $res = $wpdb->query( $wpdb->prepare("INSERT INTO $table_sgpb_subscribers (firstName, lastName, email, cDate, subscriptionType, unsubscribe_token) VALUES (%s, %s, %s, %s, %d, %s) ", $firstName, $lastName, $email, $date, $subscriptionPopupId, $unsubscribeToken) );
} // edit existing
else {
- $wpdb->query( $wpdb->prepare("UPDATE $table_sgpb_subscribers SET firstName = %s, lastName = %s, email = %s, cDate = %s, subscriptionType = %d, unsubscribered = 0 WHERE id = %d", $firstName, $lastName, $email, $date, $subscriptionPopupId, $res['id']) );
+ // Update token if it doesn't exist, otherwise keep existing token
+ $existingSubscriber = $wpdb->get_row( $wpdb->prepare("SELECT unsubscribe_token FROM $table_sgpb_subscribers WHERE id = %d", $res['id']), ARRAY_A);
+ if (empty($existingSubscriber['unsubscribe_token'])) {
+ $wpdb->query( $wpdb->prepare("UPDATE $table_sgpb_subscribers SET firstName = %s, lastName = %s, email = %s, cDate = %s, subscriptionType = %d, unsubscribered = 0, unsubscribe_token = %s WHERE id = %d", $firstName, $lastName, $email, $date, $subscriptionPopupId, $unsubscribeToken, $res['id']) );
+ } else {
+ $wpdb->query( $wpdb->prepare("UPDATE $table_sgpb_subscribers SET firstName = %s, lastName = %s, email = %s, cDate = %s, subscriptionType = %d, unsubscribered = 0 WHERE id = %d", $firstName, $lastName, $email, $date, $subscriptionPopupId, $res['id']) );
+ }
$res = 1;
}
$popupPostIds .= $subscriptionPopupId.' ';
@@ -683,18 +698,28 @@
$sgpb_check_existed = $wpdb->get_row( $wpdb->prepare("SELECT id FROM $subscribersTableName WHERE email = %s AND subscriptionType = %d", $csvData[$mapping['email']], $formId), ARRAY_A);
- $valid_firstname = isset( $csvData[$mapping['firstName']] ) ? $csvData[$mapping['firstName']] : '';
+ $valid_firstname = isset( $csvData[$mapping['firstName']] ) ? $csvData[$mapping['firstName']] : '';
$valid_lastname = isset( $csvData[$mapping['lastName']] ) ? $csvData[$mapping['lastName']] : '';
- $num_original_importrs++;
+ $num_original_importrs++;
+
+ // Generate secure unsubscribe token
+ $unsubscribeToken = AdminHelper::generateUnsubscribeToken();
+
// add new subscriber
if(empty($sgpb_check_existed)) {
if( empty( $check_column ) ) {
- $wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (firstName, lastName, email, cDate, subscriptionType, status, unsubscribed) VALUES (%s, %s, %s, %s, %d, %d, %d) ", $valid_firstname, $valid_lastname, $csvData[$mapping['email']], $date, $formId, 0, 0) );
+ $wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (firstName, lastName, email, cDate, subscriptionType, status, unsubscribed, unsubscribe_token) VALUES (%s, %s, %s, %s, %d, %d, %d, %s) ", $valid_firstname, $valid_lastname, $csvData[$mapping['email']], $date, $formId, 0, 0, $unsubscribeToken) );
} else {
- $wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (firstName, lastName, email, cDate, subscriptionType, status, unsubscribed, submittedData) VALUES (%s, %s, %s, %s, %d, %d, %d, %s) ", $valid_firstname, $valid_lastname, $csvData[$mapping['email']], $date, $formId, 0, 0, '') );
+ $wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (firstName, lastName, email, cDate, subscriptionType, status, unsubscribed, submittedData, unsubscribe_token) VALUES (%s, %s, %s, %s, %d, %d, %d, %s, %s) ", $valid_firstname, $valid_lastname, $csvData[$mapping['email']], $date, $formId, 0, 0, '', $unsubscribeToken) );
}
$number_importartSubscribers++;
- }
+ } else {
+ // Update token if it doesn't exist for existing subscriber
+ $existingSubscriber = $wpdb->get_row( $wpdb->prepare("SELECT unsubscribe_token FROM $subscribersTableName WHERE id = %d", $sgpb_check_existed['id']), ARRAY_A);
+ if (empty($existingSubscriber['unsubscribe_token'])) {
+ $wpdb->query( $wpdb->prepare("UPDATE $subscribersTableName SET unsubscribe_token = %s WHERE id = %d", $unsubscribeToken, $sgpb_check_existed['id']) );
+ }
+ }
}
// translators: %d the number of imported subscribers, %s is the title of Popup.
$notification_importartSubscribers = sprintf( __('You have imported %1$d subscribers to the `%2$s` successfully!', 'popup-builder'), $number_importartSubscribers, $formId);
@@ -892,11 +917,20 @@
$subscribersTableName = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
$list = $wpdb->get_row( $wpdb->prepare("SELECT id FROM $subscribersTableName WHERE email = %s AND subscriptionType = %d", $email, $popupPostId), ARRAY_A);
+ // Generate secure unsubscribe token
+ $unsubscribeToken = AdminHelper::generateUnsubscribeToken();
+
// When subscriber does not exist we insert to subscribers table otherwise we update user info
if(empty($list['id'])) {
- $res = $wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (firstName, lastName, email, cDate, subscriptionType) VALUES (%s, %s, %s, %s, %d) ", $firstName, $lastName, $email, $date, $popupPostId) );
+ $res = $wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (firstName, lastName, email, cDate, subscriptionType, unsubscribe_token) VALUES (%s, %s, %s, %s, %d, %s) ", $firstName, $lastName, $email, $date, $popupPostId, $unsubscribeToken) );
} else {
- $wpdb->query( $wpdb->prepare("UPDATE $subscribersTableName SET firstName = %s, lastName = %s, email = %s, cDate = %s, subscriptionType = %d WHERE id = %d", $firstName, $lastName, $email, $date, $popupPostId, $list['id']) );
+ // Update token if it doesn't exist, otherwise keep existing token
+ $existingSubscriber = $wpdb->get_row( $wpdb->prepare("SELECT unsubscribe_token FROM $subscribersTableName WHERE id = %d", $list['id']), ARRAY_A);
+ if (empty($existingSubscriber['unsubscribe_token'])) {
+ $wpdb->query( $wpdb->prepare("UPDATE $subscribersTableName SET firstName = %s, lastName = %s, email = %s, cDate = %s, subscriptionType = %d, unsubscribe_token = %s WHERE id = %d", $firstName, $lastName, $email, $date, $popupPostId, $unsubscribeToken, $list['id']) );
+ } else {
+ $wpdb->query( $wpdb->prepare("UPDATE $subscribersTableName SET firstName = %s, lastName = %s, email = %s, cDate = %s, subscriptionType = %d WHERE id = %d", $firstName, $lastName, $email, $date, $popupPostId, $list['id']) );
+ }
$res = 1;
}
if($res) {
--- a/popup-builder/com/classes/ConvertToNewVersion.php
+++ b/popup-builder/com/classes/ConvertToNewVersion.php
@@ -1,5 +1,8 @@
<?php
namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
use sgpbAdminHelper;
use SGPBConfigDataHelper;
@@ -140,7 +143,9 @@
foreach ($subscribers as $subscriber) {
$subscriber['subscriptionType'] = $this->getPostByTitle($subscriber['subscriptionType']);
$date = gmdate('Y-m-d');
- $wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (`firstName`, `lastName`, `email`, `cDate`, `subscriptionType`, `unsubscribed`) VALUES (%s, %s, %s, %s, %d, %d) ", $subscriber['firstName'], $subscriber['lastName'], $subscriber['email'], $date, $subscriber['subscriptionType'], 0) );
+ // Generate secure unsubscribe token
+ $unsubscribeToken = AdminHelper::generateUnsubscribeToken();
+ $wpdb->query( $wpdb->prepare("INSERT INTO $subscribersTableName (`firstName`, `lastName`, `email`, `cDate`, `subscriptionType`, `unsubscribed`, `unsubscribe_token`) VALUES (%s, %s, %s, %s, %d, %d, %s) ", $subscriber['firstName'], $subscriber['lastName'], $subscriber['email'], $date, $subscriber['subscriptionType'], 0, $unsubscribeToken) );
}
}
--- a/popup-builder/com/classes/Feedback.php
+++ b/popup-builder/com/classes/Feedback.php
@@ -1,6 +1,8 @@
<?php
namespace sgpb;
+defined( 'ABSPATH' ) || exit;
+
class SGPBFeedback
{
public function __construct()
--- a/popup-builder/com/classes/Filters.php
+++ b/popup-builder/com/classes/Filters.php
@@ -1,5 +1,9 @@
<?php
+
namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
use WP_Query;
use SgpbPopupConfig;
use sgpbPopupBuilderActivePackage;
--- a/popup-builder/com/classes/MediaButton.php
+++ b/popup-builder/com/classes/MediaButton.php
@@ -1,6 +1,9 @@
<?php
+
namespace sgpb;
+defined( 'ABSPATH' ) || exit;
+
class MediaButton
{
private $hideMediaButton = true;
--- a/popup-builder/com/classes/NotificationCenter.php
+++ b/popup-builder/com/classes/NotificationCenter.php
@@ -1,5 +1,8 @@
<?php
namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
use sgpbAdminHelper;
class SGPBNotificationCenter
--- a/popup-builder/com/classes/RegisterPostType.php
+++ b/popup-builder/com/classes/RegisterPostType.php
@@ -1,5 +1,8 @@
<?php
namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
use SgpbDataConfig;
use SgpbPopupConfig;
class RegisterPostType
--- a/popup-builder/com/classes/ScriptsLoader.php
+++ b/popup-builder/com/classes/ScriptsLoader.php
@@ -1,7 +1,9 @@
<?php
-
namespace sgpb;
+defined( 'ABSPATH' ) || exit;
+
+
// load popups data's from popups object
class ScriptsLoader
{
--- a/popup-builder/com/classes/Updates.php
+++ b/popup-builder/com/classes/Updates.php
@@ -1,6 +1,8 @@
<?php
namespace sgpb;
+defined( 'ABSPATH' ) || exit;
+
class Updates
{
private $licenses = array();
--- a/popup-builder/com/classes/components/Menu.php
+++ b/popup-builder/com/classes/components/Menu.php
@@ -1,7 +1,8 @@
<?php
-
namespace sgpb;
+defined( 'ABSPATH' ) || exit;
+
/**
* Class SGPBMenu
* @package sgpb
--- a/popup-builder/com/classes/popups/SubscriptionPopup.php
+++ b/popup-builder/com/classes/popups/SubscriptionPopup.php
@@ -1,5 +1,8 @@
<?php
namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
require_once(dirname(__FILE__).'/SGPopup.php');
require_once(ABSPATH.'wp-admin/includes/plugin.php');
--- a/popup-builder/com/config/config-free.php
+++ b/popup-builder/com/config/config-free.php
@@ -3,6 +3,6 @@
exit();
}
-define('SGPB_POPUP_VERSION', '4.4.2');
+define('SGPB_POPUP_VERSION', '4.4.3');
define('SGPB_POPUP_PKG', SGPB_POPUP_PKG_FREE);
define('SGPB_POPUP_BUILDER_BASENAME', 'popupbuilder-platinum/popup-builder.php');
--- a/popup-builder/com/config/config-gold.php
+++ b/popup-builder/com/config/config-gold.php
@@ -6,7 +6,7 @@
define('SGPB_ITEM_NAME', 'Gold');
define('SGPB_ITEM_ID', 84579);
-define('SGPB_POPUP_VERSION', '4.4.2');
+define('SGPB_POPUP_VERSION', '4.4.3');
// for popup builder license version
define('SGPB_VERSION_POPUP_BUILDER', SGPB_POPUP_VERSION);
define('SGPB_POPUP_PKG', SGPB_POPUP_PKG_GOLD);
--- a/popup-builder/com/config/config-platinum.php
+++ b/popup-builder/com/config/config-platinum.php
@@ -6,7 +6,7 @@
define('SGPB_ITEM_NAME', 'Platinum');
define('SGPB_ITEM_ID', 84595);
-define('SGPB_POPUP_VERSION', '4.4.2');
+define('SGPB_POPUP_VERSION', '4.4.3');
// for popup builder license version
define('SGPB_VERSION_POPUP_BUILDER', SGPB_POPUP_VERSION);
define('SGPB_POPUP_PKG', SGPB_POPUP_PKG_PLATINUM);
--- a/popup-builder/com/config/config-silver.php
+++ b/popup-builder/com/config/config-silver.php
@@ -6,7 +6,7 @@
define('SGPB_ITEM_NAME', 'Silver');
define('SGPB_ITEM_ID', 4146);
-define('SGPB_POPUP_VERSION', '4.4.2');
+define('SGPB_POPUP_VERSION', '4.4.3');
// for popup builder license version
define('SGPB_VERSION_POPUP_BUILDER', SGPB_POPUP_VERSION);
define('SGPB_POPUP_PKG', SGPB_POPUP_PKG_SILVER);
--- a/popup-builder/com/config/configPackage.php
+++ b/popup-builder/com/config/configPackage.php
@@ -3,6 +3,6 @@
exit();
}
-define('SGPB_POPUP_VERSION', '4.4.2');
+define('SGPB_POPUP_VERSION', '4.4.3');
define('SGPB_POPUP_PKG', SGPB_POPUP_PKG_FREE);
define('SGPB_POPUP_BUILDER_BASENAME', 'popupbuilder-platinum/popup-builder.php');
--- a/popup-builder/com/helpers/AdminHelper.php
+++ b/popup-builder/com/helpers/AdminHelper.php
@@ -1,5 +1,9 @@
<?php
namespace sgpb;
+
+defined( 'ABSPATH' ) || exit;
+
+
use WP_Query;
use DateTime;
use DateTimeZone;
@@ -907,10 +911,28 @@
if (isset($params['popup'])) {
$popup = $params['popup'];
}
+ // Check if this is an old MD5 token (32 characters, hexadecimal) first
+ $receivedToken = isset($params['token']) ? $params['token'] : '';
+ $isOldMd5Token = (strlen($receivedToken) == 32 && ctype_xdigit($receivedToken));
+
+ if ($isOldMd5Token) {
+ // Old unsubscribe link - show form to request new secure link
+ self::displayUnsubscribeLinkRequestForm($popup);
+ wp_die();
+ }
+
+ // If no email provided, show form
+ if (empty($email)) {
+ self::displayUnsubscribeLinkRequestForm($popup);
+ wp_die();
+ }
+
$subscribersTableName = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
$res = $wpdb->get_row( $wpdb->prepare("SELECT id FROM $subscribersTableName WHERE email = %s && subscriptionType = %s", $email, $popup), ARRAY_A);
if (!isset($res['id'])) {
- $noSubscriber = false;
+ // Subscriber not found - show form to request new link
+ self::displayUnsubscribeLinkRequestForm($popup);
+ wp_die();
}
$params['subscriberId'] = $res['id'];
@@ -918,10 +940,9 @@
if ($subscriber && $noSubscriber) {
self::deleteSubscriber($params);
}
- else if (!$noSubscriber) {
- printf( '<span>%s</span>' ,
- wp_kses_post(__('Oops, something went wrong, please try again or contact the administrator to check more info.', 'popup-builder') )
- );
+ else {
+ // Token is invalid or expired - show form to request new link
+ self::displayUnsubscribeLinkRequestForm($popup);
wp_die();
}
}
@@ -932,12 +953,29 @@
return false;
}
- $receivedToken = $params['token'];
- $realToken = md5($params['subscriberId'].$params['email']);
- if ($receivedToken == $realToken) {
- return true;
+ global $wpdb;
+ $subscribersTableName = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
+
+ // Get the stored token from database
+ $subscriber = $wpdb->get_row( $wpdb->prepare( "SELECT unsubscribe_token FROM $subscribersTableName WHERE id = %d", $params['subscriberId'] ), ARRAY_A );
+
+ if (empty($subscriber)) {
+ return false;
}
-
+
+ $receivedToken = isset($params['token']) ? $params['token'] : '';
+ $storedToken = isset($subscriber['unsubscribe_token']) ? $subscriber['unsubscribe_token'] : '';
+
+ // SECURITY: Old MD5 tokens are no longer accepted
+ // If no secure token is stored, the subscriber must request a new unsubscribe link
+ if (empty($storedToken)) {
+ // Old subscribers without secure tokens cannot use old MD5 links
+ // They must contact the administrator or request a new unsubscribe link
+ return false;
+ }
+
+ // Use secure comparison to prevent timing attacks
+ return hash_equals($storedToken, $receivedToken);
}
public static function deleteSubscriber($params = array())
@@ -990,6 +1028,135 @@
$wpdb->query( "ALTER TABLE $subscribersTableName ADD COLUMN unsubscribed INT NOT NULL DEFAULT 0 " );
}
+ /**
+ * Generate a cryptographically secure random token for unsubscribe links
+ *
+ * @return string A secure random token
+ */
+ public static function generateUnsubscribeToken()
+ {
+ // Use wp_generate_password with 32 characters for a secure random token
+ // This uses cryptographically secure random number generator
+ return wp_generate_password(32, false);
+ }
+
+ /**
+ * Add unsubscribe_token column to subscribers table if it doesn't exist
+ * This is a migration function to add the secure token column
+ */
+ public static function addUnsubscribeTokenColumn()
+ {
+ global $wpdb;
+ $subscribersTableName = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
+
+ // Check if column already exists
+ $column_exists = $wpdb->get_results( $wpdb->prepare( "SHOW COLUMNS FROM `$subscribersTableName` LIKE %s", 'unsubscribe_token' ) );
+
+ if (empty($column_exists)) {
+ $wpdb->query( "ALTER TABLE $subscribersTableName ADD COLUMN unsubscribe_token VARCHAR(255) NULL DEFAULT NULL" );
+
+ // Generate tokens for existing subscribers that don't have one
+ // This ensures backward compatibility
+ $subscribers_without_token = $wpdb->get_results( "SELECT id, email FROM $subscribersTableName WHERE unsubscribe_token IS NULL OR unsubscribe_token = ''", ARRAY_A );
+
+ foreach ($subscribers_without_token as $subscriber) {
+ $token = self::generateUnsubscribeToken();
+ $wpdb->query( $wpdb->prepare( "UPDATE $subscribersTableName SET unsubscribe_token = %s WHERE id = %d", $token, $subscriber['id'] ) );
+ }
+ }
+ }
+
+ /**
+ * Display form to request a new unsubscribe link when old link is expired
+ *
+ * @param string $popup Popup ID
+ */
+ public static function displayUnsubscribeLinkRequestForm($popup = '')
+ {
+ $homeUrl = get_home_url();
+ $actionUrl = admin_url('admin-post.php');
+
+ // Check if this is a redirect from form submission
+ $status = isset($_GET['sgpb_unsubscribe_status']) ? sanitize_text_field(wp_unslash($_GET['sgpb_unsubscribe_status'])) : '';
+ $emailValue = isset($_GET['email']) ? sanitize_email(wp_unslash($_GET['email'])) : '';
+
+ // Include the view template
+ $viewPath = SG_POPUP_VIEWS_PATH.'unsubscribeLinkRequestForm.php';
+ if (file_exists($viewPath)) {
+ include $viewPath;
+ } else {
+ // Fallback if view file doesn't exist
+ printf( '<span>%s</span>' ,
+ wp_kses_post(__('This unsubscribe link is no longer valid. Please contact the site administrator to unsubscribe from the mailing list.', 'popup-builder') )
+ );
+ }
+ }
+
+ /**
+ * Send a new secure unsubscribe link to the subscriber
+ *
+ * @param string $email Subscriber email
+ * @param string $popup Popup ID
+ * @return bool True if email sent successfully, false otherwise
+ */
+ public static function sendNewUnsubscribeLink($email, $popup = '')
+ {
+ global $wpdb;
+ $subscribersTableName = $wpdb->prefix.SGPB_SUBSCRIBERS_TABLE_NAME;
+
+ // Find subscriber by email and popup
+ $subscriber = $wpdb->get_row( $wpdb->prepare(
+ "SELECT id, firstName, lastName FROM $subscribersTableName WHERE email = %s AND subscriptionType = %s AND unsubscribed = 0",
+ $email,
+ $popup
+ ), ARRAY_A);
+
+ if (empty($subscriber)) {
+ return false;
+ }
+
+ // Generate or update secure token
+ $newToken = self::generateUnsubscribeToken();
+ $wpdb->query( $wpdb->prepare(
+ "UPDATE $subscribersTableName SET unsubscribe_token = %s WHERE id = %d",
+ $newToken,
+ $subscriber['id']
+ ));
+
+ // Build unsubscribe URL
+ $homeUrl = get_home_url();
+ $unsubscribeUrl = $homeUrl;
+ $unsubscribeUrl .= '?sgpbUnsubscribe='.urlencode($newToken);
+ $unsubscribeUrl .= '&email='.urlencode($email);
+ $unsubscribeUrl .= '&popup='.$popup;
+
+ // Get blog info for email
+ $blogName = wp_specialchars_decode(get_bloginfo('name'));
+ $adminEmail = get_bloginfo('admin_email');
+
+ // Prepare email
+ /* translators: %s: Blog name */
+ $subject = sprintf(__('[%s] New Unsubscribe Link', 'popup-builder'), $blogName);
+
+ $message = '<html><body>';
+ /* translators: %s: Subcriber first name */
+ $message .= '<p>'.sprintf(__('Hello %s,', 'popup-builder'), esc_html($subscriber['firstName'] ? $subscriber['firstName'] : '')).'</p>';
+ $message .= '<p>'.esc_html__('You requested a new unsubscribe link. Click the link below to unsubscribe from our mailing list:', 'popup-builder').'</p>';
+ $message .= '<p><a href="'.esc_url($unsubscribeUrl).'" style="background: #2271b1; color: #fff; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">'.esc_html__('Unsubscribe', 'popup-builder').'</a></p>';
+ $message .= '<p>'.esc_html__('Or copy and paste this link into your browser:', 'popup-builder').'<br>';
+ $message .= '<a href="'.esc_url($unsubscribeUrl).'">'.esc_url($unsubscribeUrl).'</a></p>';
+ $message .= '<p>'.esc_html__('If you did not request this link, please ignore this email.', 'popup-builder').'</p>';
+ $message .= '</body></html>';
+
+ $headers = array(
+ 'From: "'.$blogName.'" <'.$adminEmail.'>',
+ 'MIME-Version: 1.0',
+ 'Content-type: text/html; charset=UTF-8'
+ );
+
+ return wp_mail($email, $subject, $message, $headers);
+ }
+
public static function isPluginActive($key)
{
$allExtensions = SgpbDataConfig::allExtensionsKeys();
@@ -1111,7 +1278,7 @@
<button class="press press-grey sgpb-button-1 sgpb-close-promo-notification" data-action="sg-already-did-review"><?php esc_html_e('I already did', 'popup-builder'); ?></button>
<button class="press press-lightblue sgpb-button-3 sgpb-close-promo-notification" data-action="sg-you-worth-it"><?php esc_html_e('You worth it!', 'popup-builder'); ?></button>
<button class="press press-grey sgpb-button-2 sgpb-close-promo-notification" data-action="sg-show-popup-period" data-message-type="<?php echo esc_attr($type); ?>"><?php esc_html_e('Maybe later', 'popup-builder'); ?></button></div>
- <div> </div>
+ <div></div>
</div>
<?php
$popupContent = ob_get_clean();
--- a/popup-builder/com/helpers/ConfigDataHelper.php
+++ b/popup-builder/com/helpers/ConfigDataHelper.php
@@ -1,4 +1,6 @@
<?php
+defined( 'ABSPATH' ) || exit;
+
class SGPBConfigDataHelper
{
public static $customPostType;
--- a/popup-builder/com/libs/ListTable.php
+++ b/popup-builder/com/libs/ListTable.php
@@ -1,6 +1,9 @@
<?php
namespace sgpbDataTable;
+
+defined( 'ABSPATH' ) || exit;
+
use sgpbAdminHelper;
/**
--- a/popup-builder/com/libs/Reports.php
+++ b/popup-builder/com/libs/Reports.php
@@ -1,6 +1,10 @@
<?php
+
namespace sgpb;
+defined( 'ABSPATH' ) || exit;
+
+
class SGPBReports
{
private $type = 'debug';
--- a/popup-builder/com/libs/SGPBImporter.php
+++ b/popup-builder/com/libs/SGPBImporter.php
@@ -1,5 +1,7 @@
<?php
namespace sgpb;
+defined( 'ABSPATH' ) || exit;
+
use WP_Importer;
if (!class_exists('WP_Importer')) {
--- a/popup-builder/com/libs/Table.php
+++ b/popup-builder/com/libs/Table.php
@@ -2,6 +2,8 @@
namespace sgpbDataTable;
+defined( 'ABSPATH' ) || exit;
+
use sgpbAdminHelper;
use sgpbSubscriptionPopup;
require_once(dirname(__FILE__).'/ListTable.php');
@@ -102,6 +104,7 @@
$this->customizeQuery( $query );
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$totalItems = count( $wpdb->get_results( $query ) ); //return the total number of affected rows
if ($this->previewPopup) {
--- a/popup-builder/com/libs/WOOSL_CodeAutoUpdate.php
+++ b/popup-builder/com/libs/WOOSL_CodeAutoUpdate.php
@@ -1,6 +1,7 @@
<?php
-
namespace sgpb;
+defined( 'ABSPATH' ) || exit;
+
/**
* Allows plugins to use their own update API.
* Note: This updater is not used for Community/Hosted version of the plugin.
--- a/popup-builder/com/libs/parsers.php
+++ b/popup-builder/com/libs/parsers.php
@@ -1,5 +1,7 @@
<?php
namespace sgpb;
+defined( 'ABSPATH' ) || exit;
+
use DOMDocument;
/**
* WordPress eXtended RSS file parser implementations
--- a/popup-builder/popup-builder.php
+++ b/popup-builder/popup-builder.php
@@ -3,7 +3,7 @@
* Plugin Name: Popup Builder - Create highly converting, mobile friendly marketing popups.
* Plugin URI: https://popup-builder.com
* Description: The most complete popup plugin. Html, image, iframe, shortcode, video and many other popup types. Manage popup dimensions, effects, themes and more.
-* Version: 4.4.2
+* Version: 4.4.3
* Author: Looking Forward Software Incorporated.
* Author URI: https://popup-builder.com
* License: GPLv2
@@ -36,13 +36,13 @@
function sgpb_verify_subscriptionplus_deactivated() {
if (get_transient('sgpb_subscriptionplus_status')) {
?>
- <div class="notice notice-warning is-dismissible">
- <p><?php
- echo wp_kses_post ( __(
- 'One or more popups with the Subscription Plus Type were deactivated because you deactivated the Popup Builder Subscription Plus add-on. Click <a href="'.esc_url( admin_url( 'edit.php?post_status=trash&post_type=popupbuilder' ) ).'">here</a> to view your Popups.',
- 'popup-builder'),
- );
- ?></p>
+ <div class="notice notice-warning is-dismissible">
+ <p><?php
+ /* translators: %s: Edit Popup Link for administrator */
+ printf( wp_kses_post ( __(
+ 'One or more popups with the Subscription Plus Type were deactivated because you deactivated the Popup Builder Subscription Plus add-on. Click <a href="%s">here</a> to view your Popups.',
+ 'popup-builder')) , esc_url( admin_url( 'edit.php?post_status=trash&post_type=popupbuilder' ) ) );
+ ?></p>
</div>
<?php
// Delete the transient so it only shows once
--- a/popup-builder/public/views/unsubscribeLinkRequestForm.php
+++ b/popup-builder/public/views/unsubscribeLinkRequestForm.php
@@ -0,0 +1,143 @@
+<?php
+/* Exit if accessed directly */
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+/**
+ * Template for displaying the unsubscribe link request form
+ * Used when old MD5 unsubscribe links are detected or when tokens are invalid
+ *
+ * @var string $popup Popup ID
+ * @var string $status Status message (success, error, not_found)
+ * @var string $homeUrl Home URL
+ * @var string $actionUrl Admin post action URL
+ * @var string $emailValue Pre-filled email value if available
+ */
+?>
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title><?php esc_html_e('Request New Unsubscribe Link', 'popup-builder'); ?></title>
+ <style>
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+ background: #f0f0f1;
+ margin: 0;
+ padding: 20px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 100vh;
+ }
+ .unsubscribe-form-container {
+ background: #fff;
+ padding: 40px;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ max-width: 500px;
+ width: 100%;
+ }
+ .unsubscribe-form-container h2 {
+ margin-top: 0;
+ color: #1d2327;
+ font-size: 24px;
+ }
+ .unsubscribe-form-container p {
+ color: #646970;
+ margin-bottom: 20px;
+ line-height: 1.6;
+ }
+ .unsubscribe-form-container .form-group {
+ margin-bottom: 20px;
+ }
+ .unsubscribe-form-container label {
+ display: block;
+ margin-bottom: 8px;
+ color: #1d2327;
+ font-weight: 600;
+ }
+ .unsubscribe-form-container input[type="email"] {
+ width: 100%;
+ padding: 12px;
+ border: 1px solid #8c8f94;
+ border-radius: 4px;
+ font-size: 16px;
+ box-sizing: border-box;
+ }
+ .unsubscribe-form-container input[type="email"]:focus {
+ border-color: #2271b1;
+ outline: none;
+ box-shadow: 0 0 0 1px #2271b1;
+ }
+ .unsubscribe-form-container button {
+ background: #2271b1;
+ color: #fff;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 4px;
+ font-size: 16px;
+ cursor: pointer;
+ width: 100%;
+ font-weight: 600;
+ }
+ .unsubscribe-form-container button:hover {
+ background: #135e96;
+ }
+ .unsubscribe-form-container .message {
+ padding: 12px;
+ border-radius: 4px;
+ margin-bottom: 20px;
+ }
+ .unsubscribe-form-container .message.success {
+ background: #00a32a;
+ color: #fff;
+ }
+ .unsubscribe-form-container .message.error {
+ background: #d63638;
+ color: #fff;
+ }
+ </style>
+</head>
+<body>
+ <div class="unsubscribe-form-container">
+ <h2><?php esc_html_e('Your link has expired', 'popup-builder'); ?></h2>
+ <p><?php esc_html_e('Enter your email to receive a new unsubscribe link.', 'popup-builder'); ?></p>
+
+ <?php if (!empty($status)) : ?>
+ <?php if ($status === 'success') : ?>
+ <div class="message success">
+ <?php esc_html_e('A new unsubscribe link has been sent to your email address.', 'popup-builder'); ?>
+ </div>
+ <?php elseif ($status === 'error') : ?>
+ <div class="message error">
+ <?php esc_html_e('An error occurred. Please try again or contact the administrator.', 'popup-builder'); ?>
+ </div>
+ <?php elseif ($status === 'not_found') : ?>
+ <div class="message error">
+ <?php esc_html_e('Email address not found in our subscription list.', 'popup-builder'); ?>
+ </div>
+ <?php endif; ?>
+ <?php endif; ?>
+
+ <?php if ($status !== 'success') : ?>
+ <form method="post" action="<?php echo esc_url($actionUrl); ?>">
+ <input type="hidden" name="action" value="sgpb_request_new_unsubscribe_link">
+ <input type="hidden" name="popup" value="<?php echo esc_attr($popup); ?>">
+ <?php wp_nonce_field('sgpb_request_unsubscribe_link', 'sgpb_unsubscribe_nonce'); ?>
+ <div class="form-group">
+ <label for="sgpb_unsubscribe_email"><?php esc_html_e('Email Address', 'popup-builder'); ?></label>
+ <input type="email" id="sgpb_unsubscribe_email" name="email" required placeholder="<?php esc_attr_e('your@email.com', 'popup-builder'); ?>" value="<?php echo esc_attr($emailValue); ?>">
+ </div>
+ <button type="submit"><?php esc_html_e('Send New Unsubscribe Link', 'popup-builder'); ?></button>
+ </form>
+ <?php endif; ?>
+
+ <p style="margin-top: 20px; font-size: 14px;">
+ <a href="<?php echo esc_url($homeUrl); ?>"><?php esc_html_e('Return to homepage', 'popup-builder'); ?></a>
+ </p>
+ </div>
+</body>
+</html>
+