BaseModel.php
10 months ago
BillingModel.php
1 year ago
CartItemModel.php
10 months ago
CartModel.php
2 days ago
CouponModel.php
2 days ago
CourseModel.php
2 days ago
EnrollmentModel.php
2 days ago
LessonModel.php
9 months ago
OrderActivitiesModel.php
1 year ago
OrderItemMetaModel.php
10 months ago
OrderItemModel.php
10 months ago
OrderMetaModel.php
1 year ago
OrderModel.php
2 days ago
QuizModel.php
2 days ago
UserModel.php
1 year ago
WithdrawModel.php
2 days ago
EnrollmentModel.php
374 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Enrollment Model |
| 4 | * |
| 5 | * @package Tutor\Models |
| 6 | * @author Themeum <support@themeum.com> |
| 7 | * @link https://themeum.com |
| 8 | * @since 4.0.0 |
| 9 | */ |
| 10 | |
| 11 | namespace Tutor\Models; |
| 12 | |
| 13 | use Tutor\Cache\TutorCache; |
| 14 | use TUTOR\Course; |
| 15 | use Tutor\Helpers\QueryHelper; |
| 16 | use TUTOR\User; |
| 17 | |
| 18 | /** |
| 19 | * Class EnrollmentModel |
| 20 | * |
| 21 | * @since 4.0.0 |
| 22 | */ |
| 23 | class EnrollmentModel { |
| 24 | /** |
| 25 | * Enrollment status constants |
| 26 | * |
| 27 | * @since 4.0.0 |
| 28 | * |
| 29 | * @var string |
| 30 | */ |
| 31 | const STATUS_COMPLETED = 'completed'; |
| 32 | const STATUS_PENDING = 'pending'; |
| 33 | const STATUS_CANCEL = 'cancel'; |
| 34 | |
| 35 | /** |
| 36 | * Enrollment post type |
| 37 | * |
| 38 | * @since 4.0.0 |
| 39 | * |
| 40 | * @var string |
| 41 | */ |
| 42 | const POST_TYPE = 'tutor_enrolled'; |
| 43 | |
| 44 | |
| 45 | /** |
| 46 | * Enrollment meta |
| 47 | * |
| 48 | * @since 4.0.0 |
| 49 | */ |
| 50 | const ENROLLMENT_ORDER_ID_META = '_tutor_enrolled_by_order_id'; |
| 51 | const ENROLLMENT_PRODUCT_ID_META = '_tutor_enrolled_by_product_id'; |
| 52 | |
| 53 | /** |
| 54 | * Saving enroll information to posts table |
| 55 | * post_author = enrolled_student_id (wp_users id) |
| 56 | * post_parent = enrolled course id |
| 57 | * |
| 58 | * @since 1.0.0 |
| 59 | * @since 2.6.0 Return enrolled id |
| 60 | * @since 3.3.0 Added $fire_hook parameter. |
| 61 | * |
| 62 | * @param int $course_id course id. |
| 63 | * @param int $order_id order id. |
| 64 | * @param int $user_id user id. |
| 65 | * @param bool $fire_hook fire hook. |
| 66 | * |
| 67 | * @return int enrolled id |
| 68 | */ |
| 69 | public static function do_enroll( $course_id = 0, $order_id = 0, $user_id = 0, $fire_hook = true ) { |
| 70 | $enrolled_id = 0; |
| 71 | if ( ! CourseModel::is_course_accessible( $course_id ) ) { |
| 72 | return $enrolled_id; |
| 73 | } |
| 74 | |
| 75 | $can_enroll = apply_filters( 'tutor_allow_course_enrollment', true, $course_id ); |
| 76 | if ( is_wp_error( $can_enroll ) ) { |
| 77 | return $enrolled_id; |
| 78 | } |
| 79 | |
| 80 | $fire_hook ? do_action( 'tutor_before_enroll', $course_id ) : null; |
| 81 | $user_id = tutor_utils()->get_user_id( $user_id ); |
| 82 | $title = __( 'Course Enrolled', 'tutor' ) . ' – ' . gmdate( get_option( 'date_format' ) ) . ' @ ' . gmdate( get_option( 'time_format' ) ); |
| 83 | |
| 84 | if ( $course_id && $user_id ) { |
| 85 | $enrolled_info = self::is_enrolled( $course_id, $user_id ); |
| 86 | if ( $enrolled_info ) { |
| 87 | return $enrolled_info->ID; |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | $enrolment_status = self::STATUS_COMPLETED; |
| 92 | |
| 93 | if ( tutor_utils()->is_course_purchasable( $course_id ) ) { |
| 94 | $enrolment_status = self::STATUS_PENDING; |
| 95 | } |
| 96 | |
| 97 | $enroll_data = apply_filters( |
| 98 | 'tutor_enroll_data', |
| 99 | array( |
| 100 | 'post_type' => self::POST_TYPE, |
| 101 | 'post_title' => $title, |
| 102 | 'post_status' => $enrolment_status, |
| 103 | 'post_author' => $user_id, |
| 104 | 'post_parent' => $course_id, |
| 105 | 'post_date_gmt' => current_time( 'mysql', true ), |
| 106 | ) |
| 107 | ); |
| 108 | |
| 109 | // Insert the post into the database. |
| 110 | $is_enrolled = wp_insert_post( $enroll_data ); |
| 111 | if ( $is_enrolled ) { |
| 112 | |
| 113 | // Run this hook for both of pending and completed enrollment. |
| 114 | $fire_hook ? do_action( 'tutor_after_enroll', $course_id, $is_enrolled ) : null; |
| 115 | |
| 116 | // Mark Current User as Students with user meta data. |
| 117 | update_user_meta( $user_id, User::TUTOR_STUDENT_META, tutor_time() ); |
| 118 | |
| 119 | if ( $order_id ) { |
| 120 | // Mark order for course and user. |
| 121 | $product_id = tutor_utils()->get_course_product_id( $course_id ); |
| 122 | update_post_meta( $is_enrolled, self::ENROLLMENT_ORDER_ID_META, $order_id ); |
| 123 | update_post_meta( $is_enrolled, self::ENROLLMENT_PRODUCT_ID_META, $product_id ); |
| 124 | |
| 125 | $monetize_by = tutor_utils()->get_option( 'monetize_by' ); |
| 126 | if ( 'wc' === $monetize_by ) { |
| 127 | $order = wc_get_order( $order_id ); |
| 128 | $order->update_meta_data( Course::IS_TUTOR_ORDER_FOR_COURSE_META, tutor_time() ); |
| 129 | $order->update_meta_data( Course::TUTOR_ORDER_FOR_COURSE_ID_META . $course_id, $is_enrolled ); |
| 130 | $order->save(); |
| 131 | } elseif ( 'edd' === $monetize_by ) { |
| 132 | $payment = new \EDD_Payment( $order_id ); |
| 133 | $payment->update_meta( Course::IS_TUTOR_ORDER_FOR_COURSE_META, tutor_time() ); |
| 134 | $payment->update_meta( Course::TUTOR_ORDER_FOR_COURSE_ID_META . $course_id, $is_enrolled ); |
| 135 | $payment->save(); |
| 136 | } else { |
| 137 | update_post_meta( $order_id, Course::IS_TUTOR_ORDER_FOR_COURSE_META, tutor_time() ); |
| 138 | update_post_meta( $order_id, Course::TUTOR_ORDER_FOR_COURSE_ID_META . $course_id, $is_enrolled ); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | $enrolled_id = $is_enrolled; |
| 143 | |
| 144 | // Run this hook for completed enrollment regardless of payment provider and free/paid mode. |
| 145 | if ( $fire_hook && self::STATUS_COMPLETED === $enroll_data['post_status'] ) { |
| 146 | do_action( 'tutor_after_enrolled', $course_id, $user_id, $enrolled_id ); |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | return $enrolled_id; |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * Check if current user has been enrolled or not |
| 155 | * |
| 156 | * @since 1.0.0 |
| 157 | * |
| 158 | * @since 3.0.0 $is_complete parameter added to check with completed status |
| 159 | * Default value set true for backward compatibility. It set |
| 160 | * false then it will just check record. |
| 161 | * |
| 162 | * @since 3.3.0 param $is_complete added to cache key. |
| 163 | * @since 4.0.0 enrollment order_id and product_id added to enrollment info. |
| 164 | * |
| 165 | * @param int $course_id course id. |
| 166 | * @param int $user_id user id. |
| 167 | * @param bool $is_complete Whether to enrollment completed or not. |
| 168 | * |
| 169 | * @return array|bool|null|object |
| 170 | */ |
| 171 | public static function is_enrolled( $course_id = 0, $user_id = 0, bool $is_complete = true ) { |
| 172 | global $wpdb; |
| 173 | $course_id = tutor_utils()->get_post_id( $course_id ); |
| 174 | $user_id = tutor_utils()->get_user_id( $user_id ); |
| 175 | |
| 176 | $cache_key = "tutor_is_enrolled_{$course_id}_{$user_id}_{$is_complete}"; |
| 177 | $cached = TutorCache::get( $cache_key ); |
| 178 | |
| 179 | if ( false !== $cached ) { |
| 180 | return $cached; |
| 181 | } |
| 182 | |
| 183 | do_action( 'tutor_is_enrolled_before', $course_id, $user_id ); |
| 184 | |
| 185 | $status_clause = ''; |
| 186 | if ( $is_complete ) { |
| 187 | $status_clause = $wpdb->prepare( 'AND post_status = %s ', self::STATUS_COMPLETED ); |
| 188 | } |
| 189 | |
| 190 | //phpcs:disable |
| 191 | $get_enrolled_info = $wpdb->get_row( |
| 192 | $wpdb->prepare( |
| 193 | "SELECT ID, |
| 194 | post_author, |
| 195 | post_date, |
| 196 | post_date_gmt, |
| 197 | post_title |
| 198 | FROM {$wpdb->posts} |
| 199 | WHERE post_author > 0 |
| 200 | AND post_parent > 0 |
| 201 | AND post_type = %s |
| 202 | AND post_parent = %d |
| 203 | AND post_author = %d |
| 204 | {$status_clause}; |
| 205 | ", |
| 206 | self::POST_TYPE, |
| 207 | $course_id, |
| 208 | $user_id |
| 209 | ) |
| 210 | ); |
| 211 | //phpcs:enable |
| 212 | |
| 213 | if ( $get_enrolled_info ) { |
| 214 | $get_enrolled_info->order_id = (int) get_post_meta( $get_enrolled_info->ID, self::ENROLLMENT_ORDER_ID_META, true ); |
| 215 | $get_enrolled_info->product_id = (int) get_post_meta( $get_enrolled_info->ID, self::ENROLLMENT_PRODUCT_ID_META, true ); |
| 216 | } |
| 217 | |
| 218 | TutorCache::set( $cache_key, $get_enrolled_info ); |
| 219 | |
| 220 | if ( $get_enrolled_info ) { |
| 221 | return apply_filters( 'tutor_is_enrolled', $get_enrolled_info, $course_id, $user_id ); |
| 222 | } |
| 223 | |
| 224 | return false; |
| 225 | } |
| 226 | |
| 227 | /** |
| 228 | * Get enrollment by enrol_id |
| 229 | * |
| 230 | * @since 1.6.9 |
| 231 | * |
| 232 | * @param int $enrol_id enrol id. |
| 233 | * |
| 234 | * @return array|object |
| 235 | */ |
| 236 | public static function get_enrolment_by_enrol_id( $enrol_id = 0 ) { |
| 237 | global $wpdb; |
| 238 | |
| 239 | $enrolment = $wpdb->get_row( |
| 240 | $wpdb->prepare( |
| 241 | "SELECT enrol.id AS enrol_id, |
| 242 | enrol.post_author AS student_id, |
| 243 | enrol.post_date AS enrol_date, |
| 244 | enrol.post_title AS enrol_title, |
| 245 | enrol.post_status AS status, |
| 246 | enrol.post_parent AS course_id, |
| 247 | course.post_title AS course_title, |
| 248 | student.user_nicename, |
| 249 | student.user_email, |
| 250 | student.display_name, |
| 251 | student.ID |
| 252 | FROM {$wpdb->posts} enrol |
| 253 | INNER JOIN {$wpdb->posts} course |
| 254 | ON enrol.post_parent = course.id |
| 255 | INNER JOIN {$wpdb->users} student |
| 256 | ON enrol.post_author = student.id |
| 257 | WHERE enrol.id = %d; |
| 258 | ", |
| 259 | $enrol_id |
| 260 | ) |
| 261 | ); |
| 262 | |
| 263 | if ( $enrolment ) { |
| 264 | return $enrolment; |
| 265 | } |
| 266 | |
| 267 | return false; |
| 268 | } |
| 269 | |
| 270 | /** |
| 271 | * Get single or list of enrolled course data by a user |
| 272 | * |
| 273 | * @since 2.0.5 |
| 274 | * |
| 275 | * @param integer $user_id user id. |
| 276 | * @param integer $course_id cousrs id. |
| 277 | * |
| 278 | * @return object|mixed |
| 279 | */ |
| 280 | public static function get_enrolled_data( $user_id = 0, $course_id = 0 ) { |
| 281 | global $wpdb; |
| 282 | // If course ID provided, it will return single row data. |
| 283 | if ( $course_id > 0 ) { |
| 284 | return $wpdb->get_row( |
| 285 | $wpdb->prepare( |
| 286 | "SELECT * FROM {$wpdb->posts} |
| 287 | WHERE post_type = %s |
| 288 | AND post_parent = %d |
| 289 | AND post_status = %s |
| 290 | AND post_author = %d;", |
| 291 | self::POST_TYPE, |
| 292 | $course_id, |
| 293 | self::STATUS_COMPLETED, |
| 294 | $user_id |
| 295 | ) |
| 296 | ); |
| 297 | } else { |
| 298 | // Return all enrolled data by user ID. |
| 299 | return $wpdb->get_results( |
| 300 | $wpdb->prepare( |
| 301 | "SELECT * FROM {$wpdb->posts} |
| 302 | WHERE post_type = %s |
| 303 | AND post_status = %s |
| 304 | AND post_author = %d;", |
| 305 | self::POST_TYPE, |
| 306 | self::STATUS_COMPLETED, |
| 307 | $user_id |
| 308 | ) |
| 309 | ); |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | /** |
| 314 | * Execute bulk action for enrollment list ex: complete | cancel |
| 315 | * |
| 316 | * @since 2.0.3 |
| 317 | * @since 3.2.0 $trigger_hook param added. |
| 318 | * |
| 319 | * @param string $status hold status for updating. |
| 320 | * @param array $enrollment_ids ids that need to update. |
| 321 | * @param bool $trigger_hook optional - trigger hook or not. |
| 322 | * |
| 323 | * @return bool |
| 324 | */ |
| 325 | public static function update_enrollments( string $status, array $enrollment_ids, bool $trigger_hook = true ): bool { |
| 326 | global $wpdb; |
| 327 | $enrollment_ids_in = QueryHelper::prepare_in_clause( $enrollment_ids ); |
| 328 | $status = 'complete' === $status ? 'completed' : $status; |
| 329 | $post_table = $wpdb->posts; |
| 330 | |
| 331 | //phpcs:disable |
| 332 | $wpdb->query( |
| 333 | $wpdb->prepare( |
| 334 | "UPDATE {$post_table} |
| 335 | SET post_status = %s |
| 336 | WHERE ID IN ($enrollment_ids_in) |
| 337 | ", |
| 338 | $status |
| 339 | ) |
| 340 | ); |
| 341 | //phpcs:enable |
| 342 | |
| 343 | if ( $trigger_hook ) { |
| 344 | // Run action hook. |
| 345 | foreach ( $enrollment_ids as $id ) { |
| 346 | do_action( 'tutor_enrollment/after/' . $status, $id ); |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | return true; |
| 351 | } |
| 352 | |
| 353 | /** |
| 354 | * Delete enrollment record by providing the student and course id |
| 355 | * |
| 356 | * @since 3.4.0 |
| 357 | * |
| 358 | * @param int $student_id Student id. |
| 359 | * @param int $course_id Course id. |
| 360 | * |
| 361 | * @return bool |
| 362 | */ |
| 363 | public static function delete_enrollment_record( int $student_id, int $course_id ): bool { |
| 364 | return QueryHelper::delete( |
| 365 | 'posts', |
| 366 | array( |
| 367 | 'post_author' => $student_id, |
| 368 | 'post_parent' => $course_id, |
| 369 | 'post_type' => self::POST_TYPE, |
| 370 | ) |
| 371 | ); |
| 372 | } |
| 373 | } |
| 374 |