Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/tutor/classes/Ajax.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + <?php
2 + /**
3 + * Handle Ajax Request
4 + *
5 + * @package Tutor
6 + * @author Themeum <support@themeum.com>
7 + * @link https://themeum.com
8 + * @since 1.0.0
9 + */
10 +
11 + namespace TUTOR;
12 +
13 + if ( ! defined( 'ABSPATH' ) ) {
14 + exit;
15 + }
16 +
17 + use Tutor\Helpers\HttpHelper;
18 + use Tutor\Models\LessonModel;
19 + use Tutor\Traits\JsonResponse;
20 +
21 + /**
22 + * Ajax Class
23 + *
24 + * @since 1.0.0
25 + */
26 + class Ajax {
27 + use JsonResponse;
28 +
29 + const LOGIN_ERRORS_TRANSIENT_KEY = 'tutor_login_errors';
30 + /**
31 + * Constructor
32 + *
33 + * @since 1.0.0
34 + * @since 2.6.2 added allow_hooks param.
35 + *
36 + * @param bool $allow_hooks default value true.
37 + *
38 + * @return void
39 + */
40 + public function __construct( $allow_hooks = true ) {
41 + if ( $allow_hooks ) {
42 + add_action( 'wp_ajax_sync_video_playback', array( $this, 'sync_video_playback' ) );
43 + add_action( 'wp_ajax_nopriv_sync_video_playback', array( $this, 'sync_video_playback_noprev' ) );
44 + add_action( 'wp_ajax_tutor_place_rating', array( $this, 'tutor_place_rating' ) );
45 + add_action( 'wp_ajax_delete_tutor_review', array( $this, 'delete_tutor_review' ) );
46 +
47 + add_action( 'wp_ajax_tutor_course_add_to_wishlist', array( $this, 'tutor_course_add_to_wishlist' ) );
48 + add_action( 'wp_ajax_nopriv_tutor_course_add_to_wishlist', array( $this, 'tutor_course_add_to_wishlist' ) );
49 +
50 + /**
51 + * Ajax login
52 + *
53 + * @since v.1.6.3
54 + */
55 + add_action( 'tutor_action_tutor_user_login', array( $this, 'process_tutor_login' ) );
56 +
57 + /**
58 + * Announcement
59 + *
60 + * @since v.1.7.9
61 + */
62 + add_action( 'wp_ajax_tutor_announcement_create', array( $this, 'create_or_update_annoucement' ) );
63 + add_action( 'wp_ajax_tutor_announcement_delete', array( $this, 'delete_annoucement' ) );
64 +
65 + add_action( 'wp_ajax_tutor_youtube_video_duration', array( $this, 'ajax_youtube_video_duration' ) );
66 + }
67 + }
68 +
69 +
70 +
71 + /**
72 + * Update video information and data when necessary
73 + *
74 + * @since 1.0.0
75 + * @return void
76 + */
77 + public function sync_video_playback() {
78 + tutor_utils()->checking_nonce();
79 +
80 + $user_id = get_current_user_id();
81 + $post_id = Input::post( 'post_id', 0, Input::TYPE_INT );
82 + $duration = Input::post( 'duration' );
83 + $current_time = Input::post( 'currentTime' );
84 +
85 + if ( ! tutor_utils()->has_enrolled_content_access( 'lesson', $post_id ) ) {
86 + wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
87 + exit;
88 + }
89 +
90 + /**
91 + * Update posts attached video
92 + */
93 + $video = tutor_utils()->get_video( $post_id );
94 +
95 + if ( $duration ) {
96 + $video['duration_sec'] = $duration; // Duration in sec.
97 + $video['playtime'] = tutor_utils()->playtime_string( $duration );
98 + $video['runtime'] = tutor_utils()->playtime_array( $duration );
99 + }
100 + tutor_utils()->update_video( $post_id, $video );
101 +
102 + /**
103 + * Sync Lesson Reading Info by Users
104 + */
105 +
106 + $best_watch_time = tutor_utils()->get_lesson_reading_info( $post_id, $user_id, 'video_best_watched_time' );
107 + if ( $best_watch_time < $current_time ) {
108 + LessonModel::update_lesson_reading_info( $post_id, $user_id, 'video_best_watched_time', $current_time );
109 + }
110 +
111 + if ( Input::post( 'is_ended', false, Input::TYPE_BOOL ) ) {
112 + LessonModel::mark_lesson_complete( $post_id );
113 + LessonModel::update_lesson_reading_info( $post_id, $user_id, 'video_best_watched_time', 0 );
114 + }
115 + exit();
116 + }
117 +
118 + /**
119 + * Video playback callback for noprev
120 + *
121 + * @since 1.0.0
122 + * @return void
123 + */
124 + public function sync_video_playback_noprev() {
125 + }
126 +
127 + /**
128 + * Place rating
129 + *
130 + * @since 1.0.0
131 + * @return void
132 + */
133 + public function tutor_place_rating() {
134 + tutor_utils()->checking_nonce();
135 +
136 + $user_id = get_current_user_id();
137 + $course_id = Input::post( 'course_id' );
138 + $rating = Input::post( 'tutor_rating_gen_input', 0, Input::TYPE_INT );
139 + $review = Input::post( 'review', '', Input::TYPE_TEXTAREA );
140 +
141 + $rating <= 0 ? $rating = 1 : 0;
142 + $rating > 5 ? $rating = 5 : 0;
143 +
144 + $this->add_or_update_review( $user_id, $course_id, $rating, $review );
145 + }
146 +
147 + /**
148 + * Add/Update rating
149 + *
150 + * @param int $user_id the user id.
151 + * @param int $course_id the course id.
152 + * @param int $rating rating star number.
153 + * @param string $review review description.
154 + * @param int $review_id review id needed for api update.
155 + *
156 + * @return void|string
157 + */
158 + public function add_or_update_review( $user_id, $course_id, $rating, $review, $review_id = 0 ) {
159 + global $wpdb;
160 +
161 + $moderation = tutor_utils()->get_option( 'enable_course_review_moderation', false, true, true );
162 + $user = get_userdata( $user_id );
163 + $date = date( 'Y-m-d H:i:s', tutor_time() ); //phpcs:ignore
164 +
165 + if ( ! tutor_is_rest() && ! tutor_utils()->has_enrolled_content_access( 'course', $course_id ) ) {
166 + wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
167 + exit;
168 + }
169 +
170 + do_action( 'tutor_before_rating_placed' );
171 +
172 + $is_edit = 0 === $review_id ? false : true;
173 +
174 + if ( ! tutor_is_rest() ) {
175 + $previous_rating_id = $wpdb->get_var(
176 + $wpdb->prepare(
177 + "SELECT comment_ID
178 + from {$wpdb->comments}
179 + WHERE comment_post_ID = %d AND
180 + user_id = %d AND
181 + comment_type = 'tutor_course_rating'
182 + LIMIT 1;",
183 + $course_id,
184 + $user_id
185 + )
186 + );
187 +
188 + if ( ! empty( $previous_rating_id ) ) {
189 + $review_id = $previous_rating_id;
190 + $is_edit = true;
191 + }
192 + }
193 +
194 + if ( $is_edit ) {
195 + $wpdb->update(
196 + $wpdb->comments,
197 + array(
198 + 'comment_content' => $review,
199 + 'comment_approved' => $moderation ? 'hold' : 'approved',
200 + 'comment_date' => $date,
201 + 'comment_date_gmt' => get_gmt_from_date( $date ),
202 + ),
203 + array( 'comment_ID' => $review_id )
204 + );
205 +
206 + $rating_info = $wpdb->get_row(
207 + $wpdb->prepare(
208 + "SELECT * FROM {$wpdb->commentmeta}
209 + WHERE comment_id = %d
210 + AND meta_key = 'tutor_rating'; ",
211 + $review_id
212 + )
213 + );
214 +
215 + if ( $rating_info ) {
216 + $wpdb->update(
217 + $wpdb->commentmeta,
218 + array( 'meta_value' => $rating ),
219 + array(
220 + 'comment_id' => $review_id,
221 + 'meta_key' => 'tutor_rating',
222 + )
223 + );
224 + } else {
225 + $wpdb->insert(
226 + $wpdb->commentmeta,
227 + array(
228 + 'comment_id' => $review_id,
229 + 'meta_key' => 'tutor_rating',
230 + 'meta_value' => $rating,
231 + )
232 + );
233 + }
234 + } else {
235 + $data = array(
236 + 'comment_post_ID' => esc_sql( $course_id ),
237 + 'comment_approved' => $moderation ? 'hold' : 'approved',
238 + 'comment_type' => 'tutor_course_rating',
239 + 'comment_date' => $date,
240 + 'comment_date_gmt' => get_gmt_from_date( $date ),
241 + 'user_id' => $user_id,
242 + 'comment_author' => $user->user_login,
243 + 'comment_agent' => 'TutorLMSPlugin',
244 + );
245 + if ( $review ) {
246 + $data['comment_content'] = $review;
247 + }
248 +
249 + $wpdb->insert( $wpdb->comments, $data );
250 + $comment_id = (int) $wpdb->insert_id;
251 + $review_id = $comment_id;
252 +
253 + if ( $comment_id ) {
254 + $wpdb->insert(
255 + $wpdb->commentmeta,
256 + array(
257 + 'comment_id' => $comment_id,
258 + 'meta_key' => 'tutor_rating',
259 + 'meta_value' => $rating,
260 + )
261 + );
262 +
263 + do_action( 'tutor_after_rating_placed', $comment_id );
264 + }
265 + }
266 +
267 + if ( ! tutor_is_rest() ) {
268 + wp_send_json_success(
269 + array(
270 + 'message' => __( 'Rating placed successfully!', 'tutor' ),
271 + 'review_id' => $review_id,
272 + )
273 + );
274 + } else {
275 + return $is_edit ? 'updated' : 'created';
276 + }
277 + }
278 +
279 + /**
280 + * Delete a review
281 + *
282 + * @since 1.0.0
283 + * @since 2.6.2 added params user_id.
284 + * @param int $user_id the user id.
285 + * @return void|bool
286 + */
287 + public function delete_tutor_review( $user_id = 0 ) {
288 + if ( ! tutor_is_rest() ) {
289 + tutor_utils()->checking_nonce();
290 + }
291 +
292 + $review_id = Input::post( 'review_id' );
293 +
294 + if ( ! tutor_utils()->can_user_manage( 'review', $review_id, tutils()->get_user_id( $user_id ) ) ) {
295 + wp_send_json_error( array( 'message' => __( 'Permissioned Denied!', 'tutor' ) ) );
296 + exit;
297 + }
298 +
299 + global $wpdb;
300 + $wpdb->delete( $wpdb->commentmeta, array( 'comment_id' => $review_id ) );
301 + $wpdb->delete( $wpdb->comments, array( 'comment_ID' => $review_id ) );
302 +
303 + if ( tutor_is_rest() ) {
304 + return true;
305 + }
306 +
307 + wp_send_json_success();
308 + }
309 +
310 + /**
311 + * Add course in wishlist
312 + *
313 + * @since 1.0.0
314 + * @return void|string
315 + */
316 + public function tutor_course_add_to_wishlist() {
317 + tutor_utils()->checking_nonce();
318 +
319 + $is_enabled_wishlist = tutor_utils()->get_option( 'enable_wishlist', true );
320 + if ( ! $is_enabled_wishlist ) {
321 + wp_send_json_error( array( 'message' => __( 'Wishlist option is disabled', 'tutor' ) ) );
322 + }
323 +
324 + // Redirect login since only logged in user can add courses to wishlist.
325 + if ( ! is_user_logged_in() ) {
326 + wp_send_json_error(
327 + array(
328 + 'redirect_to' => wp_login_url( wp_get_referer() ),
329 + )
330 + );
331 + }
332 +
333 + $user_id = get_current_user_id();
334 + $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
335 +
336 + $result = $this->add_or_delete_wishlist( $user_id, $course_id );
337 +
338 + if ( tutor_is_rest() ) {
339 + return $result;
340 + } elseif ( 'added' === $result ) {
341 + wp_send_json_success(
342 + array(
343 + 'status' => 'added',
344 + 'message' => __( 'Course added to wish list', 'tutor' ),
345 + )
346 + );
347 + } else {
348 + wp_send_json_success(
349 + array(
350 + 'status' => 'removed',
351 + 'message' => __( 'Course removed from wish list', 'tutor' ),
352 + )
353 + );
354 + }
355 + }
356 +
357 + /**
358 + * Add or Delete wishlist by user_id and course_id
359 + *
360 + * @since 2.6.2
361 + *
362 + * @param int $user_id the user id.
363 + * @param int $course_id the course_id to add to the wishlist.
364 + *
365 + * @return string
366 + */
367 + public function add_or_delete_wishlist( $user_id, $course_id ) {
368 + global $wpdb;
369 +
370 + $if_added_to_list = tutor_utils()->is_wishlisted( $course_id, $user_id );
371 +
372 + $result = '';
373 +
374 + if ( $if_added_to_list ) {
375 + $wpdb->delete(
376 + $wpdb->usermeta,
377 + array(
378 + 'user_id' => $user_id,
379 + 'meta_key' => '_tutor_course_wishlist',
380 + 'meta_value' => $course_id,
381 + )
382 + );
383 +
384 + $result = 'removed';
385 + } else {
386 + add_user_meta( $user_id, '_tutor_course_wishlist', $course_id );
387 +
388 + $result = 'added';
389 + }
390 +
391 + return $result;
392 + }
393 +
394 + /**
395 + * Process tutor login
396 + *
397 + * @since 1.6.3
398 + *
399 + * @since 2.1.3 Ajax removed, validation errors
400 + * stores in session.
401 + *
402 + * @return void
403 + */
404 + public function process_tutor_login() {
405 + $validation_error = new \WP_Error();
406 +
407 + /**
408 + * Separate nonce verification added to show nonce verification
409 + * failed message in a proper way.
410 + *
411 + * @since 2.1.4
412 + */
413 + if ( ! wp_verify_nonce( $_POST[ tutor()->nonce ], tutor()->nonce_action ) ) { //phpcs:ignore
414 + $validation_error->add( 401, __( 'Nonce verification failed', 'tutor' ) );
415 + \set_transient( self::LOGIN_ERRORS_TRANSIENT_KEY, $validation_error->get_error_messages() );
416 + return;
417 + }
418 + //phpcs:disable WordPress.Security.NonceVerification.Missing
419 +
420 + /**
421 + * No sanitization/wp_unslash needed for log & pwd since WordPress
422 + * does itself
423 + *
424 + * @since 2.1.3
425 + *
426 + * @see https://developer.wordpress.org/reference/functions/wp_signon/
427 + */
428 + $username = tutor_utils()->array_get( 'log', $_POST ); //phpcs:ignore
429 + $password = tutor_utils()->array_get( 'pwd', $_POST ); //phpcs:ignore
430 + $redirect_to = isset( $_POST['redirect_to'] ) ? esc_url_raw( wp_unslash( $_POST['redirect_to'] ) ) : '';
431 + $remember = isset( $_POST['rememberme'] );
432 +
433 + try {
434 + $creds = array(
435 + 'user_login' => trim( $username ),
436 + 'user_password' => $password,
437 + 'remember' => $remember,
438 + );
439 +
440 + $validation_error = apply_filters( 'tutor_process_login_errors', $validation_error, $creds['user_login'], $creds['user_password'] );
441 +
442 + if ( $validation_error->get_error_code() ) {
443 + $validation_error->add(
444 + $validation_error->get_error_code(),
445 + $validation_error->get_error_message()
446 + );
447 + }
448 +
449 + if ( empty( $creds['user_login'] ) ) {
450 + $validation_error->add(
451 + 400,
452 + __( 'Username is required.', 'tutor' )
453 + );
454 + }
455 +
456 + // On multi-site, ensure user exists on current site, if not add them before allowing login.
457 + if ( is_multisite() ) {
458 + $user_data = get_user_by( is_email( $creds['user_login'] ) ? 'email' : 'login', $creds['user_login'] );
459 +
460 + if ( $user_data && ! is_user_member_of_blog( $user_data->ID, get_current_blog_id() ) ) {
461 + add_user_to_blog( get_current_blog_id(), $user_data->ID, 'customer' );
462 + }
463 + }
464 +
465 + // Perform the login.
466 + $user = wp_signon( apply_filters( 'tutor_login_credentials', $creds ), is_ssl() );
467 +
468 + if ( is_wp_error( $user ) ) {
469 + // If no error exist then add WP login error, to prevent error duplication.
470 + if ( ! $validation_error->has_errors() ) {
471 + $validation_error->add( 400, $user->get_error_message() );
472 + }
473 + } else {
474 + do_action( 'tutor_after_login_success', $user->ID );
475 + // Since 1.9.8 do enroll if guest attempt to enroll.
476 + $course_enroll_attempt = Input::post( 'tutor_course_enroll_attempt' );
477 + if ( ! empty( $course_enroll_attempt ) && is_a( $user, 'WP_User' ) ) {
478 + do_action( 'tutor_do_enroll_after_login_if_attempt', $course_enroll_attempt, $user->ID );
479 + }
480 + wp_safe_redirect( $redirect_to );
481 + exit();
482 + }
483 + } catch ( \Exception $e ) {
484 + do_action( 'tutor_login_failed' );
485 + $validation_error->add( 400, $e->getMessage() );
486 + } finally {
487 + // Store errors in transient data.
488 + \set_transient( self::LOGIN_ERRORS_TRANSIENT_KEY, $validation_error->get_error_messages() );
489 + }
490 + }
491 +
492 + /**
493 + * Create/Update announcement
494 + *
495 + * @since 1.7.9
496 + * @return void
497 + */
498 + public function create_or_update_annoucement() {
499 + tutor_utils()->checking_nonce();
500 +
501 + $error = array();
502 + $course_id = Input::post( 'tutor_announcement_course' );
503 + $announcement_title = Input::post( 'tutor_announcement_title' );
504 + $announcement_summary = Input::post( 'tutor_announcement_summary', '', Input::TYPE_TEXTAREA );
505 +
506 + // Check if user can manage this announcment.
507 + if ( ! tutor_utils()->can_user_manage( 'course', $course_id ) ) {
508 + wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
509 + }
510 +
511 + // Set data and sanitize it.
512 + $form_data = array(
513 + 'post_type' => 'tutor_announcements',
514 + 'post_title' => $announcement_title,
515 + 'post_content' => $announcement_summary,
516 + 'post_parent' => $course_id,
517 + 'post_status' => 'publish',
518 + );
519 +
520 + if ( Input::has( 'announcement_id' ) ) {
521 + $form_data['ID'] = Input::post( 'announcement_id' );
522 + }
523 +
524 + if ( ! empty( $form_data['ID'] ) ) {
525 + if ( ! tutor_utils()->can_user_manage( 'announcement', $form_data['ID'] ) ) {
526 + wp_send_json_error( array( 'message' => tutor_utils()->error_message() ) );
527 + }
528 + }
529 +
530 + // Validation message set.
531 + if ( empty( $form_data['post_parent'] ) ) {
532 + $error['post_parent'] = __( 'Course name required', 'tutor' );
533 +
534 + }
535 +
536 + if ( empty( $form_data['post_title'] ) ) {
537 + $error['post_title'] = __( 'Announcement title required', 'tutor' );
538 + }
539 +
540 + if ( empty( $form_data['post_content'] ) ) {
541 + $error['post_content'] = __( 'Announcement summary required', 'tutor' );
542 +
543 + }
544 +
545 + if ( empty( $form_data['post_content'] ) ) {
546 + $error['post_content'] = __( 'Announcement summary required', 'tutor' );
547 +
548 + }
549 +
550 + // If validation fails.
551 + if ( count( $error ) > 0 ) {
552 + wp_send_json_error(
553 + array(
554 + 'message' => __( 'All fields required!', 'tutor' ),
555 + 'fields' => $error,
556 + )
557 + );
558 + }
559 +
560 + // Insert or update post.
561 + $post_id = wp_insert_post( $form_data );
562 + if ( $post_id > 0 ) {
563 + $announcement = get_post( $post_id );
564 + $action_type = Input::post( 'action_type' );
565 +
566 + do_action( 'tutor_announcements/after/save', $post_id, $announcement, $action_type );
567 +
568 + $resp_message = 'create' === $action_type ? __( 'Announcement created successfully', 'tutor' ) : __( 'Announcement updated successfully', 'tutor' );
569 + wp_send_json_success( array( 'message' => $resp_message ) );
570 + }
571 +
572 + wp_send_json_error( array( 'message' => __( 'Something Went Wrong!', 'tutor' ) ) );
573 + }
574 +
575 + /**
576 + * Delete announcement
577 + *
578 + * @since 1.7.9
579 + * @return void
580 + */
581 + public function delete_annoucement() {
582 + tutor_utils()->checking_nonce();
583 +
584 + $announcement_id = Input::post( 'announcement_id' );
585 +
586 + if ( ! tutor_utils()->can_user_manage( 'announcement', $announcement_id ) ) {
587 + wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
588 + }
589 +
590 + $delete = wp_delete_post( $announcement_id );
591 + if ( $delete ) {
592 + wp_send_json_success( array( 'message' => __( 'Announcement deleted successfully', 'tutor' ) ) );
593 + }
594 +
595 + wp_send_json_error( array( 'message' => __( 'Announcement delete failed', 'tutor' ) ) );
596 + }
597 +
598 + /**
599 + * Get youtube video duration.
600 + *
601 + * @since 3.0.0
602 + *
603 + * @return void
604 + */
605 + public function ajax_youtube_video_duration() {
606 + tutor_utils()->check_nonce();
607 +
608 + $video_id = Input::post( 'video_id' );
609 + if ( empty( $video_id ) ) {
610 + $this->json_response( __( 'Video ID is required', 'tutor' ), null, HttpHelper::STATUS_BAD_REQUEST );
611 + }
612 +
613 + tutor_utils()->check_current_user_capability( 'edit_tutor_course' );
614 +
615 + $api_key = tutor_utils()->get_option( 'lesson_video_duration_youtube_api_key', '' );
616 + $url = "https://www.googleapis.com/youtube/v3/videos?id=$video_id&part=contentDetails&key=$api_key";
617 +
618 + $request = HttpHelper::get( $url );
619 + if ( HttpHelper::STATUS_OK === $request->get_status_code() ) {
620 + $response = $request->get_json();
621 + if ( isset( $response->items[0]->contentDetails->duration ) ) {
622 + $duration = $response->items[0]->contentDetails->duration;
623 + $this->json_response(
624 + __( 'Fetched duration successfully', 'tutor' ),
625 + array(
626 + 'duration' => $duration,
627 + )
628 + );
629 + }
630 + }
631 +
632 + $this->json_response(
633 + __( 'Failed to fetch duration', 'tutor' ),
634 + null,
635 + HttpHelper::STATUS_BAD_REQUEST
636 + );
637 + }
638 + }
639 +