[newsletter]
newsletter.php
PHP
A simple plugin that provides a shortcode for a newsletter signup form with admin management and unsubscribe functionality.
<?php
/*
Plugin Name: Newsletter Signup
Description: A simple plugin that provides a shortcode for a newsletter signup form with admin management and unsubscribe functionality.
Version: 2.0.3
Author: Mike Vahldieck
Plugin URI: http://it-breeze.info
Author URI: http://it-breeze.info
License: GPL2
*/
<?php
/*
Plugin Name: Newsletter Signup
Description: A simple plugin that provides a shortcode for a newsletter signup form with admin management and unsubscribe functionality.
Version: 2.0.3
Author: Mike Vahldieck
Plugin URI: http://it-breeze.info
Author URI: http://it-breeze.info
License: GPL2
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* 1. Create the database table upon activation
*/
register_activation_hook( __FILE__, 'my_newsletter_plugin_activate' );
function my_newsletter_plugin_activate() {
global $wpdb;
$table_name = $wpdb->prefix . 'newsletter_signups';
$charset_collate = $wpdb->get_charset_collate();
// SQL to create the table if not exists
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
email VARCHAR(200) NOT NULL,
status VARCHAR(20) DEFAULT 'active' NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
unsubscribed_at DATETIME NULL,
PRIMARY KEY (id),
UNIQUE KEY email (email)
) $charset_collate;";
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
dbDelta( $sql );
}
/**
* 2. Add admin menu
*/
add_action( 'admin_menu', 'my_newsletter_admin_menu' );
function my_newsletter_admin_menu() {
add_menu_page(
'Newsletter Subscribers',
'Newsletter',
'manage_options',
'newsletter-subscribers',
'my_newsletter_admin_page',
'dashicons-email-alt',
30
);
}
/**
* 3. Admin page content
*/
function my_newsletter_admin_page() {
global $wpdb;
$table_name = $wpdb->prefix . 'newsletter_signups';
// Handle bulk actions
if ( isset( $_POST['action'] ) && $_POST['action'] === 'delete_selected' && isset( $_POST['subscriber_ids'] ) ) {
if ( wp_verify_nonce( $_POST['_wpnonce'], 'bulk_delete_subscribers' ) ) {
$ids = array_map( 'intval', $_POST['subscriber_ids'] );
$placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
$wpdb->query( $wpdb->prepare( "DELETE FROM $table_name WHERE id IN ($placeholders)", $ids ) );
echo '<div class="notice notice-success"><p>Selected subscribers deleted successfully.</p></div>';
}
}
// Handle individual delete
if ( isset( $_GET['action'] ) && $_GET['action'] === 'delete' && isset( $_GET['id'] ) ) {
if ( wp_verify_nonce( $_GET['_wpnonce'], 'delete_subscriber_' . $_GET['id'] ) ) {
$wpdb->delete( $table_name, array( 'id' => intval( $_GET['id'] ) ), array( '%d' ) );
echo '<div class="notice notice-success"><p>Subscriber deleted successfully.</p></div>';
}
}
// Get all subscribers
$subscribers = $wpdb->get_results( "SELECT * FROM $table_name ORDER BY created_at DESC" );
$total_subscribers = count( $subscribers );
$active_subscribers = count( array_filter( $subscribers, function( $s ) { return $s->status === 'active'; } ) );
$unsubscribed_subscribers = $total_subscribers - $active_subscribers;
?>
<div class="wrap">
<h1>Newsletter Subscribers</h1>
<div class="newsletter-stats" style="margin: 20px 0;">
<div style="display: flex; gap: 20px;">
<div class="stats-box" style="background: #fff; padding: 15px; border-left: 4px solid #00a32a; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h3 style="margin: 0; color: #00a32a;">Active Subscribers</h3>
<p style="font-size: 24px; margin: 5px 0 0 0; font-weight: bold;"><?php echo $active_subscribers; ?></p>
</div>
<div class="stats-box" style="background: #fff; padding: 15px; border-left: 4px solid #dba617; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h3 style="margin: 0; color: #dba617;">Unsubscribed</h3>
<p style="font-size: 24px; margin: 5px 0 0 0; font-weight: bold;"><?php echo $unsubscribed_subscribers; ?></p>
</div>
<div class="stats-box" style="background: #fff; padding: 15px; border-left: 4px solid #0073aa; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h3 style="margin: 0; color: #0073aa;">Total</h3>
<p style="font-size: 24px; margin: 5px 0 0 0; font-weight: bold;"><?php echo $total_subscribers; ?></p>
</div>
</div>
</div>
<?php if ( empty( $subscribers ) ) : ?>
<p>No subscribers found.</p>
<?php else : ?>
<form method="post">
<?php wp_nonce_field( 'bulk_delete_subscribers' ); ?>
<div class="tablenav top">
<div class="alignleft actions bulkactions">
<select name="action">
<option value="-1">Bulk Actions</option>
<option value="delete_selected">Delete</option>
</select>
<input type="submit" class="button action" value="Apply" onclick="return confirm('Are you sure you want to delete the selected subscribers?');">
</div>
</div>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<td class="manage-column column-cb check-column">
<input type="checkbox" id="cb-select-all-1">
</td>
<th class="manage-column">Email</th>
<th class="manage-column">Status</th>
<th class="manage-column">Subscribed Date</th>
<th class="manage-column">Unsubscribed Date</th>
<th class="manage-column">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ( $subscribers as $subscriber ) : ?>
<tr>
<th class="check-column">
<input type="checkbox" name="subscriber_ids[]" value="<?php echo esc_attr( $subscriber->id ); ?>">
</th>
<td><strong><?php echo esc_html( $subscriber->email ); ?></strong></td>
<td>
<span class="status-<?php echo esc_attr( $subscriber->status ); ?>" style="padding: 4px 8px; border-radius: 3px; font-size: 12px; font-weight: bold; color: white; background-color: <?php echo $subscriber->status === 'active' ? '#00a32a' : '#dba617'; ?>">
<?php echo esc_html( ucfirst( $subscriber->status ) ); ?>
</span>
</td>
<td><?php echo esc_html( date( 'Y-m-d H:i:s', strtotime( $subscriber->created_at ) ) ); ?></td>
<td><?php echo $subscriber->unsubscribed_at ? esc_html( date( 'Y-m-d H:i:s', strtotime( $subscriber->unsubscribed_at ) ) ) : '—'; ?></td>
<td>
<a href="<?php echo wp_nonce_url( admin_url( 'admin.php?page=newsletter-subscribers&action=delete&id=' . $subscriber->id ), 'delete_subscriber_' . $subscriber->id ); ?>"
class="button button-small"
onclick="return confirm('Are you sure you want to delete this subscriber?');">
Delete
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</form>
<?php endif; ?>
</div>
<script>
document.getElementById('cb-select-all-1').addEventListener('change', function() {
var checkboxes = document.querySelectorAll('input[name="subscriber_ids[]"]');
for (var checkbox of checkboxes) {
checkbox.checked = this.checked;
}
});
</script>
<?php
}
/**
* 4. Register the [newsletter_signup_form] shortcode
*/
add_shortcode( 'newsletter', 'my_newsletter_form_shortcode' );
function my_newsletter_form_shortcode() {
// If the form has been submitted, process the submission
if ( isset( $_POST['my_newsletter_email'] ) ) {
return handle_my_newsletter_form_submission();
}
// Generate a simple math captcha
$num1 = rand( 1, 10 );
$num2 = rand( 1, 10 );
$captcha_answer = $num1 + $num2;
// Otherwise, display the form
ob_start();
?>
<form method="POST" action="">
<?php wp_nonce_field( 'my_newsletter_action', 'my_newsletter_nonce' ); ?>
<p>
<label for="my_newsletter_email">E-Mail Adresse:</label><br>
<input type="email" name="my_newsletter_email" id="my_newsletter_email" required style="width: 100%; max-width: 300px; padding: 8px; margin-top: 5px;">
</p>
<p>
What is <?php echo $num1; ?> + <?php echo $num2; ?>?
<input type="number" name="captcha_answer" id="captcha_answer" required style="width: 60px; padding: 8px; margin-left: 10px;" min="1" max="20">
<input type="hidden" name="captcha_expected" value="<?php echo esc_attr( $captcha_answer ); ?>">
</p>
<p style="text-align: center;">
<button type="submit" style="border-radius: 16px;">Anmelden</button>
</p>
</form>
<?php
return ob_get_clean();
}
/**
* 5. Handle the form submission
*/
function handle_my_newsletter_form_submission() {
if ( ! isset( $_POST['my_newsletter_nonce'] ) ||
! wp_verify_nonce( $_POST['my_newsletter_nonce'], 'my_newsletter_action' ) ) {
return '<p style="color: red;">Error: Security check failed. Please try again.</p>';
}
// Validate captcha first
$captcha_answer = intval( $_POST['captcha_answer'] ?? 0 );
$captcha_expected = intval( $_POST['captcha_expected'] ?? 0 );
if ( $captcha_answer !== $captcha_expected ) {
return '<p style="color: red;">Incorrect answer to the math question. Please try again.</p>' . my_newsletter_form_shortcode();
}
$email = sanitize_email( $_POST['my_newsletter_email'] );
// Basic validation
if ( empty( $email ) || ! is_email( $email ) ) {
return '<p style="color: red;">Please enter a valid email address.</p>' . my_newsletter_form_shortcode();
}
global $wpdb;
$table_name = $wpdb->prefix . 'newsletter_signups';
// Check if email already exists
$existing_subscriber = $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM $table_name WHERE email = %s LIMIT 1",
$email
) );
if ( $existing_subscriber ) {
if ( $existing_subscriber->status === 'unsubscribed' ) {
// Reactivate unsubscribed user
$wpdb->update(
$table_name,
array(
'status' => 'active',
'unsubscribed_at' => null
),
array( 'email' => $email ),
array( '%s', '%s' ),
array( '%s' )
);
return '<p style="color: green;">Welcome back! You have been resubscribed to our newsletter.</p>';
} else {
return '<p style="color: orange;">This email is already signed up for our newsletter.</p>';
}
}
// Insert into the database
$result = $wpdb->insert(
$table_name,
array(
'email' => $email,
'status' => 'active'
),
array( '%s', '%s' )
);
if ( $result === false ) {
return '<p style="color: red;">Sorry, there was an error signing you up. Please try again.</p>';
}
// Return a success message
return '<p style="color: green;">Thank you for signing up for our newsletter!</p>';
}
/**
* 6. Register the [newsletter_unsubscribe] shortcode
*/
add_shortcode( 'newsletter_unsubscribe', 'my_newsletter_unsubscribe_shortcode' );
function my_newsletter_unsubscribe_shortcode( $atts ) {
$atts = shortcode_atts( array(
'success_message' => 'You have been successfully unsubscribed from our newsletter.',
'error_message' => 'Sorry, we could not find your email address in our system.',
'form_title' => 'Unsubscribe from Newsletter'
), $atts );
// Handle unsubscribe from URL parameter (for email links)
if ( isset( $_GET['unsubscribe_email'] ) && isset( $_GET['unsubscribe_token'] ) ) {
return handle_unsubscribe_from_url();
}
// If the form has been submitted, process the unsubscription
if ( isset( $_POST['unsubscribe_email'] ) ) {
return handle_my_newsletter_unsubscribe( $atts );
}
// Otherwise, display the unsubscribe form
ob_start();
?>
<div class="newsletter-unsubscribe-form">
<h3><?php echo esc_html( $atts['form_title'] ); ?></h3>
<form method="POST" action="">
<?php wp_nonce_field( 'my_newsletter_unsubscribe_action', 'my_newsletter_unsubscribe_nonce' ); ?>
<p>
<label for="unsubscribe_email">Enter your email address to unsubscribe:</label><br>
<input type="email" name="unsubscribe_email" id="unsubscribe_email" required style="width: 100%; max-width: 300px; padding: 8px; margin-top: 5px;">
</p>
<p>
<button type="submit" style="background-color: #dc3545; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer;">
Unsubscribe
</button>
</p>
</form>
</div>
<?php
return ob_get_clean();
}
/**
* 7. Handle the unsubscribe form submission
*/
function handle_my_newsletter_unsubscribe( $atts ) {
if ( ! isset( $_POST['my_newsletter_unsubscribe_nonce'] ) ||
! wp_verify_nonce( $_POST['my_newsletter_unsubscribe_nonce'], 'my_newsletter_unsubscribe_action' ) ) {
return '<p style="color: red;">Error: Security check failed. Please try again.</p>';
}
$email = sanitize_email( $_POST['unsubscribe_email'] );
// Basic validation
if ( empty( $email ) || ! is_email( $email ) ) {
return '<p style="color: red;">Please enter a valid email address.</p>';
}
global $wpdb;
$table_name = $wpdb->prefix . 'newsletter_signups';
// Check if email exists and is active
$subscriber = $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM $table_name WHERE email = %s LIMIT 1",
$email
) );
if ( ! $subscriber ) {
return '<p style="color: red;">' . esc_html( $atts['error_message'] ) . '</p>';
}
if ( $subscriber->status === 'unsubscribed' ) {
return '<p>You have already been unsubscribed from our newsletter.</p>';
}
// Update the subscriber status to unsubscribed
$result = $wpdb->update(
$table_name,
array(
'status' => 'unsubscribed',
'unsubscribed_at' => current_time( 'mysql' )
),
array( 'email' => $email ),
array( '%s', '%s' ),
array( '%s' )
);
if ( $result === false ) {
return '<p style="color: red;">Sorry, there was an error processing your unsubscribe request. Please try again.</p>';
}
return '<p style="color: green;">' . esc_html( $atts['success_message'] ) . '</p>';
}
/**
* 8. Handle unsubscribe from URL (for email links)
*/
function handle_unsubscribe_from_url() {
$email = sanitize_email( $_GET['unsubscribe_email'] );
$token = sanitize_text_field( $_GET['unsubscribe_token'] );
// Verify token (simple hash-based verification)
$expected_token = hash( 'sha256', $email . 'unsubscribe_salt_' . wp_salt() );
if ( ! hash_equals( $expected_token, $token ) ) {
return '<p style="color: red;">Invalid unsubscribe link. Please try again.</p>';
}
global $wpdb;
$table_name = $wpdb->prefix . 'newsletter_signups';
// Check if email exists
$subscriber = $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM $table_name WHERE email = %s LIMIT 1",
$email
) );
if ( ! $subscriber ) {
return '<p style="color: red;">Email address not found in our system.</p>';
}
if ( $subscriber->status === 'unsubscribed' ) {
return '<p>You have already been unsubscribed from our newsletter.</p>';
}
// Update the subscriber status to unsubscribed
$wpdb->update(
$table_name,
array(
'status' => 'unsubscribed',
'unsubscribed_at' => current_time( 'mysql' )
),
array( 'email' => $email ),
array( '%s', '%s' ),
array( '%s' )
);
return '<p style="color: green;">You have been successfully unsubscribed from our newsletter.</p>';
}
/**
* 9. Helper function to generate unsubscribe URLs for emails
*/
function get_newsletter_unsubscribe_url( $email, $page_id = null ) {
if ( ! $page_id ) {
// Try to find a page with the unsubscribe shortcode
global $wpdb;
$page = $wpdb->get_row( $wpdb->prepare(
"SELECT ID FROM {$wpdb->posts} WHERE post_content LIKE %s AND post_status = 'publish' AND post_type = 'page' LIMIT 1",
'%[newsletter_unsubscribe%'
) );
if ( $page ) {
$page_id = $page->ID;
} else {
return home_url(); // Fallback to home page
}
}
$token = hash( 'sha256', $email . 'unsubscribe_salt_' . wp_salt() );
return add_query_arg( array(
'unsubscribe_email' => urlencode( $email ),
'unsubscribe_token' => $token
), get_permalink( $page_id ) );
}
/**
* 10. Add export functionality (bonus feature)
*/
add_action( 'wp_ajax_export_newsletter_subscribers', 'export_newsletter_subscribers' );
function export_newsletter_subscribers() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'Unauthorized' );
}
global $wpdb;
$table_name = $wpdb->prefix . 'newsletter_signups';
$subscribers = $wpdb->get_results( "SELECT email, status, created_at, unsubscribed_at FROM $table_name ORDER BY created_at DESC" );
header( 'Content-Type: text/csv' );
header( 'Content-Disposition: attachment; filename="newsletter_subscribers_' . date( 'Y-m-d' ) . '.csv"' );
$output = fopen( 'php://output', 'w' );
fputcsv( $output, array( 'Email', 'Status', 'Subscribed Date', 'Unsubscribed Date' ) );
foreach ( $subscribers as $subscriber ) {
fputcsv( $output, array(
$subscriber->email,
$subscriber->status,
$subscriber->created_at,
$subscriber->unsubscribed_at
) );
}
fclose( $output );
exit;
}
?>