diff --git a/admin/templates/html-keys-edit.php b/admin/templates/html-keys-edit.php index c83b95e..c4e2271 100644 --- a/admin/templates/html-keys-edit.php +++ b/admin/templates/html-keys-edit.php @@ -103,6 +103,80 @@
+ ++ +
+' + e.errorThrown + "
'+e.data.message+"
'+e.errorThrown+"
'+n+"
'+e.data.message+"
'+r+"
'+e.data.message+"
'+e.errorThrown+"
"),e.data.mobile_menu&&(jQuery('input[name="mobile_menu[]"]').prop("checked",!1),e.data.mobile_menu.forEach(function(e){jQuery('input[name="mobile_menu[]"][value="'+e+'"]').prop("checked",!0)}))},error:function(e,r,n){jQuery("h2, h3",a.el).first().append(''+n+"
'+e.data.message+"
'+r+"
Hello %1\$s,
+%2\$s has requested a meeting with you.
+ +Event: %3\$s
+Date: %16\$s
+Time: %4\$s
+ +Name: %2\$s
+ Company: %5\$s
+ City: %6\$s
+ Country: %7\$s
Name: %1\$s
+ Title: %8\$s
+ Organization: %9\$s
+ Location: %10\$s, %11\$s
+ Status: %12\$s
Message:
%13\$s
Hello {$user->display_name},
+The meeting scheduled on {$meeting_date} has been cancelled.
", + ['Content-Type: text/html; charset=UTF-8'] + ); + } + } + + // Notify host + $host = get_userdata($user_id); + if ($host) { + wp_mail( + $host->user_email, + 'You Cancelled a Meeting', + "Hello {$host->display_name},
+You have cancelled the meeting scheduled on {$meeting_date}.
", + ['Content-Type: text/html; charset=UTF-8'] + ); + } + + return new WP_REST_Response([ + 'code' => 200, + 'status' => 'OK', + 'message' => 'Meeting cancelled successfully.', + 'data' => ['meeting_id' => $meeting_id], + ], 200); + } + public function update_meeting_status(WP_REST_Request $request) { + if (!get_option('enable_matchmaking', false)) { + return new WP_REST_Response([ + 'code' => 403, + 'status' => 'Disabled', + 'message' => 'Matchmaking functionality is not enabled.', + 'data' => null + ], 403); + } + + global $wpdb; + + $meeting_id = intval($request->get_param('meeting_id')); + $user_id = intval($request->get_param('user_id')); + $new_status = intval($request->get_param('status')); + + if (!$meeting_id || !$user_id || !in_array($new_status, [0, 1], true)) { + return new WP_REST_Response(['code' => 400, 'message' => 'Invalid parameters.'], 400); + } + + $table = $wpdb->prefix . 'wpem_matchmaking_users_meetings'; + + $meeting = $wpdb->get_row($wpdb->prepare(" + SELECT participant_ids, event_id, meeting_date, meeting_start_time + FROM $table + WHERE id = %d", $meeting_id + )); + + if (!$meeting) { + return new WP_REST_Response(['code' => 404, 'message' => 'Meeting not found.'], 404); + } + + $participant_data = maybe_unserialize($meeting->participant_ids); + if (!is_array($participant_data)) { + $participant_data = []; + } + + if (!array_key_exists($user_id, $participant_data)) { + return new WP_REST_Response(['code' => 403, 'message' => 'You are not a participant of this meeting.'], 403); + } + + // If user is accepting, check for conflict in same slot + if ($new_status === 1) { + $event_id = $meeting->event_id; + $meeting_date = $meeting->meeting_date; + $slot = date('H:i', strtotime($meeting->meeting_start_time)); + + $conflicting_meeting = $wpdb->get_row($wpdb->prepare(" + SELECT id FROM $table + WHERE id != %d + AND event_id = %d + AND meeting_date = %s + AND meeting_start_time = %s + AND meeting_status != -1 + ", $meeting_id, $event_id, $meeting_date, $meeting->meeting_start_time)); + + if ($conflicting_meeting) { + $existing_participants = $wpdb->get_var($wpdb->prepare(" + SELECT participant_ids FROM $table WHERE id = %d + ", $conflicting_meeting->id)); + + $existing_participant_data = maybe_unserialize($existing_participants); + if (is_array($existing_participant_data) && isset($existing_participant_data[$user_id]) && $existing_participant_data[$user_id] == 1) { + return new WP_REST_Response([ + 'code' => 409, + 'message' => 'You already have a confirmed meeting scheduled at this time slot.', + ], 409); + } + } + } + + // Update this user's status + $participant_data[$user_id] = $new_status; + + // Determine overall meeting status + $meeting_status = (in_array(1, $participant_data, true)) ? 1 : 0; + + // Update meeting record + $updated = $wpdb->update( + $table, + [ + 'participant_ids' => maybe_serialize($participant_data), + 'meeting_status' => $meeting_status, + ], + ['id' => $meeting_id], + ['%s', '%d'], + ['%d'] + ); + + if ($updated === false) { + return new WP_REST_Response(['code' => 500, 'message' => 'Failed to update meeting status.'], 500); + } + + // Update availability slot for this user + /* $slot_data = maybe_unserialize(get_user_meta($user_id, '_meeting_availability_slot', true)); + if (!is_array($slot_data)) { + $slot_data = []; + } + + if (!isset($slot_data[$event_id])) { + $slot_data[$event_id] = []; + } + if (!isset($slot_data[$event_id][$meeting_date])) { + $slot_data[$event_id][$meeting_date] = []; + } + + $slot_data[$event_id][$meeting_date][date('H:i', strtotime($meeting->meeting_start_time))] = ($new_status === 1) ? 2 : 1; + + update_user_meta($user_id, '_meeting_availability_slot', $slot_data);*/ + + return new WP_REST_Response([ + 'code' => 200, + 'status' => 'OK', + 'message' => $new_status ? 'Meeting accepted.' : 'Meeting declined.', + 'data' => [ + 'meeting_id' => $meeting_id, + 'participant_id' => $user_id, + 'participant_status' => $new_status, + 'meeting_status' => $meeting_status, + ] + ], 200); + } + + /** + * Get available meeting slots + * @param WP_REST_Request $request + * @return WP_REST_Response + * @since 1.1.0 + */ + public function get_available_meeting_slots(WP_REST_Request $request) { + if (!get_option('enable_matchmaking', false)) { + return new WP_REST_Response([ + 'code' => 403, + 'status' => 'Disabled', + 'message' => 'Matchmaking functionality is not enabled.', + 'data' => null + ], 403); + } + + $user_id = intval($request->get_param('user_id')) ?: get_current_user_id(); + $default_slots = get_wpem_default_meeting_slots_for_user($user_id); + $meta = get_user_meta($user_id, '_available_for_meeting', true); + $meeting_available = ($meta !== '' && $meta !== null) ? ((int)$meta === 0 ? 0 : 1) : 1; + + return new WP_REST_Response([ + 'code' => 200, + 'status' => 'OK', + 'message' => 'Availability slots fetched successfully.', + 'data' => [ + 'available_for_meeting' => $meeting_available, + 'slots' => $default_slots + ] + ], 200); + } + public function update_availability_slots_rest(WP_REST_Request $request) { + $submitted_slots = $request->get_param('availability_slots'); // This is the "slots" array from GET + + $available_for_meeting = $request->get_param('available_for_meeting') ? 1 : 0; + $user_id = intval($request->get_param('user_id') ?: get_current_user_id()); + + if (!$user_id || !is_array($submitted_slots)) { + return new WP_REST_Response([ + 'code' => 400, + 'status' => 'ERROR', + 'message' => 'Missing or invalid parameters.' + ], 400); + } + + // Save directly in the same structure + update_user_meta($user_id, '_meeting_availability_slot', $submitted_slots); + update_user_meta($user_id, '_available_for_meeting', $available_for_meeting); + + return new WP_REST_Response([ + 'code' => 200, + 'status' => 'OK', + 'message' => 'Availability updated successfully.', + 'data' => [ + 'available_for_meeting' => $available_for_meeting, + 'slots' => $submitted_slots + ] + ], 200); + } + public function get_common_availability_slots($request) { + global $wpdb; + + $event_id = intval($request->get_param('event_id')); + $user_ids = $request->get_param('user_ids'); + $date = sanitize_text_field($request->get_param('date')); + + if (!is_array($user_ids) || empty($user_ids) || !$event_id || !$date) { + return new WP_REST_Response([ + 'code' => 400, + 'status' => 'ERROR', + 'message' => 'Invalid parameters.', + 'data' => [], + ], 400); + } + + $all_user_slots = []; + + // Step 1: Get available slots for each user (value === 1) + foreach ($user_ids as $user_id) { + $raw_data = get_wpem_default_meeting_slots_for_user($user_id, $date); + + $available_slots = array_keys(array_filter( + $raw_data, + fn($v) => $v == 1 + )); + + if (empty($available_slots)) { + return new WP_REST_Response([ + 'code' => 200, + 'status' => 'OK', + 'message' => 'No common slots found.', + 'data' => ['common_slots' => []], + ]); + } + + $all_user_slots[] = $available_slots; + } + + // Step 2: Find intersection of slots between all users + $common_slots = array_shift($all_user_slots); + foreach ($all_user_slots as $slots) { + $common_slots = array_intersect($common_slots, $slots); + } + + sort($common_slots); + + // Step 3: Find booked slots for given date + $table = $wpdb->prefix . 'wpem_matchmaking_users_meetings'; + $rows = $wpdb->get_results($wpdb->prepare( + "SELECT meeting_start_time, participant_ids, user_id + FROM {$table} + WHERE meeting_date = %s", + $date + ), ARRAY_A); + + $booked_slots = []; + foreach ($rows as $row) { + $meeting_time = date('H:i', strtotime($row['meeting_start_time'])); + $creator_id = intval($row['user_id']); + $participant_ids = maybe_unserialize($row['participant_ids']); + + if (!is_array($participant_ids)) { + $participant_ids = []; + } + + foreach ($user_ids as $u_id) { + $u_id = intval($u_id); + + // Condition 1: Creator is the user + if ($creator_id === $u_id) { + $booked_slots[] = $meeting_time; + break; + } + + // Condition 2: User is a participant with status 0 or 1 + if (isset($participant_ids[$u_id]) && in_array($participant_ids[$u_id], [0, 1], true)) { + $booked_slots[] = $meeting_time; + break; + } + } + } + + // Step 4: Build combined slot list + $combined_slots = []; + foreach ($common_slots as $slot) { + $combined_slots[] = [ + 'time' => $slot, + 'is_booked' => in_array($slot, $booked_slots, true), + ]; + } + + return new WP_REST_Response([ + 'code' => 200, + 'status' => 'OK', + 'message' => count($combined_slots) ? 'Common availability slots retrieved successfully.' : 'No common slots found.', + 'data' => [ + 'event_id' => $event_id, + 'date' => $date, + 'common_slots' => $combined_slots, + ], + ], 200); + } + public function get_matchmaking_settings(WP_REST_Request $request) { + if (!get_option('enable_matchmaking', false)) { + return new WP_REST_Response([ + 'code' => 403, + 'status' => 'Disabled', + 'message' => 'Matchmaking is disabled.', + 'data' => null + ], 403); + } + + $settings = [ + 'request_mode' => get_option('wpem_meeting_request_mode'), + 'scheduling_mode' => get_option('wpem_meeting_scheduling_mode'), + 'attendee_limit' => get_option('wpem_meeting_attendee_limit'), + 'meeting_expiration' => get_option('wpem_meeting_expiration'), + 'enable_matchmaking' => get_option('enable_matchmaking'), + 'participant_activation' => get_option('participant_activation'), + ]; + + return new WP_REST_Response([ + 'code' => 200, + 'status' => 'OK', + 'message' => 'Matchmaking settings retrieved.', + 'data' => $settings + ], 200); + } +} + +new WPEM_REST_Create_Meeting_Controller(); \ No newline at end of file diff --git a/includes/wpem-rest-matchmaking-filter-users.php b/includes/wpem-rest-matchmaking-filter-users.php new file mode 100644 index 0000000..c99160d --- /dev/null +++ b/includes/wpem-rest-matchmaking-filter-users.php @@ -0,0 +1,420 @@ +namespace, '/' . $this->rest_base, array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array($this, 'handle_filter_users'), + 'permission_callback' => array($auth_controller, 'check_authentication'), + 'args' => array( + 'profession' => array('required' => false, 'type' => 'string'), + 'company_name' => array('required' => false, 'type' => 'string'), + 'country' => array('required' => false, 'type' => 'array'), + 'city' => array('required' => false, 'type' => 'string'), + 'experience' => array('required' => false), + 'skills' => array('required' => false, 'type' => 'array'), + 'interests' => array('required' => false, 'type' => 'array'), + 'event_id' => array('required' => false, 'type' => 'integer'), + 'user_id' => array('required' => true, 'type' => 'integer'), + 'search' => array('required' => false, 'type' => 'string'), + 'per_page' => array('required' => false, 'type' => 'integer', 'default' => 5), + 'page' => array('required' => false, 'type' => 'integer', 'default' => 1), + + ), + )); + + // Your matches (with optional user_id param) + register_rest_route($this->namespace, '/your-matches', array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array($this, 'handle_your_matches'), + 'permission_callback' => array($auth_controller, 'check_authentication'), + 'args' => array( + 'user_id' => array('required' => true, 'type' => 'integer'), + ), + )); + } + + /** + * This function used to filter users + * @since 1.1.0 + */ + public function handle_your_matches($request) { + global $wpdb; + + if (!get_option('enable_matchmaking', false)) { + return new WP_REST_Response([ + 'code' => 403, + 'status' => 'Disabled', + 'message' => 'Matchmaking functionality is not enabled.', + 'data' => null + ], 403); + } + + $user_id = intval($request->get_param('user_id')); + + if (!$user_id) { + return new WP_REST_Response([ + 'code' => 401, + 'status' => 'Unauthorized', + 'message' => 'User Id not found' + ], 401); + } + + $table = $wpdb->prefix . 'wpem_matchmaking_users'; + $user_data = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE user_id = %d", $user_id), ARRAY_A); + + if (!$user_data) { + return new WP_REST_Response([ + 'code' => 404, + 'status' => 'Not Found', + 'message' => 'Matchmaking profile not found for user' + ], 404); + } + + // Build a filter request based on user's profile + $filter_request = new WP_REST_Request('GET'); + $filter_request->set_param('profession', $user_data['profession']); + $filter_request->set_param('company_name', $user_data['company_name']); + $filter_request->set_param('country', [$user_data['country']]); + $filter_request->set_param('city', $user_data['city']); + + $exp = (int)$user_data['experience']; + $filter_request->set_param('experience', [ + 'min' => max(0, $exp - 2), + 'max' => $exp + 2 + ]); + + $skills = maybe_unserialize($user_data['skills']); + if (is_array($skills)) { + $filter_request->set_param('skills', $skills); + } + + $interests = maybe_unserialize($user_data['interests']); + if (is_array($interests)) { + $filter_request->set_param('interests', $interests); + } + + $search = sanitize_text_field($request->get_param('search')); + if (!empty($search)) { + $search_like = '%' . $wpdb->esc_like($search) . '%'; + + $search_conditions = [ + "city LIKE %s", + "country LIKE %s", + "profession LIKE %s", + "skills LIKE %s", + "interests LIKE %s", + "company_name LIKE %s" + ]; + + // First name / last name from wp_usermeta + $matching_user_ids = $wpdb->get_col($wpdb->prepare(" + SELECT DISTINCT user_id + FROM {$wpdb->usermeta} + WHERE (meta_key = 'first_name' OR meta_key = 'last_name') + AND meta_value LIKE %s + ", $search_like)); + + if (!empty($matching_user_ids)) { + $placeholders = implode(',', array_fill(0, count($matching_user_ids), '%d')); + $search_conditions[] = "user_id IN ($placeholders)"; + $query_params = array_merge($query_params, array_fill(0, count($search_conditions) - 1, $search_like), $matching_user_ids); + } else { + $query_params = array_merge($query_params, array_fill(0, count($search_conditions), $search_like)); + } + + $where_clauses[] = '(' . implode(' OR ', $search_conditions) . ')'; + } + + // Reuse the filter handler + $response = $this->handle_filter_users($filter_request); + + // Exclude the user from their own matches + if ($response instanceof WP_REST_Response) { + $data = $response->get_data(); + + // Exclude self + $filtered = array_filter($data['data'], function ($item) use ($user_id) { + return $item['user_id'] != $user_id; + }); + + // Add first_name and last_name + foreach ($filtered as &$row) { + $row['first_name'] = get_user_meta($row['user_id'], 'first_name', true); + $row['last_name'] = get_user_meta($row['user_id'], 'last_name', true); + } + + $data['data'] = array_values($filtered); + $response->set_data($data); + } + + return $response; + } + + /** + * This function used to filter users + * @since 1.1.0 + * @param $request + * @return WP_REST_Response + */ + public function handle_filter_users($request) { + global $wpdb; + + if (!get_option('enable_matchmaking', false)) { + return new WP_REST_Response([ + 'code' => 403, + 'status' => 'Disabled', + 'message' => 'Matchmaking functionality is not enabled.', + 'data' => null + ], 403); + } + + $event_id = intval($request->get_param('event_id')); + $user_id = intval($request->get_param('user_id')); + + // Step 1: Validate registration + $registered_user_ids = []; + if ($event_id && $user_id) { + $registration_query = new WP_Query([ + 'post_type' => 'event_registration', + 'posts_per_page' => -1, + 'fields' => 'ids', + 'post_author' => $user_id, + ]); + + $has_registration = false; + foreach ($registration_query->posts as $registration_id) { + if (wp_get_post_parent_id($registration_id) == $event_id) { + $has_registration = true; + break; + } + } + + if (!$has_registration) { + return new WP_REST_Response([ + 'code' => 403, + 'status' => 'Forbidden', + 'message' => 'You are not registered for this event.', + 'data' => [] + ], 403); + } + + // Collect all registered users for this event + $attendee_query = new WP_Query([ + 'post_type' => 'event_registration', + 'posts_per_page' => -1, + 'fields' => 'ids' + ]); + + foreach ($attendee_query->posts as $registration_id) { + if (wp_get_post_parent_id($registration_id) == $event_id) { + $uid = intval(get_post_field('post_author', $registration_id)); + if ($uid && !in_array($uid, $registered_user_ids)) { + $registered_user_ids[] = $uid; + } + } + } + } + + if (empty($registered_user_ids)) { + return new WP_REST_Response([ + 'code' => 200, + 'status' => 'OK', + 'message' => 'No attendees found for this event.', + 'data' => [] + ], 200); + } + + // Step 2: Build user data + $profession_terms = get_event_registration_taxonomy_list('event_registration_professions'); // [slug => name] + $skills_terms = get_event_registration_taxonomy_list('event_registration_skills'); + $interests_terms = get_event_registration_taxonomy_list('event_registration_interests'); + + $users_data = []; + foreach ($registered_user_ids as $uid) { + if ($uid == $user_id) continue; + if (!get_user_meta($uid, '_matchmaking_profile', true)) continue; + + $photo = get_wpem_user_profile_photo($uid) ?: EVENT_MANAGER_REGISTRATIONS_PLUGIN_URL . '/assets/images/user-profile-photo.png'; + + // Normalize organization logo + $organization_logo = get_user_meta($uid, '_organization_logo', true); + $organization_logo = maybe_unserialize($organization_logo); + if (is_array($organization_logo)) { + $organization_logo = reset($organization_logo); + } + $organization_logo = $organization_logo ?: EVENT_MANAGER_REGISTRATIONS_PLUGIN_URL . '/assets/images/organisation-icon.jpg'; + // Profession + $profession_value = get_user_meta($uid, '_profession', true); + $profession_slug = $profession_value; + if ($profession_value && !isset($profession_terms[$profession_value])) { + $found_slug = array_search($profession_value, $profession_terms); + if ($found_slug) { + $profession_slug = $found_slug; + } + } + + // --- Skills --- + $skills_slugs = []; + $skills_arr = maybe_unserialize(get_user_meta($uid, '_skills', true)); + if (is_array($skills_arr)) { + foreach ($skills_arr as $skill) { + $term = get_term_by('slug', $skill, 'event_registration_skills'); + if (!$term) { + $term = get_term_by('name', $skill, 'event_registration_skills'); + } + if (!$term) { + $term = get_term_by('id', $skill, 'event_registration_skills'); + } + if ($term) { + $skills_slugs[] = $term->slug; + } + } + } + $skills_slugs = array_filter($skills_slugs); // remove blanks + $skills_serialized = serialize($skills_slugs); + + // --- Interests --- + $interests_slugs = []; + $interests_arr = maybe_unserialize(get_user_meta($uid, '_interests', true)); + if (is_array($interests_arr)) { + foreach ($interests_arr as $interest) { + $term = get_term_by('slug', $interest, 'event_registration_interests'); + if (!$term) { + $term = get_term_by('name', $interest, 'event_registration_interests'); + } + if (!$term) { + $term = get_term_by('id', $interest, 'event_registration_interests'); + } + if ($term) { + $interests_slugs[] = $term->slug; + } + } + } + $interests_slugs = array_filter($interests_slugs); + $interests_serialized = serialize($interests_slugs); + + $countries = wpem_get_all_countries(); + $country_value = get_user_meta($uid, '_country', true); + $country_code = ''; + if ($country_value) { + if (isset($countries[$country_value])) { + $country_code = $country_value; + } else { + $country_code = array_search($country_value, $countries); + } + } + // Get organization country value from user meta + $org_country_value = get_user_meta($uid, '_organization_country', true); + $org_country_code = ''; + if ($org_country_value) { + if (isset($countries[$org_country_value])) { + $org_country_code = $org_country_value; + } else { + $org_country_code = array_search($org_country_value, $countries); + } + } + $users_data[] = [ + 'user_id' => $uid, + 'display_name' => get_the_author_meta('display_name', $uid), + 'first_name' => get_user_meta($uid, 'first_name', true), + 'last_name' => get_user_meta($uid, 'last_name', true), + 'email' => get_userdata($uid)->user_email, + 'matchmaking_profile' => get_user_meta($uid, '_matchmaking_profile', true), + 'profile_photo' => $photo, + 'profession' => $profession_slug, + 'experience' => get_user_meta($uid, '_experience', true), + 'company_name' => get_user_meta($uid, '_company_name', true), + 'country' => $country_code, + 'city' => get_user_meta($uid, '_city', true), + 'about' => get_user_meta($uid, '_about', true), + 'skills' => $skills_serialized, + 'interests' => $interests_serialized, + 'message_notification' => get_user_meta($uid, '_message_notification', true), + 'organization_name' => get_user_meta($uid, '_organization_name', true), + 'organization_logo' => $organization_logo, + 'organization_country' => $org_country_code, + 'organization_city' => get_user_meta($uid, '_organization_city', true), + 'organization_description'=> get_user_meta($uid, '_organization_description', true), + 'organization_website' => get_user_meta($uid, '_organization_website', true), + 'available_for_meeting' => get_user_meta($uid, '_available_for_meeting', true), + 'approve_profile_status'=> get_user_meta($uid, '_approve_profile_status', true), + ]; + } + + // Step 3: Apply filters + $profession = sanitize_text_field($request->get_param('profession')); + $country = $request->get_param('country'); + $skills = $request->get_param('skills'); + $interests = $request->get_param('interests'); + $search = sanitize_text_field($request->get_param('search')); + + $filtered_users = array_filter($users_data, function($user) use ($profession, $country, $skills, $interests, $search) { + if ($profession && strtolower($user['profession']) !== strtolower($profession)) { + return false; + } + if (!empty($country) && is_array($country) && !in_array($user['country'], $country)) { + return false; + } + if (!empty($skills) && is_array($skills)) { + if (!array_intersect($skills, $user['skills'])) { + return false; + } + } + if (!empty($interests) && is_array($interests)) { + if (!array_intersect($interests, $user['interests'])) { + return false; + } + } + if ($search) { + $haystack_parts = []; + foreach ($user as $key => $val) { + if (is_array($val)) { + $haystack_parts = array_merge($haystack_parts, $val); + } else { + $haystack_parts[] = $val; + } + } + $haystack = strtolower(implode(' ', $haystack_parts)); + if (strpos($haystack, strtolower($search)) === false) { + return false; + } + } + return true; + }); + + // Step 4: Pagination + $per_page = max(1, (int) $request->get_param('per_page')); + $page = max(1, (int) $request->get_param('page')); + $total = count($filtered_users); + $offset = ($page - 1) * $per_page; + $paged_users = array_slice($filtered_users, $offset, $per_page); + + return new WP_REST_Response([ + 'code' => 200, + 'status' => 'OK', + 'message' => 'Users retrieved successfully.', + 'data' => [ + 'total_post_count' => $total, + 'current_page' => $page, + 'last_page' => ceil($total / $per_page), + 'total_pages' => ceil($total / $per_page), + 'users' => array_values($paged_users), + ], + ], 200); + } +} +new WPEM_REST_Filter_Users_Controller(); diff --git a/includes/wpem-rest-matchmaking-get-texonomy.php b/includes/wpem-rest-matchmaking-get-texonomy.php new file mode 100644 index 0000000..0bf22f2 --- /dev/null +++ b/includes/wpem-rest-matchmaking-get-texonomy.php @@ -0,0 +1,86 @@ +namespace, + '/' . $this->rest_base, + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array($this, 'get_taxonomy_terms'), + 'permission_callback' => array($auth_controller, 'check_authentication'), + 'args' => array( + 'taxonomy' => array( + 'required' => true, + 'type' => 'string', + 'description' => 'Taxonomy name (e.g., category, post_tag, custom_taxonomy).', + ) + ), + ) + ); + } + + public function get_taxonomy_terms($request) { + // Check if matchmaking is enabled + if (!get_option('enable_matchmaking', false)) { + return new WP_REST_Response(array( + 'code' => 403, + 'status' => 'Disabled', + 'message' => 'Matchmaking functionality is not enabled.', + 'data' => null + ), 403); + } + + $taxonomy = sanitize_text_field($request->get_param('taxonomy')); + + if (!taxonomy_exists($taxonomy)) { + return new WP_REST_Response(array( + 'code' => 400, + 'status' => 'Bad Request', + 'message' => 'Invalid taxonomy.', + 'data' => null + ), 400); + } + + $terms = get_terms(array( + 'taxonomy' => $taxonomy, + 'hide_empty' => false, + )); + + if (is_wp_error($terms)) { + return new WP_REST_Response(array( + 'code' => 500, + 'status' => 'Server Error', + 'message' => 'Failed to fetch terms.', + 'data' => null + ), 500); + } + + $term_list = array_map(array($this, 'format_term_data'), $terms); + + return new WP_REST_Response(array( + 'code' => 200, + 'status' => 'OK', + 'message' => 'Taxonomy terms retrieved successfully.', + 'data' => $term_list + ), 200); + } + + private function format_term_data($term) { + return array( + 'id' => $term->term_id, + 'name' => html_entity_decode($term->name, ENT_QUOTES, 'UTF-8'), + 'slug' => $term->slug, + ); + } +} + +new WPEM_REST_Taxonomy_List_Controller(); diff --git a/includes/wpem-rest-matchmaking-profile.php b/includes/wpem-rest-matchmaking-profile.php new file mode 100644 index 0000000..6931a01 --- /dev/null +++ b/includes/wpem-rest-matchmaking-profile.php @@ -0,0 +1,544 @@ +namespace, + '/attendee-profile', + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array($this, 'get_attendee_profile'), + 'permission_callback' => array($auth_controller, 'check_authentication'), + 'args' => array( + 'attendeeId' => array( + 'required' => false, + 'type' => 'integer', + ) + ), + ) + ); + + register_rest_route( + $this->namespace, + '/attendee-profile/update', + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array($this, 'update_attendee_profile'), + 'permission_callback' => array($auth_controller, 'check_authentication'), + 'args' => array( + 'user_id' => array( + 'required' => true, + 'type' => 'integer', + ), + ), + ) + ); + + register_rest_route( + $this->namespace, + '/upload-user-file', + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array($this, 'upload_user_file'), + 'permission_callback' => array($auth_controller, 'check_authentication'), + 'args' => array( + 'user_id' => array( + 'required' => true, + 'type' => 'integer', + ), + ), + ) + ); + } + + public function get_attendee_profile($request) { + if (!get_option('enable_matchmaking', false)) { + return new WP_REST_Response(array( + 'code' => 403, + 'status' => 'Disabled', + 'message' => 'Matchmaking functionality is not enabled.', + 'data' => null + ), 403); + } + + $attendee_id = $request->get_param('attendeeId'); + $countries = wpem_get_all_countries(); + if ($attendee_id) { + // Check if user exists + $user = get_user_by('ID', $attendee_id); + if (!$user) { + return new WP_REST_Response(array( + 'code' => 404, + 'status' => 'Not Found', + 'message' => 'Attendee not found.', + 'data' => null + ), 404); + } + + // Get all user meta + $user_meta = get_user_meta($attendee_id); + $photo = get_wpem_user_profile_photo($attendee_id) ?: EVENT_MANAGER_REGISTRATIONS_PLUGIN_URL . '/assets/images/user-profile-photo.png'; + $organization_logo = get_user_meta( $attendee_id, '_organization_logo', true ); + $organization_logo = maybe_unserialize( $organization_logo ); + if (is_array($organization_logo)) { + $organization_logo = reset($organization_logo); // get first value in the array + } + $organization_logo = $organization_logo ?: EVENT_MANAGER_REGISTRATIONS_PLUGIN_URL . '/assets/images/organisation-icon.jpg'; + $country_value = isset($user_meta['_country'][0]) ? sanitize_text_field($user_meta['_country'][0]) : ''; + $country_code = ''; + if ($country_value) { + if (isset($countries[$country_value])) { + $country_code = $country_value; + } else { + $country_code = array_search($country_value, $countries); + } + } + // Get organization country value from user meta + $org_country_value = isset($user_meta['_organization_country'][0]) ? sanitize_text_field($user_meta['_organization_country'][0]) : ''; + $org_country_code = ''; + if ($org_country_value) { + if (isset($countries[$org_country_value])) { + $org_country_code = $org_country_value; + } else { + $org_country_code = array_search($org_country_value, $countries); + } + } + $meta = get_user_meta($attendee_id, '_available_for_meeting', true); + $meeting_available = ($meta !== '' && $meta !== null) ? ((int)$meta === 0 ? 0 : 1) : 1; + // Get all profession terms [slug => name] + $professions = get_event_registration_taxonomy_list('event_registration_professions'); + + // Get saved profession value + $profession_value = isset($user_meta['_profession'][0]) ? sanitize_text_field($user_meta['_profession'][0]) : ''; + $profession_slug = $profession_value; + + // If it's a name, convert to slug + if ($profession_value && !isset($professions[$profession_value])) { + $found_slug = array_search($profession_value, $professions); + if ($found_slug) { + $profession_slug = $found_slug; + } + } + $skills_slugs = []; + $skills_arr = maybe_unserialize($user_meta['_skills'][0]); + if (is_array($skills_arr)) { + foreach ($skills_arr as $skill) { + $term = get_term_by('slug', $skill, 'event_registration_skills'); + if (!$term) { + $term = get_term_by('name', $skill, 'event_registration_skills'); + } + if (!$term) { + $term = get_term_by('id', $skill, 'event_registration_skills'); + } + if ($term) { + $skills_slugs[] = $term->slug; + } + } + } + $skills_slugs = array_filter($skills_slugs); // remove blanks + $skills_serialized = serialize($skills_slugs); + + // --- Interests --- + $interests_slugs = []; + $interests_arr = maybe_unserialize($user_meta['_interests'][0]); + if (is_array($interests_arr)) { + foreach ($interests_arr as $interest) { + $term = get_term_by('slug', $interest, 'event_registration_interests'); + if (!$term) { + $term = get_term_by('name', $interest, 'event_registration_interests'); + } + if (!$term) { + $term = get_term_by('id', $interest, 'event_registration_interests'); + } + if ($term) { + $interests_slugs[] = $term->slug; + } + } + } + $interests_slugs = array_filter($interests_slugs); + $interests_serialized = serialize($interests_slugs); + + // Format the profile data + $profile = array( + 'user_id' => $attendee_id, + 'display_name' => $user->display_name, + 'first_name' => isset($user_meta['first_name'][0]) ? sanitize_text_field($user_meta['first_name'][0]) : '', + 'last_name' => isset($user_meta['last_name'][0]) ? sanitize_text_field($user_meta['last_name'][0]) : '', + 'email' => $user->user_email, + 'matchmaking_profile' => isset($user_meta['_matchmaking_profile'][0]) ? (int)$user_meta['_matchmaking_profile'][0] : 0, + 'profile_photo' => $photo, + 'profession' => $profession_slug, + 'experience' => isset($user_meta['_experience'][0]) ? (float)$user_meta['_experience'][0] : 0, + 'company_name' => isset($user_meta['_company_name'][0]) ? sanitize_text_field($user_meta['_company_name'][0]) : '', + 'country' => $country_code, + 'city' => isset($user_meta['_city'][0]) ? sanitize_text_field($user_meta['_city'][0]) : '', + 'about' => isset($user_meta['_about'][0]) ? sanitize_textarea_field($user_meta['_about'][0]) : '', + //'skills' => maybe_serialize($skills_slugs), + //'interests' => maybe_serialize($interests_slugs), + 'skills' => $skills_serialized, + 'interests' => $interests_serialized, + 'message_notification' => isset($user_meta['_message_notification'][0]) ? (int)$user_meta['_message_notification'][0] : 0, + 'organization_name' => isset($user_meta['_organization_name'][0]) ? sanitize_text_field($user_meta['_organization_name'][0]) : '', + 'organization_logo' => $organization_logo, + 'organization_country' => $org_country_code, + 'organization_city' => isset($user_meta['_organization_city'][0]) ? sanitize_text_field($user_meta['_organization_city'][0]) : '', + 'organization_description' => isset($user_meta['_organization_description'][0]) ? sanitize_textarea_field($user_meta['_organization_description'][0]) : '', + 'organization_website' => isset($user_meta['_organization_website'][0]) ? sanitize_text_field($user_meta['_organization_website'][0]) : '', + 'approve_profile_status' => isset($user_meta['_approve_profile_status'][0]) ? (int)$user_meta['_approve_profile_status'][0] : 0, + 'wpem_meeting_request_mode' => isset($user_meta['_wpem_meeting_request_mode'][0]) ? $user_meta['_wpem_meeting_request_mode'][0] : 'approval', + 'available_for_meeting' => (int)$meeting_available, + ); + + return new WP_REST_Response(array( + 'code' => 200, + 'status' => 'OK', + 'message' => 'Profile retrieved successfully.', + 'data' => $profile + ), 200); + } else { + // Get all users with matchmaking profiles + $args = array( + 'meta_key' => '_matchmaking_profile', + 'meta_value' => '1', + 'meta_compare' => '=' + ); + $users = get_users($args); + + $profiles = array(); + foreach ($users as $user) { + $user_meta = get_user_meta($user->ID); + $photo = get_wpem_user_profile_photo($user->ID) ?: EVENT_MANAGER_REGISTRATIONS_PLUGIN_URL . '/assets/images/user-profile-photo.png'; + $organization_logo = get_user_meta( $user->ID, '_organization_logo', true ); + $organization_logo = maybe_unserialize( $organization_logo ); + if (is_array($organization_logo)) { + $organization_logo = reset($organization_logo); + } + $organization_logo = $organization_logo ?: EVENT_MANAGER_REGISTRATIONS_PLUGIN_URL . '/assets/images/organisation-icon.jpg'; + $country_value = isset($user_meta['_country'][0]) ? sanitize_text_field($user_meta['_country'][0]) : ''; + $country_code = ''; + if ($country_value) { + if (isset($countries[$country_value])) { + $country_code = $country_value; + } else { + $country_code = array_search($country_value, $countries); + } + } + $org_country_value = isset($user_meta['_organization_country'][0]) ? sanitize_text_field($user_meta['_organization_country'][0]) : ''; + $org_country_code = ''; + if ($org_country_value) { + if (isset($countries[$org_country_value])) { + $org_country_code = $org_country_value; + } else { + $org_country_code = array_search($org_country_value, $countries); + } + } + $meta = get_user_meta($user->ID, '_available_for_meeting', true); + $meeting_available = ($meta !== '' && $meta !== null) ? ((int)$meta === 0 ? 0 : 1) : 1; + // Profession slug logic + $professions = get_event_registration_taxonomy_list('event_registration_professions'); + $profession_value = isset($user_meta['_profession'][0]) ? sanitize_text_field($user_meta['_profession'][0]) : ''; + $profession_slug = $profession_value; + if ($profession_value && !isset($professions[$profession_value])) { + $found_slug = array_search($profession_value, $professions); + if ($found_slug) { + $profession_slug = $found_slug; + } + } + $skills_slugs = []; + $skills_arr = maybe_unserialize($user_meta['_skills'][0]); + if (is_array($skills_arr)) { + foreach ($skills_arr as $skill) { + $term = get_term_by('slug', $skill, 'event_registration_skills'); + if (!$term) { + $term = get_term_by('name', $skill, 'event_registration_skills'); + } + if (!$term) { + $term = get_term_by('id', $skill, 'event_registration_skills'); + } + if ($term) { + $skills_slugs[] = $term->slug; + } + } + } + $skills_slugs = array_filter($skills_slugs); // remove blanks + $skills_serialized = serialize($skills_slugs); + + // --- Interests --- + $interests_slugs = []; + $interests_arr = maybe_unserialize($user_meta['_interests'][0]); + if (is_array($interests_arr)) { + foreach ($interests_arr as $interest) { + $term = get_term_by('slug', $interest, 'event_registration_interests'); + if (!$term) { + $term = get_term_by('name', $interest, 'event_registration_interests'); + } + if (!$term) { + $term = get_term_by('id', $interest, 'event_registration_interests'); + } + if ($term) { + $interests_slugs[] = $term->slug; + } + } + } + $interests_slugs = array_filter($interests_slugs); + $interests_serialized = serialize($interests_slugs); + + $profiles[] = array( + 'user_id' => $user->ID, + 'display_name' => $user->display_name, + 'first_name' => isset($user_meta['first_name'][0]) ? sanitize_text_field($user_meta['first_name'][0]) : '', + 'last_name' => isset($user_meta['last_name'][0]) ? sanitize_text_field($user_meta['last_name'][0]) : '', + 'email' => $user->user_email, + 'matchmaking_profile' => isset($user_meta['_matchmaking_profile'][0]) ? (int)$user_meta['_matchmaking_profile'][0] : 0, + 'profile_photo' => $photo, + 'profession' => $profession_slug , + 'experience' => isset($user_meta['_experience'][0]) ? (float)$user_meta['_experience'][0] : 0, + 'company_name' => isset($user_meta['_company_name'][0]) ? sanitize_text_field($user_meta['_company_name'][0]) : '', + 'country' => $country_code, + 'city' => isset($user_meta['_city'][0]) ? sanitize_text_field($user_meta['_city'][0]) : '', + 'about' => isset($user_meta['_about'][0]) ? sanitize_textarea_field($user_meta['_about'][0]) : '', + 'skills' => $skills_serialized, + 'interests' => $interests_serialized, + 'message_notification' => isset($user_meta['_message_notification'][0]) ? (int)$user_meta['_message_notification'][0] : 0, + 'organization_name' => isset($user_meta['_organization_name'][0]) ? sanitize_text_field($user_meta['_organization_name'][0]) : '', + 'organization_logo' => $organization_logo, + 'organization_country' => $org_country_code, + 'organization_city' => isset($user_meta['_organization_city'][0]) ? sanitize_text_field($user_meta['_organization_city'][0]) : '', + 'organization_description' => isset($user_meta['_organization_description'][0]) ? sanitize_textarea_field($user_meta['_organization_description'][0]) : '', + 'organization_website' => isset($user_meta['_organization_website'][0]) ? sanitize_text_field($user_meta['_organization_website'][0]) : '', + 'approve_profile_status' => isset($user_meta['_approve_profile_status'][0]) ? (int)$user_meta['_approve_profile_status'][0] : 0, + 'wpem_meeting_request_mode' => isset($user_meta['_wpem_meeting_request_mode'][0]) ? $user_meta['_wpem_meeting_request_mode'][0] : 'approval', + 'available_for_meeting' => (int)$meeting_available, + ); + } + + return new WP_REST_Response(array( + 'code' => 200, + 'status' => 'OK', + 'message' => 'All profiles retrieved successfully.', + 'data' => $profiles + ), 200); + } + } + /** + * Update profile including handling file upload from device for profile_photo + */ + public function update_attendee_profile($request) { + if (!get_option('enable_matchmaking', false)) { + return new WP_REST_Response([ + 'code' => 403, + 'status' => 'Disabled', + 'message' => 'Matchmaking functionality is not enabled.' + ], 403); + } + + $user_id = $request->get_param('user_id'); + $user = get_user_by('id', $user_id); + + if (!$user) { + return new WP_REST_Response([ + 'code' => 404, + 'status' => 'Not Found', + 'message' => 'User not found.' + ], 404); + } + + // List of all meta fields we can update + $meta_fields = [ + 'profession', 'experience', 'company_name', 'country', + 'city', 'about', 'skills', 'interests', 'organization_name', + 'organization_logo', 'organization_city', 'organization_country', + 'organization_description', 'organization_website', 'message_notification', 'matchmaking_profile' + ]; + + // Handle normal meta fields + + foreach ($meta_fields as $field) { + if ($request->get_param($field) !== null) { + $value = $request->get_param($field); + + // For skills and interests, always save as serialized array + if (in_array($field, ['skills', 'interests'])) { + // Ensure value is always an array + if (!is_array($value)) { + $value = [$value]; + } + $value = array_filter($value, function($v) { + return $v !== null && $v !== ''; + }); + $value = array_values($value); // reindex after filtering + + // Save as serialized array (produces a:2:{i:0;s:...;i:1;s:...;} format) + if (!empty($value)) { + update_user_meta($user_id, '_' . $field, $value); + } else { + update_user_meta($user_id, '_' . $field, ''); + } + } else { + // For other fields + if (is_array($value)) { + $value = array_filter($value, function($v) { + return $v !== null && $v !== ''; + }); + $value = array_values($value); // reindex after filtering + } + if (!empty($value)) { + update_user_meta($user_id, '_' . $field, $value); + } else { + update_user_meta($user_id, '_' . $field, ''); // cleanup if blank + } + } + } + } + + // Handle profile_photo file upload + if (!empty($_FILES['profile_photo']) && $_FILES['profile_photo']['error'] === UPLOAD_ERR_OK) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + $upload_overrides = ['test_form' => false]; + $movefile = wp_handle_upload($_FILES['profile_photo'], $upload_overrides); + + if (isset($movefile['url'])) { + update_user_meta($user_id, '_profile_photo', esc_url_raw($movefile['url'])); + + } else { + return new WP_REST_Response([ + 'code' => 500, + 'status' => 'Error', + 'message' => 'Profile photo upload failed.' + ], 500); + } + } elseif ($request->get_param('profile_photo')) { + update_user_meta($user_id, '_profile_photo', esc_url_raw($request->get_param('profile_photo'))); + + } + + // Handle organization_logo file upload + if (!empty($_FILES['organization_logo']) && $_FILES['organization_logo']['error'] === UPLOAD_ERR_OK) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + $upload_overrides = ['test_form' => false]; + $movefile = wp_handle_upload($_FILES['organization_logo'], $upload_overrides); + + if (isset($movefile['url'])) { + update_user_meta($user_id, '_organization_logo', esc_url_raw($movefile['url'])); + } else { + return new WP_REST_Response([ + 'code' => 500, + 'status' => 'Error', + 'message' => 'Organization logo upload failed.' + ], 500); + } + } elseif ($request->get_param('organization_logo')) { + update_user_meta($user_id, '_organization_logo', esc_url_raw($request->get_param('organization_logo'))); + } + + // Update basic WP user fields + if ($request->get_param('first_name')) { + update_user_meta($user_id, 'first_name', sanitize_text_field($request->get_param('first_name'))); + } + + if ($request->get_param('last_name')) { + update_user_meta($user_id, 'last_name', sanitize_text_field($request->get_param('last_name'))); + } + + if ($request->get_param('email')) { + $email = sanitize_email($request->get_param('email')); + $email_exists = email_exists($email); + + if ($email_exists && $email_exists != $user_id) { + return new WP_REST_Response([ + 'code' => 400, + 'status' => 'Error', + 'message' => 'Email already in use.' + ], 400); + } + + $result = wp_update_user([ + 'ID' => $user_id, + 'user_email' => $email + ]); + + if (is_wp_error($result)) { + return new WP_REST_Response([ + 'code' => 500, + 'status' => 'Error', + 'message' => $result->get_error_message() + ], 500); + } + } + + return new WP_REST_Response([ + 'code' => 200, + 'status' => 'OK', + 'message' => 'Profile updated successfully.' + ], 200); + } + + public function upload_user_file($request) { + if (!get_option('enable_matchmaking', false)) { + return new WP_REST_Response([ + 'code' => 403, + 'status' => 'Disabled', + 'message' => 'Matchmaking functionality is not enabled.' + ], 403); + } + + $user_id = $request->get_param('user_id'); + $user = get_user_by('id', $user_id); + + if (!$user) { + return new WP_REST_Response([ + 'code' => 404, + 'status' => 'Not Found', + 'message' => 'User not found.' + ], 404); + } + + if (empty($_FILES['file'])) { + return new WP_REST_Response([ + 'code' => 400, + 'status' => 'Error', + 'message' => 'No file uploaded.' + ], 400); + } + + require_once ABSPATH . 'wp-admin/includes/file.php'; + + $file = $_FILES['file']; + $upload_overrides = ['test_form' => false]; + $movefile = wp_handle_upload($file, $upload_overrides); + + if (!isset($movefile['url'])) { + return new WP_REST_Response([ + 'code' => 500, + 'status' => 'Error', + 'message' => 'File upload failed.' + ], 500); + } + + $file_url = esc_url_raw($movefile['url']); + + // Update both profile photo meta fields + update_user_meta($user_id, '_profile_photo', $file_url); + + + return new WP_REST_Response([ + 'code' => 200, + 'status' => 'OK', + 'message' => 'File uploaded and stored successfully.', + 'data' => [ + 'profile_photo' => $file_url, + 'meta_updated' => true + ] + ], 200); + } + +} +new WPEM_REST_MatchMaking_Profile_Controller(); \ No newline at end of file diff --git a/includes/wpem-rest-matchmaking-user-messages.php b/includes/wpem-rest-matchmaking-user-messages.php new file mode 100644 index 0000000..495c82c --- /dev/null +++ b/includes/wpem-rest-matchmaking-user-messages.php @@ -0,0 +1,391 @@ +namespace, '/' . $this->rest_base, array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array($this, 'handle_send_message'), + 'permission_callback' => array($auth_controller, 'check_authentication'), + 'args' => array( + 'senderId' => array('required' => true), + 'receiverId' => array('required' => true), + 'message' => array('required' => false), + 'image' => array('required' => false), + ), + )); + + register_rest_route($this->namespace, '/get-messages', array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array($this, 'handle_get_messages'), + 'permission_callback' => array($auth_controller, 'check_authentication'), + 'args' => array( + 'senderId' => array('required' => true, 'type' => 'integer'), + 'receiverId' => array('required' => true, 'type' => 'integer'), + 'page' => array('required' => false, 'type' => 'integer', 'default' => 1), + 'per_page' => array('required' => false, 'type' => 'integer', 'default' => 20), + ), + )); + // Get conversation list endpoint + register_rest_route($this->namespace, '/get-conversation-list', array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array($this, 'handle_get_conversation_list'), + 'permission_callback' => array($auth_controller, 'check_authentication'), + 'args' => array( + 'user_id' => array('required' => true, 'type' => 'integer'), + 'event_ids' => array('required' => true, 'type' => 'array', 'items' => array('type' => 'integer')), + 'paged' => array('required' => false, 'type' => 'integer', 'default' => 1), + 'per_page' => array('required' => false, 'type' => 'integer', 'default' => 10), + ), + )); + } + public function handle_send_message($request) { + global $wpdb; + + if (!get_option('enable_matchmaking', false)) { + return new WP_REST_Response([ + 'code' => 403, + 'status' => 'Disabled', + 'message' => 'Matchmaking functionality is not enabled.', + ], 403); + } + + $sender_id = intval($request->get_param('senderId')); + $receiver_id = intval($request->get_param('receiverId')); + $text_message = sanitize_textarea_field($request->get_param('message')); + + // Get minimal user objects just for email addresses + $sender_user = get_user_by('id', $sender_id); + $receiver_user = get_user_by('id', $receiver_id); + + if (!$sender_user || !$receiver_user) { + return new WP_REST_Response([ + 'code' => 404, + 'status' => 'Not Found', + 'message' => 'Sender or Receiver not found.', + ], 404); + } + + // Get other user data from meta + $sender_first = get_user_meta($sender_id, 'first_name', true); + $sender_last = get_user_meta($sender_id, 'last_name', true); + $sender_display_name = trim("$sender_first $sender_last"); + + $receiver_first = get_user_meta($receiver_id, 'first_name', true); + $receiver_last = get_user_meta($receiver_id, 'last_name', true); + $receiver_display_name = trim("$receiver_first $receiver_last"); + + // Get notification preferences + $sender_notify = get_user_meta($sender_id, '_message_notification', true); + $receiver_notify = get_user_meta($receiver_id, '_message_notification', true); + + if ($sender_notify != 1 || $receiver_notify != 1) { + return new WP_REST_Response([ + 'code' => 403, + 'status' => 'Forbidden', + 'message' => 'Both sender and receiver must have message notifications enabled.', + ], 403); + } + + $image_url = ''; + if (!empty($_FILES['image']['tmp_name'])) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + $uploaded = wp_handle_upload($_FILES['image'], ['test_form' => false]); + if (!isset($uploaded['error'])) { + $image_url = esc_url_raw($uploaded['url']); + } + } + + $final_message = ''; + if ($text_message && $image_url) { + $final_message = $text_message . "\n\n" . $image_url; + } elseif ($text_message) { + $final_message = $text_message; + } elseif ($image_url) { + $final_message = $image_url; + } else { + return new WP_REST_Response([ + 'code' => 400, + 'status' => 'Bad Request', + 'message' => 'Either message or image is required.', + ], 400); + } + + // Insert into DB + $table = $wpdb->prefix . 'wpem_matchmaking_users_messages'; + $first_message_id = $wpdb->get_var($wpdb->prepare( + "SELECT id FROM $table + WHERE (sender_id = %d AND receiver_id = %d) + OR (sender_id = %d AND receiver_id = %d) + ORDER BY created_at ASC LIMIT 1", + $sender_id, $receiver_id, + $receiver_id, $sender_id + )); + $parent_id = $first_message_id ?: 0; + + $wpdb->insert($table, [ + 'parent_id' => $parent_id, + 'sender_id' => $sender_id, + 'receiver_id' => $receiver_id, + 'message' => $final_message, + 'created_at' => current_time('mysql') + ], ['%d', '%d', '%d', '%s', '%s']); + + $insert_id = $wpdb->insert_id; + + // --- EMAIL SECTION --- + $headers = ['Content-Type: text/html; charset=UTF-8']; + + // Build email body for receiver (your format) + $receiver_body = "Hello, this is a message from {$sender_first}