From 9b408542afe551e6cad357d80b24db578ef7ad4d Mon Sep 17 00:00:00 2001
From: carpentryplus25
Date: Thu, 5 Mar 2026 10:30:37 -0500
Subject: [PATCH] Automated Build Hash updates
---
dapper.php | 265 ++++++++++++++++++++++++++++++++++++++++------
dapper_backup.php | 47 ++++----
2 files changed, 259 insertions(+), 53 deletions(-)
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.
Custom Admin Panel Login Logo
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.
>
-
+ Paypal Human Verification Checkbox
+
+
+
+ >
@@ -1448,13 +1597,27 @@ function dapper_settings_page_content() {
add_action('admin_menu', 'dapper_settings_page');
+/*
+// Load media uploader and custom admin JS only on Dapper settings page
+*/
function enqueue_media_uploader_scripts() {
- if (isset($_GET['page']) && $_GET['page'] === 'dapper-settings') {
+ // 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();
- wp_enqueue_script('my-admin-js', plugin_dir_url(__FILE__) . 'admin.js', array('jquery'), null, false);
+
+ // 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');
+
+add_action( 'admin_enqueue_scripts', 'enqueue_media_uploader_scripts' );
/*
// Retrieves the mailing list user with search and pagination
@@ -1541,6 +1704,44 @@ function get_email_template_content_by_name($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
diff --git a/dapper_backup.php b/dapper_backup.php
index 9f4f1b2..fc8f098 100644
--- a/dapper_backup.php
+++ b/dapper_backup.php
@@ -321,51 +321,56 @@ add_action('admin_post_dapper_backup_dl', 'dapper_handle_backup_download');
function dapper_handle_backup_download() {
if (!current_user_can('manage_options')) {
- wp_die('Access denied.');
+ wp_die('Access denied.', 403);
}
- $file = isset($_GET['f']) ? basename($_GET['f']) : '';
+ $file = isset($_GET['f']) ? basename($_GET['f']) : '';
$folder = isset($_GET['folder']) ? basename($_GET['folder']) : '';
$nonce = $_GET['_wpnonce'] ?? '';
if (!wp_verify_nonce($nonce, 'dapper_dl_' . md5($file))) {
- wp_die('Security check failed.');
+ wp_die('Security check failed.', 403);
}
$path = DAPPER_BACKUP_BASE_DIR . '/' . $folder . '/' . $file;
if (!file_exists($path) || !is_readable($path)) {
- wp_die('File not found or inaccessible.');
+ wp_die('File not found or not readable.', 404);
}
- // Disable output buffering & compression for large files
- if (ob_get_level()) ob_end_clean();
- header_remove('Content-Encoding'); // Prevent gzip
+ // Kill all output buffering
+ while (ob_get_level() > 0) {
+ ob_end_clean();
+ }
- // Proper headers for binary download
+ // Turn off compression
+ if (function_exists('apache_setenv')) {
+ @apache_setenv('no-gzip', 1);
+ }
+ @ini_set('zlib.output_compression', 'Off');
+
+ // Headers
header('Content-Description: File Transfer');
- header('Content-Type: application/zip'); // Force zip type
- header('Content-Disposition: attachment; filename="' . $file . '"');
- header('Content-Transfer-Encoding: binary');
+ header('Content-Type: application/zip');
+ header('Content-Disposition: attachment; filename="' . $file . '";');
header('Expires: 0');
- header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+ header('Cache-Control: must-revalidate');
header('Pragma: public');
-
- // Chunked output to avoid memory issues
header('Content-Length: ' . filesize($path));
- // Stream file in 1 MB chunks
- $chunk_size = 1024 * 1024; // 1 MB
- $handle = fopen($path, 'rb');
+ // Chunked read — very memory efficient
+ $chunk = 1 * 1024 * 1024; // 1 MB
+ $handle = @fopen($path, 'rb');
if ($handle === false) {
- wp_die('Failed to open file for reading.');
+ wp_die('Cannot open file for reading.');
}
while (!feof($handle)) {
- echo fread($handle, $chunk_size);
- flush(); // Send chunk immediately
+ echo @fread($handle, $chunk);
+ flush();
+ if (connection_aborted()) break;
}
- fclose($handle);
+ @fclose($handle);
exit;
}