ID, $user->user_email, 'subscribed'); } //make_images_dir(); make_backup_dir(); // Mark setup completed update_option('dapper_setup_complete', true); } } register_activation_hook(__FILE__, 'dapper_activation'); /* // Depreciated in v1.1.1 to be removed in later versions // Checks for an images directory inside the dapper plugin // Creates the 'images' folder when plugin gets activated // Used for custom logos on login page */ function make_images_dir() { $dapper_root = plugin_dir_path(__FILE__); $image_dir = $dapper_root . '/images'; if (!file_exists($image_dir)){ wp_mkdir_p($image_dir); } } /* // Adds users to the dapper_mailing _list initially // Also manages the mailing list // Has three required arguments // @param $user_id integer id of user in mailing list // @param $email string email of user // @param $sub_status string 'subscribed' or 'unsubscribed' */ function update_mailing_list($user_id, $email, $sub_status) { global $wpdb; $table_name = $wpdb->prefix . 'dapper_mailing_list'; // Check if the user is already in the mailing list $existing_user = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE user_id = %d", $user_id)); if (!$existing_user) { // If the user is not in the list, insert the new record $wpdb->insert( $table_name, array( 'user_id' => $user_id, 'email' => $email, 'subscription_status' => $sub_status, ), array('%d', '%s', '%s') ); } else { // If the user is already in the list, update the subscription status $wpdb->update( $table_name, array('subscription_status' => $sub_status), array('user_id' => $user_id), array('%s'), array('%d') ); } } /* // Called during intial run as part of the setup process // Generates the database table for the mailing list */ function create_mailing_list_table() { global $wpdb; $table_name = $wpdb->prefix . 'dapper_mailing_list'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, user_id mediumint(9) NOT NULL, email varchar(255) NOT NULL, subscription_status varchar(20) NOT NULL, PRIMARY KEY (id) ) $charset_collate;"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta($sql); } /* // Get the total number of mailing list users // Returns an Integer */ function get_mailing_list_users() { global $wpdb; $table_name = $wpdb->prefix . 'dapper_mailing_list'; // Assuming your table has a WordPress prefix // Fetch users from the mailing list table $query = "SELECT * FROM $table_name"; $results = $wpdb->get_results($query); return $results; } /* // Removes a user from the dapper mailing list // Called from unsubscribe links asking for removal // Called when a user is manually removed from the mailing list // @param $user_id integer id of the passed in user required */ function remove_user_from_mailing_list($user_id) { global $wpdb; $table_name = $wpdb->prefix . 'dapper_mailing_list'; // Remove the user from the mailing list $wpdb->delete($table_name, array('user_id' => $user_id)); } // Create Email Template post type // Todo: Should probably move to some sort of init plugin function function create_email_template_post_type() { $labels = array( 'name' => __('Dapper'), 'singular_name' => __('Dapper'), 'add_new' => __('Add New Template'), 'add_new_item' => __('Add New Email Template'), 'edit_item' => __('Edit Email Template'), 'new_item' => __('New Email Template'), 'view_item' => __('View Email Template'), 'view_items' => __('View Email Templates'), 'search_items' => __('Search Email Templates'), 'not_found' => __('No Email Templates found'), 'not_found_in_trash' => __('No Email Templates found in Trash'), 'parent_item_colon' => '', 'all_items' => __('All Email Templates'), 'archives' => __('Email Template Archives'), 'attributes' => __('Email Template Attributes'), 'insert_into_item' => __('Insert into email template'), 'uploaded_to_this_item' => __('Uploaded to this email template'), 'featured_image' => _x('Featured Image', 'email template'), 'set_featured_image' => _x('Set featured image', 'email template'), 'remove_featured_image' => _x('Remove featured image', 'email template'), 'use_featured_image' => _x('Use as featured image', 'email template'), 'menu_name' => __('Dapper'), 'filter_items_list' => __('Filter email templates list'), 'items_list_navigation' => __('Email Templates list navigation'), 'items_list' => __('Email Templates list'), 'item_published' => __('Email Template published'), 'item_published_privately' => __('Email Template published privately'), 'item_reverted_to_draft' => __('Email Template reverted to draft'), 'item_scheduled' => __('Email Template scheduled'), 'item_updated' => __('Email Template updated'), ); $args = array( 'labels' => $labels, 'public' => true, 'has_archive' => true, 'supports' => array('title', 'editor'), 'menu_icon' => 'dashicons-email-alt', ); register_post_type('email_template', $args); } add_action('init', 'create_email_template_post_type'); // Create Campaign post type // Todo: Should most likly be in some sort of init plugin function function create_campaign_post_type() { register_post_type('email_campaign', array( 'labels' => array( 'name' => __('Email Campaigns'), 'singular_name' => __('Email Campaign') ), 'public' => true, 'has_archive' => true, 'supports' => array('title', 'editor'), 'show_in_menu' => false, ) ); } add_action('init', 'create_campaign_post_type'); // Useful for sending out daily newsletters function get_latest_email_template_id() { // Query to get the latest email template post $args = array( 'post_type' => 'email_template', 'posts_per_page' => 1, 'order' => 'DESC', 'orderby' => 'date', ); $template_query = new WP_Query($args); if ($template_query->have_posts()) { $template_query->the_post(); return get_the_ID(); // Return the ID of the latest email template post } return 0; // Return 0 if no template is found } /* // Actual email sending function calls wp_mail() // Requires three arguments: // @param string $to; the email recipient // @param string $subject; what the email is about // @param string $message; the body of the message to be sent can be html with embedded php. // Requires a working email server in conjuntion with webserver */ function send_custom_email($to, $subject, $message) { //$headers = array('Content-Type: text/html; charset=UTF-8'); // Additional headers if needed $headers = get_option('dapper_headers', ''); // Send the email wp_mail($to, $subject, $message, $headers); } // Define WooCommerce order statuses $order_statuses = array( 'completed' => 'completed', 'shipped' => 'shipped', 'delivered' => 'delivered', 'pending_payment' => 'pending', 'failed' => 'failed', 'draft' => 'checkout-draft', 'cancelled' => 'cancelled', 'refunded' => 'refunded', 'processing' => 'processing', 'on_hold' => 'on-hold', 'partial_shipped' => 'partial-shipped' // Add more statuses as needed ); function get_possible_order_statuses() { $order_statuses = array( 'wc-pending' => _x( 'Pending payment', 'Order status', 'woocommerce' ), 'wc-processing' => _x( 'Processing', 'Order status', 'woocommerce' ), 'wc-on-hold' => _x( 'On hold', 'Order status', 'woocommerce' ), 'wc-completed' => _x( 'Completed', 'Order status', 'woocommerce' ), 'wc-cancelled' => _x( 'Cancelled', 'Order status', 'woocommerce' ), 'wc-refunded' => _x( 'Refunded', 'Order status', 'woocommerce' ), 'wc-failed' => _x( 'Failed', 'Order status', 'woocommerce' ), ); return $order_statuses; } /** * Conditionally load WooCommerce-dependent features. Modulized use. */ function dapper_woo_conditional_load() { // Only proceed if WooCommerce exists AND toggle is enabled if ( ! class_exists( 'WooCommerce' ) || get_option( 'dapper_enable_woo_integration', 'on' ) !== 'on' ) { return; } // Called by the WooCommerce order delivered webhook when an order is marked delivered // Useful for sending out review links // Todo: make review template and set this function up for that use. function send_custom_email_on_order_completion($order_id) { // Get the order object $order = wc_get_order($order_id); // Get the customer email from the order $to = $order->get_billing_email(); // Get the name of the email template controlled in settings $template_name = get_option('dapper_woo_status_delivered', ''); // Get the email template content $message = get_email_template_content_by_name($template_name); // Replace placeholders with actual data $message = str_replace('[CustomerName]', $order->get_billing_first_name(), $message); $message = str_replace('[OrderNumber]', $order->get_order_number(), $message); // Get product names, quantities, and order total $product_info = array(); $total_quantity = 0; foreach ( $order->get_items() as $item_id => $item ) { $product = $item->get_product(); $quantity = $item->get_quantity(); $total_quantity += $quantity; // Guard against deleted/missing products if ( $product && is_object( $product ) && method_exists( $product, 'get_name' ) ) { $product_name = $product->get_name(); $original_product_slug = $product->get_slug(); $product_info[] = "$product_name (Qty: $quantity)"; // Slug cleanup and review link only if product exists $product_slug = preg_replace( '/-(s|m|l|xl|2xl|3xl|4xl|5xl)$/i', '', $original_product_slug ); $review_link = get_option( 'dapper_review_link_structure', '' ); $review_link .= "/$product_slug/#reviews"; $product_review_link = $review_link; $message = str_replace( "[ReviewLink_0]", $product_review_link, $message ); } else { // Fallback for missing product — still count quantity, use generic name $product_info[] = "Unknown Product (Qty: $quantity)"; dapper_debug_log( "Missing/deleted product in order #$order_id, item $item_id" ); // Note: No review link added for this item } } // Get the numeric order total without HTML or currency symbols $order_total_numeric = get_order_total_numeric($order_id); // Replace [ProductName], [Quantity], [OrderTotal] with actual data $message = str_replace('[ProductName]', implode(', ', $product_info), $message); $message = str_replace('[Quantity]', $total_quantity, $message); $message = str_replace('[OrderTotal]', $order_total_numeric, $message); // Replace [ReviewLink_*] placeholders with actual review links in your email template $message = preg_replace('/\[ReviewLink_(\d+)\]/', '[ReviewLink_$1]', $message); // Add more replacements as needed // Check if the email and template content exist if ($to && $message) { $subject = 'Order Delivered'; // Send the email send_custom_email($to, $subject, $message); } } /* Called by WooCommerce order status changed webhook callback when an order is marked shipped // Useful for sending the tracking info to the customer requires // Advanced Shipment Tracking for WooCommerce plugin otherwise tracking is null */ function send_custom_email_on_order_shipped($order_id) { // Get the order object $order = wc_get_order($order_id); // Get the customer email from the order $to = $order->get_billing_email(); // Get the name of the email template controlled in settings $template_name = get_option('dapper_woo_status_shipped', ''); // Get the email template content $message = get_email_template_content_by_name($template_name); // Replace placeholders with actual data $message = str_replace('[CustomerName]', $order->get_billing_first_name(), $message); $message = str_replace('[OrderNumber]', $order->get_order_number(), $message); // Get product names, quantities, and order total $product_info = array(); $total_quantity = 0; foreach ( $order->get_items() as $item_id => $item ) { $product = $item->get_product(); $quantity = $item->get_quantity(); $total_quantity += $quantity; if ( $product && is_object( $product ) && method_exists( $product, 'get_name' ) ) { $product_name = $product->get_name(); $product_info[] = "$product_name (Qty: $quantity)"; } else { $product_info[] = "Unknown Product (Qty: $quantity)"; dapper_debug_log( "Missing/deleted product in order #$order_id, item $item_id" ); } } // Get the numeric order total without HTML or currency symbols $order_total_numeric = get_order_total_numeric($order_id); // Replace [ProductName], [Quantity], [OrderTotal] with actual data $message = str_replace('[ProductName]', implode(', ', $product_info), $message); $message = str_replace('[Quantity]', $total_quantity, $message); $message = str_replace('[OrderTotal]', $order_total_numeric, $message); $order_notes = $order->get_customer_order_notes(); // Check if function exist if ( function_exists( 'ast_get_tracking_items' ) ) { $tracking_items = ast_get_tracking_items($order_id); foreach($tracking_items as $tracking_item){ $tracking_number = $tracking_item['tracking_number']; $tracking_provider = $tracking_item['formatted_tracking_provider']; $tracking_link = $tracking_item['formatted_tracking_link']; $date_shipped = date_i18n( get_option( 'date_format' ), $tracking_item['date_shipped'] ); if (!$tracking_number) { dapper_debug_log('Tracking number is blank/null/empty'); } if ($tracking_number) { dapper_debug_log('Tracking number is:' . $tracking_number); $message = str_replace('[TrackingNumber]', $tracking_number, $message); $message = str_replace('[TrackingLink]', $tracking_link, $message); } } } else { $message = str_replace('[TrackingNumber]', '', $message); $message = str_replace('[TrackingLink]', '', $message); } // Check if the email and template content exist if ($to && $message) { $subject = 'Order Shipped'; // Send the email send_custom_email($to, $subject, $message); } } ; /* // WooCommerce Order Status Changed Webhook Callback function // Handles the switch betweent different order statuses // Has three required arguments // @param $order_id integer the passed in Order // @param $old_status string the passed in old order status // @param $new_status string the passed in new order status */ function handle_woocommerce_order_status_changed($order_id, $old_status, $new_status) { global $order_statuses; // Debugging statements dapper_debug_log('New Status: ' . $new_status); dapper_debug_log('Order Statuses: ' . print_r($order_statuses, true)); dapper_debug_log( "Order status changed: Order #$order_id from '$old_status' to '$new_status'" ); // Check if the new status is in our defined statuses if (in_array($new_status, $order_statuses)) { // Perform actions based on the order status switch ($new_status) { case $order_statuses['delivered']: send_custom_email_on_order_completion($order_id); dapper_debug_log('Case 1: Delivered'); break; case $order_statuses['completed']: send_custom_email_on_order_shipped($order_id); dapper_debug_log('Case 2: Shipped(which is completed status actually)'); break; case $order_statuses['pending_payment']: dapper_debug_log('Case 3: Pending Payment'); break; case $order_statuses['processing']: dapper_debug_log('Case 4: Processing'); break; case $order_statuses['on_hold']: dapper_debug_log('Case 5: On Hold'); break; case $order_statuses['partial_shipped']: dapper_debug_log('Case 6: Partially Shipped'); break; case $order_statuses['cancelled']: dapper_debug_log('Case 7: Cancelled'); break; case $order_statuses['refunded']: dapper_debug_log('Case 8: Refunded'); break; case $order_statuses['failed']: dapper_debug_log('Case 9: Failed'); break; case $order_statuses['draft']: dapper_debug_log('Case 10: Draft'); break; default: dapper_debug_log('Unknown Status: ' . $new_status); // Add more cases for other statuses if needed } } } // Hook the status changed action add_action( 'woocommerce_order_status_changed', 'handle_woocommerce_order_status_changed', 10, 3 ); // Honeypot – only load if Woo + toggle on add_action( 'woocommerce_register_form_start', 'advanced_add_honeypots' ); add_filter( 'woocommerce_registration_errors', 'advanced_validate_registration', 10, 3 ); // Define the honeypot functions inside too (or keep them global if you prefer) function advanced_add_honeypots() { $random_name = 'hp_' . substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'), 0, 8); // Random field name ?>
$value) { if (strpos($key, 'hp_') === 0 && !empty($value)) { $errors->add('honeypot_error', __('Spam detected. Registration blocked.', 'woocommerce')); return $errors; } } // Existing timestamp check... if (isset($_POST['form_timestamp']) && (time() - intval($_POST['form_timestamp']) < 5)) { $errors->add('timestamp_error', __('Submission too fast. Humans only.', 'woocommerce')); } // === NEW ENHANCED EMAIL PATTERN BLOCK === // Target common bot patterns: pure alphanumeric usernames 8+ chars, no dots/hyphens/underscores/numbers mixed naturally $email_lower = strtolower($email); $local_part = substr($email, 0, strpos($email, '@')); // List of domains the bots are currently using $suspicious_domains = array('hotmail.com', 'outlook.com', 'protonmail.com', 'gmail.com', 'yahoo.com'); // Add more if they switch $domain = substr(strrchr($email_lower, "@"), 1); if (in_array($domain, $suspicious_domains)) { // Pattern: only letters/numbers, 8+ characters, NO dots, hyphens, underscores, or other separators if (preg_match('/^[a-zA-Z0-9]{8,}$/', $local_part) && !str_contains($local_part, '.') && !str_contains($local_part, '-') && !str_contains($local_part, '_')) { $errors->add('email_pattern_error', __('Invalid email address. Please use a real, personal email.', 'woocommerce')); return $errors; // Early exit once blocked } } // === END NEW BLOCK === return $errors; } /** * Returns true if the current posted payment method is any known PayPal variant * * */ function dapper_is_paypal_payment_method() { if (!isset($_POST['payment_method'])) { return false; } $method = $_POST['payment_method']; $paypal_methods = [ 'paypal', 'ppec_paypal', // older PayPal Express Checkout 'ppcp-gateway', // PayPal Payments (most common now) 'paypal_express', 'braintree_paypal', // if using Braintree integration // add more here if you ever see other variants in logs ]; return in_array($method, $paypal_methods, true); } // =================== // CHECKOUT HONEYPOT + ANTI-BOT LAYERS // =================== // Add hidden fields to checkout form add_action('woocommerce_checkout_before_customer_details', 'dapper_checkout_honeypot_fields'); function dapper_checkout_honeypot_fields() { // Hidden honeypot field — bots tend to fill all inputs $honeypot_name = 'dapper_hp_' . wp_generate_password(8, false); // random per load ?> $value) { if (strpos($key, 'dapper_hp_') === 0 && !empty(trim($value))) { $errors->add('dapper_honeypot_error', __('Sorry, we detected automated behavior. Please try again.', 'dapper')); return; } } // JS check: add tiny JS that sets the field (bots often ignore add('human_check_error', __('Please check the "I\'m not a robot" box to continue with PayPal.', 'dapper')); return; } if (time() - $time > 300) { $errors->add('human_check_error', __('Verification timed out. Please refresh and try again.', 'dapper')); return; } if (strpos($token, 'human_') !== 0 || strlen($token) < 20) { $errors->add('human_check_error', __('Invalid verification. Please try again.', 'dapper')); } } // 1. Checkbox on SINGLE PRODUCT pages (near PayPal button) add_action('woocommerce_paypal_payments_single_product_button', 'dapper_human_checkbox_product', 5); // Product page checkbox - use core Woo hook + JS to force show near Add to Cart / PayPal area add_action('woocommerce_after_add_to_cart_button', 'dapper_human_checkbox_product', 999); // High priority = late output function dapper_human_checkbox_product() { dapper_debug_log('Universal product checkbox output attempt - Product ID: ' . get_the_ID()); if (get_option('dapper_enable_paypal_human_check', 'on') !== 'on') { return; } global $product; if (!$product || !$product->is_purchasable()) return; ?>
This prevents automated spam. Takes 2 seconds.
This quick check helps stop automated spam. Takes 2 seconds.
| Status | Action | |
|---|---|---|
| ' . esc_html($user->email) . ' | '; echo '' . esc_html($user->subscription_status) . ' | '; echo ''; echo ''; echo ' | '; echo '
Backup created successfully! Refresh to see new files below.
Backup failed — check debug log. Try unchecking some folders if memory is low.