table_name = $wpdb->prefix . 'cfep_fields'; } /** * Get all fields (default WooCommerce + custom) * This is the NEW main method that merges everything */ public function get_all_fields($section = null, $enabled_only = false) { // Get WooCommerce default fields $wc_loader = CFEP_WC_Fields_Loader::instance(); $default_fields = $wc_loader->get_default_fields(); // Get custom fields from database $custom_fields = $this->get_custom_fields($section, $enabled_only); // Get user overrides for default fields $overrides = $this->get_field_overrides(); $all_fields = array(); // Process default fields with overrides foreach ($default_fields as $field) { $field_key = $field['field_key']; // Check if section filter applies if ($section && $field['section'] !== $section) { continue; } // Apply user overrides if they exist if (isset($overrides[$field_key])) { $field = array_merge($field, $overrides[$field_key]); } // Check enabled filter if ($enabled_only && !$field['enabled']) { continue; } $all_fields[] = $field; } // Add custom fields foreach ($custom_fields as $field) { $all_fields[] = $field; } // Sort by position usort($all_fields, function($a, $b) { return $a['position'] - $b['position']; }); return $all_fields; } /** * Get only custom fields from database */ public function get_custom_fields($section = null, $enabled_only = false) { global $wpdb; $where = array('is_default = 0'); if ($section) { $where[] = $wpdb->prepare('section = %s', $section); } if ($enabled_only) { $where[] = 'enabled = 1'; } $sql = "SELECT * FROM {$this->table_name} WHERE " . implode(' AND ', $where) . " ORDER BY position ASC"; $cache_key = 'cfep_custom_fields_' . md5($sql); $results = wp_cache_get($cache_key, 'cfep'); if ($results === false) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $results = $wpdb->get_results($sql, ARRAY_A); wp_cache_set($cache_key, $results, 'cfep', 300); } return array_map(array($this, 'parse_field_data'), $results); } /** * Get user overrides for default fields * Returns array with field_key as key */ public function get_field_overrides() { global $wpdb; $sql = "SELECT * FROM {$this->table_name} WHERE is_default = 1"; $cache_key = 'cfep_field_overrides'; $results = wp_cache_get($cache_key, 'cfep'); if ($results === false) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $results = $wpdb->get_results($sql, ARRAY_A); wp_cache_set($cache_key, $results, 'cfep', 300); } $overrides = array(); foreach ($results as $row) { $overrides[$row['field_key']] = $this->parse_field_data($row); } return $overrides; } /** * Get a specific field (checks both default and custom) */ public function get_field($field_key) { // Check if it's a default field with override $override = $this->get_field_override($field_key); if ($override) { return $override; } // Check if it's a custom field global $wpdb; $cache_key = 'cfep_field_' . $field_key; $result = wp_cache_get($cache_key, 'cfep'); if ($result === false) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE field_key = %s AND is_default = 0", $field_key ), ARRAY_A ); wp_cache_set($cache_key, $result, 'cfep', 300); } if ($result) { return $this->parse_field_data($result); } // Check if it's a default field without override $wc_loader = CFEP_WC_Fields_Loader::instance(); $default_field = $wc_loader->get_default_field($field_key); return $default_field; } /** * Get override for a specific default field */ public function get_field_override($field_key) { global $wpdb; $cache_key = 'cfep_field_override_' . $field_key; $result = wp_cache_get($cache_key, 'cfep'); if ($result === false) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE field_key = %s AND is_default = 1", $field_key ), ARRAY_A ); wp_cache_set($cache_key, $result, 'cfep', 300); } if (!$result) { return null; } // Merge with original default field $wc_loader = CFEP_WC_Fields_Loader::instance(); $default_field = $wc_loader->get_default_field($field_key); if (!$default_field) { return null; } return array_merge($default_field, $this->parse_field_data($result)); } /** * Save field (handles both custom and default field overrides) */ public function save_field($data) { global $wpdb; $field_data = CFEP_Field_Types::sanitize_field_data($data); if (empty($field_data['field_key'])) { $field_data['field_key'] = 'cfep_' . uniqid(); } // Check if this is a default field $wc_loader = CFEP_WC_Fields_Loader::instance(); $is_default = $wc_loader->is_default_field($field_data['field_key']); $insert_data = array( 'field_key' => $field_data['field_key'], 'field_type' => $field_data['field_type'], 'section' => $field_data['section'], 'label' => $field_data['label'], 'placeholder' => $field_data['placeholder'], 'description' => $field_data['description'], 'required' => absint($field_data['required']), 'enabled' => absint($field_data['enabled']), 'is_default' => $is_default ? 1 : 0, 'can_delete' => $is_default ? 0 : 1, 'position' => absint($field_data['position']), 'field_class' => $field_data['field_class'], 'options' => maybe_serialize($field_data['options']), 'conditional_logic' => maybe_serialize($field_data['conditional_logic']), 'validation_rules' => maybe_serialize($field_data['validation_rules']), ); // Check if record exists // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $existing = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE field_key = %s", $field_data['field_key'] ), ARRAY_A ); if ($existing) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $result = $wpdb->update( $this->table_name, $insert_data, array('field_key' => $field_data['field_key']), array('%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%d', '%d', '%d', '%s', '%s', '%s', '%s'), array('%s') ); wp_cache_delete('cfep_field_' . $field_data['field_key'], 'cfep'); wp_cache_delete('cfep_field_override_' . $field_data['field_key'], 'cfep'); wp_cache_delete('cfep_field_overrides', 'cfep'); if ($result === false) { return false; } else { return true; } } else { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $result = $wpdb->insert( $this->table_name, $insert_data, array('%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%d', '%d', '%d', '%s', '%s', '%s', '%s') ); wp_cache_delete('cfep_field_overrides', 'cfep'); if ($result === false) { return false; } else { return true; } } } /** * Delete field (only if can_delete = 1) */ public function delete_field($field_key) { global $wpdb; // Get field to check if it can be deleted // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $field = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE field_key = %s", $field_key ), ARRAY_A ); if (!$field) { // Check if it's a default field (not in DB yet) $wc_loader = CFEP_WC_Fields_Loader::instance(); if ($wc_loader->is_default_field($field_key)) { return false; // Cannot delete default fields } return false; // Field doesn't exist } // Check can_delete flag if (isset($field['can_delete']) && $field['can_delete'] == 0) { return false; } // Delete the field // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $result = $wpdb->delete( $this->table_name, array('field_key' => $field_key), array('%s') ); wp_cache_delete('cfep_field_' . $field_key, 'cfep'); wp_cache_delete('cfep_field_override_' . $field_key, 'cfep'); wp_cache_delete('cfep_field_overrides', 'cfep'); return $result !== false; } /** * Reset default field to WooCommerce defaults */ public function reset_default_field($field_key) { global $wpdb; // Check if it's a default field $wc_loader = CFEP_WC_Fields_Loader::instance(); if (!$wc_loader->is_default_field($field_key)) { return false; // Not a default field } // Delete the override // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $wpdb->delete( $this->table_name, array( 'field_key' => $field_key, 'is_default' => 1 ), array('%s', '%d') ); wp_cache_delete('cfep_field_override_' . $field_key, 'cfep'); wp_cache_delete('cfep_field_overrides', 'cfep'); return true; } /** * Update field positions * Handles both custom fields (in DB) and default fields (creates override) */ public function update_field_positions($positions) { global $wpdb; $success_count = 0; $error_count = 0; $wc_loader = CFEP_WC_Fields_Loader::instance(); foreach ($positions as $field_key => $position) { // Check if field exists in database // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $exists = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$this->table_name} WHERE field_key = %s", $field_key ) ); if ($exists) { // Field exists - just update position // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $result = $wpdb->update( $this->table_name, array('position' => absint($position)), array('field_key' => sanitize_key($field_key)), array('%d'), array('%s') ); wp_cache_delete('cfep_field_' . $field_key, 'cfep'); wp_cache_delete('cfep_field_override_' . $field_key, 'cfep'); if ($result === false) { $error_count++; } else { $success_count++; } } else { // Check if it's a default WooCommerce field if ($wc_loader->is_default_field($field_key)) { // Create override record for default field (just to store position) $default_field = $wc_loader->get_default_field($field_key); if ($default_field) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $result = $wpdb->insert( $this->table_name, array( 'field_key' => $field_key, 'field_type' => $default_field['type'], 'section' => $default_field['section'], 'label' => $default_field['label'], 'placeholder' => $default_field['placeholder'] ?? '', 'description' => $default_field['description'] ?? '', 'required' => $default_field['required'], 'enabled' => 1, 'is_default' => 1, 'can_delete' => 0, 'position' => absint($position), 'field_class' => $default_field['field_class'], 'options' => maybe_serialize($default_field['options'] ?? array()), 'conditional_logic' => '', 'validation_rules' => '' ), array('%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%d', '%d', '%d', '%s', '%s', '%s', '%s') ); if ($result === false) { $error_count++; } else { $success_count++; } } } else { $error_count++; } } } return $error_count === 0; } /** * Parse field data (unserialize options) */ private function parse_field_data($field) { if (!$field) { return null; } $field['options'] = maybe_unserialize($field['options']); $field['conditional_logic'] = maybe_unserialize($field['conditional_logic']); $field['validation_rules'] = maybe_unserialize($field['validation_rules']); if (!is_array($field['options'])) { $field['options'] = array(); } return $field; } }