1958 lines
80 KiB
PHP
1958 lines
80 KiB
PHP
<?php
|
||
/*
|
||
Plugin Name: Dapper
|
||
Description: Lightweight WooCommerce utility for automated order emails, checkout/registration anti-spam (honeypot + JS + speed checks), custom email templates & campaigns, mailing list management, and custom WP-admin login logo branding. Future: robust automated site backups & restores. Ideal for managed hosting and reliable WooCommerce stores — no third-party dependencies.
|
||
Version: 1.1.1
|
||
Author: William Thompson
|
||
Plugin URI: https://theblueduckpond.com
|
||
Author URI: https://eastofthehaw.com
|
||
Text Domain: dapper
|
||
Domain Path: /languages
|
||
* Changelog: See readme.txt (short) and CHANGELOG.md (full)
|
||
*/
|
||
|
||
// Exit if accesses directly
|
||
if ( ! defined('ABSPATH')) {
|
||
exit;
|
||
}
|
||
// Plugin constants
|
||
define( 'DAPPER_VERSION', '1.1.1' );
|
||
define( 'DAPPER_PATH', plugin_dir_path( __FILE__ ) );
|
||
define( 'DAPPER_URL', plugin_dir_url( __FILE__ ) );
|
||
define( 'DAPPER_BASENAME', plugin_basename( __FILE__ ) );
|
||
|
||
// Include statements
|
||
include "dapper_backup.php";
|
||
|
||
/*
|
||
// Runs when the activation hook gets called
|
||
// when the plugin first get activated
|
||
*/
|
||
function dapper_activation() {
|
||
if (!get_option('dapper_setup_complete')) {
|
||
// Running setup tasks
|
||
create_mailing_list_table();// function to create the dapper_mailing_list table
|
||
|
||
$existing_users = get_users();// Get the user table array
|
||
// Loop through the existing users and add them to the mailing list
|
||
foreach ($existing_users as $user) {
|
||
update_mailing_list($user->ID, $user->user_email, 'subscribed');
|
||
}
|
||
// Depreciated in v1.1.1 to be removed in later versions
|
||
// Creates the 'images' folder when plugin gets activated
|
||
//make_images_dir();
|
||
make_backup_dir();
|
||
// Mark setup completed
|
||
update_option('dapper_setup_complete', true);
|
||
}
|
||
}
|
||
register_activation_hook(__FILE__, 'dapper_activation');
|
||
|
||
/**
|
||
* Get build identifier from generated build-info.php (CI auto-generated)
|
||
* Fallbacks: local git (dev only), then 'dev-local'
|
||
*
|
||
* @return string Short commit hash or descriptive fallback
|
||
*/
|
||
function dapper_get_build_identifier() {
|
||
static $build = null;
|
||
if ( $build !== null ) {
|
||
return $build;
|
||
}
|
||
|
||
$info_file = DAPPER_PATH . 'dapper/build-info.php';
|
||
|
||
if ( file_exists( $info_file ) && is_readable( $info_file ) ) {
|
||
$info = include $info_file;
|
||
if ( is_array( $info ) && ! empty( $info['commit'] ) && preg_match( '/^[0-9a-f]{7,40}$/', $info['commit'] ) ) {
|
||
$build = substr( $info['commit'], 0, 8 ); // enforce short
|
||
|
||
if ( ! empty( $info['branch'] ) && $info['branch'] !== 'main' && $info['branch'] !== 'master' ) {
|
||
$build .= ' (' . $info['branch'] . ')';
|
||
}
|
||
|
||
if ( ! empty( $info['built'] ) ) {
|
||
$build .= ' – built ' . $info['built'];
|
||
}
|
||
|
||
return $build;
|
||
}
|
||
}
|
||
|
||
// Optional: local dev fallback if you're working without CI yet
|
||
$git_dir = DAPPER_PATH . '.git';
|
||
if ( is_dir( $git_dir ) ) {
|
||
// Your original git-reading logic here as secondary fallback...
|
||
// (keep it if you want, but comment out in production deploys)
|
||
}
|
||
|
||
$build = 'dev-local';
|
||
return $build;
|
||
}
|
||
|
||
define( 'DAPPER_BUILD', dapper_get_build_identifier() );
|
||
|
||
/*
|
||
// 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
|
||
?>
|
||
<input type="hidden" name="form_timestamp" value="<?php echo time(); ?>">
|
||
<p style="position:absolute; left:-9999px; top:-9999px;">
|
||
<input type="text" name="<?php echo esc_attr($random_name); ?>" value="" autocomplete="off" tabindex="-1">
|
||
</p>
|
||
<?php
|
||
}
|
||
|
||
function advanced_validate_registration($errors, $username, $email) {
|
||
// Existing honeypot check...
|
||
foreach ($_POST as $key => $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
|
||
?>
|
||
<div style="position:absolute; left:-9999px; top:-9999px; height:0; overflow:hidden; opacity:0;">
|
||
<input type="text" name="<?php echo esc_attr($honeypot_name); ?>" value="" autocomplete="off" tabindex="-1" />
|
||
<!-- Second trap: field that should remain empty unless JS runs -->
|
||
<input type="hidden" name="dapper_js_check" value="" />
|
||
</div>
|
||
|
||
<!-- Timestamp to block very fast submissions -->
|
||
<input type="hidden" name="dapper_form_time" value="<?php echo time(); ?>" />
|
||
<?php
|
||
}
|
||
|
||
// Validate on checkout
|
||
add_action('woocommerce_after_checkout_validation', 'dapper_validate_checkout_anti_spam', 10, 2);
|
||
|
||
function dapper_validate_checkout_anti_spam($data, $errors) {
|
||
// Honeypot trap: if any field starting with dapper_hp_ has a value → bot
|
||
foreach ($_POST as $key => $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 <script>)
|
||
// You can enqueue a tiny JS file or inline it
|
||
// For now, if dapper_js_check is still empty → likely no JS → possible bot
|
||
if (empty($_POST['dapper_js_check'])) {
|
||
// Optional: make this warning only, or block if spam is very high
|
||
// $errors->add('dapper_js_error', __('JavaScript required for checkout. Please enable it.', 'dapper'));
|
||
// For now just log — tighten later if needed
|
||
dapper_debug_log("Checkout submission without JS check - possible bot - Order data: " . print_r($data, true));
|
||
}
|
||
|
||
// Timestamp: block submissions < 6 seconds (bots are fast)
|
||
if (isset($_POST['dapper_form_time'])) {
|
||
$time_diff = time() - intval($_POST['dapper_form_time']);
|
||
if ($time_diff < 6) {
|
||
$errors->add('dapper_speed_error', __('Submission too fast — please try again.', 'dapper'));
|
||
}
|
||
}
|
||
}
|
||
add_action('wp_footer', 'dapper_checkout_js_check', 100);
|
||
function dapper_checkout_js_check() {
|
||
if (is_checkout()) {
|
||
?>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
var jsField = document.querySelector('input[name="dapper_js_check"]');
|
||
if (jsField) jsField.value = 'human_' + Math.random().toString(36).substring(2);
|
||
});
|
||
</script>
|
||
<?php
|
||
}
|
||
}
|
||
// Add custom human verification before PayPal submission
|
||
add_action('woocommerce_checkout_before_customer_details', 'dapper_add_custom_human_check');
|
||
|
||
function dapper_add_custom_human_check() {
|
||
if (get_option('dapper_enable_paypal_human_check', 'on') !== 'on') {
|
||
return;
|
||
}
|
||
if ( ! dapper_is_paypal_payment_method() && ! is_checkout() ) {
|
||
return;
|
||
}
|
||
|
||
?>
|
||
<div id="dapper-human-check" style="margin:20px 0; padding:15px; background:#f8f9fa; border:1px solid #ddd; border-radius:4px; display:none;">
|
||
<strong>Verify you're human to use PayPal</strong><br>
|
||
<label><input type="checkbox" id="dapper_human_checkbox"> I'm not a robot</label>
|
||
<input type="hidden" name="dapper_human_token" id="dapper_human_token" value="">
|
||
<input type="hidden" name="dapper_human_time" id="dapper_human_time" value="<?php echo time(); ?>">
|
||
<p class="description" style="color:#555; margin-top:8px;">This helps protect against automated spam. Takes 2 seconds.</p>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const paypalMethods = ['paypal', 'ppec_paypal', 'ppcp-gateway', 'paypal_express', 'braintree_paypal'];
|
||
const checkDiv = document.getElementById('dapper-human-check');
|
||
|
||
// Function to show/hide based on current selected method
|
||
function updateHumanCheckVisibility() {
|
||
const selectedRadio = document.querySelector('input[name="payment_method"]:checked');
|
||
if (selectedRadio && paypalMethods.includes(selectedRadio.value)) {
|
||
checkDiv.style.display = 'block';
|
||
} else {
|
||
checkDiv.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// Run immediately on load
|
||
updateHumanCheckVisibility();
|
||
|
||
// Listen for changes on payment radios
|
||
document.querySelectorAll('input[name="payment_method"]').forEach(radio => {
|
||
radio.addEventListener('change', updateHumanCheckVisibility);
|
||
});
|
||
|
||
// Watch for dynamic payment section updates (WooCommerce often reloads parts via AJAX)
|
||
const paymentBox = document.querySelector('#payment') || document.body;
|
||
const observer = new MutationObserver(updateHumanCheckVisibility);
|
||
observer.observe(paymentBox, { childList: true, subtree: true });
|
||
|
||
// Token generation
|
||
const checkbox = document.getElementById('dapper_human_checkbox');
|
||
if (checkbox) {
|
||
checkbox.addEventListener('change', function() {
|
||
const tokenField = document.getElementById('dapper_human_token');
|
||
tokenField.value = this.checked
|
||
? 'human_' + Math.random().toString(36).substring(2) + '_' + Date.now()
|
||
: '';
|
||
});
|
||
}
|
||
});
|
||
</script>
|
||
<?php
|
||
}
|
||
|
||
// Validate on checkout
|
||
add_action('woocommerce_after_checkout_validation', 'dapper_validate_custom_human_check', 15, 2);
|
||
|
||
function dapper_validate_custom_human_check($data, $errors) {
|
||
if (!dapper_is_paypal_payment_method()) {
|
||
return;
|
||
}
|
||
|
||
$token = trim( $_POST['dapper_human_token'] ?? '' );
|
||
$time = (int) ($_POST['dapper_human_time'] ?? 0);
|
||
|
||
if (empty($token)) {
|
||
$errors->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;
|
||
|
||
?>
|
||
<div id="dapper-human-check-product" style="margin:20px 0; padding:15px; background:#fff3cd; border:1px solid #ffeeba; border-radius:4px; font-weight:bold;">
|
||
<strong>Human Verification Required</strong><br>
|
||
<label style="display:block; margin:12px 0; font-size:1.1em;">
|
||
<input type="checkbox" id="dapper_human_checkbox_product" required style="transform:scale(1.6); margin-right:12px; vertical-align:middle;">
|
||
Confirm I'm not a robot
|
||
</label>
|
||
<p style="color:#856404; font-size:0.95em;">This prevents automated spam.</p>
|
||
</div>
|
||
|
||
<script>
|
||
(function() {
|
||
console.log('Universal Dapper product protection initialized');
|
||
|
||
const CHECKBOX_ID = 'dapper_human_checkbox_product';
|
||
const CHECKBOX_CONTAINER_ID = 'dapper-human-check-product';
|
||
|
||
let observer = null;
|
||
|
||
function disablePayPalButtons() {
|
||
// Target common PayPal Smart Button patterns universally
|
||
document.querySelectorAll(
|
||
'[id*="paypal"], [class*="paypal"], [data-paypal], [data-pp], ' +
|
||
'iframe[src*="paypal.com"], div[role="button"][aria-label*="PayPal"], ' +
|
||
'button[aria-label*="Pay with PayPal"], .pp-button, .smart-button'
|
||
).forEach(el => {
|
||
// Strong disable: remove pointer events + overlay
|
||
el.style.pointerEvents = 'none';
|
||
el.style.userSelect = 'none';
|
||
el.style.opacity = '0.45';
|
||
el.style.cursor = 'not-allowed';
|
||
el.style.position = 'relative';
|
||
|
||
// Add overlay if missing
|
||
if (!el.querySelector('.dapper-universal-overlay')) {
|
||
const overlay = document.createElement('div');
|
||
overlay.className = 'dapper-universal-overlay';
|
||
overlay.style.position = 'absolute';
|
||
overlay.style.inset = '0';
|
||
overlay.style.background = 'rgba(0,0,0,0.3)';
|
||
overlay.style.display = 'flex';
|
||
overlay.style.alignItems = 'center';
|
||
overlay.style.justifyContent = 'center';
|
||
overlay.style.color = '#fff';
|
||
overlay.style.fontWeight = 'bold';
|
||
overlay.style.fontSize = '1.3em';
|
||
overlay.style.textAlign = 'center';
|
||
overlay.style.padding = '15px';
|
||
overlay.style.zIndex = '10';
|
||
overlay.innerText = 'Check verification box above to enable PayPal';
|
||
el.appendChild(overlay);
|
||
}
|
||
});
|
||
}
|
||
|
||
function enablePayPalButtons() {
|
||
document.querySelectorAll('.dapper-universal-overlay').forEach(overlay => {
|
||
overlay.remove();
|
||
});
|
||
// Re-enable (PayPal may override, but overlay removal helps)
|
||
document.querySelectorAll(
|
||
'[id*="paypal"], [class*="paypal"], [data-paypal], [data-pp], ' +
|
||
'iframe[src*="paypal.com"], div[role="button"][aria-label*="PayPal"]'
|
||
).forEach(el => {
|
||
el.style.pointerEvents = 'auto';
|
||
el.style.opacity = '1';
|
||
el.style.cursor = 'pointer';
|
||
});
|
||
}
|
||
|
||
function checkAndProtect() {
|
||
const checkbox = document.getElementById(CHECKBOX_ID);
|
||
if (!checkbox) return;
|
||
|
||
const container = document.getElementById(CHECKBOX_CONTAINER_ID);
|
||
if (container) container.style.display = 'block';
|
||
|
||
// Disable until checked
|
||
if (!checkbox.checked) {
|
||
disablePayPalButtons();
|
||
}
|
||
|
||
checkbox.addEventListener('change', () => {
|
||
if (checkbox.checked) {
|
||
enablePayPalButtons();
|
||
console.log('Dapper: Checkbox checked → PayPal unlocked');
|
||
} else {
|
||
disablePayPalButtons();
|
||
console.log('Dapper: Checkbox unchecked → PayPal locked');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Run immediately + watch for PayPal injection
|
||
checkAndProtect();
|
||
|
||
observer = new MutationObserver((mutations) => {
|
||
if (document.querySelector('[id*="paypal"], [class*="paypal"], iframe[src*="paypal.com"]')) {
|
||
checkAndProtect();
|
||
}
|
||
});
|
||
|
||
observer.observe(document.body, { childList: true, subtree: true });
|
||
|
||
// Retry safety net (Flatsome/PayPal can be very late)
|
||
setTimeout(checkAndProtect, 1500);
|
||
setTimeout(checkAndProtect, 4000);
|
||
setTimeout(checkAndProtect, 8000);
|
||
})();
|
||
</script>
|
||
<?php
|
||
}
|
||
|
||
// 2. Checkbox on CART page (near PayPal button)
|
||
add_action('woocommerce_proceed_to_checkout', 'dapper_human_checkbox_cart', 15);
|
||
|
||
function dapper_human_checkbox_cart() {
|
||
dapper_debug_log('Attempting cart checkbox output');
|
||
|
||
if (get_option('dapper_enable_paypal_human_check', 'on') !== 'on') {
|
||
return;
|
||
}
|
||
|
||
?>
|
||
<div id="dapper-human-check-cart" style="margin:20px 0; padding:15px; background:#fff3cd; border:1px solid #ffeeba; border-radius:4px; font-weight:bold;">
|
||
<strong>Human Verification Required for PayPal Express</strong><br>
|
||
<label style="display:block; margin:12px 0; font-size:1.1em;">
|
||
<input type="checkbox" id="dapper_human_checkbox_cart" required style="transform:scale(1.6); margin-right:12px; vertical-align:middle;">
|
||
Confirm I'm not a robot to unlock PayPal
|
||
</label>
|
||
<p style="color:#856404; font-size:0.95em;">This quick check helps stop automated spam. Takes 2 seconds.</p>
|
||
</div>
|
||
|
||
<script>
|
||
(function() {
|
||
console.log('Universal Dapper cart protection initialized');
|
||
|
||
const CHECKBOX_ID = 'dapper_human_checkbox_cart';
|
||
const CONTAINER_ID = 'dapper-human-check-cart';
|
||
|
||
function initCartCheckbox() {
|
||
const checkbox = document.getElementById(CHECKBOX_ID);
|
||
const container = document.getElementById(CONTAINER_ID);
|
||
|
||
if (!checkbox || !container) {
|
||
console.log('Dapper cart: Checkbox or container missing');
|
||
return false;
|
||
}
|
||
|
||
// Universal PayPal selectors (same as product page)
|
||
const paypalContainers = document.querySelectorAll(
|
||
'[id*="paypal"], [class*="paypal"], [data-paypal], [data-pp], ' +
|
||
'iframe[src*="paypal.com"], div[role="button"][aria-label*="PayPal"], ' +
|
||
'button[aria-label*="Pay with PayPal"], .pp-button, .smart-button, ' +
|
||
'.woocommerce-proceed-to-checkout .paypal-smart-button, ' +
|
||
'.wc-proceed-to-checkout .paypal-button-container'
|
||
);
|
||
|
||
if (paypalContainers.length === 0) {
|
||
console.log('Dapper cart: No PayPal containers found yet - retrying');
|
||
return false;
|
||
}
|
||
|
||
console.log('Dapper cart: Found ' + paypalContainers.length + ' PayPal container(s)');
|
||
|
||
// Force checkbox visible
|
||
container.style.display = 'block';
|
||
|
||
function disablePayPal() {
|
||
paypalContainers.forEach(container => {
|
||
container.style.pointerEvents = 'none';
|
||
container.style.opacity = '0.45';
|
||
container.style.cursor = 'not-allowed';
|
||
container.style.position = 'relative';
|
||
|
||
if (!container.querySelector('.dapper-universal-overlay')) {
|
||
const overlay = document.createElement('div');
|
||
overlay.className = 'dapper-universal-overlay';
|
||
overlay.style.position = 'absolute';
|
||
overlay.style.inset = '0';
|
||
overlay.style.background = 'rgba(0,0,0,0.3)';
|
||
overlay.style.display = 'flex';
|
||
overlay.style.alignItems = 'center';
|
||
overlay.style.justifyContent = 'center';
|
||
overlay.style.color = '#fff';
|
||
overlay.style.fontWeight = 'bold';
|
||
overlay.style.fontSize = '1.3em';
|
||
overlay.style.textAlign = 'center';
|
||
overlay.style.padding = '15px';
|
||
overlay.style.zIndex = '10';
|
||
overlay.innerText = 'Check verification box above to enable PayPal';
|
||
container.appendChild(overlay);
|
||
}
|
||
});
|
||
}
|
||
|
||
function enablePayPal() {
|
||
document.querySelectorAll('.dapper-universal-overlay').forEach(overlay => overlay.remove());
|
||
paypalContainers.forEach(container => {
|
||
container.style.pointerEvents = 'auto';
|
||
container.style.opacity = '1';
|
||
container.style.cursor = 'pointer';
|
||
});
|
||
}
|
||
|
||
// Start disabled
|
||
disablePayPal();
|
||
|
||
checkbox.addEventListener('change', () => {
|
||
if (checkbox.checked) {
|
||
enablePayPal();
|
||
console.log('Dapper cart: Checkbox checked → PayPal unlocked');
|
||
} else {
|
||
disablePayPal();
|
||
console.log('Dapper cart: Checkbox unchecked → PayPal locked');
|
||
}
|
||
});
|
||
|
||
return true;
|
||
}
|
||
|
||
// Run now + retry for dynamic load
|
||
let attempts = 0;
|
||
const maxAttempts = 20;
|
||
const interval = setInterval(() => {
|
||
if (initCartCheckbox() || attempts >= maxAttempts) {
|
||
clearInterval(interval);
|
||
}
|
||
attempts++;
|
||
}, 500);
|
||
|
||
initCartCheckbox();
|
||
})();
|
||
</script>
|
||
<?php
|
||
}
|
||
|
||
// 3. Server-side block: cancel any PayPal order missing valid token or showing bot patterns
|
||
add_action('woocommerce_new_order', 'dapper_block_fake_paypal_orders', 10, 1);
|
||
|
||
function dapper_block_fake_paypal_orders($order_id) {
|
||
$order = wc_get_order($order_id);
|
||
if (!$order) return;
|
||
|
||
$method = $order->get_payment_method();
|
||
if (!in_array($method, ['ppcp-gateway', 'paypal', 'ppec_paypal', 'paypal_express'], true)) {
|
||
return;
|
||
}
|
||
|
||
// Check token from product or cart (whichever was used)
|
||
$token = trim($_POST['dapper_human_token_product'] ?? $_POST['dapper_human_token_cart'] ?? '');
|
||
$time = (int) ($_POST['dapper_human_time_product'] ?? $_POST['dapper_human_time_cart'] ?? 0);
|
||
|
||
$token_ok = !empty($token) &&
|
||
strpos($token, 'human_') === 0 &&
|
||
strlen($token) >= 20 &&
|
||
(time() - $time <= 600); // 10 min window
|
||
|
||
// If token is missing/invalid → check other bot signs
|
||
if (!$token_ok) {
|
||
$first = strtolower(trim($order->get_billing_first_name() ?: ''));
|
||
$last = strtolower(trim($order->get_billing_last_name() ?: ''));
|
||
$company = strtolower(trim($order->get_billing_company() ?: ''));
|
||
|
||
$lowercase_name = ($first === $order->get_billing_first_name() && $last === $order->get_billing_last_name());
|
||
$suspicious_company = in_array($company, ['pogab', 'rayazifi', 'lunom', 'topap', 'potanginamo', '']) ||
|
||
preg_match('/^[a-z]{5,10}$/', $company);
|
||
|
||
if ($lowercase_name || $suspicious_company || (float)$order->get_total_tax() === 0) {
|
||
$order->update_status('cancelled', 'Blocked: Invalid or missing human verification for PayPal Express (bot pattern detected).');
|
||
dapper_debug_log("Blocked fake PayPal order #$order_id - No valid token | Name: $first $last | Company: $company");
|
||
}
|
||
}
|
||
}
|
||
|
||
// Trashes obvious Blocked paypal orders
|
||
|
||
add_action('woocommerce_order_status_cancelled', 'dapper_trash_obvious_bot_paypal_orders', 20, 1);
|
||
|
||
function dapper_trash_obvious_bot_paypal_orders($order_id) {
|
||
if (!class_exists('WooCommerce')) return;
|
||
|
||
$order = wc_get_order($order_id);
|
||
if (!$order) return;
|
||
|
||
// Only act on PayPal orders
|
||
$method = $order->get_payment_method();
|
||
if (!in_array($method, ['paypal', 'ppec_paypal', 'ppcp-gateway', 'paypal_express', 'braintree_paypal'], true)) {
|
||
return;
|
||
}
|
||
|
||
// Check if this cancellation came from our bot block function
|
||
$notes = $order->get_customer_order_notes();
|
||
$latest_note = !empty($notes) ? end($notes)->comment_content : '';
|
||
|
||
$bot_patterns = [
|
||
'Blocked: Invalid or missing human verification',
|
||
'bot pattern detected',
|
||
];
|
||
|
||
$is_bot = false;
|
||
foreach ($bot_patterns as $pattern) {
|
||
if (stripos($latest_note, $pattern) !== false) {
|
||
$is_bot = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Extra safety: very young cancelled orders with 0 tax and suspicious name/company
|
||
if (!$is_bot) {
|
||
$age_minutes = (time() - strtotime($order->get_date_created())) / 60;
|
||
$first = strtolower(trim($order->get_billing_first_name() ?: ''));
|
||
$last = strtolower(trim($order->get_billing_last_name() ?: ''));
|
||
$company = strtolower(trim($order->get_billing_company() ?: ''));
|
||
|
||
if ($age_minutes < 15 &&
|
||
(float)$order->get_total_tax() === 0 &&
|
||
($first === $last || preg_match('/^[a-z]{5,12}$/', $company))) {
|
||
$is_bot = true;
|
||
}
|
||
}
|
||
|
||
if ($is_bot) {
|
||
wp_trash_post($order_id);
|
||
$order->add_order_note('Moved to trash by Dapper — obvious PayPal bot order');
|
||
dapper_debug_log("Trashed obvious bot PayPal order #$order_id");
|
||
}
|
||
}
|
||
|
||
|
||
|
||
}
|
||
add_action( 'plugins_loaded', 'dapper_woo_conditional_load' ); // Late hook – safe for class_exists()
|
||
|
||
// ────────────────────────────────────────────────
|
||
// Contact Form 7 protection (always loaded if CF7 exists)
|
||
// ────────────────────────────────────────────────
|
||
|
||
if (class_exists('WPCF7')) {
|
||
|
||
// Option to enable/disable CF7 protection separately
|
||
// You can add this to settings later if you want
|
||
|
||
add_action('wpcf7_before_send_mail', 'dapper_cf7_human_check', 9, 3);
|
||
|
||
function dapper_cf7_human_check($contact_form, &$abort, $submission) {
|
||
// Skip if disabled via future option
|
||
// if (get_option('dapper_enable_cf7_human_check', 'on') !== 'on') return;
|
||
|
||
$form_id = $contact_form->id();
|
||
|
||
// Very fast submission (< 5 seconds) → almost always bot
|
||
$posted_time = isset($_POST['dapper_cf7_time']) ? (int)$_POST['dapper_cf7_time'] : 0;
|
||
if ($posted_time && (time() - $posted_time < 5)) {
|
||
$abort = true;
|
||
$submission->add_error('dapper_speed', 'Submission too fast — please try again.');
|
||
dapper_debug_log("CF7 #$form_id blocked — too fast");
|
||
return;
|
||
}
|
||
|
||
// Honeypot field (random name)
|
||
foreach ($_POST as $key => $val) {
|
||
if (strpos($key, 'dapper_cf7_hp_') === 0 && strlen(trim($val)) > 0) {
|
||
$abort = true;
|
||
$submission->add_error('dapper_honeypot', 'Spam detected.');
|
||
dapper_debug_log("CF7 #$form_id blocked — honeypot filled");
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Optional: require JS-set token (stronger)
|
||
$token = trim($_POST['dapper_cf7_token'] ?? '');
|
||
if (empty($token) || strpos($token, 'cf7human_') !== 0) {
|
||
$abort = true;
|
||
$submission->add_error('dapper_js', 'Please enable JavaScript and try again.');
|
||
dapper_debug_log("CF7 #$form_id blocked — missing/invalid JS token");
|
||
}
|
||
}
|
||
|
||
// Add fields + JS to every CF7 form
|
||
add_filter('wpcf7_form_elements', 'dapper_cf7_inject_anti_spam_fields');
|
||
|
||
function dapper_cf7_inject_anti_spam_fields($form) {
|
||
$honeypot_name = 'dapper_cf7_hp_' . wp_generate_password(7, false);
|
||
|
||
$hidden_fields = '
|
||
<input type="hidden" name="dapper_cf7_time" value="' . time() . '" />
|
||
<input type="hidden" name="dapper_cf7_token" id="dapper_cf7_token" value="" />
|
||
<p style="position:absolute; left:-9999px; height:1px; overflow:hidden;">
|
||
<input type="text" name="' . esc_attr($honeypot_name) . '" value="" autocomplete="off" tabindex="-1" />
|
||
</p>';
|
||
|
||
// Insert just before </form>
|
||
$form = str_replace('</form>', $hidden_fields . '</form>', $form);
|
||
|
||
return $form;
|
||
}
|
||
|
||
// Tiny JS — add to footer when CF7 shortcode exists on page
|
||
add_action('wp_footer', 'dapper_cf7_anti_spam_js', 90);
|
||
|
||
function dapper_cf7_anti_spam_js() {
|
||
if (!function_exists('wpcf7') || !did_action('wp_footer')) return;
|
||
?>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.querySelectorAll('.wpcf7 form').forEach(form => {
|
||
const tokenField = form.querySelector('#dapper_cf7_token');
|
||
if (tokenField) {
|
||
tokenField.value = 'cf7human_' + Math.random().toString(36).substring(2) + '_' + Date.now();
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
<?php
|
||
}
|
||
|
||
add_filter( 'wpcf7_form_elements', 'dapper_cf7_inject_human_checkbox', 20 );
|
||
|
||
function dapper_cf7_inject_human_checkbox( $form ) {
|
||
if ( get_option( 'dapper_enable_cf7_human_checkbox', 'on' ) !== 'on' ) {
|
||
return $form;
|
||
}
|
||
|
||
$checkbox_html = '
|
||
<div class="dapper-cf7-human-check" style="margin: 1.5em 0; padding: 1em; background: #f8f9fa; border: 1px solid #ccd0d4; border-radius: 4px; text-align: center;">
|
||
<label style="font-size: 1.1em; cursor: pointer; user-select: none;">
|
||
<input type="checkbox" name="dapper_cf7_human_confirm" id="dapper_cf7_human_confirm" value="1" required style="transform: scale(1.4); margin-right: 0.8em; vertical-align: middle;">
|
||
I am human / not a robot
|
||
</label>
|
||
<input type="hidden" name="dapper_cf7_human_token" id="dapper_cf7_human_token" value="">
|
||
<input type="hidden" name="dapper_cf7_human_time" value="' . time() . '">
|
||
<p style="margin: 0.6em 0 0; font-size: 0.9em; color: #555;">Quick check to help stop spam. Thanks!</p>
|
||
</div>';
|
||
|
||
// Insert just before the submit button / </form>
|
||
$form = preg_replace( '/(<button[^>]*type=["\']submit["\'][^>]*>.*?<\/button>)/is', $checkbox_html . '$1', $form );
|
||
|
||
// Fallback: if no <button type="submit"> found, put before </form>
|
||
if ( strpos( $form, $checkbox_html ) === false ) {
|
||
$form = str_replace( '</form>', $checkbox_html . '</form>', $form );
|
||
}
|
||
|
||
return $form;
|
||
}
|
||
|
||
// 2. Very small JS — runs on every page that has CF7 (cheap)
|
||
add_action( 'wp_footer', 'dapper_cf7_human_checkbox_js', 95 );
|
||
|
||
function dapper_cf7_human_checkbox_js() {
|
||
// Only output if at least one CF7 form exists on page
|
||
if ( ! did_action( 'wpcf7_enqueue_scripts' ) ) {
|
||
return;
|
||
}
|
||
|
||
?>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.querySelectorAll('.wpcf7 form').forEach(form => {
|
||
const checkbox = form.querySelector('#dapper_cf7_human_confirm');
|
||
const tokenField = form.querySelector('#dapper_cf7_human_token');
|
||
|
||
if (!checkbox || !tokenField) return;
|
||
|
||
// Enable token only when checked
|
||
checkbox.addEventListener('change', () => {
|
||
tokenField.value = checkbox.checked
|
||
? 'cf7_human_' + Math.random().toString(36).substring(2,10) + '_' + Date.now()
|
||
: '';
|
||
});
|
||
|
||
// Optional: disable submit until checked (stronger UX)
|
||
const submitBtn = form.querySelector('input[type="submit"], button[type="submit"]');
|
||
if (submitBtn) {
|
||
submitBtn.disabled = true;
|
||
checkbox.addEventListener('change', () => {
|
||
submitBtn.disabled = !checkbox.checked;
|
||
});
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
<?php
|
||
}
|
||
|
||
// 3. Server-side validation — block if missing / invalid / too fast
|
||
add_action( 'wpcf7_before_send_mail', 'dapper_cf7_validate_human_checkbox', 11, 3 );
|
||
|
||
function dapper_cf7_validate_human_checkbox( $contact_form, &$abort, $submission ) {
|
||
if ( get_option( 'dapper_enable_cf7_human_checkbox', 'on' ) !== 'on' ) {
|
||
return;
|
||
}
|
||
|
||
$posted = $_POST;
|
||
|
||
// A. Checkbox must be checked
|
||
if ( empty( $posted['dapper_cf7_human_confirm'] ) ) {
|
||
$abort = true;
|
||
$submission->add_error( 'dapper_human', __( 'Please confirm you are human.', 'dapper' ) );
|
||
dapper_debug_log( "CF7 #{$contact_form->id()} blocked — human checkbox not checked" );
|
||
return;
|
||
}
|
||
|
||
// B. Token must exist and look valid
|
||
$token = trim( $posted['dapper_cf7_human_token'] ?? '' );
|
||
if ( empty( $token ) || strpos( $token, 'cf7_human_' ) !== 0 || strlen( $token ) < 18 ) {
|
||
$abort = true;
|
||
$submission->add_error( 'dapper_human', __( 'Verification failed. Please try again.', 'dapper' ) );
|
||
dapper_debug_log( "CF7 #{$contact_form->id()} blocked — invalid/missing human token" );
|
||
return;
|
||
}
|
||
|
||
// C. Not submitted in < 4 seconds (very strong signal of bot)
|
||
$time = (int) ( $posted['dapper_cf7_human_time'] ?? 0 );
|
||
if ( $time && ( time() - $time < 4 ) ) {
|
||
$abort = true;
|
||
$submission->add_error( 'dapper_human', __( 'Submission too fast — please try again.', 'dapper' ) );
|
||
dapper_debug_log( "CF7 #{$contact_form->id()} blocked — human check too fast" );
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
function add_campaign_management_page() {
|
||
add_submenu_page(
|
||
'edit.php?post_type=email_template', // Parent menu slug
|
||
'Campaigns', // Page title
|
||
'Campaigns', // Menu title
|
||
'manage_options', // Capability required
|
||
'email-campaigns', // Page slug
|
||
'render_campaign_management_page' // Callback function to render the page
|
||
);
|
||
}
|
||
|
||
add_action('admin_menu', 'add_campaign_management_page');
|
||
|
||
|
||
|
||
function render_campaign_management_page() {
|
||
?>
|
||
<div class="wrap">
|
||
<h2>Email Campaigns</h2>
|
||
|
||
<ul>
|
||
<?php
|
||
$campaigns = get_posts(array('post_type' => 'email_campaign', 'posts_per_page' => -1));
|
||
|
||
foreach ($campaigns as $campaign) {
|
||
echo '<li>
|
||
<a href="' . get_edit_post_link($campaign->ID) . '">' . esc_html($campaign->post_title) . '</a>
|
||
- <a href="' . get_delete_post_link($campaign->ID) . '">Delete</a>
|
||
</li>';
|
||
}
|
||
?>
|
||
</ul>
|
||
<a href="<?php echo admin_url('post-new.php?post_type=email_campaign'); ?>" class="button">Add New Campaign</a>
|
||
|
||
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/*
|
||
// Updates a users subscription status
|
||
// Called when only when changing a status
|
||
// @param $user_id integer id of passed in users
|
||
// @param $new_status string "subscribed" or "unsubscribed"
|
||
*/
|
||
function update_subscription_status($user_id, $new_status) {
|
||
global $wpdb;
|
||
|
||
$table_name = $wpdb->prefix . 'dapper_mailing_list';
|
||
|
||
// Update the subscription status
|
||
$wpdb->update(
|
||
$table_name,
|
||
array('subscription_status' => $new_status),
|
||
array('user_id' => $user_id)
|
||
);
|
||
}
|
||
|
||
|
||
/*
|
||
// Displays the Mailing List users on the setting page
|
||
// Only one call
|
||
*/
|
||
function display_subscribed_users() {
|
||
if ( ! current_user_can('manage_options') ) {
|
||
wp_die( __('Sorry, you are not allowed to manage this.', 'dapper') );
|
||
}
|
||
|
||
// ------------------- ADD THIS SECURITY CHECK -------------------
|
||
// Only process if it's a POST from our form
|
||
if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
|
||
// This checks the nonce AND dies with error if invalid/missing
|
||
check_admin_referer( 'dapper_subscription_action', 'dapper_subscription_nonce' );
|
||
|
||
// Now it's safe to process
|
||
if (isset($_POST['update_subscription_status'])) {
|
||
$user_id = intval($_POST['user_id']);
|
||
$new_status = sanitize_text_field($_POST['new_status']);
|
||
update_subscription_status($user_id, $new_status);
|
||
}
|
||
|
||
if (isset($_POST['remove_user_from_list'])) {
|
||
$user_id = intval($_POST['user_id']);
|
||
remove_user_from_mailing_list($user_id);
|
||
}
|
||
}
|
||
|
||
// Retrieve mailing list users
|
||
$mailing_list_users = get_mailing_list_users();
|
||
|
||
// Display users in HTML
|
||
echo '<h2>Mailing List Users</h2>';
|
||
echo '<table>';
|
||
echo '<tr><th>Email</th><th>Status</th><th>Action</th></tr>';
|
||
foreach ($mailing_list_users as $user) {
|
||
echo '<tr>';
|
||
echo '<td>' . esc_html($user->email) . '</td>';
|
||
echo '<td>' . esc_html($user->subscription_status) . '</td>';
|
||
echo '<td>';
|
||
echo '<form method="post">';
|
||
echo wp_nonce_field( 'dapper_subscription_action', 'dapper_subscription_nonce');
|
||
echo '<input type="hidden" name="user_id" value="' . esc_attr($user->user_id) . '">';
|
||
echo '<select name="new_status">';
|
||
echo '<option value="subscribed">Subscribed</option>';
|
||
echo '<option value="unsubscribed">Unsubscribed</option>';
|
||
echo '</select>';
|
||
echo '<input type="submit" name="update_subscription_status" value="Update Status">';
|
||
echo '<input type="submit" name="remove_user_from_list" value="Remove">';
|
||
echo '</form>';
|
||
echo '</td>';
|
||
echo '</tr>';
|
||
}
|
||
echo '</table>';
|
||
}
|
||
|
||
|
||
function dapper_settings_page() {
|
||
add_submenu_page(
|
||
'edit.php?post_type=email_template',
|
||
'Dapper Settings',
|
||
'Settings',
|
||
'manage_options',
|
||
'dapper-settings',
|
||
'dapper_settings_page_content'
|
||
);
|
||
}
|
||
|
||
|
||
|
||
function dapper_settings_page_content() {
|
||
dapper_debug_log('settings page');
|
||
?>
|
||
<div class="wrap">
|
||
<h2>Dapper Settings</h2>
|
||
<h2 class= "nav-tab-wrapper">
|
||
<a href="#general" class="nav-tab">General</a>
|
||
<a href="#email-settings" class="nav-tab">Email Settings</a>
|
||
<a href="#subscription" class="nav-tab">Mailing List</a>
|
||
<a href="#backup" class="nav-tab">Backups</a>
|
||
</h2>
|
||
|
||
<div id="general">
|
||
<form method="post" action="options.php">
|
||
<?php
|
||
settings_fields('dapper-settings-group');
|
||
do_settings_sections('dapper-settings');
|
||
?>
|
||
<h3>Debug Mode</h3>
|
||
<p>Debug Mode adds entries to the webserver error log file.</p>
|
||
<label for="dapper_enable_debug">Enable Debug Mode</label>
|
||
<input type="checkbox" id="dapper_enable_debug" name="dapper_enable_debug"<?php if (get_option('dapper_enable_debug') == 'on') echo "checked='checked'"; ?>>
|
||
<h3>Custom Admin Panel Login Logo</h5>
|
||
<p>Toggles to enable the custom logo on the admin panel login.</p>
|
||
<label for="dapper_admin_panel_custom_logo_enable">Enable Custom Logo</label>
|
||
<input type="checkbox" id="dapper_admin_panel_custom_logo_enable" name="dapper_admin_panel_custom_logo_enable"<?php if (get_option('dapper_admin_panel_custom_logo_enable') == 'on') echo "checked='checked'"; ?>>
|
||
<p>Image files need to be formated for 120 pixels x 120 pixels
|
||
</p>
|
||
<label for="dapper_admin_panel_custom_logo_path">Logo Path:</label>
|
||
<input type="text" id="dapper_admin_panel_custom_logo_path" name="dapper_admin_panel_custom_logo_path" value="<?php echo esc_attr(get_option('dapper_admin_panel_custom_logo_path')); ?>">
|
||
<input type="button" value="Upload Image" class="button-secondary" id="upload_image_button" />
|
||
<p>I Love Lana</p>
|
||
<div id="custom_logo_preview">
|
||
<?php
|
||
$custom_logo_url = esc_url(get_option('dapper_admin_panel_custom_logo_path'));
|
||
if ($custom_logo_url) {
|
||
echo '<img src="' . $custom_logo_url . '" alt="Custom Logo Preview" style="max-width: 120px; max-height: 120px;" />';
|
||
} else {
|
||
// Show a placeholder image when the custom logo is not set
|
||
echo '<img src="' . content_url() . '/uploads/woocommerce-placeholder-247x296.png" alt="Generic Image" style="max-width: 120px; max-height: 120px;" />';
|
||
}
|
||
?>
|
||
</div>
|
||
<h3>WooCommerce Integration</h3>
|
||
<p>Enable automatic order status emails, tracking notifications, and registration anti-spam honeypot. Disable if not using WooCommerce.</p>
|
||
<label for="dapper_enable_woo_integration">Enable WooCommerce Features</label>
|
||
<input type="checkbox" id="dapper_enable_woo_integration" name="dapper_enable_woo_integration" <?php checked( get_option( 'dapper_enable_woo_integration', 'on' ), 'on' ); ?>>
|
||
<h3>Paypal Human Verification Checkbox</h3>
|
||
<?php
|
||
$woo_enabled = get_option('dapper_enable_woo_integration', 'on') === 'on';
|
||
$paypal_forced_off = !$woo_enabled;
|
||
?>
|
||
<label for="dapper_enable_paypal_human_check">
|
||
Enable Human Verification for PayPal/Add to Cart
|
||
<?php if ($paypal_forced_off): ?>
|
||
<span style="color:#d63638; font-size:0.95em;">(requires WooCommerce integration)</span>
|
||
<?php endif; ?>
|
||
</label>
|
||
<input type="checkbox" id="dapper_enable_paypal_human_check" name="dapper_enable_paypal_human_check"
|
||
<?php checked(get_option('dapper_enable_paypal_human_check', 'on'), 'on'); ?>
|
||
<?php echo $paypal_forced_off ? 'disabled' : ''; ?>>
|
||
<h3>Contact Form 7 Protection</h3>
|
||
<label for="dapper_enable_cf7_human_checkbox">Enable "I'm human" checkbox on all CF7 forms</label>
|
||
<input type="checkbox" id="dapper_enable_cf7_human_checkbox" name="dapper_enable_cf7_human_checkbox"
|
||
<?php checked( get_option( 'dapper_enable_cf7_human_checkbox', 'on' ), 'on' ); ?>>
|
||
<?php
|
||
submit_button();
|
||
?>
|
||
|
||
</form>
|
||
<div class="notice notice-info inline" style="margin: 40px 0 20px; padding: 12px 16px; text-align: center; border-left: 4px solid #2271b1;">
|
||
<p style="margin: 0; font-size: 13px; color: #1d2327;">
|
||
<strong>Dapper v<?php echo esc_html( defined('DAPPER_VERSION') ? DAPPER_VERSION : 'unknown' ); ?></strong>
|
||
• commit
|
||
<code style="background: #f0f0f1; padding: 2px 6px; border-radius: 3px; font-family: Consolas, Monaco, 'Lucida Console', monospace;">
|
||
<?php echo esc_html( DAPPER_BUILD ); ?>
|
||
</code>
|
||
<?php if ( DAPPER_BUILD === 'dev-local' ) : ?>
|
||
<span style="color: #d63638; font-style: italic;">(local dev – no git detected)</span>
|
||
<?php endif; ?>
|
||
</p>
|
||
</div>
|
||
|
||
</div>
|
||
<div id="email-settings">
|
||
<form method="post" action="options.php">
|
||
<?php
|
||
settings_fields('dapper-email-settings-group');
|
||
do_settings_sections('dapper-settings');
|
||
?>
|
||
|
||
<h3>Email Header</h3>
|
||
<p>Example: From: Your Name <youremail@example.com> <br>Content-Type: text/html Reply-to: Your Name</p>
|
||
<label for="dapper_headers">Email Headers:</label>
|
||
<input type="text" id="dapper_headers" name="dapper_headers" value="<?php echo esc_attr(get_option('dapper_headers')); ?>">
|
||
<h3>Review Link Structure</h3>
|
||
<p>Example: https://www.example.com/products</p>
|
||
<label for="dapper_review_link_structure">Review Link Structure:</label>
|
||
<input type="text" id="dapper_review_link_structure" name="dapper_review_link_structure" value="<?php echo esc_attr(get_option('dapper_review_link_structure')); ?>"><label for="dapper_review_link_structure_end">/$product_slug/#reviews</label>
|
||
<h3>Order Status Delivered</h3>
|
||
<p>Enter your template for WooCommerce Order Status Delivered Leave blank to disable</p>
|
||
<label for="dapper_woo_status_delivered">WooCommerce Order Status Delivered:</label>
|
||
<input type="text" id="dapper_woo_status_delivered" name="dapper_woo_status_delivered" value="<?php echo esc_attr(get_option('dapper_woo_status_delivered')); ?>">
|
||
<h3>Order Status Shipped</h3>
|
||
<p>Enter your template for WooCommerce Order Status Shipped Leave blank to disable</p>
|
||
<label for="dapper_woo_status_shipped">WooCommerce Order Status Shipped:</label>
|
||
<input type="text" id="dapper_woo_status_shipped" name="dapper_woo_status_shipped" value="<?php echo esc_attr(get_option('dapper_woo_status_shipped')); ?>">
|
||
<h3>Order Status Partially Shipped</h3>
|
||
<p>Enter your template for WooCommerce Order Status Partially Shipped Leave blank to disable</p>
|
||
<label for="dapper_woo_status_partial_shipped">WooCommerce Order Status Partially Shipped:</label>
|
||
<input type="text" id="dapper_woo_status_partial_shipped" name="dapper_woo_status_partial_shipped" value="<?php echo esc_attr(get_option('dapper_woo_status_partial_shipped')); ?>">
|
||
<h3>Order Status Pending Payment</h3>
|
||
<p>Enter your template for WooCommerce Order Status Pending Payment Leave blank to disable</p>
|
||
<label for="dapper_woo_status_pending_payment">WooCommerce Order Status pending Payment:</label>
|
||
<input type="text" id="dapper_woo_status_pending_payment" name="dapper_woo_status_pending_payment" value="<?php echo esc_attr(get_option('dapper_woo_status_pending_payment')); ?>">
|
||
<h3>Order Status Processing</h3>
|
||
<p>Enter your template for WooCommerce Order Status Processing Leave blank to disable</p>
|
||
<label for="dapper_woo_status_processing">WooCommerce Order Status Processing:</label>
|
||
<input type="text" id="dapper_woo_status_processing" name="dapper_woo_status_processing" value="<?php echo esc_attr(get_option('dapper_woo_status_processing')); ?>">
|
||
<h3>Order Status Failed</h3>
|
||
<p>Enter your template for WooCommerce Order Status Failed Leave blank to disable</p>
|
||
|
||
<label for="dapper_woo_status_failed">WooCommerce Order Status Failed:</label>
|
||
|
||
|
||
<input type="text" id="dapper_woo_status_failed" name="dapper_woo_status_failed" value="<?php echo esc_attr(get_option('dapper_woo_status_failed')); ?>">
|
||
|
||
<h3>Order Status Draft</h3>
|
||
<p>Enter your template for WooCommerce Order Status Draft Leave blank to disable</p>
|
||
|
||
<label for="dapper_woo_status_draft">WooCommerce Order Status Draft:</label>
|
||
|
||
|
||
<input type="text" id="dapper_woo_status_draft" name="dapper_woo_status_draft" value="<?php echo esc_attr(get_option('dapper_woo_status_draft')); ?>">
|
||
|
||
<h3>Order Status Refunded</h3>
|
||
<p>Enter your template for WooCommerce Order Status Refunded Leave blank to disable</p>
|
||
|
||
<label for="dapper_woo_status_refunded">WooCommerce Order Status Refunded:</label>
|
||
|
||
|
||
<input type="text" id="dapper_woo_status_refunded" name="dapper_woo_status_refunded" value="<?php echo esc_attr(get_option('dapper_woo_status_refunded')); ?>">
|
||
|
||
<h3>Order Status Cancelled</h3>
|
||
<p>Enter your template for WooCommerce Order Status Cancelled Leave blank to disable</p>
|
||
|
||
<label for="dapper_woo_status_cancelled">WooCommerce Order Status Cancelled:</label>
|
||
|
||
|
||
<input type="text" id="dapper_woo_status_cancelled" name="dapper_woo_status_cancelled" value="<?php echo esc_attr(get_option('dapper_woo_status_cancelled')); ?>">
|
||
|
||
<h3>Order Status On Hold</h3>
|
||
<p>Enter your template for WooCommerce Order Status On Hold Leave blank to disable</p>
|
||
|
||
<label for="dapper_woo_status_on_hold">WooCommerce Order Status On Hold:</label>
|
||
|
||
|
||
<input type="text" id="dapper_woo_status_on_hold" name="dapper_woo_status_on_hold" value="<?php echo esc_attr(get_option('dapper_woo_status_on_hold')); ?>">
|
||
<?php
|
||
submit_button();
|
||
?>
|
||
</form>
|
||
</div>
|
||
<div id="subscription">
|
||
<?php display_subscribed_users(); ?>
|
||
</div>
|
||
<div id="backup">
|
||
<?php
|
||
if (isset($_GET['backup'])) {
|
||
if ($_GET['backup'] === 'success') {
|
||
echo '<div class="notice notice-success is-dismissible"><p>Backup created successfully! Refresh to see new files below.</p></div>';
|
||
} elseif ($_GET['backup'] === 'failed') {
|
||
echo '<div class="notice notice-error is-dismissible"><p>Backup failed — check debug log. Try unchecking some folders if memory is low.</p></div>';
|
||
}
|
||
}
|
||
?>
|
||
|
||
|
||
<form method="post" action="options.php">
|
||
<?php settings_fields('dapper-backup-group'); ?>
|
||
<h3>Backup Settings</h3>
|
||
<p>Configure what to include in backups. Uncheck large folders if you have memory limits.</p>
|
||
|
||
<label><input type="checkbox" name="dapper_backup_themes" <?php checked(get_option('dapper_backup_themes', 'on'), 'on'); ?>> Include Themes folder</label><br>
|
||
<label><input type="checkbox" name="dapper_backup_plugins" <?php checked(get_option('dapper_backup_plugins', 'on'), 'on'); ?>> Include Plugins folder</label><br>
|
||
<label><input type="checkbox" name="dapper_backup_include_media" <?php checked(get_option('dapper_backup_include_media'), 'on'); ?>> Include Media (/uploads) — <strong>Warning: may exceed 128 MB limit on large sites</strong></label>
|
||
|
||
<?php submit_button('Save Backup Settings'); ?>
|
||
</form>
|
||
|
||
<!-- Manual Backup -->
|
||
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="margin: 30px 0;">
|
||
<input type="hidden" name="action" value="dapper_manual_backup">
|
||
<?php wp_nonce_field('dapper_create_backup_nonce'); ?>
|
||
<input type="submit" name="dapper_create_backup_now" class="button button-primary button-large" value="Create Backup Now">
|
||
<p class="description">Creates database export + selected folders. May take time on large sites.</p>
|
||
</form>
|
||
|
||
<?php
|
||
// Safe call — only if function exists
|
||
if (function_exists('display_old_backup_list')) {
|
||
display_old_backup_list();
|
||
} else {
|
||
echo '<p style="color:red;">Backup list function not available. Check if dapper_backup.php loaded correctly (syntax error?).</p>';
|
||
}
|
||
?>
|
||
</div>
|
||
|
||
</div>
|
||
<script>
|
||
jQuery(function($){
|
||
$('.nav-tab-wrapper a').click(function(){
|
||
var tab_id = $(this).attr('href');
|
||
$('.nav-tab-wrapper a').removeClass('nav-tab-active');
|
||
$('.wrap > div').hide();
|
||
$(this).addClass('nav-tab-active');
|
||
$(tab_id).show();
|
||
return false;
|
||
});
|
||
|
||
// Hide all content divs except the one initially active
|
||
$('.wrap > div').not(':first').hide();
|
||
});
|
||
</script>
|
||
|
||
<?php
|
||
}
|
||
|
||
add_action('admin_menu', 'dapper_settings_page');
|
||
|
||
/*
|
||
// Load media uploader and custom admin JS only on Dapper settings page
|
||
*/
|
||
function enqueue_media_uploader_scripts() {
|
||
// Check if we're on the Dapper settings page
|
||
if ( isset( $_GET['page'] ) && $_GET['page'] === 'dapper-settings' ) {
|
||
// Load WordPress media library (for image upload button)
|
||
wp_enqueue_media();
|
||
|
||
// Load our admin.js (handles upload button + preview)
|
||
wp_enqueue_script(
|
||
'my-admin-js',
|
||
plugin_dir_url( __FILE__ ) . 'admin.js',
|
||
array( 'jquery' ),
|
||
null,
|
||
false
|
||
);
|
||
}
|
||
}
|
||
|
||
add_action( 'admin_enqueue_scripts', 'enqueue_media_uploader_scripts' );
|
||
|
||
/*
|
||
// Retrieves the mailing list user with search and pagination
|
||
// @param $search string passed in from search bar
|
||
// @param $page integer used as a page placeholder
|
||
// @param $per_page integer holds the number of users per page
|
||
// Returns user search results
|
||
*/
|
||
function getting_mailing_list_users($search, $page, $per_page) {
|
||
global $wpdb;
|
||
|
||
$offset = ($page - 1) * $per_page;
|
||
|
||
$table_name = $wpdb->prefix . 'dapper_mailing_list';
|
||
|
||
$where = '';
|
||
if (!empty($search)) {
|
||
$where = "WHERE email LIKE '%$search%'";
|
||
}
|
||
|
||
$sql = "SELECT * FROM $table_name $where LIMIT $per_page OFFSET $offset";
|
||
$users = $wpdb->get_results($sql);
|
||
|
||
return $users;
|
||
}
|
||
|
||
/*
|
||
// Register plugin settings through wordpress method register_setting
|
||
*/
|
||
function dapper_register_settings() {
|
||
register_setting('dapper-settings-group', 'dapper_option');
|
||
register_setting('dapper-email-settings-group', 'dapper_headers');
|
||
register_setting('dapper-email-settings-group', 'dapper_review_link_structure');
|
||
register_setting('dapper-email-settings-group', 'dapper_woo_status_delivered');
|
||
register_setting('dapper-email-settings-group', 'dapper_woo_status_shipped');
|
||
register_setting('dapper-email-settings-group', 'dapper_woo_status_partial_shipped');
|
||
register_setting('dapper-email-settings-group', 'dapper_woo_status_processing');
|
||
register_setting('dapper-email-settings-group', 'dapper_woo_status_failed');
|
||
register_setting('dapper-email-settings-group', 'dapper_woo_status_draft');
|
||
register_setting('dapper-email-settings-group', 'dapper_woo_status_refunded');
|
||
register_setting('dapper-email-settings-group', 'dapper_woo_status_cancelled');
|
||
register_setting('dapper-email-settings-group', 'dapper_woo_status_on_hold');
|
||
register_setting('dapper-email-settings-group', 'dapper_woo_status_pending_payment');
|
||
register_setting('dapper-settings-group', 'dapper_enable_debug');
|
||
register_setting('dapper-settings-group', 'dapper_admin_panel_custom_logo_path');
|
||
register_setting('dapper-settings-group', 'dapper_admin_panel_custom_logo_enable');
|
||
register_setting('dapper-settings-group', 'dapper_setup_complete');
|
||
register_setting('dapper-settings-group', 'dapper_enable_backup');
|
||
register_setting('dapper-backup-group', 'dapper_enable_backup');
|
||
register_setting('dapper-settings-group', 'dapper_enable_woo_integration');
|
||
register_setting('dapper-backup-group', 'dapper_backup_include_media');
|
||
register_setting('dapper-backup-group', 'dapper_backup_themes');
|
||
register_setting('dapper-backup-group', 'dapper_backup_plugins');
|
||
register_setting('dapper-backup-group', 'dapper_backup_include_media');
|
||
register_setting('dapper-settings-group', 'dapper_enable_paypal_human_check');
|
||
register_setting( 'dapper-settings-group', 'dapper_enable_cf7_human_checkbox' );
|
||
// Add other settings as needed
|
||
}
|
||
|
||
add_action('admin_init', 'dapper_register_settings');
|
||
/*
|
||
// Debug wrapper to add Dapper Debug to the front of all debuging messages
|
||
// Call this instead of directly calling error_log
|
||
// @param $message string of passed in message required
|
||
*/
|
||
function dapper_debug_log($message) {
|
||
if (get_option('dapper_enable_debug', 0) == 'on') {
|
||
error_log('[Dapper Debug] ' . $message);
|
||
}
|
||
}
|
||
/*
|
||
// Utility function used to get contents of an email template
|
||
// to then swap out place holder data with real data
|
||
// @param $template_name string name of passed in email template required
|
||
// Returns an empty string if the passed in email template doesn't exist
|
||
*/
|
||
function get_email_template_content_by_name($template_name) {
|
||
$template_post = get_page_by_title($template_name, OBJECT, 'email_template');
|
||
|
||
if ($template_post) {
|
||
// Use 'post_content' to get the HTML content of the post
|
||
return $template_post->post_content;
|
||
}
|
||
dapper_debug_log( "No email template found with title: " . $template_name );
|
||
return ''; // Return an empty string if the template is not found
|
||
}
|
||
|
||
add_action('updated_option', 'dapper_sync_paypal_human_check_with_woo', 10, 3);
|
||
|
||
/*
|
||
// Automatically disable PayPal Human Verification when WooCommerce integration is turned off.
|
||
//
|
||
// This prevents the human verification checkbox from staying enabled when
|
||
// WooCommerce features are disabled (since the checkbox relies on WooCommerce hooks).
|
||
//
|
||
// Hook: updated_option
|
||
// @param string $option The updated option name.
|
||
// @param mixed $old_value Previous value.
|
||
// @param mixed $value New value.
|
||
//
|
||
// @return void
|
||
*/
|
||
function dapper_sync_paypal_human_check_with_woo( $option, $old_value, $value ) {
|
||
// Only care about the WooCommerce integration toggle
|
||
if ( 'dapper_enable_woo_integration' !== $option ) {
|
||
return;
|
||
}
|
||
|
||
// If WooCommerce integration is being turned off
|
||
if ( 'on' !== $value ) {
|
||
// Force-disable the PayPal human verification checkbox feature
|
||
update_option( 'dapper_enable_paypal_human_check', 'off' );
|
||
|
||
// Debug trace when enabled
|
||
if ( get_option( 'dapper_enable_debug', 'off' ) === 'on' ) {
|
||
dapper_debug_log(
|
||
sprintf(
|
||
'WooCommerce integration disabled → PayPal Human Verification turned off (old: %s → new: %s)',
|
||
esc_html( $old_value ?? 'unset' ),
|
||
esc_html( $value )
|
||
)
|
||
);
|
||
}
|
||
}
|
||
}
|
||
/*
|
||
// Retrieves the order total in Numeric value only
|
||
// @param $order_id integer of passed in order id required
|
||
// Returns 0 if $order_id is null
|
||
*/
|
||
function get_order_total_numeric($order_id) {
|
||
if (! class_exists('WooCommerce')){
|
||
return 0;
|
||
}
|
||
$order = wc_get_order($order_id);
|
||
|
||
return $order ? (float) $order->get_total() : 0;
|
||
}
|
||
|
||
|
||
/*
|
||
// Persistent Object Cache clearing on update to prevent a race condition
|
||
// @param $option passed in string from hook
|
||
*/
|
||
function dapper_clear_alloptions_cache($option) {
|
||
if ( ! wp_installing() ) {
|
||
$alloptions = wp_load_alloptions();
|
||
if ( isset( $alloptions[ $option ] ) ) {
|
||
wp_cache_delete( 'alloptions', 'options' );
|
||
}
|
||
}
|
||
}
|
||
add_action( 'added_option', 'dapper_clear_alloptions_cache' );
|
||
add_action( 'updated_option', 'dapper_clear_alloptions_cache' );
|
||
add_action( 'deleted_option', 'dapper_clear_alloptions_cache' );
|
||
|
||
|
||
/*
|
||
// Customizing the login form on the admin panel
|
||
// place your custom logo in your active theme folder inside a folder
|
||
// named "images" and on the Dapper pluging settings page enter
|
||
// the logo image path
|
||
*/
|
||
function dapper_login_logo() {
|
||
if (get_option('dapper_admin_panel_custom_logo_enable') === 'on') {
|
||
|
||
|
||
?>
|
||
<style type="text/css">
|
||
#login h1 a, .login h1 a {
|
||
background-image: url(<?php echo esc_url( get_option('dapper_admin_panel_custom_logo_path') ); ?>);
|
||
height:120px;
|
||
width:120px;
|
||
background-size: 120px 120px;
|
||
background-repeat: no-repeat;
|
||
padding-bottom: 30px;
|
||
}
|
||
</style>
|
||
<?php }
|
||
|
||
}
|
||
add_action( 'login_enqueue_scripts', 'dapper_login_logo' );
|
||
|
||
/**
|
||
* Load plugin text domain for translations.
|
||
*/
|
||
function dapper_load_textdomain() {
|
||
load_plugin_textdomain( 'dapper', false, dirname( DAPPER_BASENAME ) . '/languages' );
|
||
}
|
||
add_action( 'init', 'dapper_load_textdomain' );
|
||
|
||
// Handle manual backup via admin-post (secure & reliable)
|
||
add_action('admin_post_dapper_manual_backup', 'dapper_process_manual_backup');
|
||
|
||
function dapper_process_manual_backup() {
|
||
set_time_limit(0);
|
||
if (!current_user_can('manage_options')) {
|
||
wp_die('Access denied.');
|
||
}
|
||
|
||
check_admin_referer('dapper_create_backup_nonce');
|
||
|
||
set_transient('dapper_backup_in_progress', true, 600); // 10 min lock
|
||
|
||
$result = dapper_create_new_backup();
|
||
|
||
delete_transient('dapper_backup_in_progress');
|
||
|
||
if ($result !== false) {
|
||
$redirect = add_query_arg(
|
||
array(
|
||
'page' => 'dapper-settings',
|
||
'backup' => 'success',
|
||
),
|
||
admin_url('admin.php')
|
||
);
|
||
} else {
|
||
$redirect = add_query_arg(
|
||
array(
|
||
'page' => 'dapper-settings',
|
||
'backup' => 'failed',
|
||
),
|
||
admin_url('admin.php')
|
||
);
|
||
}
|
||
|
||
wp_safe_redirect($redirect);
|
||
exit;
|
||
}
|