PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 All 139 releases
learnpress / inc / MCP / Domain / EnrollmentTools.php

EnrollmentTools.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.8, at inc/MCP/Domain/EnrollmentTools.php

389 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace LearnPress\MCP\Domain;
4
5 use LearnPress\MCP\Mappers\ResponseMapper;
6 use LearnPress\MCP\Support\Errors;
7 use LearnPress\MCP\Support\Pagination;
8 use LearnPress\MCP\Support\Permissions;
9 use LearnPress\MCP\Support\Sanitizer;
10 use LearnPress\MCP\Support\Validator;
11 use LearnPress\Databases\UserItemsDB;
12 use LearnPress\Filters\UserItemsFilter;
13 use LearnPress\Models\CourseModel;
14 use LearnPress\Models\UserItems\UserCourseModel;
15 use LearnPress\Models\UserItems\UserItemModel;
16 use LearnPress\Models\UserModel;
17 use LP_User_Items_Filter;
18 use Throwable;
19 use WP_Error;
20
21 defined( 'ABSPATH' ) || exit;
22
23 /**
24 * Enrollment create/update executors.
25 *
26 * Uses LearnPress user-item models so course enrollment caches stay consistent.
27 * Avoids creating duplicate active enrollments.
28 */
29 class EnrollmentTools {
30
31 /**
32 * Statuses considered an active enrollment.
33 */
34 const ACTIVE_STATUSES = array(
35 UserItemModel::STATUS_ENROLLED,
36 UserItemModel::STATUS_PURCHASED,
37 UserItemModel::STATUS_FINISHED,
38 UserItemModel::STATUS_COMPLETED,
39 );
40
41 /**
42 * Execute `learnpress/enroll-student`.
43 *
44 * @param mixed $input Ability input.
45 *
46 * @return array|WP_Error
47 */
48 public static function enroll_student( $input ) {
49 $args = is_array( $input ) ? $input : array();
50 $user_id = Validator::require_id( $args, 'user_id' );
51 if ( is_wp_error( $user_id ) ) {
52 return $user_id;
53 }
54 $course_id = Validator::require_id( $args, 'course_id' );
55 if ( is_wp_error( $course_id ) ) {
56 return $course_id;
57 }
58
59 $user = Validator::find_user( $user_id );
60 if ( is_wp_error( $user ) ) {
61 return $user;
62 }
63 $course = Validator::find_course( $course_id );
64 if ( is_wp_error( $course ) ) {
65 return $course;
66 }
67
68 if ( ! Permissions::can_manage_enrollments() ) {
69 return Errors::forbidden();
70 }
71
72 $status = Validator::enrollment_status( $args['status'] ?? '' );
73 if ( is_wp_error( $status ) ) {
74 return $status;
75 }
76 if ( '' === $status ) {
77 $status = UserItemModel::STATUS_ENROLLED;
78 }
79 if ( ! in_array( $status, Validator::enroll_create_statuses(), true ) ) {
80 return Errors::invalid(
81 __( 'enroll-student status must be "enrolled" or "purchased". Use update-enrollment for other states.', 'learnpress' )
82 );
83 }
84
85 try {
86 $existing = UserCourseModel::find( $user_id, $course_id, false );
87 if ( $existing instanceof UserCourseModel
88 && in_array( $existing->get_status(), self::ACTIVE_STATUSES, true ) ) {
89 return self::enroll_result( $existing, true );
90 }
91
92 $start_time = gmdate( 'Y-m-d H:i:s', time() );
93 if ( isset( $args['start_time'] ) && '' !== $args['start_time'] ) {
94 $parsed = Sanitizer::datetime( $args['start_time'] );
95 if ( null === $parsed ) {
96 return Errors::invalid( __( 'start_time is not a valid date/time.', 'learnpress' ) );
97 }
98 $start_time = $parsed;
99 }
100
101 $enrollment = new UserCourseModel();
102 $enrollment->user_id = $user_id;
103 $enrollment->item_id = $course_id;
104 $enrollment->item_type = LP_COURSE_CPT;
105 $enrollment->ref_type = '';
106 $enrollment->status = $status;
107 $enrollment->graduation = UserItemModel::GRADUATION_IN_PROGRESS;
108 $enrollment->start_time = $start_time;
109 $enrollment->save();
110
111 return self::enroll_result( $enrollment, false );
112 } catch ( Throwable $e ) {
113 return Errors::from_throwable( $e );
114 }
115 }
116
117 /**
118 * Execute `learnpress/update-enrollment`.
119 *
120 * @param mixed $input Ability input.
121 *
122 * @return array|WP_Error
123 */
124 public static function update_enrollment( $input ) {
125 $args = is_array( $input ) ? $input : array();
126 $enrollment_id = Validator::require_id( $args, 'enrollment_id' );
127 if ( is_wp_error( $enrollment_id ) ) {
128 return $enrollment_id;
129 }
130
131 $enrollment = self::find_by_id( $enrollment_id );
132 if ( ! $enrollment instanceof UserCourseModel ) {
133 return Errors::not_found( __( 'Course enrollment not found.', 'learnpress' ) );
134 }
135
136 if ( ! Permissions::can_manage_enrollments() ) {
137 return Errors::forbidden();
138 }
139
140 if ( ! empty( $args['user_id'] ) && absint( $args['user_id'] ) !== (int) $enrollment->user_id ) {
141 return Errors::not_found( __( 'Enrollment does not match the provided user.', 'learnpress' ) );
142 }
143 if ( ! empty( $args['course_id'] ) && absint( $args['course_id'] ) !== (int) $enrollment->item_id ) {
144 return Errors::not_found( __( 'Enrollment does not match the provided course.', 'learnpress' ) );
145 }
146
147 $status = Validator::enrollment_status( $args['status'] ?? '' );
148 if ( is_wp_error( $status ) ) {
149 return $status;
150 }
151 $graduation = Validator::graduation( $args['graduation'] ?? '' );
152 if ( is_wp_error( $graduation ) ) {
153 return $graduation;
154 }
155
156 // Do not silently reopen a finished/completed enrollment to an active state.
157 if ( '' !== $status
158 && in_array( $enrollment->get_status(), array( UserItemModel::STATUS_FINISHED, UserItemModel::STATUS_COMPLETED ), true )
159 && in_array( $status, Validator::enroll_create_statuses(), true ) ) {
160 return Errors::invalid(
161 __( 'Cannot reopen a finished enrollment. Create a new enrollment instead.', 'learnpress' )
162 );
163 }
164
165 $start_time = null;
166 if ( isset( $args['start_time'] ) && '' !== $args['start_time'] ) {
167 $start_time = Sanitizer::datetime( $args['start_time'] );
168 if ( null === $start_time ) {
169 return Errors::invalid( __( 'start_time is not a valid date/time.', 'learnpress' ) );
170 }
171 }
172 $end_time = null;
173 if ( isset( $args['end_time'] ) && '' !== $args['end_time'] ) {
174 $end_time = Sanitizer::datetime( $args['end_time'] );
175 if ( null === $end_time ) {
176 return Errors::invalid( __( 'end_time is not a valid date/time.', 'learnpress' ) );
177 }
178 }
179
180 try {
181 if ( '' !== $status ) {
182 $enrollment->status = $status;
183 }
184 if ( '' !== $graduation ) {
185 $enrollment->graduation = $graduation;
186 }
187 if ( null !== $start_time ) {
188 $enrollment->start_time = $start_time;
189 }
190 if ( null !== $end_time ) {
191 $enrollment->end_time = $end_time;
192 }
193
194 $enrollment->save();
195
196 return array( 'enrollment' => ResponseMapper::enrollment( $enrollment ) );
197 } catch ( Throwable $e ) {
198 return Errors::from_throwable( $e );
199 }
200 }
201
202 /**
203 * Execute `learnpress/get-student-progress`.
204 *
205 * @param mixed $input Ability input.
206 *
207 * @return array|WP_Error
208 */
209 public static function get_student_progress( $input ) {
210 $args = Sanitizer::input_array( $input, 'learnpress/get-student-progress' );
211 if ( is_wp_error( $args ) ) {
212 return $args;
213 }
214
215 $user_id = Validator::require_id( $args, 'user_id' );
216 if ( is_wp_error( $user_id ) ) {
217 return $user_id;
218 }
219 $course_id = Validator::require_id( $args, 'course_id' );
220 if ( is_wp_error( $course_id ) ) {
221 return $course_id;
222 }
223
224 try {
225 $user_course = UserCourseModel::find( $user_id, $course_id, true );
226 if ( ! $user_course instanceof UserCourseModel ) {
227 return Errors::not_found( __( 'Enrollment not found for this user and course.', 'learnpress' ) );
228 }
229
230 $course = CourseModel::find( $course_id, true );
231 $user = UserModel::find( $user_id, true );
232 $results = $user_course->calculate_course_results();
233 $count = (int) ( $results['count_items'] ?? 0 );
234 $done = (int) ( $results['completed_items'] ?? 0 );
235 $percent = $count > 0 ? round( ( $done * 100 ) / $count, 2 ) : 0;
236
237 return array(
238 'progress' => array(
239 'user' => array(
240 'user_id' => $user_id,
241 'display_name' => $user instanceof UserModel ? $user->get_display_name() : '',
242 'email' => $user instanceof UserModel ? $user->get_email() : '',
243 ),
244 'course' => array(
245 'course_id' => $course_id,
246 'title' => $course instanceof CourseModel ? $course->get_title() : '',
247 ),
248 'enrollment' => array(
249 'status' => (string) $user_course->status,
250 'graduation' => (string) $user_course->graduation,
251 'start_time' => (string) $user_course->start_time,
252 'end_time' => (string) $user_course->end_time,
253 ),
254 'result' => array(
255 'count_items' => $count,
256 'completed_items' => $done,
257 'progress_percent' => $percent,
258 'evaluate_type' => (string) ( $results['evaluate_type'] ?? '' ),
259 'pass' => (int) ( $results['pass'] ?? 0 ),
260 'result' => (float) ( $results['result'] ?? 0 ),
261 'items' => (array) ( $results['items'] ?? array() ),
262 ),
263 ),
264 );
265 } catch ( Throwable $e ) {
266 return Errors::internal();
267 }
268 }
269
270 /**
271 * Execute `learnpress/get-enrollments`.
272 *
273 * @param mixed $input Ability input.
274 *
275 * @return array|WP_Error
276 */
277 public static function get_enrollments( $input ) {
278 $args = Sanitizer::input_array( $input, 'learnpress/get-enrollments' );
279 if ( is_wp_error( $args ) ) {
280 return $args;
281 }
282
283 $page = Pagination::page( $args['page'] ?? 1 );
284 $per_page = Pagination::per_page( $args['per_page'] ?? 10 );
285 $filter = new UserItemsFilter();
286 $filter->item_type = LP_COURSE_CPT;
287 $filter->page = $page;
288 $filter->limit = $per_page;
289 $filter->order_by = 'ui.user_item_id';
290 $filter->order = 'DESC';
291
292 if ( ! empty( $args['course_id'] ) ) {
293 $filter->item_id = absint( $args['course_id'] );
294 }
295 if ( ! empty( $args['user_id'] ) ) {
296 $filter->user_id = absint( $args['user_id'] );
297 }
298
299 $statuses = Sanitizer::status_list( $args['status'] ?? null );
300 if ( count( $statuses ) === 1 ) {
301 $filter->status = $statuses[0];
302 } elseif ( count( $statuses ) > 1 ) {
303 $filter->statues = $statuses;
304 }
305
306 try {
307 $total_rows = 0;
308 $rows = UserItemsDB::getInstance()->get_user_items( $filter, $total_rows );
309 $rows = is_array( $rows ) ? $rows : array();
310 $items = array();
311
312 foreach ( $rows as $row ) {
313 $enroll_user_id = absint( $row->user_id ?? 0 );
314 $enroll_course_id = absint( $row->item_id ?? 0 );
315 $course = $enroll_course_id > 0 ? CourseModel::find( $enroll_course_id, true ) : false;
316 $user = $enroll_user_id > 0 ? UserModel::find( $enroll_user_id, true ) : false;
317
318 $items[] = array(
319 'enrollment_id' => absint( $row->user_item_id ?? 0 ),
320 'user' => array(
321 'user_id' => $enroll_user_id,
322 'display_name' => $user instanceof UserModel ? $user->get_display_name() : '',
323 'email' => $user instanceof UserModel ? $user->get_email() : '',
324 ),
325 'course' => array(
326 'course_id' => $enroll_course_id,
327 'title' => $course instanceof CourseModel ? $course->get_title() : '',
328 ),
329 'status' => (string) ( $row->status ?? '' ),
330 'graduation' => (string) ( $row->graduation ?? '' ),
331 'start_time' => (string) ( $row->start_time ?? '' ),
332 'end_time' => (string) ( $row->end_time ?? '' ),
333 'ref_id' => absint( $row->ref_id ?? 0 ),
334 'ref_type' => (string) ( $row->ref_type ?? '' ),
335 'parent_id' => absint( $row->parent_id ?? 0 ),
336 );
337 }
338
339 return array(
340 'items' => $items,
341 'pagination' => array(
342 'page' => $page,
343 'per_page' => $per_page,
344 'total_items' => (int) $total_rows,
345 'total_pages' => Pagination::total_pages( (int) $total_rows, $per_page ),
346 ),
347 );
348 } catch ( Throwable $e ) {
349 return Errors::internal();
350 }
351 }
352
353 /**
354 * Resolve a course enrollment by its user_item_id.
355 *
356 * @param int $enrollment_id Enrollment ID.
357 *
358 * @return UserCourseModel|false
359 */
360 protected static function find_by_id( int $enrollment_id ) {
361 $filter = new LP_User_Items_Filter();
362 $filter->user_item_id = $enrollment_id;
363 $filter->item_type = LP_COURSE_CPT;
364
365 $model = UserCourseModel::get_user_item_model_from_db( $filter );
366
367 return $model instanceof UserCourseModel ? $model : false;
368 }
369
370 /**
371 * Build the enroll-student result payload.
372 *
373 * @param UserCourseModel $enrollment Enrollment model.
374 * @param bool $existing Whether this was a pre-existing enrollment.
375 *
376 * @return array
377 */
378 protected static function enroll_result( UserCourseModel $enrollment, bool $existing ): array {
379 return array(
380 'enrollment_id' => (int) $enrollment->get_user_item_id(),
381 'user_id' => (int) $enrollment->user_id,
382 'course_id' => (int) $enrollment->item_id,
383 'status' => (string) $enrollment->get_status(),
384 'start_time' => (string) $enrollment->get_start_time(),
385 'already_enrolled' => $existing,
386 );
387 }
388 }
389