namespace, '/' . $this->base . '/get-user-courses', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_user_courses'], 'permission_callback' => [$this, 'check_permission'], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/get-user-course-by-id', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_user_course_by_id'], 'permission_callback' => [$this, 'check_permission'], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/post-update-lesson-time', [ [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'post_update_lesson_time'], 'permission_callback' => [$this, 'check_permission'], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/post-update-lesson-time-statistics', [ [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [$this, 'post_update_lesson_time_statistics'], 'permission_callback' => [$this, 'check_permission'], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/post-set-active', [ [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'post_set_active'], 'permission_callback' => [$this, 'check_permission'], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/post-mark-as-complete', [ [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'post_mark_as_complete'], 'permission_callback' => [$this, 'check_permission'], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/post-mark-as-incomplete', [ [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'post_mark_as_incomplete'], 'permission_callback' => [$this, 'check_permission'], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/get-user-purchases', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_user_purchases'], 'permission_callback' => [$this, 'check_permission'], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/get-user-wishlist', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_user_wishlist'], 'permission_callback' => [$this, 'check_permission'], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/get-user-profile', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_user_profile'], 'permission_callback' => [$this, 'check_permission'], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/post-update-user-photo', [ [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [$this, 'post_update_user_photo'], 'permission_callback' => [$this, 'check_permission'], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/post-update-user-profile', [ [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [$this, 'post_update_user_profile'], 'permission_callback' => [$this, 'check_permission'], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/post-update-user-password', [ [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [$this, 'post_update_user_password'], 'permission_callback' => [$this, 'check_permission'], ], ] ); register_rest_route( $this->namespace, '/' . $this->base . '/get-user-certificate-by-id', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_user_certificate_by_id'], 'permission_callback' => function ($request) { return wp_verify_nonce( $request->get_header('X-WP-Nonce'), 'wp_rest' ); }, ], ] ); } public function get_user_courses($request) { // 1) Pre-parameter filter: allow callers to mutate incoming params before validation $params = apply_filters('acadlix_pre_get_user_courses_params', $request->get_params(), $request); $userId = $params['user_id'] ?? $request->get_param('user_id') ?? 0; if (empty($userId)) { return new WP_Error('no_data_found', __('Required user_id', 'acadlix'), array('status' => 404)); } // 2) Payload/Data Filter: normalize values before core execution $payload = apply_filters('acadlix_get_user_courses_payload', [ 'user_id' => intval($userId), 'page' => intval($params['page'] ?? 0), 'pageSize' => intval($params['pageSize'] ?? 10), 'search' => $params['search'] ?? null, 'category_id' => $params['category_id'] ?? null, ], $params); $skip = $payload['page'] * $payload['pageSize']; $enrolledCourses = acadlix() ->model() ->courseEnrollment() ->getEnrolledCourses( $payload['user_id'], $payload['search'], $skip, $payload['pageSize'], ['course'], $payload['category_id'] ); $categoryIds = $enrolledCourses['category_ids'] ?? []; $categories = acadlix() ->model() ->wpTermTaxonomy() ->ofCourseCategory() ->with('term') ->where('count', '>', 0) ->whereHas('term', function ($query) use ($categoryIds) { $query->whereIn('term_id', $categoryIds); }) ->get(); $res = [ 'categories' => $categories, 'total' => $enrolledCourses['total'], 'courses' => $enrolledCourses['courses'], // 'enrolled_courses' => $enrolledCourses, ]; // 3) Lifecycle Action: allow listeners to react to the fetch do_action('acadlix_get_user_courses', $res, $payload, $params); // 4) Outbound Response Filter: allow other modules to modify the response $res = apply_filters('acadlix_get_user_courses_response', $res, $payload, $params); return rest_ensure_response($res); } public function get_user_course_by_id($request) { // 1) Pre-parameter filter: allow callers to mutate incoming params before validation $params = apply_filters('acadlix_pre_get_user_course_by_id_params', $request->get_params(), $request); $courseId = $params['course_id'] ?? $request->get_param('course_id') ?? null; $userId = $params['user_id'] ?? $request->get_param('user_id') ?? null; $required_fields = array('course_id', 'user_id'); foreach ($required_fields as $field) { $param = !empty($params[$field]) ? $params[$field] : $request->get_param($field); if (empty($param)) { /* translators: %s is the required field */ $errors[] = sprintf(__('The %s parameter is required.', 'acadlix'), $field); } } if (!empty($errors)) { return new WP_Error('missing_params', implode(' ', $errors), array('status' => 400)); } // 2) Payload/Data Filter: normalize values before core execution $payload = apply_filters('acadlix_get_user_course_by_id_payload', [ 'course_id' => intval($courseId), 'user_id' => intval($userId), ], $params); // Fetch course with sections and statistics $course = acadlix() ->model() ->course() ->with([ 'sections', 'sections.contents' => function ($query) use ($payload) { $query->with([ 'course_statistics' => function ($query) use ($payload) { $query->where('user_id', $payload['user_id']); } ]); }, 'announcements', ]) ->find($payload['course_id']); $course->completion_percentage = $course->getCourseCompletionPercentage($payload['user_id']); $course_statistics = acadlix() ->model() ->courseStatistic() ->where('course_id', $payload['course_id']) ->where('user_id', $payload['user_id']) ->where('is_active', true) ->first(); if ($course_statistics) { $courseSectionContentId = $course_statistics->course_section_content_id ?? null; } else { $firstSection = $course->sections->first(); $firstContent = $firstSection?->contents?->first(); $courseSectionContentId = $firstContent?->ID ?? null; } $certificate = acadlix() ->model() ->userActivityMeta() ->ofCertificate() ->where('user_id', $payload['user_id']) ->where('type_id', $payload['course_id']) ->first(); $res = []; if ($certificate) { $res['certificate'] = $certificate; } $res['course'] = $course; $res['course_section_content_id'] = $courseSectionContentId; // 3) Lifecycle Action: allow listeners to react to the fetch do_action('acadlix_get_user_course_by_id', $res, $payload, $params); // 4) Outbound Response Filter: allow other modules to modify the response $res = apply_filters('acadlix_get_user_course_by_id_response', $res, $payload, $params); return rest_ensure_response($res); } public function post_update_lesson_time($request) { // 1) Pre-parameter filter: allow callers to mutate incoming params before validation $params = apply_filters('acadlix_pre_post_update_lesson_time_params', $request->get_params(), $request); $lessonId = $params['lesson_id'] ?? $request->get_param('lesson_id') ?? null; if (empty($lessonId)) { return new WP_Error('missing_params', __('The lesson_id parameter is required.', 'acadlix'), array('status' => 400)); } // 2) Payload/Data Filter: normalize values before core execution $payload = apply_filters('acadlix_post_update_lesson_time_payload', [ 'lesson_id' => intval($lessonId), 'hours' => intval($params['hours'] ?? 0), 'minutes' => intval($params['minutes'] ?? 0), 'seconds' => intval($params['seconds'] ?? 0), ], $params); // Prepare meta data with prefixed keys $meta = [ 'hours' => $payload['hours'], 'minutes' => $payload['minutes'], 'seconds' => $payload['seconds'], ]; $meta = !empty($meta) && is_array($meta) ? acadlix()->helper()->cpt()->acadlix_add_prefix_meta_keys($meta, 'lesson') : []; // Check if lesson exists and update $lesson = acadlix()->model()->lesson()->ofLesson()->find($payload['lesson_id']); if ($lesson) { acadlix()->model()->lesson()->updateLesson($payload['lesson_id'], [], $meta); } $res = [ 'success' => true, 'lesson' => acadlix()->model()->lesson()->ofLesson()->find($payload['lesson_id']) ]; // 3) Lifecycle Action: allow listeners to react to the update do_action('acadlix_post_update_lesson_time', $res, $payload, $params); // 4) Outbound Response Filter: allow other modules to modify the response $res = apply_filters('acadlix_post_update_lesson_time_response', $res, $payload, $params); return rest_ensure_response($res); } public function post_update_lesson_time_statistics($request) { // 1) Pre-parameter filter: allow callers to mutate incoming params before validation $params = apply_filters('acadlix_pre_post_update_lesson_time_statistics_params', $request->get_params(), $request); $courseStatisticId = $params['course_statistic_id'] ?? $request->get_param('course_statistic_id') ?? null; $metaValue = $params['meta_value'] ?? $request->get_param('meta_value') ?? null; $required_fields = array('course_statistic_id', 'meta_value'); foreach ($required_fields as $field) { $param = $params[$field] ?? $request->get_param($field) ?? null; if (empty($param)) { /* translators: %s is the required field */ $errors[] = sprintf(__('The %s parameter is required.', 'acadlix'), $field); } } if (!empty($errors)) { return new WP_Error('missing_params', implode(' ', $errors), array('status' => 400)); } // 2) Payload/Data Filter: normalize values before core execution $payload = apply_filters('acadlix_post_update_lesson_time_statistics_payload', [ 'course_statistic_id' => intval($courseStatisticId), 'meta_value' => $metaValue, ], $params); // Update course statistic $courseStatistic = acadlix()->model()->courseStatistic()->find($payload['course_statistic_id']); if ($courseStatistic) { $courseStatistic->update([ 'meta_value' => $payload['meta_value'] ]); } $res = [ 'success' => true, 'course_statistic' => acadlix()->model()->courseStatistic()->find($payload['course_statistic_id']) ]; // 3) Lifecycle Action: allow listeners to react to the update do_action('acadlix_post_update_lesson_time_statistics', $res, $payload, $params); // 4) Outbound Response Filter: allow other modules to modify the response $res = apply_filters('acadlix_post_update_lesson_time_statistics_response', $res, $payload, $params); return rest_ensure_response($res); } public function post_set_active($request) { // 1) Pre-parameter filter: allow callers to mutate incoming params before validation $params = apply_filters('acadlix_pre_post_set_active_params', $request->get_params(), $request); $courseId = $params['course_id'] ?? $request->get_param('course_id') ?? null; $courseSectionId = $params['course_section_id'] ?? $request->get_param('course_section_id') ?? null; $courseSectionContentId = $params['course_section_content_id'] ?? $request->get_param('course_section_content_id') ?? null; $userId = $params['user_id'] ?? $request->get_param('user_id') ?? null; $metaType = $params['meta_type'] ?? $request->get_param('meta_type') ?? null; $required_fields = array('course_id', 'course_section_id', 'course_section_content_id', 'user_id'); foreach ($required_fields as $field) { $param = !empty($params[$field]) ? $params[$field] : $request->get_param($field); if (empty($param)) { /* translators: %s is the required field */ $errors[] = sprintf(__('The %s parameter is required.', 'acadlix'), $field); } } if (!empty($errors)) { return new WP_Error('missing_params', implode(' ', $errors), array('status' => 400)); } // 2) Payload/Data Filter: normalize values before core execution $payload = apply_filters('acadlix_post_set_active_payload', [ 'course_section_id' => intval($courseSectionId), 'course_id' => intval($courseId), 'course_section_content_id' => intval($courseSectionContentId), 'user_id' => intval($userId), 'meta_type' => $metaType, ], $params); // Deactivate all existing course statistics for this user/course $course_statistics = acadlix() ->model() ->courseStatistic() ->where('course_id', $payload['course_id']) ->where('user_id', $payload['user_id']) ->get(); $courseEnrollment = acadlix() ->model() ->courseEnrollment() ->where('course_id', $payload['course_id']) ->where('user_id', $payload['user_id']) ->first(); if ($courseEnrollment) { $courseEnrollment->update([ 'course_section_content_id' => $payload['course_section_content_id'], ]); if ($courseEnrollment->started_at === null) { $courseEnrollment->update([ 'started_at' => current_time('mysql') ]); } } if ($course_statistics->count() > 0) { foreach ($course_statistics as $course_statistic) { $course_statistic->update([ 'is_active' => false ]); } } // Try to find or create active statistic $active_statistic = acadlix() ->model() ->courseStatistic() ->where('course_id', $payload['course_id']) ->where('course_section_content_id', $payload['course_section_content_id']) ->where('user_id', $payload['user_id']) ->first(); if ($active_statistic) { $active_statistic->update([ 'is_active' => true ]); // 3a) Conditional action: existing statistic updated do_action('acadlix_post_set_active_updated', $active_statistic, $payload, $params); } else { $active_statistic = acadlix() ->model() ->courseStatistic() ->create([ 'course_id' => $payload['course_id'], 'course_section_content_id' => $payload['course_section_content_id'], 'user_id' => $payload['user_id'], 'is_active' => true, 'is_completed' => false, 'meta_type' => $payload['meta_type'], ]); // 3b) Conditional action: new statistic created do_action('acadlix_post_set_active_created', $active_statistic, $payload, $params); } $res = [ 'active_statistic' => $active_statistic, 'success' => true ]; // 4) Outbound Response Filter: allow other modules to modify the response $res = apply_filters('acadlix_post_set_active_response', $res, $payload, $params); return rest_ensure_response($res); } public function post_mark_as_complete($request) { // 1) Pre-parameter filter: allow callers to mutate incoming params before validation $params = apply_filters('acadlix_pre_post_mark_as_complete_params', $request->get_params(), $request); $required_fields = array('course_id', 'course_section_content_id', 'user_id'); $errors = []; foreach ($required_fields as $field) { $param = $params[$field] ?? $request->get_param($field) ?? null; if (empty($param)) { /* translators: %s is the required field */ $errors[] = sprintf(__('The %s parameter is required.', 'acadlix'), $field); } } if (!empty($errors)) { return new WP_Error('missing_params', implode(' ', $errors), array('status' => 400)); } // 2) Payload/Data Filter: normalize values before core execution $payload = apply_filters('acadlix_post_mark_as_complete_payload', [ 'user_id' => intval($params['user_id'] ?? $request->get_param('user_id')), 'course_id' => intval($params['course_id'] ?? $request->get_param('course_id')), 'course_section_content_id' => intval($params['course_section_content_id'] ?? $request->get_param('course_section_content_id')), ], $params); $res = [ 'success' => true, 'course_full_completed' => false, 'course_completion_percentage' => 0, 'active_statistic' => null, ]; $active_statistic = acadlix() ->model() ->courseStatistic() ->where('course_id', $payload['course_id']) ->where('course_section_content_id', $payload['course_section_content_id']) ->where('user_id', $payload['user_id']) ->first(); $courseEnrollment = acadlix() ->model() ->courseEnrollment() ->where('course_id', $payload['course_id']) ->where('user_id', $payload['user_id']) ->first(); if ($active_statistic) { $active_statistic->update([ 'is_completed' => true ]); // 3a) Conditional action: existing statistic updated do_action('acadlix_post_mark_as_complete_updated', $active_statistic, $payload, $params); } else { $active_statistic = acadlix() ->model() ->courseStatistic() ->create([ 'course_id' => $payload['course_id'], 'course_section_content_id' => $payload['course_section_content_id'], 'user_id' => $payload['user_id'], 'is_active' => true, 'is_completed' => true, ]); // 3b) Conditional action: new statistic created do_action('acadlix_post_mark_as_complete_created', $active_statistic, $payload, $params); } $res['active_statistic'] = $active_statistic; // Save complete date if ($active_statistic) { acadlix() ->model() ->userActivityMeta() ->create([ 'user_id' => $payload['user_id'], 'type' => 'course_statistic', 'type_id' => $active_statistic->id, 'meta_key' => 'course_statistic_complete_time', // phpcs:ignore 'meta_value' => time(), // phpcs:ignore ]); } $course = acadlix() ->model() ->course() ->ofCourse() ->find($payload['course_id']); $course_completion_percentage = 0; if ($course) { $course_completion_percentage = $course->getCourseCompletionPercentage($payload['user_id']) ?? 0; $courseEnrollment->update([ 'progress_percent' => $course_completion_percentage, ]); $res['course_completion_percentage'] = $course_completion_percentage; if ($course_completion_percentage == 100) { $courseEnrollment->update([ 'status' => CourseEnrollment::STATUS_COMPLETED, 'completed_at' => current_time('mysql'), ]); $res['course_full_completed'] = true; // handle certificate creation $certificate = acadlix()->model()->userActivityMeta()->ofCertificate() ->where('user_id', $payload['user_id']) ->where('type_id', $payload['course_id']) ->first(); if (!$certificate && ($course->rendered_metas['enable_certificate'] ?? false)) { $custom_logo_id = get_theme_mod('custom_logo'); $logo_url = wp_get_attachment_image_url($custom_logo_id, 'full'); $certificate_id = acadlix()->helper()->acadlix_generate_certificate_id(); // Construct mapping names efficiently $instructor_names = []; if (empty($course->users) || count($course->users) === 0) { $instructor_names[] = get_the_author_meta('display_name', $course->post_author); } else { foreach ($course->users as $u) { $instructor_names[] = get_the_author_meta('display_name', is_object($u) ? $u->ID : $u); } } $data = [ "certificate_id" => $certificate_id, "logo" => $logo_url ?? "", "acadlix_certificate_authorised_name" => acadlix()->helper()->acadlix_get_option('acadlix_certificate_authorised_name') ?? "", "acadlix_certificate_authorised_company" => acadlix()->helper()->acadlix_get_option('acadlix_certificate_authorised_company') ?? "", "acadlix_certificate_show_instructor_name_on_certificate" => acadlix()->helper()->acadlix_get_option('acadlix_certificate_show_instructor_name_on_certificate'), "acadlix_certificate_show_course_completion_date_on_certificate" => acadlix()->helper()->acadlix_get_option('acadlix_certificate_show_course_completion_date_on_certificate'), "acadlix_certificate_signature" => acadlix()->helper()->acadlix_get_option('acadlix_certificate_signature') ?? [], "template" => acadlix()->helper()->acadlix_get_option('acadlix_certificate_template') ?? "classic-landscape", "instructor" => implode(', ', $instructor_names), "user_name" => get_the_author_meta('display_name', $payload['user_id']), "course_name" => $course->post_title ?? "", "completion_date" => time(), ]; $certificate = acadlix() ->model() ->userActivityMeta() ->create([ 'user_id' => $payload['user_id'], 'type' => 'certificate', 'type_id' => $payload['course_id'], // Will be updated after certificate creation 'meta_key' => 'certificate', // phpcs:ignore 'meta_value' => $data, // phpcs:ignore 'reference_id' => $certificate_id, ]); $res['certificate'] = $certificate; } // Operational hooks do_action('acadlix_course_completed', $payload['course_id'], $payload['user_id']); acadlix()->email()->send( acadlix()->email()->blueprint()->courseCompletion() ->create() ->courseId($payload['course_id']) ->userId($payload['user_id']) ); } } // 4) Outbound Response Filter: allow other modules to modify the response $res = apply_filters('acadlix_post_mark_as_complete_response', $res, $payload, $params); return rest_ensure_response($res); } public function post_mark_as_incomplete($request) { // 1) Pre-parameter filter $params = apply_filters('acadlix_pre_post_mark_as_incomplete_params', $request->get_params(), $request); $required_fields = ['course_id', 'course_section_content_id', 'user_id']; $errors = []; // FIXED: Initialized to prevent undefined variable errors foreach ($required_fields as $field) { $param = $params[$field] ?? $request->get_param($field) ?? null; if (empty($param)) { /* translators: %s is the required field */ $errors[] = sprintf(__('The %s parameter is required.', 'acadlix'), $field); } } if (!empty($errors)) { return new WP_Error('missing_params', implode(' ', $errors), ['status' => 400]); } // 2) Payload Filter $payload = apply_filters('acadlix_post_mark_as_incomplete_payload', [ 'user_id' => intval($params['user_id'] ?? $request->get_param('user_id')), 'course_id' => intval($params['course_id'] ?? $request->get_param('course_id')), 'course_section_content_id' => intval($params['course_section_content_id'] ?? $request->get_param('course_section_content_id')), ], $params); $res = [ 'success' => true, 'course_completion_percentage' => 0, 'active_statistic' => null, ]; $active_statistic = acadlix()->model()->courseStatistic() ->where('course_id', $payload['course_id']) ->where('course_section_content_id', $payload['course_section_content_id']) ->where('user_id', $payload['user_id']) ->first(); $courseEnrollment = acadlix() ->model() ->courseEnrollment() ->where('course_id', $payload['course_id']) ->where('user_id', $payload['user_id']) ->first(); if ($active_statistic) { $active_statistic->update(['is_completed' => false]); // 3a) Conditional Action: Updated existing statistic do_action('acadlix_post_mark_as_incomplete_updated', $active_statistic, $payload, $params); } else { $active_statistic = acadlix()->model()->courseStatistic()->create([ 'course_id' => $payload['course_id'], 'course_section_content_id' => $payload['course_section_content_id'], 'user_id' => $payload['user_id'], 'is_active' => true, 'is_completed' => false, ]); // 3b) Conditional Action: Created new statistic record do_action('acadlix_post_mark_as_incomplete_created', $active_statistic, $payload, $params); } $res['active_statistic'] = $active_statistic; // Save incomplete timestamp if ($active_statistic) { acadlix()->model()->userActivityMeta()->create([ 'user_id' => $payload['user_id'], 'type' => 'course_statistic', 'type_id' => $active_statistic->id, 'meta_key' => 'course_statistic_incomplete_time', 'meta_value' => time(), ]); } $course = acadlix() ->model() ->course() ->find($payload['course_id']); if ($course) { $course_completion_percentage = $course->getCourseCompletionPercentage($payload['user_id']) ?? 0; $courseEnrollment->update([ 'progress_percent' => $course_completion_percentage, 'status' => $course_completion_percentage < 100 ? CourseEnrollment::STATUS_ACTIVE : CourseEnrollment::STATUS_COMPLETED, ]); $res['course_completion_percentage'] = $course_completion_percentage; } // 4) Outbound response filter $res = apply_filters('acadlix_post_mark_as_incomplete_response', $res, $payload, $params); return rest_ensure_response($res); } public function get_user_purchases($request) { // 1) Pre-parameter filter: allow callers to mutate incoming params before validation $params = apply_filters('acadlix_pre_get_user_purchases_params', $request->get_params(), $request); $userId = $params['user_id'] ?? $request->get_param('user_id') ?? 0; if (empty($userId)) { return new WP_Error('no_data_found', __('Required user_id', 'acadlix'), array('status' => 404)); } // 2) Payload/Data Filter: normalize values before core execution $payload = apply_filters('acadlix_get_user_purchases_payload', [ 'user_id' => intval($userId), 'page' => intval($params['page'] ?? 0), 'pageSize' => intval($params['pageSize'] ?? 10), ], $params); $skip = $payload['page'] * $payload['pageSize']; $orders = acadlix()->model()->order() ->with(['order_items', 'order_metas', 'user']) ->where('user_id', $payload['user_id']) ->orderBy('created_at', 'desc'); $res = [ 'total' => $orders->count(), 'orders' => $orders->skip($skip)->take($payload['pageSize'])->get(), ]; // 3) Lifecycle Action: allow listeners to react to the fetch do_action('acadlix_get_user_purchases', $res, $payload, $params); // 4) Outbound Response Filter: allow other modules to modify the response $res = apply_filters('acadlix_get_user_purchases_response', $res, $payload, $params); return rest_ensure_response($res); } public function get_user_wishlist($request) { // 1) Pre-parameter filter: allow callers to mutate incoming params before validation $params = apply_filters('acadlix_pre_get_user_wishlist_params', $request->get_params(), $request); $userId = $params['user_id'] ?? $request->get_param('user_id') ?? 0; if (empty($userId)) { return new WP_Error('no_data_found', __('Required user_id', 'acadlix'), array('status' => 404)); } // 2) Payload/Data Filter: normalize values before core execution $payload = apply_filters('acadlix_get_user_wishlist_payload', [ 'user_id' => intval($userId), 'page' => intval($params['page'] ?? 0), 'pageSize' => intval($params['pageSize'] ?? 10), ], $params); $skip = $payload['page'] * $payload['pageSize']; $wishlistQuery = acadlix() ->model() ->userActivityMeta() ->ofWishlist() ->where('user_id', $payload['user_id']) ->orderBy('created_at', 'desc'); $res = [ 'total' => $wishlistQuery->count(), 'wishlist' => $wishlistQuery ->skip($skip) ->take($payload['pageSize']) ->get() ->map(function ($item) { $resolved = $item->resolveWishlistItem(); if ($resolved) { $item->item = $resolved['item']; $item->permalink = $resolved['permalink']; } return $item; }), ]; // 3) Lifecycle Action: allow listeners to react to the fetch do_action('acadlix_get_user_wishlist', $res, $payload, $params); // 4) Outbound Response Filter: allow other modules to modify the response $res = apply_filters('acadlix_get_user_wishlist_response', $res, $payload, $params); return rest_ensure_response($res); } public function get_user_profile($request) { // 1) Pre-parameter filter: allow callers to mutate incoming params before validation $params = apply_filters('acadlix_pre_get_user_profile_params', $request->get_params(), $request); $userId = $params['user_id'] ?? $request->get_param('user_id') ?? 0; if (empty($userId)) { return new WP_Error('no_data_found', __('Required user_id', 'acadlix'), array('status' => 404)); } // 2) Payload/Data Filter: normalize values before core execution $payload = apply_filters('acadlix_get_user_profile_payload', [ 'user_id' => intval($userId), ], $params); $res = [ 'user' => acadlix()->model()->wpUsers()->with('user_metas')->where('ID', $payload['user_id'])->first(), ]; // 3) Lifecycle Action: allow listeners to react to the fetch do_action('acadlix_get_user_profile', $res, $payload, $params); // 4) Outbound Response Filter: allow other modules to modify the response $res = apply_filters('acadlix_get_user_profile_response', $res, $payload, $params); return rest_ensure_response($res); } public function post_update_user_photo($request) { // 1) Pre-parameter filter $params = apply_filters('acadlix_pre_post_update_user_photo_params', $request->get_params(), $request); // Context Security: Fallback to the currently logged-in user if no ID is passed or if unauthorized $current_user_id = get_current_user_id(); $target_user_id = intval($params['user_id'] ?? $request->get_param('user_id') ?? $current_user_id); // Authorization Guard: Prevent non-admins from changing other users' photos if ($target_user_id !== $current_user_id && !current_user_can('edit_users')) { return new WP_Error('rest_forbidden', __('You do not have permission to perform this action.', 'acadlix'), ['status' => 403]); } // Required core WordPress admin files for handling uploads if (!function_exists('wp_generate_attachment_metadata')) { require_once ABSPATH . 'wp-admin/includes/image.php'; } if (!function_exists('wp_handle_upload')) { require_once ABSPATH . 'wp-admin/includes/file.php'; } if (!function_exists('wp_insert_attachment')) { require_once ABSPATH . 'wp-admin/includes/media.php'; } if ($target_user_id === 0) { return new WP_Error('missing_user_id', __('Valid user ID is required.', 'acadlix'), ['status' => 400]); } $files = $request->get_file_params(); if (empty($files['file'])) { return new WP_Error('no_file', __('No file uploaded', 'acadlix'), ['status' => 400]); } $file = $files['file']; $wp_filetype = wp_check_filetype($file['name'], null); $allowed_mimes = ['image/jpeg', 'image/png', 'image/jpg']; if (!in_array($wp_filetype['type'], $allowed_mimes, true)) { return new WP_Error('invalid_file_type', __('Only JPG and PNG files are allowed', 'acadlix'), ['status' => 400]); } // 2) Payload Filter $payload = apply_filters('acadlix_post_update_user_photo_payload', [ 'user_id' => $target_user_id, 'file' => $file ], $params); $upload_overrides = ['test_form' => false]; $uploaded_file = wp_handle_upload($payload['file'], $upload_overrides); if (isset($uploaded_file['error'])) { return new WP_Error('upload_error', $uploaded_file['error'], ['status' => 500]); } // Create attachment reference record $attachment_id = wp_insert_attachment([ 'guid' => $uploaded_file['url'], 'post_mime_type' => $wp_filetype['type'], 'post_title' => sanitize_file_name($payload['file']['name']), 'post_content' => '', 'post_status' => 'inherit' ], $uploaded_file['file']); if (is_wp_error($attachment_id) || !$attachment_id) { return new WP_Error('attachment_error', __('Failed to create attachment record.', 'acadlix'), ['status' => 500]); } // Generate downscaled imagery sizes and metadata profile $attachment_data = wp_generate_attachment_metadata($attachment_id, $uploaded_file['file']); wp_update_attachment_metadata($attachment_id, $attachment_data); $image_url = wp_get_attachment_url($attachment_id); // CLEANUP FIX: Delete the old profile picture attachment from storage to avoid bloating $old_attachment_id = get_user_meta($payload['user_id'], '_acadlix_profile_photo_id', true); if (!empty($old_attachment_id)) { wp_delete_attachment($old_attachment_id, true); // true forces bypass trash } // Keep records of both the URL and Attachment ID for easier meta querying update_user_meta($payload['user_id'], '_acadlix_profile_photo', $image_url); update_user_meta($payload['user_id'], '_acadlix_profile_photo_id', $attachment_id); // 3) Action Hooks do_action('acadlix_user_photo_updated', $payload['user_id'], $image_url, $attachment_id); $res = [ 'success' => true, 'url' => $image_url, 'user' => acadlix()->model()->wpUsers()->with('user_metas')->where('ID', $payload['user_id'])->first(), ]; // 4) Outbound response filter $res = apply_filters('acadlix_post_update_user_photo_response', $res, $payload, $params); return rest_ensure_response($res); } public function post_update_user_profile($request) { // 1) Pre-parameter filter: allow callers to mutate incoming params before validation $params = apply_filters('acadlix_pre_post_update_user_profile_params', $request->get_json_params(), $request); $userId = $request->get_param('user_id'); if (empty($userId)) { return new WP_Error('no_data_found', __('Required user_id', 'acadlix'), array('status' => 404)); } // 2) Payload/Data Filter: normalize values before core execution $payload = apply_filters('acadlix_post_update_user_profile_payload', [ 'user_id' => intval($userId), 'first_name' => sanitize_text_field($params['first_name'] ?? ''), 'last_name' => sanitize_text_field($params['last_name'] ?? ''), 'user_url' => sanitize_text_field($params['user_url'] ?? ''), 'description' => sanitize_text_field($params['description'] ?? ''), 'phonecode' => sanitize_text_field($params['phonecode'] ?? ''), 'isocode' => sanitize_text_field($params['isocode'] ?? ''), 'phone_number' => sanitize_text_field($params['phone_number'] ?? ''), 'address' => sanitize_text_field($params['address'] ?? ''), 'country' => sanitize_text_field($params['country'] ?? ''), 'city' => sanitize_text_field($params['city'] ?? ''), 'zip_code' => sanitize_text_field($params['zip_code'] ?? ''), 'photo' => sanitize_text_field($params['photo'] ?? ''), ], $params); wp_update_user([ 'ID' => $payload['user_id'], 'display_name' => trim($payload['first_name'] . ' ' . $payload['last_name']), 'user_url' => $payload['user_url'], ]); update_user_meta($payload['user_id'], 'first_name', $payload['first_name']); update_user_meta($payload['user_id'], 'last_name', $payload['last_name']); update_user_meta($payload['user_id'], 'description', $payload['description']); update_user_meta($payload['user_id'], '_acadlix_profile_phonecode', $payload['phonecode']); update_user_meta($payload['user_id'], '_acadlix_profile_isocode', $payload['isocode']); update_user_meta($payload['user_id'], '_acadlix_profile_phone_number', $payload['phone_number']); update_user_meta($payload['user_id'], '_acadlix_profile_address', $payload['address']); update_user_meta($payload['user_id'], '_acadlix_profile_country', $payload['country']); update_user_meta($payload['user_id'], '_acadlix_profile_city', $payload['city']); update_user_meta($payload['user_id'], '_acadlix_profile_zip_code', $payload['zip_code']); // update_user_meta($payload['user_id'], '_acadlix_profile_photo', $payload['photo']); $res = [ 'user' => acadlix()->model()->wpUsers()->with('user_metas')->where('ID', $payload['user_id'])->first(), ]; // 3) Lifecycle Action: allow listeners to react to the update do_action('acadlix_post_update_user_profile', $res, $payload, $params); // 4) Outbound Response Filter: allow other modules to modify the response $res = apply_filters('acadlix_post_update_user_profile_response', $res, $payload, $params); return rest_ensure_response($res); } public function post_update_user_password($request) { // 1) Pre-parameter filter: allow callers to mutate incoming params before validation $params = apply_filters('acadlix_pre_post_update_user_password_params', $request->get_json_params(), $request); $user = wp_get_current_user(); if (!$user || !$user->ID) { return new WP_Error('no_user', __('User not found', 'acadlix'), array('status' => 404)); } $payload = apply_filters('acadlix_post_update_user_password_payload', [ 'user_id' => $user->ID, 'old_password' => sanitize_text_field($params['old_password'] ?? ''), 'new_password' => sanitize_text_field($params['new_password'] ?? ''), 'confirm_new_password' => sanitize_text_field($params['confirm_new_password'] ?? ''), ], $params); // Validate fields if (empty($payload['old_password']) || empty($payload['new_password']) || empty($payload['confirm_new_password'])) { return new WP_Error('missing_fields', __('All password fields are required', 'acadlix'), array('status' => 400)); } // Check old password if (!wp_check_password($payload['old_password'], $user->user_pass, $user->ID)) { return new WP_Error('incorrect_old_password', __('Old password is incorrect', 'acadlix'), array('status' => 400)); } // Check new password match if ($payload['new_password'] !== $payload['confirm_new_password']) { return new WP_Error('password_mismatch', __('Passwords do not match', 'acadlix'), array('status' => 400)); } // Optional password strength validation if (strlen($payload['new_password']) < 8) { return new WP_Error('weak_password', __('Password must be at least 8 characters', 'acadlix'), array('status' => 400)); } // Update password wp_set_password($payload['new_password'], $user->ID); // Re-login user (important) wp_set_auth_cookie($user->ID); $res = [ 'message' => __('Password updated successfully', 'acadlix'), 'user' => acadlix()->model()->wpUsers()->with('user_metas')->where('ID', $user->ID)->first(), ]; // 3) Lifecycle Action: allow listeners to react to the password update do_action('acadlix_post_update_user_password', $res, $payload, $params); // 4) Outbound Response Filter: allow other modules to modify the response $res = apply_filters('acadlix_post_update_user_password_response', $res, $payload, $params); return rest_ensure_response($res); } public function get_user_certificate_by_id($request) { // 1) Pre-parameter filter: allow callers to mutate incoming params before validation $params = apply_filters('acadlix_pre_get_user_certificate_by_id_params', $request->get_params(), $request); $certificateId = $params['certificate_id'] ?? $request->get_param('certificate_id'); if (empty($certificateId)) { return new WP_Error('no_data_found', __('Required certificate_id', 'acadlix'), array('status' => 404)); } // 2) Payload/Data Filter: normalize values before core execution $payload = apply_filters('acadlix_get_user_certificate_by_id_payload', [ 'certificate_id' => sanitize_text_field($certificateId), ], $params); $certificate = acadlix() ->model() ->userActivityMeta() ->ofCertificate() ->where('reference_id', $payload['certificate_id']) ->first(); if (!$certificate) { return new WP_Error('no_data_found', __('Certificate not found', 'acadlix'), array('status' => 404)); } $res = ['certificate' => $certificate]; // 3) Lifecycle Action: allow listeners to react to the fetch do_action('acadlix_get_user_certificate_by_id', $res, $payload, $params); // 4) Outbound Response Filter: allow other modules to modify the response $res = apply_filters('acadlix_get_user_certificate_by_id_response', $res, $payload, $params); return rest_ensure_response($res); } public function check_permission($request) { if (!is_user_logged_in()) { return new WP_Error('not_logged_in', 'Login required', ['status' => 401]); } $user_id = absint($request->get_param('user_id') ?? 0); if ($user_id && get_current_user_id() !== $user_id) { return new WP_Error('forbidden', 'Access denied', ['status' => 403]); } return true; } }