get_charset_collate();
$sql = "CREATE TABLE $table_name (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
url TEXT NOT NULL,
ip_address VARCHAR(45) NULL,
referrer TEXT NULL,
count INT(11) NOT NULL DEFAULT 1,
last_redirected DATETIME NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY url_hash (url(191)),
KEY idx_ip_address (ip_address),
KEY idx_last_redirected (last_redirected),
KEY idx_created_at (created_at)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
// Clear any previous errors
$wpdb->last_error = '';
$result = dbDelta($sql);
// Check for errors
if ($wpdb->last_error) {
error_log("P404 Plugin: Database error creating table - " . $wpdb->last_error);
return false;
}
// Verify table was created successfully
$table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name));
if ($table_exists != $table_name) {
error_log("P404 Plugin: Table creation verification failed");
return false;
}
error_log("P404 Plugin: New table created with all columns");
return true;
} catch (Exception $e) {
error_log("P404 Plugin: Exception creating new table - " . $e->getMessage());
return false;
}
}
function create_redirects_table()
{
global $wpdb;
try {
$table_name = $wpdb->prefix . 'redirects_404';
// Check if upgrade was completed
$email_update = get_option('P404_email_update', 0);
if ($email_update == 0) {
// Attempt the upgrade
$upgrade_success = p404_handle_database_upgrade_secure();
if ($upgrade_success) {
update_option('P404_email_update', 1);
error_log("P404 Plugin: Database upgrade completed successfully");
} else {
error_log("P404 Plugin: Database upgrade failed");
return false;
}
}
// Verify table exists
$table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name));
if ($wpdb->last_error) {
error_log('P404 Plugin: Database error checking table existence - ' . $wpdb->last_error);
return false;
}
return ($table_exists == $table_name);
} catch (Exception $e) {
error_log('P404 Plugin: Exception in create_redirects_table - ' . $e->getMessage());
return false;
}
}
function migrate_redirected_links()
{
global $wpdb;
$table_name = $wpdb->prefix . 'redirects_404';
$option_name = 'options-404-redirect-group';
$option_value = get_option($option_name);
// Check if there are any links to migrate
if (empty($option_value['redirected_links'])) {
return;
}
$counter = 0; // Initialize counter
foreach ($option_value['redirected_links'] as $redirect) {
// Break the loop if 3000 records have been processed
if ($counter >= 3000) {
break;
}
$url = $redirect['link'];
$date = date("Y-m-d H:i:s", strtotime($redirect['date']));
// Check if the URL already exists in the table
$existing = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE url = %s", $url));
if ($existing) {
// Update the count and last_redirected for existing URLs
$wpdb->update(
$table_name,
[
'count' => $existing->count + 1,
'last_redirected' => $date
],
['id' => $existing->id]
);
} else {
// Insert new URL records
$wpdb->insert(
$table_name,
[
'url' => $url,
'count' => 1,
'last_redirected' => $date
]
);
}
$counter++; // Increment counter
}
// Remove `redirected_links` from the options if all records were migrated
if ($counter < count($option_value['redirected_links'])) {
} else {
unset($option_value['redirected_links']);
update_option($option_name, $option_value);
}
}
function p404_handle_database_upgrade_secure()
{
global $wpdb;
$table_name = $wpdb->prefix . 'redirects_404';
try {
// Check if table exists with error handling
$wpdb->last_error = '';
$table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name));
if ($wpdb->last_error) {
error_log('P404 Plugin: Database error checking table - ' . $wpdb->last_error);
return false;
}
if ($table_exists == $table_name) {
// Table exists, alter the columns
$alter_success = p404_alter_existing_table_secure($table_name);
if (!$alter_success) {
return false;
}
} else {
// Table doesn't exist, create new one with all columns
$create_success = p404_create_new_table_with_columns_secure($table_name);
if (!$create_success) {
return false;
}
}
return true; // Success
} catch (Exception $e) {
error_log("P404 Plugin: Database upgrade exception - " . $e->getMessage());
return false;
}
}
// FIXED: Enhanced table alteration with error handling
function p404_alter_existing_table_secure($table_name)
{
global $wpdb;
try {
// Get existing columns with error handling
$wpdb->last_error = '';
$columns = $wpdb->get_results("DESCRIBE {$table_name}");
if ($wpdb->last_error) {
error_log("P404 Plugin: Failed to describe table structure - " . $wpdb->last_error);
return false;
}
if ($columns === false || empty($columns)) {
error_log("P404 Plugin: Unable to get table structure");
return false;
}
$existing_columns = array();
foreach ($columns as $column) {
$existing_columns[] = $column->Field;
}
$changes_made = false;
// Add IP column if it doesn't exist
if (!in_array('ip_address', $existing_columns)) {
$wpdb->last_error = '';
$result = $wpdb->query("ALTER TABLE {$table_name} ADD COLUMN ip_address VARCHAR(45) NULL AFTER url");
if ($result === false || $wpdb->last_error) {
error_log("P404 Plugin: Failed to add ip_address column - " . $wpdb->last_error);
return false;
}
error_log("P404 Plugin: Added ip_address column to existing table");
$changes_made = true;
}
// Add Referrer column if it doesn't exist
if (!in_array('referrer', $existing_columns)) {
$wpdb->last_error = '';
$result = $wpdb->query("ALTER TABLE {$table_name} ADD COLUMN referrer TEXT NULL AFTER ip_address");
if ($result === false || $wpdb->last_error) {
error_log("P404 Plugin: Failed to add referrer column - " . $wpdb->last_error);
return false;
}
error_log("P404 Plugin: Added referrer column to existing table");
$changes_made = true;
}
// Add indexes for better performance (these may fail if they already exist, that's OK)
if ($changes_made) {
// Suppress errors for index creation since they might already exist
$wpdb->suppress_errors(true);
$wpdb->query("ALTER TABLE {$table_name} ADD INDEX idx_ip_address (ip_address)");
$wpdb->query("ALTER TABLE {$table_name} ADD INDEX idx_last_redirected (last_redirected)");
$wpdb->query("ALTER TABLE {$table_name} ADD INDEX idx_url_hash (url(191))");
$wpdb->suppress_errors(false);
}
error_log("P404 Plugin: Existing table altered successfully");
return true;
} catch (Exception $e) {
error_log("P404 Plugin: Error altering existing table - " . $e->getMessage());
return false;
}
}
function p404_migrate_options()
{
// Check if migration is already completed
$migration_status = get_option('p404_migration_status2', '1'); // Default: 1 (not migrated)
if ($migration_status === '1') {
// Perform migration
$options = get_option(OPTIONS404, array());
if (!isset($options['p404_redirect_type'])) {
$options['p404_redirect_type'] = '301'; // Default: Permanent Redirect
}
// Save the updated options
update_option(OPTIONS404, $options);
setup_redirects_table_and_migrate();
// Update the migration status to `2` (migrated)
update_option('p404_migration_status2', '2');
}
}
add_action('plugins_loaded', 'p404_migrate_options');
function P404REDIRECT_HideMsg()
{
add_option('P404REDIRECT_upgrade_msg', 'hidemsg');
}
function P404REDIRECT_HideAlert()
{
update_option('P404_alert_msg', 'hidemsg');
}
add_action('admin_post_clear_redirects_log', 'clear_redirects_log_handler');
function clear_redirects_log_handler()
{
// Verify the nonce for security
if (!isset($_POST['clear_redirects_nonce']) || !wp_verify_nonce($_POST['clear_redirects_nonce'], 'clear_redirects_log')) {
wp_die('Security check failed.');
}
global $wpdb;
$table_name = $wpdb->prefix . 'redirects_404';
// Clear the table data
$wpdb->query("TRUNCATE TABLE $table_name");
// Reset the `links` count in options
$options = P404REDIRECT_get_my_options();
$options['links'] = 0;
P404REDIRECT_update_my_options($options);
// Redirect back to the 404 URLs tab with a success message
wp_redirect(admin_url('admin.php?page=all-404-redirect-to-homepage.php&mytab=404urls'));
exit;
}
function sample_admin_notice__error()
{
$class = 'notice notice-error';
$links_count = P404REDIRECT_read_option_value('links', 0);
if (get_option('P404_alert_msg') != 'hidemsg' && $links_count > 500) {
$message = __('
All 404 Redirect to Homepage
Warning, You have many broken links that hurt your site\'s rank in search engines, UPGRADE your plugin and empower your site\'s SEO. Dismiss this message or check the plugin settings.', 'sample-text-domain');
printf('', esc_attr($class), $message);
?>
';
echo '';
echo '';
echo ' Have many broken links?. keep track of 404 errors using our powerfull SEO Redirection Plugin to show and fix all broken links & 404 errors that occur on your site. or ';
echo ' Dismiss this message';
echo ' ';
echo ' | ';
echo '';
}
?>
2000) { // URLs shouldn't be this long
error_log('P404 Plugin: Unusually long URI detected');
return home_url();
}
return $protocol . $host . $uri;
}
//----------------------------------------------------
function P404REDIRECT_migrate_existing_options()
{
// Fetch the current options
$options = get_option(OPTIONS404, array());
// Add new options if they are missing
if (!isset($options['p404_logging_status'])) {
$options['p404_logging_status'] = '1'; // Default: enabled
}
if (!isset($options['p404_logging_expiration_date'])) {
$options['p404_logging_expiration_date'] = '1'; // Default: 1 month
}
if (!isset($options['p404_redirect_type'])) {
$options['p404_redirect_type'] = '301'; // Default: Permanent Redirect
}
// Save the updated options back to the database
update_option(OPTIONS404, $options);
}
function P404REDIRECT_init_my_options()
{
add_option(OPTIONS404);
// Initialize default options
$options = array();
$options['p404_redirect_to'] = site_url();
$options['p404_status'] = '1';
$options['img_p404_status'] = '2';
$options['p404_execlude_media'] = '1';
$options['links'] = 0;
$options['install_date'] = date("Y-m-d h:i a");
$options['p404_redirect_type'] = '301';
update_option(OPTIONS404, $options);
}
//----------------------------------------------------
function P404REDIRECT_update_my_options($options)
{
update_option(OPTIONS404, $options);
}
//----------------------------------------------------
function P404REDIRECT_get_my_options()
{
$options = get_option(OPTIONS404);
if (!$options) {
P404REDIRECT_init_my_options();
$options = get_option(OPTIONS404);
}
return $options;
}
/* read_option_value ------------------------------------------------- */
function P404REDIRECT_read_option_value($key, $default = '')
{
$options = P404REDIRECT_get_my_options();
if (array_key_exists($key, $options)) {
return $options[$key];
} else {
P404REDIRECT_save_option_value($key, $default);
return $default;
}
}
/* save_option_value ------------------------------------------------- */
function P404REDIRECT_save_option_value($key, $value)
{
$options = P404REDIRECT_get_my_options();
$options[$key] = $value;
P404REDIRECT_update_my_options($options);
}
function P404REDIRECT_add_redirected_link($link)
{
global $wpdb;
try {
// Sanitize the URL
$link = P404REDIRECT_get_current_URL();
if (empty($link) || $link === home_url()) {
return false; // Don't log homepage or empty URLs
}
// Increment the redirect count in options
$links = P404REDIRECT_read_option_value('links', 0);
P404REDIRECT_save_option_value('links', $links + 1);
// Get visitor information securely
$ip_address = p404_get_visitor_ip();
$referrer = p404_get_referrer();
// Define the table name
$table_name = $wpdb->prefix . 'redirects_404';
// Check if the exact same URL, IP, and referrer combination exists recently (within 1 hour)
$wpdb->last_error = '';
$recent_entry = $wpdb->get_row($wpdb->prepare("
SELECT * FROM {$table_name}
WHERE url = %s
AND ip_address = %s
AND referrer = %s
AND last_redirected > %s
ORDER BY last_redirected DESC
LIMIT 1
", $link, $ip_address, $referrer, date('Y-m-d H:i:s', strtotime('-1 hour'))));
if ($wpdb->last_error) {
error_log('P404 Plugin: Database error checking recent entry - ' . $wpdb->last_error);
return false;
}
if ($recent_entry) {
// Update existing recent entry
$wpdb->last_error = '';
$result = $wpdb->update(
$table_name,
array(
'count' => $recent_entry->count + 1,
'last_redirected' => current_time('mysql')
),
array('id' => $recent_entry->id),
array('%d', '%s'),
array('%d')
);
if ($wpdb->last_error) {
error_log('P404 Plugin: Database error updating redirect count - ' . $wpdb->last_error);
return false;
}
} else {
// Insert new record
$wpdb->last_error = '';
$result = $wpdb->insert(
$table_name,
array(
'url' => $link,
'ip_address' => $ip_address,
'referrer' => $referrer,
'count' => 1,
'last_redirected' => current_time('mysql')
),
array('%s', '%s', '%s', '%d', '%s')
);
if ($wpdb->last_error) {
error_log('P404 Plugin: Database error inserting new redirect - ' . $wpdb->last_error);
return false;
}
}
return true;
} catch (Exception $e) {
error_log('P404 Plugin: Exception in add_redirected_link - ' . $e->getMessage());
return false;
}
}
function p404_get_visitor_ip()
{
$ip_keys = array(
'HTTP_CF_CONNECTING_IP', // Cloudflare
'HTTP_CLIENT_IP', // Proxy
'HTTP_X_FORWARDED_FOR', // Load Balancer/Proxy
'HTTP_X_FORWARDED', // Proxy
'HTTP_X_CLUSTER_CLIENT_IP', // Cluster
'HTTP_FORWARDED_FOR', // Proxy
'HTTP_FORWARDED', // Proxy
'HTTP_X_REAL_IP', // Nginx
'REMOTE_ADDR' // Standard
);
foreach ($ip_keys as $key) {
if (!empty($_SERVER[$key])) {
$ip_list = explode(',', sanitize_text_field($_SERVER[$key]));
foreach ($ip_list as $ip) {
$ip = trim($ip);
// Validate IP format
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
continue;
}
// Skip private/reserved ranges for public IPs
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
// For development/local environments, accept private IPs
if (
in_array($ip, array('127.0.0.1', '::1')) ||
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6)
) {
return $ip;
}
}
}
}
// Fallback to REMOTE_ADDR with validation
$remote_addr = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field($_SERVER['REMOTE_ADDR']) : 'unknown';
if (filter_var($remote_addr, FILTER_VALIDATE_IP)) {
return $remote_addr;
}
return 'unknown';
}
function p404_get_referrer()
{
if (!isset($_SERVER['HTTP_REFERER']) || empty($_SERVER['HTTP_REFERER'])) {
return 'Direct Access';
}
$referrer = sanitize_text_field($_SERVER['HTTP_REFERER']);
// Validate URL format
if (!filter_var($referrer, FILTER_VALIDATE_URL)) {
return 'Invalid Referrer';
}
// Limit referrer length to prevent database issues
if (strlen($referrer) > 500) {
$referrer = substr($referrer, 0, 497) . '...';
}
// Optional: Filter out suspicious referrers
$blocked_patterns = array(
'javascript:',
'data:',
'vbscript:',
'file:',
'ftp:'
);
foreach ($blocked_patterns as $pattern) {
if (stripos($referrer, $pattern) !== false) {
return 'Blocked Referrer';
}
}
return esc_url_raw($referrer);
}
//----------------------------------------------------
function P404REDIRECT_option_msg($msg)
{
echo '';
}
//----------------------------------------------------
function P404REDIRECT_info_option_msg($msg)
{
echo '';
}
//----------------------------------------------------
function P404REDIRECT_warning_option_msg($msg)
{
echo '';
}
//----------------------------------------------------
function P404REDIRECT_success_option_msg($msg)
{
echo '';
}
//----------------------------------------------------
function P404REDIRECT_failure_option_msg($msg)
{
echo '';
}
//----------------------------------------------------
//** updated 2/2/2020
function P404REDIRECT_there_is_cache()
{
$plugins = get_site_option('active_plugins');
if (is_array($plugins)) {
foreach ($plugins as $the_plugin) {
if (stripos($the_plugin, 'cache') !== false) {
return $the_plugin;
}
}
}
return '';
}
//-----------
// Initialize email scheduling when plugin is activated
function p404_setup_email_notifications()
{
// Check if email notifications are enabled
$email_enabled = P404REDIRECT_read_option_value('email_notifications_enabled', '2');
if ($email_enabled == '1') {
$frequency = P404REDIRECT_read_option_value('email_frequency', 'weekly');
// Clear any existing schedules first
p404_clear_email_schedules();
// Calculate next run time based on frequency (NOT immediate)
$next_run_time = p404_calculate_next_run_time($frequency);
// Schedule the appropriate email based on frequency
if ($frequency == 'daily' && !wp_next_scheduled('p404_daily_email_summary')) {
wp_schedule_event($next_run_time, 'daily', 'p404_daily_email_summary');
} elseif ($frequency == 'weekly' && !wp_next_scheduled('p404_weekly_email_summary')) {
wp_schedule_event($next_run_time, 'weekly', 'p404_weekly_email_summary');
} elseif ($frequency == 'monthly' && !wp_next_scheduled('p404_monthly_email_summary')) {
wp_schedule_event($next_run_time, 'monthly', 'p404_monthly_email_summary');
}
// Log scheduling
} else {
// Clear any existing schedules if notifications are disabled
p404_clear_email_schedules();
}
}
function p404_calculate_next_run_time($frequency)
{
switch ($frequency) {
case 'daily':
// Schedule for tomorrow at 9 AM
$next_run = strtotime('tomorrow 9:00 AM');
break;
case 'weekly':
// Schedule for next Monday at 9 AM
$next_run = strtotime('next Monday 9:00 AM');
break;
case 'monthly':
// Schedule for first day of next month at 9 AM
$next_run = strtotime('first day of next month 9:00 AM');
break;
default:
// Default to next day 9 AM
$next_run = strtotime('tomorrow 9:00 AM');
}
return $next_run;
}
function p404_clear_email_schedules()
{
wp_clear_scheduled_hook('p404_daily_email_summary');
wp_clear_scheduled_hook('p404_weekly_email_summary');
wp_clear_scheduled_hook('p404_monthly_email_summary');
}
// Hook the email functions
add_action('p404_daily_email_summary', 'p404_send_daily_email');
add_action('p404_weekly_email_summary', 'p404_send_weekly_email');
add_action('p404_monthly_email_summary', 'p404_send_monthly_email');
// Email sending functions
function p404_send_daily_email()
{
p404_send_email_summary('daily');
}
function p404_send_weekly_email()
{
p404_send_email_summary('weekly');
}
function p404_send_monthly_email()
{
p404_send_email_summary('monthly');
}
// Main email sending function
function p404_send_email_summary($period)
{
// Check if notifications are still enabled
$email_enabled = P404REDIRECT_read_option_value('email_notifications_enabled', '2');
if ($email_enabled != '1') {
error_log("P404 Plugin: Email notifications disabled, skipping {$period} email");
return;
}
// Prevent duplicate emails within same hour
$last_sent_key = 'last_email_sent_' . $period;
$last_sent = P404REDIRECT_read_option_value($last_sent_key, '');
if (!empty($last_sent)) {
$last_sent_time = strtotime($last_sent);
$current_time = time();
$time_diff = $current_time - $last_sent_time;
// If email was sent within last hour, skip
if ($time_diff < 3600) { // 3600 seconds = 1 hour
error_log("P404 Plugin: {$period} email already sent recently (" . round($time_diff / 60) . " minutes ago), skipping");
return;
}
}
$notification_email = P404REDIRECT_read_option_value('notification_email', get_option('admin_email'));
// Get comprehensive 404 statistics for the period
$stats = p404_get_period_statistics($period);
// Only send email if there are 404 errors
if ($stats['total_errors'] == 0) {
error_log("P404 Plugin: No 404 errors found for {$period} period, skipping email");
return;
}
// Generate enhanced email content
$subject = p404_get_email_subject($period, $stats['total_errors']);
$message = p404_generate_email_content($period, $stats);
// Enhanced email headers
$headers = array(
'Content-Type: text/html; charset=UTF-8',
'From: ' . get_bloginfo('name') . ' <' . get_option('admin_email') . '>',
'Reply-To: ' . get_option('admin_email')
);
// Update last sent time BEFORE sending (to prevent race conditions)
P404REDIRECT_save_option_value($last_sent_key, current_time('mysql'));
// Send email with error handling
$sent = wp_mail($notification_email, $subject, $message, $headers);
if ($sent) {
// Log successful email send
error_log("P404 Plugin: {$period} email summary sent successfully to {$notification_email}");
} else {
// Log failed email send and reset the timestamp
error_log("P404 Plugin: Failed to send {$period} email summary to {$notification_email}");
// Reset the timestamp since email failed
P404REDIRECT_save_option_value($last_sent_key, $last_sent);
}
}
// Get statistics for a specific period
function p404_get_period_statistics($period)
{
global $wpdb;
$table_name = $wpdb->prefix . 'redirects_404';
// Set date range based on period
switch ($period) {
case 'daily':
$start_date = date('Y-m-d 00:00:00');
$end_date = date('Y-m-d 23:59:59');
$previous_start = date('Y-m-d 00:00:00', strtotime('-1 day'));
$previous_end = date('Y-m-d 23:59:59', strtotime('-1 day'));
break;
case 'weekly':
$start_date = date('Y-m-d 00:00:00', strtotime('-7 days'));
$end_date = date('Y-m-d 23:59:59');
$previous_start = date('Y-m-d 00:00:00', strtotime('-14 days'));
$previous_end = date('Y-m-d 23:59:59', strtotime('-7 days'));
break;
case 'monthly':
$start_date = date('Y-m-01 00:00:00');
$end_date = date('Y-m-t 23:59:59');
$previous_start = date('Y-m-01 00:00:00', strtotime('-1 month'));
$previous_end = date('Y-m-t 23:59:59', strtotime('-1 month'));
break;
default:
$start_date = date('Y-m-d 00:00:00', strtotime('-7 days'));
$end_date = date('Y-m-d 23:59:59');
$previous_start = date('Y-m-d 00:00:00', strtotime('-14 days'));
$previous_end = date('Y-m-d 23:59:59', strtotime('-7 days'));
}
// Get total errors
$total_errors = $wpdb->get_var($wpdb->prepare("
SELECT SUM(count)
FROM {$table_name}
WHERE last_redirected BETWEEN %s AND %s
", $start_date, $end_date));
// Get previous period errors for comparison
$previous_errors = $wpdb->get_var($wpdb->prepare("
SELECT SUM(count)
FROM {$table_name}
WHERE last_redirected BETWEEN %s AND %s
", $previous_start, $previous_end));
// Get unique URLs
$unique_urls = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(DISTINCT url)
FROM {$table_name}
WHERE last_redirected BETWEEN %s AND %s
", $start_date, $end_date));
// Get unique IPs
$unique_ips = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(DISTINCT ip_address)
FROM {$table_name}
WHERE last_redirected BETWEEN %s AND %s
AND ip_address IS NOT NULL
AND ip_address != 'unknown'
", $start_date, $end_date));
// Get top 5 URLs
$top_urls = $wpdb->get_results($wpdb->prepare("
SELECT url, SUM(count) as hits
FROM {$table_name}
WHERE last_redirected BETWEEN %s AND %s
GROUP BY url
ORDER BY hits DESC
LIMIT 5
", $start_date, $end_date), ARRAY_A);
// Get top referrers
$top_referrers = $wpdb->get_results($wpdb->prepare("
SELECT
CASE
WHEN referrer = 'Direct Access' THEN 'Direct Access'
WHEN referrer LIKE '%%facebook.com%%' THEN 'Facebook'
WHEN referrer LIKE '%%google.com%%' THEN 'Google'
WHEN referrer LIKE '%%twitter.com%%' THEN 'Twitter'
WHEN referrer LIKE '%%linkedin.com%%' THEN 'LinkedIn'
ELSE SUBSTRING_INDEX(REPLACE(REPLACE(referrer, 'https://', ''), 'http://', ''), '/', 1)
END as referrer_domain,
SUM(count) as hits,
COUNT(DISTINCT ip_address) as unique_visitors
FROM {$table_name}
WHERE last_redirected BETWEEN %s AND %s
AND referrer IS NOT NULL
GROUP BY referrer_domain
ORDER BY hits DESC
LIMIT 5
", $start_date, $end_date), ARRAY_A);
// Get daily trend (last 7 days)
$daily_trend = $wpdb->get_results($wpdb->prepare("
SELECT
DATE(last_redirected) as date,
SUM(count) as errors
FROM {$table_name}
WHERE last_redirected BETWEEN %s AND %s
GROUP BY DATE(last_redirected)
ORDER BY date ASC
", date('Y-m-d 00:00:00', strtotime('-7 days')), date('Y-m-d 23:59:59')), ARRAY_A);
// Get new vs recurring URLs
$new_urls = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(DISTINCT url)
FROM {$table_name} t1
WHERE t1.last_redirected BETWEEN %s AND %s
AND NOT EXISTS (
SELECT 1 FROM {$table_name} t2
WHERE t2.url = t1.url
AND t2.last_redirected < %s
)
", $start_date, $end_date, $start_date));
// Calculate percentage change
$percentage_change = 0;
if ($previous_errors > 0) {
$percentage_change = (($total_errors - $previous_errors) / $previous_errors) * 100;
}
return array(
'period' => $period,
'start_date' => $start_date,
'end_date' => $end_date,
'total_errors' => intval($total_errors),
'previous_errors' => intval($previous_errors),
'percentage_change' => round($percentage_change, 1),
'unique_urls' => intval($unique_urls),
'unique_ips' => intval($unique_ips),
'new_urls' => intval($new_urls),
'recurring_urls' => intval($unique_urls) - intval($new_urls),
'top_urls' => $top_urls,
'top_referrers' => $top_referrers,
'daily_trend' => $daily_trend,
'avg_errors_per_day' => $period == 'daily' ? intval($total_errors) : round(intval($total_errors) / 7, 1)
);
}
// Generate email subject
function p404_get_email_subject($period, $error_count)
{
$site_name = get_bloginfo('name');
//$period_text = ucfirst(string: $period);
$period_text = isset($period) ? ucfirst($period) : 'Weekly';
return "404 Analytics Report - {$site_name} ({$period_text}: " . number_format($error_count) . " errors)";
}
// Generate email content
function p404_generate_email_content($period, $stats)
{
$site_name = get_bloginfo('name');
//$period_text = ucfirst($period);
$period_text = isset($period) ? ucfirst($period) : 'Weekly';
$date_range = p404_format_date_range($period, $stats['start_date'], $stats['end_date']);
ob_start();
?>
404 Error Analytics Report
General Statistics
Total 404 Errors
0 ? '↗' : '↘'; ?> % vs previous
1): ?>
7-Day Trend Analysis
| Date |
Errors |
Trend |
$day):
$trend_indicator = '';
if ($index > 0) {
if ($day['errors'] > $prev_errors) {
$trend_indicator = '↗';
} elseif ($day['errors'] < $prev_errors) {
$trend_indicator = '↘';
} else {
$trend_indicator = '→';
}
}
$prev_errors = $day['errors'];
?>
|
|
|
|
Most Frequent 404 URLs
| URL |
Hits |
% |
0 ? round(($url_data['hits'] / $stats['total_errors']) * 100, 1) : 0;
?>
|
|
|
%
|
Top Traffic Sources Causing 404s
URL Analysis
Priority Action Items
($stats['total_errors'] * 0.1); // URLs with >10% of total errors
});
?>
High Priority (Immediate Action Required)
0): ?>
- Fix high-impact URLs: URLs are causing % of all errors
100): ?>
- Critical error volume: errors detected - immediate investigation needed
50): ?>
- Error spike detected: % increase from previous period
Medium Priority
- Review referrer sources - focus on traffic
- Set up redirects for new broken URLs
- Monitor unique visitors experiencing 404s
5): ?>
- Address recurring broken URLs
Maintenance Tasks
- Update internal links to prevent future 404s
- Contact external sites linking to broken URLs
- Implement custom 404 page with search functionality
- Regular weekly monitoring to catch issues early
0): ?>
Performance Impact Analysis
SEO Impact: 404 errors can negatively affect search engine rankings and user experience.
User Experience: visitors encountered broken links, potentially leading to lost conversions.
Server Load: Processing 404 requests consumes server resources unnecessarily.
604800, // 7 days
'display' => 'Weekly'
);
$schedules['monthly'] = array(
'interval' => 2635200, // 30.5 days
'display' => 'Monthly'
);
return $schedules;
}
/// Clean up scheduled events on plugin deactivation
register_deactivation_hook(__FILE__, 'p404_cleanup_email_schedules');
function p404_cleanup_email_schedules()
{
p404_clear_email_schedules();
}
function p404_update_email_schedules()
{
p404_setup_email_notifications();
}
function p404_send_test_email()
{
if (!current_user_can('manage_options')) {
return false;
}
// Temporarily bypass duplicate protection for test
$stats = p404_get_period_statistics('weekly');
if ($stats['total_errors'] == 0) {
// Create fake stats for testing
$stats['total_errors'] = 1;
$stats['unique_urls'] = 1;
$stats['unique_ips'] = 1;
$stats['top_urls'] = array(array('url' => 'https://example.com/test', 'hits' => 1));
}
$subject = "TEST: " . p404_get_email_subject('weekly', $stats['total_errors']);
$message = p404_generate_email_content('weekly', $stats);
$admin_email = get_option('admin_email');
$headers = array(
'Content-Type: text/html; charset=UTF-8',
'From: ' . get_bloginfo('name') . ' <' . $admin_email . '>'
);
$sent = wp_mail($admin_email, $subject, $message, $headers);
if ($sent) {
error_log("P404 Plugin: Test email sent successfully to {$admin_email}");
} else {
error_log("P404 Plugin: Failed to send test email to {$admin_email}");
}
return $sent;
}
add_action('wp_ajax_delete_previous_404_image', 'handle_delete_previous_404_image');
function handle_delete_previous_404_image()
{
// Check nonce for security
if (!wp_verify_nonce($_POST['nonce'], 'delete_404_image_nonce')) {
wp_die('Security check failed');
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_die('Insufficient permissions');
}
$image_id = intval($_POST['image_id']);
$option_name = sanitize_text_field($_POST['option_name']);
if ($image_id) {
// Get current options
$options = get_option('p404_redirect_options', array());
// Clear the specific option if it matches the image being deleted
if (isset($options[$option_name]) && $options[$option_name] == $image_id) {
$options[$option_name] = '';
update_option('p404_redirect_options', $options);
}
// Delete the attachment
$deleted = wp_delete_attachment($image_id, true);
if ($deleted) {
wp_send_json_success('Image deleted successfully and option cleared');
} else {
wp_send_json_error('Failed to delete image');
}
} else {
wp_send_json_error('Invalid image ID');
}
}
add_action('wp_ajax_send_test_404_email', 'handle_send_test_404_email');
function handle_send_test_404_email()
{
// Check nonce for security
if (!wp_verify_nonce($_POST['nonce'], 'test_404_email_nonce')) {
wp_send_json_error('Security check failed');
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
$email = sanitize_email($_POST['email']);
if (!is_email($email)) {
wp_send_json_error('Invalid email address');
}
// Check if we have enough redirects
$total_redirects = P404REDIRECT_read_option_value('links', 0);
if ($total_redirects < 5) {
wp_send_json_error('Not enough redirect data available. Need at least 5 redirects to send test email.');
}
try {
// Generate test email with actual data
$test_stats = p404_generate_test_email_stats();
$email_content = p404_generate_email_content('weekly', $test_stats);
$subject = p404_get_email_subject('weekly', $test_stats['total_errors']);
// Set up HTML email
add_filter('wp_mail_content_type', 'p404_set_html_content_type');
// Send the email
$sent = wp_mail($email, $subject, $email_content);
// Remove filter after sending
remove_filter('wp_mail_content_type', 'p404_set_html_content_type');
if ($sent) {
wp_send_json_success('Test email sent successfully to ' . $email);
} else {
wp_send_json_error('Failed to send email. Please check your mail configuration.');
}
} catch (Exception $e) {
wp_send_json_error('Error generating email: ' . $e->getMessage());
}
}
// Function to set HTML content type for emails
function p404_set_html_content_type()
{
return 'text/html';
}
// Function to generate test email statistics using real data
function p404_generate_test_email_stats()
{
global $wpdb;
$table_name = $wpdb->prefix . 'redirects_404';
// Get date ranges
$end_date = date('Y-m-d');
$start_date = date('Y-m-d', strtotime('-7 days'));
$prev_start_date = date('Y-m-d', strtotime('-14 days'));
$prev_end_date = date('Y-m-d', strtotime('-8 days'));
// Get current period stats
$current_period_errors = $wpdb->get_var($wpdb->prepare("
SELECT SUM(count)
FROM {$table_name}
WHERE DATE(last_redirected) BETWEEN %s AND %s
", $start_date, $end_date)) ?: 0;
// Get previous period stats for comparison
$previous_period_errors = $wpdb->get_var($wpdb->prepare("
SELECT SUM(count)
FROM {$table_name}
WHERE DATE(last_redirected) BETWEEN %s AND %s
", $prev_start_date, $prev_end_date)) ?: 0;
// Calculate percentage change
$percentage_change = 0;
if ($previous_period_errors > 0) {
$percentage_change = round((($current_period_errors - $previous_period_errors) / $previous_period_errors) * 100, 1);
} elseif ($current_period_errors > 0) {
$percentage_change = 100;
}
// Get unique URLs
$unique_urls = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(DISTINCT url)
FROM {$table_name}
WHERE DATE(last_redirected) BETWEEN %s AND %s
", $start_date, $end_date)) ?: 0;
// Get unique IPs
$unique_ips = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(DISTINCT ip_address)
FROM {$table_name}
WHERE DATE(last_redirected) BETWEEN %s AND %s
AND ip_address IS NOT NULL
AND ip_address != 'unknown'
", $start_date, $end_date)) ?: 0;
// Get top URLs
$top_urls = $wpdb->get_results($wpdb->prepare("
SELECT url, SUM(count) as hits
FROM {$table_name}
WHERE DATE(last_redirected) BETWEEN %s AND %s
GROUP BY url
ORDER BY hits DESC
LIMIT 10
", $start_date, $end_date), ARRAY_A);
// Format top URLs
$formatted_top_urls = array();
foreach ($top_urls as $url_data) {
$formatted_top_urls[] = array(
'url' => $url_data['url'],
'hits' => intval($url_data['hits'])
);
}
// Get top referrers (if referrer column exists)
$referrer_column_exists = $wpdb->get_var("SHOW COLUMNS FROM {$table_name} LIKE 'referrer'");
$top_referrers = array();
if ($referrer_column_exists) {
$referrer_data = $wpdb->get_results($wpdb->prepare("
SELECT
CASE
WHEN referrer IS NULL OR referrer = '' THEN 'Direct Access'
WHEN referrer LIKE '%google.%' THEN 'Google Search'
WHEN referrer LIKE '%bing.%' THEN 'Bing Search'
WHEN referrer LIKE '%facebook.%' THEN 'Facebook'
WHEN referrer LIKE '%twitter.%' THEN 'Twitter'
ELSE SUBSTRING_INDEX(SUBSTRING_INDEX(referrer, '/', 3), '/', -1)
END as referrer_domain,
SUM(count) as hits,
COUNT(DISTINCT ip_address) as unique_visitors
FROM {$table_name}
WHERE DATE(last_redirected) BETWEEN %s AND %s
GROUP BY referrer_domain
ORDER BY hits DESC
LIMIT 5
", $start_date, $end_date), ARRAY_A);
foreach ($referrer_data as $ref) {
$top_referrers[] = array(
'referrer_domain' => $ref['referrer_domain'],
'hits' => intval($ref['hits']),
'unique_visitors' => intval($ref['unique_visitors'])
);
}
} else {
// Fallback referrers for demo
$top_referrers = array(
array('referrer_domain' => 'Google Search', 'hits' => max(1, intval($current_period_errors * 0.4)), 'unique_visitors' => max(1, intval($unique_ips * 0.6))),
array('referrer_domain' => 'Direct Access', 'hits' => max(1, intval($current_period_errors * 0.3)), 'unique_visitors' => max(1, intval($unique_ips * 0.4))),
array('referrer_domain' => 'Other Sites', 'hits' => max(1, intval($current_period_errors * 0.2)), 'unique_visitors' => max(1, intval($unique_ips * 0.3)))
);
}
// Get daily trend for the past 7 days
$daily_trend = $wpdb->get_results($wpdb->prepare("
SELECT
DATE(last_redirected) as date,
SUM(count) as errors
FROM {$table_name}
WHERE DATE(last_redirected) BETWEEN %s AND %s
GROUP BY DATE(last_redirected)
ORDER BY date ASC
", $start_date, $end_date), ARRAY_A);
// Fill in missing days with 0 errors
$formatted_daily_trend = array();
for ($i = 6; $i >= 0; $i--) {
$check_date = date('Y-m-d', strtotime("-{$i} days"));
$found = false;
foreach ($daily_trend as $day) {
if ($day['date'] == $check_date) {
$formatted_daily_trend[] = array(
'date' => $check_date,
'errors' => intval($day['errors'])
);
$found = true;
break;
}
}
if (!$found) {
$formatted_daily_trend[] = array(
'date' => $check_date,
'errors' => 0
);
}
}
// Calculate new vs recurring URLs
$total_unique_urls_ever = $wpdb->get_var("SELECT COUNT(DISTINCT url) FROM {$table_name}") ?: 0;
$new_urls = max(0, $unique_urls - intval($total_unique_urls_ever * 0.3)); // Estimate
$recurring_urls = $unique_urls - $new_urls;
// Calculate average errors per day
$avg_errors_per_day = $current_period_errors > 0 ? round($current_period_errors / 7, 1) : 0;
return array(
'total_errors' => $current_period_errors,
'unique_urls' => $unique_urls,
'unique_ips' => $unique_ips,
'percentage_change' => $percentage_change,
'top_urls' => $formatted_top_urls,
'top_referrers' => $top_referrers,
'daily_trend' => $formatted_daily_trend,
'new_urls' => $new_urls,
'recurring_urls' => $recurring_urls,
'avg_errors_per_day' => $avg_errors_per_day,
'start_date' => $start_date,
'end_date' => $end_date
);
}
// Function to schedule or send regular email reports (for future implementation)
function p404_schedule_email_reports()
{
// This function can be used to set up WordPress cron jobs for automated emails
$email_enabled = P404REDIRECT_read_option_value('email_notifications_enabled', '2');
$email_frequency = P404REDIRECT_read_option_value('email_frequency', 'weekly');
if ($email_enabled == '1') {
// Schedule based on frequency
if (!wp_next_scheduled('p404_send_scheduled_email')) {
switch ($email_frequency) {
case 'daily':
wp_schedule_event(strtotime('tomorrow 9:00 AM'), 'daily', 'p404_send_scheduled_email');
break;
case 'weekly':
wp_schedule_event(strtotime('next monday 9:00 AM'), 'weekly', 'p404_send_scheduled_email');
break;
case 'monthly':
wp_schedule_event(strtotime('first day of next month 9:00 AM'), 'monthly', 'p404_send_scheduled_email');
break;
}
}
} else {
// Unschedule if disabled
wp_clear_scheduled_hook('p404_send_scheduled_email');
}
}
// Hook for scheduled emails (add this to your main plugin file)
add_action('p404_send_scheduled_email', 'p404_send_automatic_email_report');
function p404_send_automatic_email_report()
{
$email_enabled = P404REDIRECT_read_option_value('email_notifications_enabled', '2');
if ($email_enabled != '1') return;
$notification_email = P404REDIRECT_read_option_value('notification_email', get_option('admin_email'));
$email_frequency = P404REDIRECT_read_option_value('email_frequency', 'weekly');
// Generate stats based on frequency
$stats = p404_generate_email_stats_for_period($email_frequency);
// Only send if there are errors to report
if ($stats['total_errors'] > 0) {
$email_content = p404_generate_email_content($email_frequency, $stats);
$subject = p404_get_email_subject($email_frequency, $stats['total_errors']);
add_filter('wp_mail_content_type', 'p404_set_html_content_type');
wp_mail($notification_email, $subject, $email_content);
remove_filter('wp_mail_content_type', 'p404_set_html_content_type');
}
}
function p404_generate_email_stats_for_period($period)
{
// Similar to p404_generate_test_email_stats but with different date ranges based on period
// Implementation would be similar but with dynamic date ranges
switch ($period) {
case 'daily':
return p404_generate_test_email_stats(); // Use current implementation
case 'weekly':
return p404_generate_test_email_stats(); // Use current implementation
case 'monthly':
// Implement monthly stats (30 days)
return p404_generate_test_email_stats(); // For now, use current
default:
return p404_generate_test_email_stats();
}
}