diff --git a/dapper.php b/dapper.php index 97b1283..9c3d90e 100644 --- a/dapper.php +++ b/dapper.php @@ -38,6 +38,8 @@ function dapper_activation() { 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 @@ -47,47 +49,48 @@ function dapper_activation() { register_activation_hook(__FILE__, 'dapper_activation'); /** - * Get short Git commit hash (8 chars) as build identifier. - * Safe fallback if .git is missing (e.g. on production deploy). + * 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_git_build() { +function dapper_get_build_identifier() { static $build = null; - if ($build !== null) return $build; - - $git_dir = DAPPER_PATH . '.git'; - if (!is_dir($git_dir)) { - $build = 'dev-local'; + if ( $build !== null ) { return $build; } - $head_path = $git_dir . '/HEAD'; - if (!file_exists($head_path)) { - $build = 'git-head-missing'; - return $build; - } + $info_file = DAPPER_PATH . 'build-info.php'; - $head_content = trim(file_get_contents($head_path)); + 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']; + } - if (preg_match('#^ref: (refs/heads/.+)$#', $head_content, $matches)) { - $ref_path = $git_dir . '/' . $matches[1]; - if (file_exists($ref_path)) { - $commit = trim(file_get_contents($ref_path)); - $build = substr($commit, 0, 8); return $build; } } - // Detached HEAD fallback - if (strlen($head_content) >= 40 && preg_match('/^[0-9a-f]{40}$/', $head_content)) { - $build = substr($head_content, 0, 8); - 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 = 'git-unknown'; + $build = 'dev-local'; return $build; } -define('DAPPER_BUILD', dapper_get_git_build()); +define( 'DAPPER_BUILD', dapper_get_build_identifier() ); /* // Depreciated in v1.1.1 to be removed in later versions @@ -1095,12 +1098,148 @@ function dapper_woo_conditional_load() { } } + // 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 = ' + + +
'; + + // Insert just before + $form = str_replace('', $hidden_fields . '', $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; + ?> + + Debug Mode adds entries to the webserver error log file. > - - > -Shows a quick "I'm not a robot" checkbox when PayPal is selected. Helps block bots without third-party services.
Toggles to enable the custom logo on the admin panel login.
@@ -1282,7 +1418,20 @@ function dapper_settings_page_content() {Enable automatic order status emails, tracking notifications, and registration anti-spam honeypot. Disable if not using WooCommerce.
> - +