hp /* Plugin Name: Product Machine Description: A plugin to process product data and prepare it for WooCommerce import. Version: 1.3 Author: bucknelius wp-content/plugins/product-machine/product-machine.php */ // from_hostgator_for_5th/public_html/wp-content/plugins/product-machine/product-machine.php // Exit if accessed directly if (!defined('ABSPATH')) { exit; } // Global variable to collect errors global $pm_errors; $pm_errors = []; // // Custom error handler function pm_error_handler($errno, $errstr, $errfile, $errline) { // require_once plugin_dir_path(__FILE__) . 'includes/error-handling.php'; // set_error_handler('pm_error_handler'); // Add main menu and submenu pages function product_machine_menu() { require_once plugin_dir_path(__FILE__) . 'includes/admin-menu.php'; // Include the Preview Data Handler function pm_preview_data() require_once plugin_dir_path(__FILE__) . 'includes/preview-data-handler.php'; // Function to read the last N lines of a file function pm_get_last_lines_of_file($filepath, $lines = 100) require_once plugin_dir_path(__FILE__) . 'includes/utility-functions.php'; // function product_machine_export_csv($products) require_once plugin_dir_path(__FILE__) . 'includes/export-csv.php'; //function product_machine_process_raw_data($raw_data, $invoice_base, $sku_usage) require_once plugin_dir_path(__FILE__) . 'includes/data-processing.php'; //function pm_get_external_db_connection() require_once plugin_dir_path(__FILE__) . 'includes/database-connection.php'; // Include the Admin Scripts Handler function product_machine_admin_scripts($hook) require_once plugin_dir_path(__FILE__) . 'includes/admin-scripts-handler.php'; // Admin helper: generate SKU button require_once plugin_dir_path(__FILE__) . 'includes/admin-generate-sku.php'; // Create truck sizes table on plugin activation function product_machine_create_truck_sizes_table() { $conn = pm_get_external_db_connection(); if (!$conn) { error_log('Product Machine: Could not connect to external database, using WordPress database for truck sizes table'); product_machine_create_truck_sizes_table_wp(); return; } $sql = "CREATE TABLE IF NOT EXISTS truck_sizes ( id INT AUTO_INCREMENT PRIMARY KEY, brand VARCHAR(100) NOT NULL, model VARCHAR(100) NOT NULL, hanger_width_mm DECIMAL(5,2) NOT NULL, axle_width_inches DECIMAL(6,3) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY unique_truck (brand, model) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"; if ($conn->query($sql) === TRUE) { error_log('Product Machine: truck_sizes table created successfully'); product_machine_populate_sample_truck_sizes(); } else { error_log('Product Machine: Error creating truck_sizes table: ' . $conn->error); } $conn->close(); } // Create truck sizes table in WordPress database as fallback function product_machine_create_truck_sizes_table_wp() { global $wpdb; $table_name = $wpdb->prefix . 'product_machine_truck_sizes'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id INT AUTO_INCREMENT PRIMARY KEY, brand VARCHAR(100) NOT NULL, model VARCHAR(100) NOT NULL, hanger_width_mm DECIMAL(5,2) NOT NULL, axle_width_inches DECIMAL(6,3) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY unique_truck (brand, model) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); error_log('Product Machine: truck_sizes table created in WordPress database'); product_machine_populate_sample_truck_sizes_wp(); } // Populate sample truck sizes function product_machine_populate_sample_truck_sizes() { $conn = pm_get_external_db_connection(); if (!$conn) { return; } // Common skateboard truck sizes with accurate axle widths // Based on real truck specifications, axle widths are typically in 1/8" increments $truck_sizes = [ ['ACE', '44', 44.00, 8.000], ['ACE', '55', 55.00, 8.250], ['ACE', '66', 66.00, 8.500], ['Independent', '139', 139.00, 7.875], ['Independent', '144', 144.00, 8.125], ['Independent', '149', 149.00, 8.375], ['Independent', '159', 159.00, 8.625], ['Independent', '169', 169.00, 8.875], ['Thunder', '143', 143.00, 8.000], ['Thunder', '147', 147.00, 8.250], ['Thunder', '151', 151.00, 8.500], ['Thunder', '161', 161.00, 8.750], ['Venture', '5.0', 127.00, 7.750], ['Venture', '5.25', 133.00, 8.000], ['Venture', '5.8', 147.00, 8.250], ['Venture', '6.1', 155.00, 8.500], ['Krux', '8.0', 203.00, 8.000], ['Krux', '8.25', 210.00, 8.250], ['Krux', '8.5', 216.00, 8.500], ['Tensor', '5.25', 133.00, 8.000], ['Tensor', '5.5', 140.00, 8.125], ['Paris', '150mm', 150.00, 8.250], ['Paris', '180mm', 180.00, 8.750], ['Bear', '852', 216.00, 8.500], ['Bear', '840', 203.00, 8.000], ['Caliber', '44°', 184.00, 8.500], ['Caliber', '50°', 184.00, 8.500] ]; $stmt = $conn->prepare("INSERT IGNORE INTO truck_sizes (brand, model, hanger_width_mm, axle_width_inches) VALUES (?, ?, ?, ?)"); foreach ($truck_sizes as $truck) { $stmt->bind_param("ssdd", $truck[0], $truck[1], $truck[2], $truck[3]); $stmt->execute(); } $stmt->close(); $conn->close(); error_log('Product Machine: Sample truck sizes populated'); } // Populate sample truck sizes in WordPress database function product_machine_populate_sample_truck_sizes_wp() { global $wpdb; $table_name = $wpdb->prefix . 'product_machine_truck_sizes'; // Common skateboard truck sizes with accurate axle widths $truck_sizes = [ ['ACE', '44', 44.00, 8.000], ['ACE', '55', 55.00, 8.250], ['ACE', '66', 66.00, 8.500], ['Independent', '139', 139.00, 7.875], ['Independent', '144', 144.00, 8.125], ['Independent', '149', 149.00, 8.375], ['Independent', '159', 159.00, 8.625], ['Independent', '169', 169.00, 8.875], ['Thunder', '143', 143.00, 8.000], ['Thunder', '147', 147.00, 8.250], ['Thunder', '151', 151.00, 8.500], ['Thunder', '161', 161.00, 8.750], ['Venture', '5.0', 127.00, 7.750], ['Venture', '5.25', 133.00, 8.000], ['Venture', '5.8', 147.00, 8.250], ['Venture', '6.1', 155.00, 8.500], ['Krux', '8.0', 203.00, 8.000], ['Krux', '8.25', 210.00, 8.250], ['Krux', '8.5', 216.00, 8.500], ['Tensor', '5.25', 133.00, 8.000], ['Tensor', '5.5', 140.00, 8.125], ['Paris', '150mm', 150.00, 8.250], ['Paris', '180mm', 180.00, 8.750], ['Bear', '852', 216.00, 8.500], ['Bear', '840', 203.00, 8.000], ['Caliber', '44°', 184.00, 8.500], ['Caliber', '50°', 184.00, 8.500] ]; foreach ($truck_sizes as $truck) { $wpdb->replace($table_name, [ 'brand' => $truck[0], 'model' => $truck[1], 'hanger_width_mm' => $truck[2], 'axle_width_inches' => $truck[3] ]); } error_log('Product Machine: Sample truck sizes populated in WordPress database'); } // Ensure truck sizes table exists (auto-create if needed) function product_machine_ensure_truck_sizes_table() { global $wpdb; $table_name = $wpdb->prefix . 'product_machine_truck_sizes'; // Check if table exists $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name; if (!$table_exists) { // Create table $sql = "CREATE TABLE $table_name ( id INT AUTO_INCREMENT PRIMARY KEY, brand VARCHAR(100) NOT NULL, model VARCHAR(100) NOT NULL, hanger_width_mm DECIMAL(5,2) NOT NULL, axle_width_inches DECIMAL(6,3) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY unique_truck (brand, model) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"; $wpdb->query($sql); // Populate with comprehensive truck data including NHS and DLXSF $truck_sizes = [ // ACE Trucks ['ACE', '44', 44.00, 8.000], ['ACE', '55', 55.00, 8.250], ['ACE', '66', 66.00, 8.500], // Independent Trucks ['Independent', '139', 139.00, 7.875], ['Independent', '144', 144.00, 8.125], ['Independent', '149', 149.00, 8.375], ['Independent', '159', 159.00, 8.625], ['Independent', '169', 169.00, 8.875], // Thunder Trucks ['Thunder', '143', 143.00, 8.000], ['Thunder', '147', 147.00, 8.250], ['Thunder', '151', 151.00, 8.500], ['Thunder', '161', 161.00, 8.750], // Venture Trucks ['Venture', '5.0', 127.00, 7.750], ['Venture', '5.25', 133.00, 8.000], ['Venture', '5.8', 147.00, 8.250], ['Venture', '6.1', 155.00, 8.500], // NHS (Natural High Skateboarding) Trucks ['NHS', '5.0', 127.00, 7.750], ['NHS', '5.25', 133.00, 8.000], ['NHS', '5.5', 140.00, 8.125], ['NHS', '5.8', 147.00, 8.250], ['NHS', '6.1', 155.00, 8.500], // DLXSF (Deluxe San Francisco) Trucks ['DLXSF', '139', 139.00, 7.875], ['DLXSF', '144', 144.00, 8.125], ['DLXSF', '149', 149.00, 8.375], ['DLXSF', '159', 159.00, 8.625], ['DLXSF', '169', 169.00, 8.875], // Other popular brands ['Krux', '8.0', 203.00, 8.000], ['Krux', '8.25', 210.00, 8.250], ['Krux', '8.5', 216.00, 8.500], ['Tensor', '5.25', 133.00, 8.000], ['Tensor', '5.5', 140.00, 8.125], ['Paris', '150mm', 150.00, 8.250], ['Paris', '180mm', 180.00, 8.750], ['Bear', '852', 216.00, 8.500], ['Bear', '840', 203.00, 8.000], ['Caliber', '44°', 184.00, 8.500], ['Caliber', '50°', 184.00, 8.500] ]; foreach ($truck_sizes as $truck) { $wpdb->insert($table_name, [ 'brand' => $truck[0], 'model' => $truck[1], 'hanger_width_mm' => $truck[2], 'axle_width_inches' => $truck[3] ]); } } } // Helper function to get truck sizes database connection (external or WordPress) function pm_get_truck_sizes_db() { $conn = pm_get_external_db_connection(); if ($conn) { return ['type' => 'external', 'conn' => $conn, 'table' => 'truck_sizes']; } else { global $wpdb; return ['type' => 'wordpress', 'conn' => $wpdb, 'table' => $wpdb->prefix . 'product_machine_truck_sizes']; } } // Lookup truck axle width from reference table based on product description function pm_lookup_truck_width($description) { global $wpdb; $table_name = $wpdb->prefix . 'product_machine_truck_sizes'; // Check if table exists $table_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)); if ($table_exists !== $table_name) { return null; } // Define truck brand patterns to match in description $brands = ['ACE', 'Independent', 'Thunder', 'Venture', 'NHS', 'DLXSF', 'Krux', 'Tensor', 'Paris', 'Bear', 'Caliber']; // Try to extract brand and model from description foreach ($brands as $brand) { // Case-insensitive brand match with flexible spacing if (preg_match('/\b' . preg_quote($brand, '/') . '\b/i', $description, $brand_match)) { // Common truck model patterns: // ACE 44, ACE 55, ACE 66 // Independent 139, 144, 149, 159, 169 // Thunder 143, 147, 151, 161 // Venture 5.0, 5.25, 5.8, 6.1 // NHS 5.0, 5.25, 5.5, 5.8, 6.1 // DLXSF 139, 144, 149, 159, 169 // Krux 8.0, 8.25, 8.5 // Pattern: brand followed by optional "AF" or similar, then model number if (preg_match('/\b' . preg_quote($brand, '/') . '\s*(?:AF[I1]\s*)?(\d{2,3}|\d\.\d{1,2})\b/i', $description, $model_match)) { $model = $model_match[1]; // Query database for matching truck $result = $wpdb->get_row($wpdb->prepare( "SELECT axle_width_inches FROM $table_name WHERE LOWER(brand) = LOWER(%s) AND model = %s LIMIT 1", $brand, $model )); if ($result && $result->axle_width_inches) { // Format to remove unnecessary trailing zeros (8.000 -> 8.0, 8.250 -> 8.25) $width = (float)$result->axle_width_inches; // Use rtrim to remove trailing zeros, but keep at least one decimal place $formatted = rtrim(rtrim(number_format($width, 3, '.', ''), '0'), '.'); // Ensure at least one decimal place if (strpos($formatted, '.') === false) { $formatted .= '.0'; } return $formatted; } } } } return null; // Special-case: 'bearing' + 'lube/speed cream/grease' -> Lubricants if (preg_match('/\bbearings?\b/', $name) && preg_match('/\b(lube|lubes|lubricant|lubricants|speed cream|speedcream|grease|wax|silicone)\b/', $name)) { return [ 'category_name' => 'Accessories > Lubricants', 'category_code' => 'LU' ]; } } // Register activation hook register_activation_hook(__FILE__, 'product_machine_create_truck_sizes_table'); // Add admin action to initialize truck sizes table add_action('wp_ajax_pm_init_truck_table', function() { if (!current_user_can('manage_options')) { wp_die('Unauthorized'); } // Force creation of WordPress table since external DB is likely unavailable global $wpdb; $table_name = $wpdb->prefix . 'product_machine_truck_sizes'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id INT AUTO_INCREMENT PRIMARY KEY, brand VARCHAR(100) NOT NULL, model VARCHAR(100) NOT NULL, hanger_width_mm DECIMAL(5,2) NOT NULL, axle_width_inches DECIMAL(6,3) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY unique_truck (brand, model) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"; $result = $wpdb->query($sql); if ($result !== false) { // Add sample data $truck_sizes = [ ['ACE', '44', 44.00, 8.000], ['ACE', '55', 55.00, 8.250], ['Independent', '149', 149.00, 8.375], ['Thunder', '147', 147.00, 8.250], ['Venture', '5.25', 133.00, 8.000] ]; foreach ($truck_sizes as $truck) { $wpdb->replace($table_name, [ 'brand' => $truck[0], 'model' => $truck[1], 'hanger_width_mm' => $truck[2], 'axle_width_inches' => $truck[3] ]); } $count = $wpdb->get_var("SELECT COUNT(*) FROM $table_name"); wp_send_json_success(['message' => "Table created successfully with $count rows", 'count' => $count]); } else { wp_send_json_error(['message' => 'Table creation failed: ' . $wpdb->last_error]); } }); // Enqueue scripts and styles for the plugin add_action('admin_enqueue_scripts', 'product_machine_admin_scripts'); add_action( 'admin_notices', function () { global $pm_errors; if ( empty( $pm_errors ) ) { return; } echo '

Product-Machine debug:
'; foreach ( $pm_errors as $msg ) { echo esc_html( $msg ) . '
'; } echo '

'; // clear so we don’t print the same lines 10× on ajax-driven pages $pm_errors = []; } ); //dequeue elex because they suck function dequeue_usps_shipping_js() { // Check if we're not on a WooCommerce or USPS settings page if (!is_admin() || !isset($_GET['page']) || $_GET['page'] !== 'woocommerce_wf_shipping_usps_settings') { wp_dequeue_script('wf_common_js'); // Replace with the correct handle of the script } } add_action('admin_enqueue_scripts', 'dequeue_usps_shipping_js', 100); // Conditionally dequeue the flexible shipping UPS script if it's loaded in the admin add_action('admin_enqueue_scripts', function () { if (isset($_GET['page']) && $_GET['page'] === 'product-machine-unpack-words') { wp_dequeue_script('flexible-shipping-ups-blocks-integration-frontend'); } }, 100); // Force scripts to load in the footer on specific admin page add_action('admin_enqueue_scripts', function () { if (isset($_GET['page']) && $_GET['page'] === 'product-machine-unpack-words') { global $wp_scripts; foreach ($wp_scripts->queue as $handle) { $wp_scripts->registered[$handle]->args = 1; } } }); add_action('wp_print_scripts', function () { if (isset($_GET['page']) && $_GET['page'] === 'product-machine-unpack-words') { wp_deregister_script('flexible-shipping-ups-blocks-integration-frontend'); } }, 100); function forcefully_deregister_flexible_shipping_script() { wp_deregister_script('flexible-shipping-ups-blocks-integration-frontend'); } add_action('wp_enqueue_scripts', 'forcefully_deregister_flexible_shipping_script', 100); add_action('admin_enqueue_scripts', 'forcefully_deregister_flexible_shipping_script', 100); add_action('login_enqueue_scripts', 'forcefully_deregister_flexible_shipping_script', 100); // Fetch unpack words from the database (AJAX handler) add_action('wp_ajax_pm_fetch_unpack_words', 'pm_fetch_unpack_words'); function pm_fetch_unpack_words() { check_ajax_referer('pm_ajax_nonce', 'nonce'); // Connect to the external database $db = pm_get_external_db_connection(); if (!$db) { wp_send_json_error('Database connection error: ' . mysqli_connect_error()); } $results = []; $query = "SELECT unpacking_word_ref_num, unpacking_word FROM product_machine_unpack_words"; $result = $db->query($query); if ($result) { while ($row = $result->fetch_assoc()) { $results[] = $row; } // Log the results for debugging error_log(print_r($results, true)); // Send the results directly without wrapping in 'data' key wp_send_json_success($results); } else { wp_send_json_error('Error fetching data: ' . $db->error); } $db->close(); wp_die(); // End the AJAX request } // Delete an unpack word by ID (AJAX handler) add_action('wp_ajax_pm_delete_unpack_word', 'pm_delete_unpack_word'); function pm_delete_unpack_word() { check_ajax_referer('pm_ajax_nonce', 'nonce'); if (!isset($_POST['id'])) { wp_send_json_error('Invalid ID'); } // Connect to the external database $db = pm_get_external_db_connection(); if (!$db) { wp_send_json_error('Database connection error: ' . mysqli_connect_error()); } $id = intval($_POST['id']); // Some tables use 'unpacking_word_ref_num' as primary key; support both $query = "DELETE FROM product_machine_unpack_words WHERE unpacking_word_ref_num = ? OR id = ?"; $stmt = $db->prepare($query); $stmt->bind_param('ii', $id, $id); $stmt->execute(); if ($stmt->affected_rows > 0) { wp_send_json_success(); } else { $err = $stmt->error ?: $db->error ?: 'Unknown error'; error_log('pm_delete_unpack_word: delete failed: ' . $err); wp_send_json_error('Failed to delete record: ' . $err); } $stmt->close(); $db->close(); wp_die(); // End the AJAX request } // Save (create or update) an unpack word (AJAX handler) add_action('wp_ajax_pm_save_unpack_word', 'pm_save_unpack_word'); function pm_save_unpack_word() { check_ajax_referer('pm_ajax_nonce', 'nonce'); if (!isset($_POST['unpack_word'])) { wp_send_json_error('Missing unpack_word parameter'); } $unpack_word = sanitize_text_field($_POST['unpack_word']); if ($unpack_word === '') { wp_send_json_error('Unpack word cannot be empty'); } $id = isset($_POST['id']) ? intval($_POST['id']) : 0; $db = pm_get_external_db_connection(); if (!$db) { wp_send_json_error('Database connection error: ' . mysqli_connect_error()); } if ($id > 0) { $query = "UPDATE product_machine_unpack_words SET unpacking_word = ? WHERE unpacking_word_ref_num = ? OR id = ?"; $stmt = $db->prepare($query); if (!$stmt) { wp_send_json_error('Prepare failed: ' . $db->error); } $stmt->bind_param('sii', $unpack_word, $id, $id); $stmt->execute(); if ($stmt->affected_rows >= 0) { wp_send_json_success(); } else { wp_send_json_error('Failed to update record'); } $stmt->close(); $db->close(); wp_die(); } // Insert new unpack word // Include unpacking_word_selling_quan with a reasonable default (1) to avoid missing-default DB errors $query = "INSERT INTO product_machine_unpack_words (unpacking_word, unpacking_word_replace, unpacking_word_received_quan, unpacking_word_selling_quan) VALUES (?, '', 1, 1)"; $stmt = $db->prepare($query); if (!$stmt) { error_log('pm_save_unpack_word: prepare failed: ' . $db->error); wp_send_json_error('Prepare failed: ' . $db->error); } $stmt->bind_param('s', $unpack_word); $stmt->execute(); if ($stmt->affected_rows > 0) { wp_send_json_success(['insert_id' => $db->insert_id]); } else { $err = $stmt->error ?: $db->error ?: 'Unknown error'; error_log('pm_save_unpack_word: insert failed: ' . $err); error_log('pm_save_unpack_word: insert failed, attempting comprehensive schema-aware fallback: ' . $err); // Attempt a comprehensive schema-aware fallback: include any NOT NULL columns without defaults $cols_res = $db->query("SHOW COLUMNS FROM product_machine_unpack_words"); $required_cols = ['unpacking_word', 'unpacking_word_replace', 'unpacking_word_received_quan']; $values = []; // seed values for the default columns $values['unpacking_word'] = $unpack_word; $values['unpacking_word_replace'] = ''; $values['unpacking_word_received_quan'] = 1; if ($cols_res) { while ($col = $cols_res->fetch_assoc()) { $field = $col['Field']; // skip auto-increment PKs and columns we already have if (isset($values[$field])) { continue; } if (strpos($col['Extra'], 'auto_increment') !== false) { continue; } // If column is NOT NULL and has no default, we must supply a value $is_not_null = (strtoupper($col['Null']) === 'NO'); $has_default = !is_null($col['Default']); if ($is_not_null && !$has_default) { // choose sensible default based on type $type = $col['Type']; if (preg_match('/int|decimal|float|double|numeric/i', $type)) { $values[$field] = 1; } elseif (preg_match('/date|time|timestamp/i', $type)) { $values[$field] = '1970-01-01'; } else { $values[$field] = ''; } } } } // If we found any extra required columns, build a prepared INSERT with placeholders for all columns we will write if (count($values) > 0) { error_log('pm_save_unpack_word: comprehensive fallback will write columns: ' . json_encode(array_keys($values)) . ' values: ' . json_encode(array_values($values))); $cols = array_keys($values); $placeholders = array_fill(0, count($cols), '?'); $insert_query = "INSERT INTO product_machine_unpack_words (" . implode(', ', $cols) . ") VALUES (" . implode(', ', $placeholders) . ")"; $fallback_stmt = $db->prepare($insert_query); if ($fallback_stmt) { // Build types and bind values $types = ''; $bind_vals = []; foreach ($cols as $c) { $v = $values[$c]; if (is_int($v)) { $types .= 'i'; } else { $types .= 's'; } $bind_vals[] = $v; } // prepare bind_param arguments $bind_names = []; $bind_names[] = & $types; foreach ($bind_vals as $k => $v) { $bind_names[] = & $bind_vals[$k]; } call_user_func_array([$fallback_stmt, 'bind_param'], $bind_names); $fallback_stmt->execute(); if ($fallback_stmt->affected_rows > 0) { wp_send_json_success(['insert_id' => $db->insert_id, 'fallback' => true]); } else { error_log('pm_save_unpack_word: comprehensive fallback prepare/execute failed: ' . ($fallback_stmt->error ?: $db->error)); } } else { error_log('pm_save_unpack_word: failed to prepare comprehensive fallback: ' . $db->error); } } // As a last resort, attempt a direct INSERT with minimal columns we control $escaped_word = $db->real_escape_string($unpack_word); $fallback_query = "INSERT INTO product_machine_unpack_words (unpacking_word, unpacking_word_replace, unpacking_word_received_quan) VALUES ('" . $escaped_word . "', '', 1)"; if ($db->query($fallback_query)) { error_log('pm_save_unpack_word: direct fallback insert succeeded, id=' . $db->insert_id); wp_send_json_success(['insert_id' => $db->insert_id, 'fallback' => true, 'direct' => true]); } else { error_log('pm_save_unpack_word: direct fallback insert failed: ' . $db->error); } // If duplicate entry, try to locate existing row and return it if (stripos($err, 'duplicate') !== false || stripos($err, 'Duplicate') !== false) { $search = $db->real_escape_string($unpack_word); $res = $db->query("SELECT unpacking_word_ref_num AS id FROM product_machine_unpack_words WHERE unpacking_word = '" . $search . "' LIMIT 1"); if ($res && $row = $res->fetch_assoc()) { wp_send_json_success(['insert_id' => intval($row['id']), 'duplicate' => true]); } wp_send_json_error('Duplicate record exists but could not fetch it.'); } wp_send_json_error('Failed to insert record: ' . $err); } $stmt->close(); $db->close(); wp_die(); } // Handle AJAX request for data preview add_action('wp_ajax_pm_preview_data', 'pm_preview_data'); // Lightweight diagnostic endpoint to test admin-ajax reachability add_action('wp_ajax_pm_diag', function () { check_ajax_referer('pm_ajax_nonce', 'nonce'); $info = [ 'php' => phpversion(), 'wp' => get_bloginfo('version'), 'time' => date('c'), 'memory_limit' => ini_get('memory_limit'), 'max_execution_time' => ini_get('max_execution_time'), 'loaded_extensions' => array_slice(get_loaded_extensions(), 0, 10), ]; wp_send_json_success(['diag' => $info]); }); // Early fatal catcher for preview requests (runs before handler) add_action('admin_init', function () { if (!is_admin()) { return; } if (!isset($_POST['action']) || $_POST['action'] !== 'pm_preview_data') { return; } // Install a shutdown handler to emit JSON if something fatal happens before pm_preview_data executes $start = microtime(true); register_shutdown_function(function () use ($start) { $e = error_get_last(); if ($e && in_array($e['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) { while (ob_get_level() > 0) { @ob_end_clean(); } if (!headers_sent()) { header('Content-Type: application/json; charset=utf-8'); } echo wp_json_encode([ 'success' => false, 'data' => [ 'message' => 'Fatal before handler', 'fatal' => $e, 'diag' => [ 'duration_ms' => round((microtime(true) - $start) * 1000, 2) ] ] ]); exit; } }); }); /* * Admin product list search: include direct_distributor_sku and wholesaler_sku in the search * without affecting default title/content search. Filters are limited to the admin products list. */ add_filter('posts_join', function ($join, $query) { global $pagenow, $wpdb; if (!is_admin() || $pagenow !== 'edit.php') return $join; if (!isset($query->query['post_type']) || $query->query['post_type'] !== 'product') return $join; if (empty($query->query['s'])) return $join; // Join postmeta table for the SKU meta keys so we can search against them $join .= " LEFT JOIN {$wpdb->postmeta} pm_sku ON ({$wpdb->posts}.ID = pm_sku.post_id AND pm_sku.meta_key IN ('_sku','wholesaler_sku','direct_distributor_sku'))"; return $join; }, 10, 2); add_filter('posts_where', function ($where, $query) { global $pagenow, $wpdb; if (!is_admin() || $pagenow !== 'edit.php') return $where; if (!isset($query->query['post_type']) || $query->query['post_type'] !== 'product') return $where; if (empty($query->query['s'])) return $where; $q = $wpdb->esc_like($query->query['s']); $like = "%{$q}%"; // Keep the existing title/content search, but OR in meta value searches $where .= $wpdb->prepare(" OR pm_sku.meta_value LIKE %s", $like); return $where; }, 10, 2); add_filter('posts_distinct', function ($distinct, $query) { global $pagenow; if (!is_admin() || $pagenow !== 'edit.php') return $distinct; if (!isset($query->query['post_type']) || $query->query['post_type'] !== 'product') return $distinct; if (empty($query->query['s'])) return $distinct; return 'DISTINCT'; }, 10, 2); // REST API fallback for preview to avoid admin-ajax proxies/WAF blocking add_action('rest_api_init', function () { register_rest_route('product-machine/v1', '/preview', [ 'methods' => 'POST', 'permission_callback' => function () { return current_user_can('manage_options'); }, 'callback' => function (WP_REST_Request $request) { // Start output buffering to prevent any notices/warnings from breaking JSON ob_start(); // CRITICAL: Check database connection FIRST global $wpdb; if (!$wpdb || $wpdb->last_error) { ob_end_clean(); return new WP_REST_Response([ 'success' => false, 'data' => [ 'message' => '🚨 DATABASE CONNECTION FAILED - Cannot process preview without database access', 'error' => $wpdb ? $wpdb->last_error : 'WordPress database object is null', 'db_host' => defined('DB_HOST') ? DB_HOST : 'undefined' ] ], 500); } // Test a simple query to verify DB is actually working $test_query = $wpdb->get_var("SELECT 1"); if ($test_query !== '1') { ob_end_clean(); return new WP_REST_Response([ 'success' => false, 'data' => [ 'message' => '🚨 DATABASE NOT RESPONDING - Query test failed', 'error' => $wpdb->last_error ?: 'Query returned: ' . var_export($test_query, true) ] ], 500); } $raw_data = (string) $request->get_param('raw_data'); $invoice = (string) $request->get_param('invoicename'); $sku_usage = (string) $request->get_param('sku_usage'); $debug = (int) $request->get_param('debug'); $lite = (bool) $request->get_param('lite'); if ($raw_data === '') { ob_end_clean(); return new WP_REST_Response(['success' => false, 'data' => ['message' => 'No data provided.']], 400); } try { $products = product_machine_process_raw_data($raw_data, $invoice, $sku_usage, $lite); } catch (Throwable $t) { ob_end_clean(); return new WP_REST_Response([ 'success' => false, 'data' => [ 'message' => 'Processing exception', 'throwable' => [ 'type' => get_class($t), 'msg' => $t->getMessage(), 'file' => $t->getFile(), 'line' => $t->getLine() ] ] ], 500); } $debug_log = ''; if ($debug) { $debug_log = pm_get_last_lines_of_file(WP_CONTENT_DIR . '/debug.log', 100, 'product-machine'); } // Discard any unexpected output ob_end_clean(); return new WP_REST_Response([ 'success' => true, 'data' => [ 'products' => $products, 'errors' => [], 'debug_log' => $debug_log ] ], 200); } ]); // Import one row idempotently by invoice and line register_rest_route('product-machine/v1', '/import-one', [ 'methods' => 'POST', 'permission_callback' => function () { return current_user_can('manage_options'); }, 'callback' => function (WP_REST_Request $request) { // CRITICAL: Check database connection FIRST (same behavior as preview route) global $wpdb; if (!$wpdb || $wpdb->last_error) { return new WP_REST_Response([ 'success' => false, 'data' => [ 'message' => '🚨 DATABASE CONNECTION FAILED - Cannot import without database access', 'error' => $wpdb ? $wpdb->last_error : 'WordPress database object is null', 'db_host' => defined('DB_HOST') ? DB_HOST : 'undefined' ] ], 500); } // Basic DB sanity check $test_query = $wpdb->get_var("SELECT 1"); if ($test_query !== '1') { return new WP_REST_Response([ 'success' => false, 'data' => [ 'message' => '🚨 DATABASE NOT RESPONDING - Query test failed', 'error' => $wpdb->last_error ?: 'Query returned: ' . var_export($test_query, true) ] ], 500); } $invoice = (string) $request->get_param('invoice'); $line = (string) $request->get_param('line'); // e.g. 006 $name = (string) $request->get_param('name'); $sku = (string) $request->get_param('sku'); $price = (string) $request->get_param('price'); $cost = (string) $request->get_param('cost'); $qty = (int) $request->get_param('qty'); $category = (string) $request->get_param('category'); $brand = (string) $request->get_param('brand'); if ($invoice === '' || $line === '') { return new WP_REST_Response(['success' => false, 'data' => ['message' => 'Missing invoice or line']], 400); } // Build a deterministic meta key and SKU fallback for idempotency $invoice_line_id = $invoice . '-' . str_pad($line, 3, '0', STR_PAD_LEFT); // If no SKU, generate a deterministic base using known data if ($sku === '') { $brand_code = get_brand_code($brand); $category_code = ''; $cat_info = find_product_category($name); if (is_array($cat_info)) { $category_code = $cat_info['category_code'] ?? ''; } $sku = generate_sku($category_code, $brand_code, $name); } // Check if a product with this invoice_line id already exists $existing = get_posts([ 'post_type' => 'product', 'meta_key' => 'pm_invoice_line', 'meta_value' => $invoice_line_id, 'posts_per_page' => 1, 'fields' => 'ids' ]); if (!empty($existing)) { $product_id = (int) $existing[0]; $product = wc_get_product($product_id); } else { // Try by SKU next (in case prior imports used SKU instead of meta). // Use pm_get_product_id_by_any_sku so we also look for wholesaler/distributor SKUs. $product_id = pm_get_product_id_by_any_sku($sku); if ($product_id) { $product = wc_get_product($product_id); } else { $product = new WC_Product(); } } // Apply updates if ($name !== '') { $product->set_name($name); $product->set_description($name); } if ($price !== '') { $product->set_price($price); $product->set_regular_price($price); } if ($sku !== '') { $product->set_sku($sku); } // Enable stock management and set quantity $product->set_manage_stock(true); if ($qty > 0) { $product->set_stock_quantity($qty); $product->set_stock_status('instock'); } else { $product->set_stock_quantity(0); $product->set_stock_status('outofstock'); } // Categories - handle hierarchical categories if ($category !== '') { $term_id = create_hierarchical_category($category); if ($term_id) { $product->set_category_ids([$term_id]); } } // Attributes handled below via taxonomy-aware merge // Attributes - merge with existing attributes using taxonomy when available try { $brand = isset($request['brand']) ? sanitize_text_field($request['brand']) : ''; $width_in = isset($request['width_in']) ? sanitize_text_field($request['width_in']) : ''; $size = isset($request['size']) ? sanitize_text_field($request['size']) : ''; $durometer = isset($request['durometer']) ? sanitize_text_field($request['durometer']) : ''; $existing_attrs = $product->get_attributes(); if (!is_array($existing_attrs)) { $existing_attrs = []; } // Helper to upsert a taxonomy attribute (or custom if taxonomy missing) $upsert_attr = function($attrs, $tax_slug, $label, $value) { if ($value === '' || $value === null) return $attrs; $is_tax = taxonomy_exists($tax_slug); $attr_obj = new WC_Product_Attribute(); if ($is_tax) { // Ensure term exists $term = get_term_by('name', $value, $tax_slug); if (!$term || is_wp_error($term)) { $ins = wp_insert_term($value, $tax_slug); if (!is_wp_error($ins) && isset($ins['term_id'])) { $term_id = (int)$ins['term_id']; } else { // fallback: cannot create term, treat as custom $is_tax = false; } } if ($is_tax) { $term = get_term_by('name', $value, $tax_slug); // refresh $term_id = $term && !is_wp_error($term) ? (int)$term->term_id : 0; $attr_obj->set_id(wc_attribute_taxonomy_id_by_name(str_replace('pa_', '', $tax_slug)) ?: 0); $attr_obj->set_name($tax_slug); $attr_obj->set_options($term_id ? [$term_id] : []); $attr_obj->set_visible(true); $attr_obj->set_variation(false); $attr_obj->set_position(0); // Merge: replace if exists by slug key, else add $attrs[$tax_slug] = $attr_obj; return $attrs; } } // Custom attribute fallback $attr_obj->set_id(0); $attr_obj->set_name($label); $attr_obj->set_options([$value]); $attr_obj->set_visible(true); $attr_obj->set_variation(false); $attr_obj->set_position(0); // Merge: replace by lowercase label key $attrs[strtolower($label)] = $attr_obj; return $attrs; }; // Upsert Brand and Width if (!empty($brand)) { $existing_attrs = $upsert_attr($existing_attrs, 'pa_brand', 'Brand', $brand); } if (!empty($width_in)) { $existing_attrs = $upsert_attr($existing_attrs, 'pa_width', 'Width', $width_in); } // Upsert Size (wheel diameter) and Durometer when present if (!empty($size)) { $existing_attrs = $upsert_attr($existing_attrs, 'pa_wheel-diameter', 'Size', $size); } if (!empty($durometer)) { $existing_attrs = $upsert_attr($existing_attrs, 'pa_durometer', 'Durometer', $durometer); } if (!empty($existing_attrs)) { $product->set_attributes($existing_attrs); } } catch (Throwable $e) { // keep going; attributes are best-effort } // Save and add invoice meta for idempotency try { $product_id = $product->save(); } catch (Throwable $e) { return new WP_REST_Response(['success' => false, 'data' => ['message' => 'Save failed', 'exception' => $e->getMessage()]], 500); } update_post_meta($product_id, 'pm_invoice', $invoice); update_post_meta($product_id, 'pm_invoice_line', $invoice_line_id); // Also save to ACF fields 'invoice' and 'invoice_line_number' if ACF is active (dual-field sync) if (function_exists('update_field')) { // Write base invoice ID to ACF 'invoice' field update_field('invoice', $invoice, $product_id); // Also try by field key if name mapping is not present @update_field('field_6339fa1d40f53', $invoice, $product_id); // Write full invoice line number to ACF 'invoice_line_number' field update_field('invoice_line_number', $invoice_line_id, $product_id); // Also try by field key if name mapping is not present @update_field('field_6339f9f540f52', $invoice_line_id, $product_id); } // Ensure raw meta is set so ACF can read values even if field key missing (dual-field guarantee) update_post_meta($product_id, 'invoice', $invoice); update_post_meta($product_id, '_invoice', 'field_6339fa1d40f53'); update_post_meta($product_id, 'invoice_line_number', $invoice_line_id); update_post_meta($product_id, '_invoice_line_number', 'field_6339f9f540f52'); // Update cost meta fields: ACF 'cost' and COGS plugin meta '_alg_wc_cog_cost' if ($cost !== '') { // ACF field write (by name; field key fallback optional if known) if (function_exists('update_field')) { @update_field('cost', $cost, $product_id); } // Raw post meta for ACF value update_post_meta($product_id, 'cost', $cost); // Common COGS plugin field update_post_meta($product_id, '_alg_wc_cog_cost', $cost); // Optional fallback some themes/plugins read @update_post_meta($product_id, '_cost', $cost); } // Build a normalized attributes string from the saved product and compare with expected $current_attributes_str = pm_build_current_attributes_string($product); // Build expected attributes string from input values (presence-based) $expected_pairs = []; if (!empty($brand)) { $expected_pairs[] = 'Brand: ' . $brand; } if (!empty($width_in)) { $w = rtrim($width_in, '"'); if (strpos($w, '"') === false) { $w = $w . '"'; } $expected_pairs[] = 'Width: ' . $w; } if (!empty($size)) { $expected_pairs[] = 'Size: ' . $size; } if (!empty($durometer)) { $expected_pairs[] = 'Durometer: ' . $durometer; } $expected_attributes_str = implode(', ', $expected_pairs); $attributes_match = pm_attributes_match($expected_attributes_str, $current_attributes_str); return new WP_REST_Response(['success' => true, 'data' => [ 'product_id' => $product_id, 'sku' => $product->get_sku(), 'invoice' => $invoice, 'invoice_line' => $invoice_line_id, 'edit_link' => get_edit_post_link($product_id), 'product_title' => get_the_title($product_id), 'current_attributes' => $current_attributes_str, 'expected_attributes' => $expected_attributes_str, 'attributes_match' => $attributes_match ]], 200); } ]); // Cleanup orphaned categories endpoint register_rest_route('product-machine/v1', '/cleanup-categories', [ 'methods' => 'POST', 'permission_callback' => function () { return current_user_can('manage_options'); }, 'callback' => function (WP_REST_Request $request) { $cleaned_up = cleanup_orphaned_categories(); return new WP_REST_Response(['success' => true, 'data' => [ 'cleaned_up' => $cleaned_up, 'message' => 'Category cleanup completed' ]], 200); } ]); // CRUD endpoints for Manage Products GUI // List all products register_rest_route('product-machine/v1', '/products', [ 'methods' => 'GET', 'permission_callback' => function () { return current_user_can('manage_options'); }, 'callback' => function (WP_REST_Request $request) { // Quick DB sanity check global $wpdb; if (!$wpdb || $wpdb->last_error) { return new WP_REST_Response(['success' => false, 'data' => ['message' => 'Database unavailable']], 500); } $args = [ 'post_type' => 'product', 'posts_per_page' => -1, 'post_status' => 'any' ]; $products_query = new WP_Query($args); $products = []; foreach ($products_query->posts as $post) { $product = wc_get_product($post->ID); if (!$product) continue; $products[] = [ 'id' => $post->ID, 'sku' => $product->get_sku(), 'name' => $product->get_name(), 'invoice_line' => get_post_meta($post->ID, 'pm_invoice_line', true), 'price' => $product->get_price(), 'stock' => $product->get_stock_quantity(), 'category' => implode(', ', wp_get_post_terms($post->ID, 'product_cat', ['fields' => 'names'])), 'brand' => get_post_meta($post->ID, '_wc_brand', true) // or from attributes ]; } return new WP_REST_Response(['success' => true, 'data' => ['products' => $products]], 200); } ]); // Get single product register_rest_route('product-machine/v1', '/products/(?P\d+)', [ 'methods' => 'GET', 'Categories' => $product_data['Categories'] ?? '', 'Category_Code' => $product_data['category_code'] ?? '', 'callback' => function (WP_REST_Request $request) { $product_id = (int) $request['id']; $product = wc_get_product($product_id); if (!$product) { return new WP_REST_Response(['success' => false, 'data' => ['message' => 'Product not found']], 404); } $invoice_line = get_post_meta($product_id, 'pm_invoice_line', true); $invoice = get_post_meta($product_id, 'pm_invoice', true); $line_parts = explode('-', $invoice_line); return new WP_REST_Response(['success' => true, 'data' => ['product' => [ 'id' => $product_id, 'sku' => $product->get_sku(), 'name' => $product->get_name(), 'invoice' => $invoice, 'line' => count($line_parts) > 1 ? end($line_parts) : '', 'price' => $product->get_price(), 'stock' => $product->get_stock_quantity(), 'category' => implode(', ', wp_get_post_terms($product_id, 'product_cat', ['fields' => 'names'])), 'brand' => get_post_meta($product_id, '_wc_brand', true) ]]], 200); } ]); // Create product register_rest_route('product-machine/v1', '/products', [ 'methods' => 'POST', 'permission_callback' => function () { return current_user_can('manage_options'); }, 'callback' => function (WP_REST_Request $request) { $sku = sanitize_text_field($request->get_param('sku')); $name = sanitize_text_field($request->get_param('name')); $price = (float) $request->get_param('price'); $qty = (int) $request->get_param('qty'); $category = sanitize_text_field($request->get_param('category')); $brand = sanitize_text_field($request->get_param('brand')); $invoice = sanitize_text_field($request->get_param('invoice')); $line = sanitize_text_field($request->get_param('line')); $cost = sanitize_text_field($request->get_param('cost')); if (empty($sku) || empty($name)) { return new WP_REST_Response(['success' => false, 'data' => ['message' => 'SKU and Name are required']], 400); } // Check if SKU already exists in any of the SKU fields (standard/wholesaler/distributor) if (pm_get_product_id_by_any_sku($sku)) { return new WP_REST_Response(['success' => false, 'data' => ['message' => 'SKU already exists']], 400); } $product = new WC_Product(); $product->set_sku($sku); $product->set_name($name); $product->set_description($name); $product->set_price($price); $product->set_regular_price($price); $product->set_stock_quantity($qty); if ($category) { $term = term_exists($category, 'product_cat'); if (!$term) $term = wp_insert_term($category, 'product_cat'); if (!is_wp_error($term)) { $product->set_category_ids([(int) ($term['term_id'] ?? $term->term_id ?? 0)]); } } $product_id = $product->save(); if ($invoice && $line) { $invoice_line_id = $invoice . '-' . str_pad($line, 3, '0', STR_PAD_LEFT); update_post_meta($product_id, 'pm_invoice', $invoice); update_post_meta($product_id, 'pm_invoice_line', $invoice_line_id); // Also save to ACF field 'invoice_line_number' if ACF is active if (function_exists('update_field')) { update_field('invoice_line_number', $invoice_line_id, $product_id); // Also ensure the base invoice ACF field is set update_field('invoice', $invoice, $product_id); @update_field('field_6339fa1d40f53', $invoice, $product_id); @update_field('field_6339f9f540f52', $invoice_line_id, $product_id); } // Ensure raw postmeta is present as fallback for ACF update_post_meta($product_id, 'invoice', $invoice); update_post_meta($product_id, '_invoice', 'field_6339fa1d40f53'); update_post_meta($product_id, 'invoice_line_number', $invoice_line_id); update_post_meta($product_id, '_invoice_line_number', 'field_6339f9f540f52'); } // Save optionally-supplied SKU-variant meta if present $direct_distributor_sku = sanitize_text_field($request->get_param('direct_distributor_sku')); $wholesaler_sku = sanitize_text_field($request->get_param('wholesaler_sku')); if (!empty($direct_distributor_sku)) { update_post_meta($product_id, 'direct_distributor_sku', $direct_distributor_sku); if (function_exists('update_field')) { update_field('direct_distributor_sku', $direct_distributor_sku, $product_id); } } if (!empty($wholesaler_sku)) { update_post_meta($product_id, 'wholesaler_sku', $wholesaler_sku); if (function_exists('update_field')) { update_field('wholesaler_sku', $wholesaler_sku, $product_id); } } if ($brand) { update_post_meta($product_id, '_wc_brand', $brand); } // Dual-field cost update if present if (!empty($cost)) { if (function_exists('update_field')) { @update_field('cost', $cost, $product_id); } update_post_meta($product_id, 'cost', $cost); update_post_meta($product_id, '_alg_wc_cog_cost', $cost); @update_post_meta($product_id, '_cost', $cost); } return new WP_REST_Response(['success' => true, 'data' => ['product_id' => $product_id]], 200); } ]); // Update product (excludes qty/stock) register_rest_route('product-machine/v1', '/products/(?P\d+)', [ 'methods' => 'PUT', 'permission_callback' => function () { return current_user_can('manage_options'); }, 'callback' => function (WP_REST_Request $request) { // Quick DB sanity check global $wpdb; if (!$wpdb || $wpdb->last_error) { return new WP_REST_Response(['success' => false, 'data' => ['message' => 'Database unavailable']], 500); } $product_id = (int) $request['id']; $product = wc_get_product($product_id); if (!$product) { return new WP_REST_Response(['success' => false, 'data' => ['message' => 'Product not found']], 404); } $sku = sanitize_text_field($request->get_param('sku')); $name = sanitize_text_field($request->get_param('name')); $price = (float) $request->get_param('price'); $category = sanitize_text_field($request->get_param('category')); $brand = sanitize_text_field($request->get_param('brand')); $invoice = sanitize_text_field($request->get_param('invoice')); $line = sanitize_text_field($request->get_param('line')); $cost = sanitize_text_field($request->get_param('cost')); if ($sku) $product->set_sku($sku); if ($name) { $product->set_name($name); $product->set_description($name); } if ($price) { $product->set_price($price); $product->set_regular_price($price); } // NOTE: qty is NOT updated on edit mode per requirements if ($category) { $term = term_exists($category, 'product_cat'); if (!$term) $term = wp_insert_term($category, 'product_cat'); if (!is_wp_error($term)) { $product->set_category_ids([(int) ($term['term_id'] ?? $term->term_id ?? 0)]); } } $product->save(); if ($invoice && $line) { $invoice_line_id = $invoice . '-' . str_pad($line, 3, '0', STR_PAD_LEFT); update_post_meta($product_id, 'pm_invoice', $invoice); update_post_meta($product_id, 'pm_invoice_line', $invoice_line_id); // Also save to ACF field 'invoice_line_number' if ACF is active if (function_exists('update_field')) { update_field('invoice_line_number', $invoice_line_id, $product_id); // Keep the base invoice ACF field in sync as well update_field('invoice', $invoice, $product_id); @update_field('field_6339fa1d40f53', $invoice, $product_id); @update_field('field_6339f9f540f52', $invoice_line_id, $product_id); } // Fallback raw postmeta to keep ACF in sync update_post_meta($product_id, 'invoice', $invoice); update_post_meta($product_id, '_invoice', 'field_6339fa1d40f53'); update_post_meta($product_id, 'invoice_line_number', $invoice_line_id); update_post_meta($product_id, '_invoice_line_number', 'field_6339f9f540f52'); } if ($brand) { update_post_meta($product_id, '_wc_brand', $brand); } // Dual-field cost update if present if (!empty($cost)) { if (function_exists('update_field')) { @update_field('cost', $cost, $product_id); } update_post_meta($product_id, 'cost', $cost); update_post_meta($product_id, '_alg_wc_cog_cost', $cost); @update_post_meta($product_id, '_cost', $cost); } return new WP_REST_Response(['success' => true, 'data' => ['product_id' => $product_id]], 200); } ]); // Delete product register_rest_route('product-machine/v1', '/products/(?P\d+)', [ 'methods' => 'DELETE', 'permission_callback' => function () { return current_user_can('manage_options'); }, 'callback' => function (WP_REST_Request $request) { $product_id = (int) $request['id']; $product = wc_get_product($product_id); if (!$product) { return new WP_REST_Response(['success' => false, 'data' => ['message' => 'Product not found']], 404); } $product->delete(true); // force delete return new WP_REST_Response(['success' => true, 'data' => ['message' => 'Product deleted']], 200); } ]); // Get truck sizes register_rest_route('product-machine/v1', '/truck-sizes', [ 'methods' => WP_REST_Server::READABLE, 'permission_callback' => function () { return current_user_can('manage_options'); }, 'callback' => function () { global $wpdb; $table_name = $wpdb->prefix . 'product_machine_truck_sizes'; $exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)); if ($exists !== $table_name) { return new WP_REST_Response(['success' => true, 'data' => []], 200); } $rows = $wpdb->get_results("SELECT id, brand, model, hanger_width_mm, axle_width_inches, created_at, updated_at FROM $table_name ORDER BY brand, model", ARRAY_A); if ($wpdb->last_error) { return new WP_REST_Response([ 'success' => false, 'data' => ['message' => 'Database error: ' . $wpdb->last_error] ], 500); } $truck_sizes = array_map(function ($row) { return [ 'id' => (int)$row['id'], 'brand' => $row['brand'], 'model' => $row['model'], 'hanger_width_mm' => (float)$row['hanger_width_mm'], 'axle_width_inches' => (float)$row['axle_width_inches'], 'created_at' => $row['created_at'], 'updated_at' => $row['updated_at'], ]; }, $rows ?: []); return new WP_REST_Response(['success' => true, 'data' => $truck_sizes], 200); } ]); // Get single truck size register_rest_route('product-machine/v1', '/truck-sizes/(?P\d+)', [ 'methods' => 'GET', 'permission_callback' => function () { return current_user_can('manage_options'); }, 'callback' => function (WP_REST_Request $request) { global $wpdb; $id = (int) $request['id']; $table_name = $wpdb->prefix . 'product_machine_truck_sizes'; $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $id), ARRAY_A); if (!$row) { return new WP_REST_Response(['success' => false, 'data' => ['message' => 'Truck size not found']], 404); } $truck_size = [ 'id' => (int)$row['id'], 'brand' => $row['brand'], 'model' => $row['model'], 'hanger_width_mm' => (float)$row['hanger_width_mm'], 'axle_width_inches' => (float)$row['axle_width_inches'], 'created_at' => $row['created_at'], 'updated_at' => $row['updated_at'] ]; return new WP_REST_Response(['success' => true, 'data' => $truck_size], 200); } ]); // Create truck size register_rest_route('product-machine/v1', '/truck-sizes', [ 'methods' => WP_REST_Server::CREATABLE, 'permission_callback' => function () { return current_user_can('manage_options'); }, 'callback' => function (WP_REST_Request $request) { global $wpdb; $params = $request->get_json_params(); $required = ['brand', 'model', 'hanger_width_mm', 'axle_width_inches']; foreach ($required as $field) { if (!isset($params[$field]) || $params[$field] === '') { return new WP_REST_Response([ 'success' => false, 'data' => ['message' => sprintf('Missing required field: %s', $field)] ], 400); } } if (!is_numeric($params['hanger_width_mm']) || !is_numeric($params['axle_width_inches'])) { return new WP_REST_Response([ 'success' => false, 'data' => ['message' => 'Widths must be numeric values'] ], 400); } $table_name = $wpdb->prefix . 'product_machine_truck_sizes'; $axle_width = round((float)$params['axle_width_inches'] * 8) / 8; $result = $wpdb->insert($table_name, [ 'brand' => sanitize_text_field($params['brand']), 'model' => sanitize_text_field($params['model']), 'hanger_width_mm' => round((float)$params['hanger_width_mm'], 2), 'axle_width_inches' => $axle_width ], ['%s', '%s', '%f', '%f']); if (!$result) { $message = $wpdb->last_error ?: 'Unknown database error'; if (stripos($message, 'duplicate') !== false) { return new WP_REST_Response([ 'success' => false, 'data' => ['message' => 'Truck size already exists for that brand/model'] ], 409); } return new WP_REST_Response([ 'success' => false, 'data' => ['message' => 'Failed to create truck size: ' . $message] ], 500); } return new WP_REST_Response([ 'success' => true, 'data' => ['id' => $wpdb->insert_id] ], 201); } ]); // Update truck size register_rest_route('product-machine/v1', '/truck-sizes/(?P\d+)', [ 'methods' => WP_REST_Server::EDITABLE, 'permission_callback' => function () { return current_user_can('manage_options'); }, 'callback' => function (WP_REST_Request $request) { global $wpdb; $id = (int) $request['id']; $params = $request->get_json_params(); $required = ['brand', 'model', 'hanger_width_mm', 'axle_width_inches']; foreach ($required as $field) { if (!isset($params[$field]) || $params[$field] === '') { return new WP_REST_Response([ 'success' => false, 'data' => ['message' => sprintf('Missing required field: %s', $field)] ], 400); } } if (!is_numeric($params['hanger_width_mm']) || !is_numeric($params['axle_width_inches'])) { return new WP_REST_Response([ 'success' => false, 'data' => ['message' => 'Widths must be numeric values'] ], 400); } $table_name = $wpdb->prefix . 'product_machine_truck_sizes'; $axle_width = round((float)$params['axle_width_inches'] * 8) / 8; $result = $wpdb->update( $table_name, [ 'brand' => sanitize_text_field($params['brand']), 'model' => sanitize_text_field($params['model']), 'hanger_width_mm' => round((float)$params['hanger_width_mm'], 2), 'axle_width_inches' => $axle_width ], ['id' => $id], ['%s', '%s', '%f', '%f'], ['%d'] ); if ($result === false) { $message = $wpdb->last_error ?: 'Unknown database error'; if (stripos($message, 'duplicate') !== false) { return new WP_REST_Response([ 'success' => false, 'data' => ['message' => 'Truck size already exists for that brand/model'] ], 409); } return new WP_REST_Response([ 'success' => false, 'data' => ['message' => 'Failed to update truck size: ' . $message] ], 500); } if ($result === 0) { $exists = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $table_name WHERE id = %d", $id)); if (!$exists) { return new WP_REST_Response([ 'success' => false, 'data' => ['message' => 'Truck size not found'] ], 404); } } return new WP_REST_Response([ 'success' => true, 'data' => ['message' => 'Truck size updated'] ], 200); } ]); // Delete truck size register_rest_route('product-machine/v1', '/truck-sizes/(?P\d+)', [ 'methods' => 'DELETE', 'permission_callback' => function () { return current_user_can('manage_options'); }, 'callback' => function (WP_REST_Request $request) { global $wpdb; $id = (int) $request['id']; $table_name = $wpdb->prefix . 'product_machine_truck_sizes'; $result = $wpdb->delete($table_name, ['id' => $id]); if ($result === false) { return new WP_REST_Response(['success' => false, 'data' => ['message' => 'Failed to delete truck size: ' . $wpdb->last_error]], 500); } if ($result === 0) { return new WP_REST_Response(['success' => false, 'data' => ['message' => 'Truck size not found']], 404); } return new WP_REST_Response(['success' => true, 'data' => ['message' => 'Truck size deleted']], 200); } ]); // Debug endpoint to initialize truck sizes table register_rest_route('product-machine/v1', '/truck-sizes-debug', [ 'methods' => 'GET', 'permission_callback' => function () { return current_user_can('manage_options'); }, 'callback' => function (WP_REST_Request $request) { $conn = pm_get_external_db_connection(); if (!$conn) { return new WP_REST_Response(['success' => false, 'data' => ['message' => 'Database connection failed']], 500); } // Check if table exists $result = $conn->query("SHOW TABLES LIKE 'truck_sizes'"); $table_exists = $result && $result->num_rows > 0; if (!$table_exists) { // Create table product_machine_create_truck_sizes_table(); $message = 'Table created and sample data populated'; } else { // Check row count $count_result = $conn->query("SELECT COUNT(*) as count FROM truck_sizes"); $row_count = $count_result ? $count_result->fetch_assoc()['count'] : 0; if ($row_count == 0) { product_machine_populate_sample_truck_sizes(); $message = 'Table exists but was empty - sample data populated'; } else { $message = 'Table exists with ' . $row_count . ' rows'; } } $conn->close(); return new WP_REST_Response(['success' => true, 'data' => ['message' => $message, 'table_exists' => $table_exists]], 200); } ]); }); // Extra guard: dequeue problematic third-party script on our admin pages add_action('admin_enqueue_scripts', function ($hook) { $pm_pages = [ 'toplevel_page_product-machine', 'product-machine_page_product-machine-brands', 'product-machine_page_product-machine-unpack-words', 'product-machine_page_product-machine-manage-products', 'product-machine_page_product-machine-truck-sizes' ]; if (!in_array($hook, $pm_pages)) { return; } global $wp_scripts; if (!$wp_scripts) { return; } foreach ((array) $wp_scripts->queue as $handle) { $reg = $wp_scripts->registered[$handle] ?? null; if (!$reg) { continue; } $src = (string) ($reg->src ?? ''); if (strpos($src, 'wf_common') !== false) { wp_dequeue_script($handle); wp_deregister_script($handle); error_log('product-machine: dequeued script handle ' . $handle . ' (wf_common*.js) on ' . $hook); } } }, 1000); // Define the function if needed in `product-machine.php` function product_machine_unpack_words_page() { if (!current_user_can('manage_options')) { return; } include plugin_dir_path(__FILE__) . 'templates/product-machine-unpack-words.php'; } // Function to get unpacking words from the database function get_unpacking_words() { // Connect to the external database $db = pm_get_external_db_connection(); if (!$db) { return []; } $result = $db->query("SELECT * FROM product_machine_unpack_words"); if (!$result) { $db->close(); return []; } $unpacking_words = []; while ($row = $result->fetch_assoc()) { $unpacking_words[] = $row; } $db->close(); return $unpacking_words; } add_action('init', 'register_brand_taxonomy'); function register_brand_taxonomy() { $attribute_slug = 'brand-name'; $attribute_label = 'Brand'; if (!taxonomy_exists($attribute_slug)) { register_taxonomy( $attribute_slug, apply_filters('woocommerce_taxonomy_objects_' . $attribute_slug, ['product']), apply_filters('woocommerce_taxonomy_args_' . $attribute_slug, [ 'labels' => ['name' => $attribute_label], 'hierarchical' => true, 'show_ui' => false, 'query_var' => true, 'rewrite' => false, ]) ); } } // Register custom attribute taxonomies for 'wheel-diameter' and 'durometer' add_action('init', 'register_custom_attributes_taxonomy'); function register_custom_attributes_taxonomy() { $attributes = [ ['name' => 'wheel-diameter', 'label' => 'Wheel Diameter'], ['name' => 'durometer', 'label' => 'Durometer'] ]; foreach ($attributes as $attribute) { $attribute_taxonomy = wc_attribute_taxonomy_name($attribute['name']); // Check if taxonomy is already registered if (!taxonomy_exists($attribute_taxonomy)) { register_taxonomy( $attribute_taxonomy, apply_filters('woocommerce_taxonomy_objects_' . $attribute_taxonomy, ['product']), apply_filters('woocommerce_taxonomy_args_' . $attribute_taxonomy, [ 'labels' => [ 'name' => $attribute['label'], ], 'hierarchical' => true, 'show_ui' => false, 'query_var' => true, 'rewrite' => false, ]) ); } } } // Function to get pebs_cat_margin from the database function get_pebs_cat_margin($pebs_cat_code) { static $margin_cache = []; if (array_key_exists($pebs_cat_code, $margin_cache)) { return $margin_cache[$pebs_cat_code]; } // First, try to read margin from the in-plugin Category Reference option $cats = get_option('product_machine_category_ref', []); if (is_array($cats) && !empty($cats)) { foreach ($cats as $c) { if (isset($c['code']) && strtoupper(trim($c['code'])) === strtoupper(trim($pebs_cat_code))) { // Expect 'margin' as percent (e.g., 50 = 50%). Convert to multiplier. $m = isset($c['margin']) ? floatval($c['margin']) : null; if ($m !== null && $m !== '') { $mult = 1.0 + ($m / 100.0); $margin_cache[$pebs_cat_code] = $mult; return $margin_cache[$pebs_cat_code]; } } } } // Fallback: connect to the external database (legacy source) $db = pm_get_external_db_connection(); if (!$db) { $margin_cache[$pebs_cat_code] = false; return false; } // Prepare and execute the query $stmt = $db->prepare("SELECT pebs_cat_margin FROM product_machine WHERE pebs_cat_code = ?"); $stmt->bind_param('s', $pebs_cat_code); $stmt->execute(); $stmt->bind_result($pebs_cat_margin); $stmt->fetch(); $stmt->close(); $db->close(); // Normalize legacy DB value: if it's a reasonable percent (1..100) treat as percent, if it's a multiplier (<1 or >1 with decimals) use as-is if ($pebs_cat_margin === null || $pebs_cat_margin === '') { $margin_cache[$pebs_cat_code] = false; return false; } $val = floatval($pebs_cat_margin); if ($val > 0 && $val <= 100 && intval($val) == $val) { // integer percent like 50 -> 1.5 $mult = 1.0 + ($val / 100.0); } elseif ($val > 0 && $val < 1.0) { // already a multiplier like 0.5 (unlikely) — treat as multiplier $mult = $val; } elseif ($val > 1.0 && $val < 10.0) { // could be multiplier like 1.5 or percent expressed as 150 (unlikely); assume multiplier $mult = $val; } else { // Fallback: treat as false $margin_cache[$pebs_cat_code] = false; return false; } $margin_cache[$pebs_cat_code] = $mult; return $margin_cache[$pebs_cat_code]; } function map_raw_data_to_wc_csv_fields($product_data) { return [ 'ID' => '', 'Type' => 'simple', 'SKU' => $product_data['SKU'] ?? '', 'Name' => $product_data['Name'] ?? '', 'Published' => $product_data['Published'] ?? '1', 'Is featured?' => $product_data['Is_featured'] ?? '0', 'Visibility in catalog' => $product_data['Visibility_in_catalog'] ?? 'visible', 'Short description' => $product_data['Short_description'] ?? '', 'Description' => $product_data['Description'] ?? '', 'Tax status' => $product_data['Tax_status'] ?? 'taxable', 'In stock?' => '1', 'Stock' => $product_data['Stock'] ?? '0', 'Regular price' => $product_data['Regular_price'] ?? '', 'Categories' => $product_data['Categories'] ?? '', 'Weight (lbs)' => $product_data['Weight_lbs'] ?? '', 'Width (in)' => $product_data['Width_in'] ?? '', // Add other fields as required by WooCommerce... ]; } // add_action('admin_enqueue_scripts', 'product_machine_admin_scripts'); function pm_test_db_connection() { $db = pm_get_external_db_connection(); if (!$db) { echo 'Database connection error: ' . mysqli_connect_error(); } else { echo 'Database connection successful!'; $db->close(); } die(); // Stop further execution } function product_machine_import_data() { if (empty($_POST['raw-data'])) { echo '

Please provide the raw data.

'; return; } $raw_data = sanitize_textarea_field($_POST['raw-data']); $invoice_name = isset($_POST['invoicename']) ? sanitize_text_field($_POST['invoicename']) : ''; $outputstyle = isset($_POST['outputstyle']) ? sanitize_text_field($_POST['outputstyle']) : 'screen'; $sku_usage = isset($_POST['sku_usage']) ? sanitize_text_field($_POST['sku_usage']) : 'default'; $products = product_machine_process_raw_data($raw_data, $invoice_name, $sku_usage); if ($outputstyle == 'csv') { product_machine_export_csv($products); } else { $errors = []; $success_count = 0; } foreach ($products as $product_data) { // Create or update product in WooCommerce // function create_or_update_product($quantity, $name, $price, $product_data, $sku) $result = create_or_update_product( $product_data['Stock'], $product_data['Name'], $product_data['Regular_price'], $product_data, $product_data['SKU'] ); // create_or_update_product returns product ID (int); count as success $success_count++; } if (!empty($pm_errors)) { echo '

Warnings:
' . implode('
', array_map('esc_html', $pm_errors)) . '

'; } // Display results if ($success_count > 0) { echo '

Successfully imported ' . $success_count . ' products.

'; } if (!empty($errors)) { echo '

' . implode('
', $errors) . '

'; } } // Main plugin page function product_machine_page() { if (!current_user_can('manage_options')) { return; } if (!function_exists('wp_create_nonce')) { echo 'Fatal: wp_create_nonce not defined during product_machine_page call'; return; } // Auto-complete the invoice/invoice_line_number TODO once, if it's still open. try { $todos = get_option('product_machine_todos', []); if (is_array($todos)) { $changed = false; $has_cost_row = false; foreach ($todos as $i => $t) { $desc = isset($t['description']) ? strtolower($t['description']) : ''; $status = isset($t['status']) ? $t['status'] : 'not-started'; if ($status !== 'completed' && strpos($desc, 'invoice') !== false && strpos($desc, 'invoice_line') !== false) { $todos[$i]['status'] = 'completed'; // Prefer the code commit that fixed the write path $todos[$i]['commit'] = 'ed3e5d09'; if (!isset($todos[$i]['completed_at']) || empty($todos[$i]['completed_at'])) { $todos[$i]['completed_at'] = date('c'); } $changed = true; } // Also find/auto-complete the cost fields row if present if (strpos($desc, 'cost') !== false && (strpos($desc, 'cog') !== false || strpos($desc, 'alg_wc_cog_cost') !== false)) { $has_cost_row = true; if ($status !== 'completed') { $todos[$i]['status'] = 'completed'; $todos[$i]['commit'] = 'b906964e'; // commit that writes both fields + preview check if (!isset($todos[$i]['completed_at']) || empty($todos[$i]['completed_at'])) { $todos[$i]['completed_at'] = date('c'); } $changed = true; } } } // Ensure an Attributes fact-checker row exists and is completed $has_attr_row = false; foreach ($todos as $i => $t) { $desc = isset($t['description']) ? strtolower($t['description']) : ''; if (strpos($desc, 'attribute') !== false && (strpos($desc, 'fact-check') !== false || strpos($desc, 'checker') !== false)) { $has_attr_row = true; if ($todos[$i]['status'] !== 'completed') { $todos[$i]['status'] = 'completed'; // Reserve 'commit' for real hashes; record timestamp in 'completed_at' if (empty($todos[$i]['completed_at'])) { $todos[$i]['completed_at'] = date('c'); } if (!isset($todos[$i]['commit']) || !preg_match('/^[0-9a-f]{7,40}$/i', (string) $todos[$i]['commit'])) { $todos[$i]['commit'] = ''; } $changed = true; } } } if (!$has_attr_row) { $todos[] = [ 'description' => "Attributes fact-checker after 'Update' (Brand/Width/Size/Durometer; immediate UI flip)", 'status' => 'completed', 'commit' => '', // no specific commit hash recorded 'completed_at' => date('c'), ]; $changed = true; } // Ensure a Brand Add URL/schema fix row exists and is completed $has_brand_row = false; foreach ($todos as $i => $t) { $desc = isset($t['description']) ? strtolower($t['description']) : ''; if (strpos($desc, 'brand') !== false && (strpos($desc, 'url') !== false || strpos($desc, 'eastern_brand_code') !== false)) { $has_brand_row = true; if ($todos[$i]['status'] !== 'completed') { $todos[$i]['status'] = 'completed'; if (empty($todos[$i]['completed_at'])) { $todos[$i]['completed_at'] = date('c'); } if (!isset($todos[$i]['commit']) || !preg_match('/^[0-9a-f]{7,40}$/i', (string) $todos[$i]['commit'])) { $todos[$i]['commit'] = ''; } $changed = true; } } } if (!$has_brand_row) { $todos[] = [ 'description' => "Brand Add: strip http(s) from URL and include eastern_brand_code", 'status' => 'completed', 'commit' => '', 'completed_at' => date('c'), ]; $changed = true; } // If there is no cost QA row at all, append one with explicit wording if (!$has_cost_row) { $todos[] = [ 'description' => "Both costs QA'd and updated w/ 'Update' (ACF cost + _alg_wc_cog_cost)", 'status' => 'completed', 'commit' => 'b906964e', 'completed_at' => date('c'), ]; $changed = true; } // Remove duplication: 'Bones Bearings Tool' TODO is considered part of 'Bones brand verification' if ($changed) { update_option('product_machine_todos', $todos); } // Deduplicate TODOs by normalized description, preferring entries with a commit $normalized = []; $deduped = []; foreach ($todos as $t) { $key = strtolower(trim(preg_replace('/\s+/', ' ', ($t['description'] ?? '')))); if ($key === '') continue; if (!isset($normalized[$key])) { $normalized[$key] = $t; } else { // If the existing entry doesn't have a commit but new one does, replace $existing = $normalized[$key]; $existing_commit = isset($existing['commit']) ? $existing['commit'] : ''; $new_commit = isset($t['commit']) ? $t['commit'] : ''; if (empty($existing_commit) && !empty($new_commit)) { $normalized[$key] = $t; } // else keep existing } } // Turn back into numeric-indexed array foreach ($normalized as $k => $v) { $deduped[] = $v; } if (count($deduped) !== count($todos)) { $todos = $deduped; update_option('product_machine_todos', $todos); } // Also auto-complete several TODOs with known commit hashes (6-12) $auto_complete_map = [ 'add sticker' => '4fb59ef6', // #6: Add 'sticker' keyword 'sticker keyword' => '4fb59ef6', 'sticky header' => 'c6ef1f6c', // #7-8 sticky header fix (merged redundant #7/#8) 'sticky headers' => 'c6ef1f6c', // accept plural phrasing 'sticky header fix' => 'c6ef1f6c', // alternate phrasing '20pk' => '9e081bdc', // #9 migration for 20pk 'prioritize sticker' => '6628313c', // #10: sticker category priority 'tools category code' => '02dc21f7', // #11: change TL to TO 'bones brand processing' => '76cc5d3d', // #12: bones verification script ]; $ac_changed = false; foreach ($auto_complete_map as $needle => $commit_hash) { $found = false; foreach ($todos as $i => $t) { $desc = isset($t['description']) ? strtolower($t['description']) : ''; if (stripos($desc, $needle) !== false) { $found = true; if (!isset($todos[$i]['status']) || $todos[$i]['status'] !== 'completed') { $todos[$i]['status'] = 'completed'; $todos[$i]['commit'] = $commit_hash; if (!isset($todos[$i]['completed_at']) || empty($todos[$i]['completed_at'])) { $todos[$i]['completed_at'] = date('c'); } $ac_changed = true; } else { // ensure commit is filled if (empty($todos[$i]['commit'])) { $todos[$i]['commit'] = $commit_hash; $ac_changed = true; } } break; } } if (!$found) { // append as completed if not found $todos[] = [ 'description' => ucfirst($needle), 'status' => 'completed', 'commit' => $commit_hash, 'completed_at' => date('c'), ]; $ac_changed = true; } } if ($ac_changed) { update_option('product_machine_todos', $todos); } // Remove redundant 'Bones Bearings Tool' todo entries (duplicate of Bones verification) $removed_any = false; foreach ($todos as $idx => $t) { if (!empty($t['description']) && stripos($t['description'], 'Bones Bearings Tool') !== false) { unset($todos[$idx]); $removed_any = true; } } if ($removed_any) { // reindex array $todos = array_values($todos); update_option('product_machine_todos', $todos); } } } catch (Throwable $t) { // Non-fatal: keep page loading if option is unavailable error_log('PM TODO auto-complete skipped: ' . $t->getMessage()); } // Process form submission if (isset($_POST['submit']) && $_POST['submit'] == "Import Products") { check_admin_referer('product_machine_nonce_action', 'product_machine_nonce_field'); product_machine_import_data(); } // Display the form ?>

Product Machine

Fatal error: Uncaught Error: Call to undefined function wp_create_nonce() in /var/www/html/wp-includes/functions.php:1894 Stack trace: #0 /var/www/html/wp-content/plugins/product-machine/product-machine.php(2114): wp_nonce_field('product_machine...', 'product_machine...') #1 /var/www/html/wp-settings.php(522): include_once('/var/www/html/w...') #2 /var/www/html/wp-config.php(96): require_once('/var/www/html/w...') #3 /var/www/html/wp-load.php(50): require_once('/var/www/html/w...') #4 /var/www/html/wp-blog-header.php(13): require_once('/var/www/html/w...') #5 /var/www/html/index.php(17): require('/var/www/html/w...') #6 {main} thrown in /var/www/html/wp-includes/functions.php on line 1894