// ============================================================== // NATIVE SAVE FUNCTION (With Array/String Fixes) // ============================================================== public function ajax_save_user_interaction() { check_ajax_referer('dd_app_auth_nonce', 'security'); if (!is_user_logged_in()) wp_send_json_error(); $user_id = get_current_user_id(); $type = sanitize_text_field($_POST['interaction_type']); $ref = sanitize_text_field(wp_unslash($_POST['reference'])); $meta_key = '_dd_user_' . $type . 's'; // 1. Force clean unserialization in case of old DB artifacts $raw_data = get_user_meta($user_id, $meta_key, true); if (is_string($raw_data)) { $raw_data = maybe_unserialize($raw_data); } $existing_data = is_array($raw_data) ? $raw_data : array(); if ($type === 'note') { $text = isset($_POST['note_text']) ? wp_kses_post(wp_unslash($_POST['note_text'])) : ''; $tags = isset($_POST['note_tags']) ? sanitize_text_field(wp_unslash($_POST['note_tags'])) : '[]'; $existing_data[$ref] = array( 'timestamp' => current_time('mysql'), 'text' => $text, 'tags' => json_decode($tags, true) ); } elseif ($type === 'highlight') { $style = isset($_POST['highlight_style']) ? sanitize_text_field($_POST['highlight_style']) : ''; // THE FIX: Save as a flat string, NOT an array, so class-dd-app-formatters.php can read it! $existing_data[$ref] = $style; } elseif ($type === 'bookmark') { $title = isset($_POST['bookmark_title']) ? sanitize_text_field($_POST['bookmark_title']) : 'Bookmark'; $existing_data[$ref] = array('title' => $title, 'timestamp' => current_time('mysql')); } // Actively hunt and destroy any ghost notes in the entire array before saving if ($type === 'note') { foreach ($existing_data as $k => $v) { if (is_array($v) && isset($v['text'])) { $raw_test = strip_tags(html_entity_decode($v['text'], ENT_QUOTES | ENT_HTML5, 'UTF-8')); $nuked_test = preg_replace('/[^a-zA-Z0-9]/', '', $raw_test); $has_tags = !empty($v['tags']); if (empty($nuked_test) && !$has_tags) { unset($existing_data[$k]); } } } } update_user_meta($user_id, $meta_key, $existing_data); wp_send_json_success(); } // ============================================================== // NATIVE DELETE FUNCTION (With Array/String Fixes) // ============================================================== public function ajax_delete_user_interaction() { check_ajax_referer('dd_app_auth_nonce', 'security'); if (!is_user_logged_in()) wp_send_json_error(); $user_id = get_current_user_id(); $type = sanitize_text_field($_POST['interaction_type']); $ref = sanitize_text_field(wp_unslash($_POST['reference'])); $meta_key = '_dd_user_' . $type . 's'; // 1. Force clean unserialization in case of old DB artifacts $raw_data = get_user_meta($user_id, $meta_key, true); if (is_string($raw_data)) { $raw_data = maybe_unserialize($raw_data); } $existing_data = is_array($raw_data) ? $raw_data : array(); $normalized_ref = preg_replace('/[^a-z0-9]/', '', strtolower($ref)); foreach ($existing_data as $k => $v) { if (preg_replace('/[^a-z0-9]/', '', strtolower($k)) === $normalized_ref) { unset($existing_data[$k]); } } // Clean ghost notes safely if ($type === 'note') { foreach ($existing_data as $k => $v) { if (is_array($v) && isset($v['text'])) { $raw_test = strip_tags(html_entity_decode($v['text'], ENT_QUOTES | ENT_HTML5, 'UTF-8')); $nuked_test = preg_replace('/[^a-zA-Z0-9]/', '', $raw_test); if (empty($nuked_test) && empty($v['tags'])) { unset($existing_data[$k]); } } } } // THE FIX: If the array is entirely empty, literally delete the meta row so no empty arrays get cached if (empty($existing_data)) { delete_user_meta($user_id, $meta_key); } else { update_user_meta($user_id, $meta_key, $existing_data); } wp_send_json_success(); }