PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 4.0.0
Tutor LMS – eLearning and online course solution v4.0.0
4.0.0 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / classes / Q_And_A.php
tutor / classes Last commit date
Addons.php 17 hours ago Admin.php 17 hours ago Ajax.php 17 hours ago Announcements.php 17 hours ago Assets.php 17 hours ago Backend_Page_Trait.php 1 year ago BaseController.php 1 year ago Config.php 17 hours ago Container.php 11 months ago Course.php 17 hours ago Course_Embed.php 3 years ago Course_Filter.php 17 hours ago Course_List.php 17 hours ago Course_Settings_Tabs.php 17 hours ago Course_Widget.php 1 year ago Custom_Validation.php 17 hours ago Dashboard.php 17 hours ago Earnings.php 9 months ago FormHandler.php 17 hours ago Frontend.php 17 hours ago Gutenberg.php 1 year ago Icon.php 17 hours ago Input.php 17 hours ago Instructor.php 17 hours ago Instructors_List.php 17 hours ago Lesson.php 17 hours ago Options_V2.php 17 hours ago Permalink.php 17 hours ago Post_types.php 1 day ago Private_Course_Access.php 17 hours ago Q_And_A.php 17 hours ago Question_Answers_List.php 11 months ago Quiz.php 17 hours ago QuizBuilder.php 17 hours ago Quiz_Attempts_List.php 17 hours ago RestAPI.php 2 years ago Reviews.php 17 hours ago Rewrite_Rules.php 2 years ago SampleCourse.php 17 hours ago Shortcode.php 17 hours ago Singleton.php 1 year ago Student.php 17 hours ago Students_List.php 1 year ago Taxonomies.php 1 year ago Template.php 17 hours ago Theme_Compatibility.php 3 years ago Tools.php 1 year ago Tools_V2.php 4 weeks ago Tutor.php 17 hours ago TutorEDD.php 17 hours ago Tutor_Base.php 2 years ago Tutor_Setup.php 17 hours ago Upgrader.php 17 hours ago User.php 17 hours ago UserPreference.php 17 hours ago Utils.php 17 hours ago Video_Stream.php 3 years ago WhatsNew.php 10 months ago Withdraw.php 17 hours ago Withdraw_Requests_List.php 11 months ago WooCommerce.php 17 hours ago
Q_And_A.php
558 lines
1 <?php
2 /**
3 * Manage Q & A
4 *
5 * @package Tutor\Q_And_A
6 * @author Themeum <support@themeum.com>
7 * @link https://themeum.com
8 * @since 1.0.0
9 */
10
11 namespace TUTOR;
12
13 defined( 'ABSPATH' ) || exit;
14
15 use Tutor\Helpers\QueryHelper;
16 use Tutor\Helpers\UrlHelper;
17 use Tutor\Models\EnrollmentModel;
18 use Tutor\Traits\JsonResponse;
19 /**
20 * Question answer management
21 *
22 * @since 1.0.0
23 */
24 class Q_And_A {
25 use JsonResponse;
26
27 /**
28 * List of all possible Q&A question statuses.
29 *
30 * @since 3.7.2
31 *
32 * @var string[]
33 */
34 const STATUS_LIST = array(
35 'all',
36 'read',
37 'unread',
38 'important',
39 'archived',
40 );
41
42 /**
43 * Register hooks
44 *
45 * @param boolean $register_hooks true/false to execute the hooks.
46 */
47 public function __construct( $register_hooks = true ) {
48 if ( ! $register_hooks ) {
49 return;
50 }
51
52 add_filter( 'tutor_learning_area_sub_page_nav_item', array( $this, 'add_learning_area_menu' ), 10, 2 );
53
54 add_action( 'wp_ajax_tutor_qna_create_update', array( $this, 'tutor_qna_create_update' ) );
55 add_action( 'wp_ajax_tutor_qna_update', array( $this, 'ajax_qna_update' ) );
56 add_action( 'wp_ajax_tutor_delete_dashboard_question', array( $this, 'tutor_delete_dashboard_question' ) );
57 add_action( 'wp_ajax_tutor_qna_single_action', array( $this, 'tutor_qna_single_action' ) );
58 add_action( 'wp_ajax_tutor_qna_bulk_action', array( $this, 'process_bulk_action' ) );
59 add_action( 'wp_ajax_tutor_q_and_a_load_more', array( $this, 'load_more' ) );
60 add_action( 'wp_ajax_tutor_qna_load_replies', array( $this, 'load_replies' ) );
61 }
62
63 /**
64 * Check if Q&A feature is enabled.
65 *
66 * @since 4.0.0
67 *
68 * @return bool
69 */
70 public static function is_enabled() {
71 return (bool) get_tutor_option( 'enable_q_and_a_on_course' );
72 }
73
74 /**
75 * Check if Q&A is enabled for a specific course.
76 *
77 * @since 4.0.0
78 *
79 * @param int $course_id course id.
80 *
81 * @return bool
82 */
83 public static function is_enabled_for_course( $course_id ) {
84 return self::is_enabled() && 'yes' === get_post_meta( $course_id, Course::COURSE_ENABLE_QA_META, true );
85 }
86
87 /**
88 * Add learning area menu
89 *
90 * @since 4.0.0
91 *
92 * @param array $menu_items the array of nav items.
93 * @param string $base_url the base url.
94 *
95 * @return array
96 */
97 public function add_learning_area_menu( $menu_items, $base_url ) {
98 global $tutor_course_id;
99
100 $user_id = get_current_user_id();
101 $is_enabled_for_course = self::is_enabled_for_course( $tutor_course_id );
102 $can_access = EnrollmentModel::is_enrolled( $tutor_course_id ) || tutor_utils()->has_user_course_content_access( $user_id, $tutor_course_id );
103
104 if ( $is_enabled_for_course && $can_access ) {
105 $qna_item = array(
106 'qna' => array(
107 'title' => __( 'Q&A', 'tutor' ),
108 'icon' => Icon::QA,
109 'url' => UrlHelper::add_query_params( $base_url, array( 'subpage' => 'qna' ) ),
110 'template' => tutor_get_template( 'learning-area.subpages.qna' ),
111 ),
112 );
113
114 // Remove existing Q&A if Tutor already added it.
115 unset( $menu_items['qna'] );
116
117 $menu_items = $qna_item + $menu_items;
118 }
119
120 return $menu_items;
121 }
122
123 /**
124 * Check user has access to QnA.
125 *
126 * @since 2.6.1
127 *
128 * @param int $user_id user id.
129 * @param int $course_id course id.
130 *
131 * @return boolean
132 */
133 public static function has_qna_access( $user_id, $course_id ) {
134 $is_public_course = Course_List::is_public( $course_id );
135
136 $has_access = $is_public_course
137 || User::is_admin()
138 || tutor_utils()->is_instructor_of_this_course( $user_id, $course_id )
139 || EnrollmentModel::is_enrolled( $course_id, $user_id );
140 return $has_access;
141 }
142
143 /**
144 * Handle QnA create/update via AJAX.
145 *
146 * @since 1.0.0
147 *
148 * @return void
149 */
150 public function tutor_qna_create_update() {
151 tutor_utils()->checking_nonce();
152
153 $user_id = get_current_user_id();
154 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
155 $question_id = Input::post( 'question_id', 0, Input::TYPE_INT );
156 $context = Input::post( 'context' );
157
158 if ( $question_id ) {
159 $course_id = tutor_utils()->get_course_id_by( 'qa_question', $question_id );
160 }
161
162 if ( ! $course_id || ! $this->has_qna_access( $user_id, $course_id ) ) {
163 $this->response_bad_request( tutor_utils()->error_message() );
164 }
165
166 $qna_text = Input::post( 'answer', '', tutor()->has_pro ? Input::TYPE_KSES_POST : Input::TYPE_TEXTAREA );
167
168 if ( ! $qna_text ) {
169 $this->response_bad_request( __( 'Empty Content Not Allowed!', 'tutor' ) );
170 }
171
172 // Prepare user info.
173 $user = get_userdata( $user_id );
174 $date = gmdate( 'Y-m-d H:i:s', tutor_time() );
175
176 $qna_object = new \stdClass();
177 $qna_object->user_id = $user_id;
178 $qna_object->course_id = $course_id;
179 $qna_object->question_id = $question_id;
180 $qna_object->qna_text = $qna_text;
181 $qna_object->user = $user;
182 $qna_object->date = $date;
183
184 $question_id = $this->inset_qna( $qna_object );
185
186 // Provide the html now.
187 // phpcs:disable WordPress.Security.NonceVerification.Missing
188 ob_start();
189 tutor_load_template_from_custom_path(
190 tutor()->path . '/views/qna/qna-single.php',
191 array(
192 'question_id' => $question_id,
193 'back_url' => isset( $_POST['back_url'] ) ? esc_url_raw( wp_unslash( $_POST['back_url'] ) ) : '',
194 'context' => $context,
195 )
196 );
197 wp_send_json_success(
198 array(
199 'html' => ob_get_clean(),
200 'editor_id' => 'tutor_qna_reply_editor_' . $question_id,
201 )
202 );
203 }
204
205 /**
206 * Function to insert Q&A
207 *
208 * @param object $qna_object the object to insert.
209 * @return int
210 */
211 public function inset_qna( $qna_object ) {
212 $course_id = $qna_object->course_id;
213 $question_id = $qna_object->question_id;
214 $qna_text = $qna_object->qna_text;
215 $user_id = $qna_object->user_id;
216 $user = $qna_object->user;
217 $date = $qna_object->date;
218
219 // Insert data prepare.
220 $data = apply_filters(
221 'tutor_qna_insert_data',
222 array(
223 'comment_post_ID' => $course_id,
224 'comment_author' => $user->user_login,
225 'comment_date' => $date,
226 'comment_date_gmt' => get_gmt_from_date( $date ),
227 'comment_content' => $qna_text,
228 'comment_approved' => 'approved',
229 'comment_agent' => 'TutorLMSPlugin',
230 'comment_type' => 'tutor_q_and_a',
231 'comment_parent' => $question_id,
232 'user_id' => $user_id,
233 )
234 );
235
236 global $wpdb;
237
238 // Insert new question/answer.
239 $wpdb->insert( $wpdb->comments, $data );
240 ! $question_id ? $question_id = (int) $wpdb->insert_id : 0;
241
242 // Mark the question unseen if action made from student.
243 $asker_id = $this->get_asker_id( $question_id );
244 $self = $asker_id == $user_id;
245 update_comment_meta( $question_id, 'tutor_qna_read' . ( $self ? '' : '_' . $asker_id ), 0 );
246
247 do_action( 'tutor_after_asked_question', $data );
248
249 // question_id != 0 means it's a reply.
250 $reply_id = Input::post( 'question_id', 0, Input::TYPE_INT );
251 $answer_id = (int) $wpdb->insert_id;
252 if ( 0 !== $reply_id && ( current_user_can( 'administrator' ) || tutor_utils()->is_instructor_of_this_course( $user_id, $course_id ) ) ) {
253 do_action( 'tutor_after_answer_to_question', $answer_id );
254 }
255
256 return $question_id;
257 }
258
259 /**
260 * Update question [frontend dashboard]
261 *
262 * @since 4.0.0
263 */
264 public function ajax_qna_update() {
265 tutor_utils()->checking_nonce();
266
267 $question_id = Input::post( 'question_id', 0, Input::TYPE_INT );
268 if ( ! $question_id || ! tutor_utils()->can_user_manage( 'qa_question', $question_id ) ) {
269 $this->response_bad_request( tutor_utils()->error_message( 'authorization' ) );
270 }
271
272 $qna_text = Input::post( 'answer', '', tutor()->has_pro ? Input::TYPE_KSES_POST : Input::TYPE_TEXTAREA );
273 if ( ! $qna_text ) {
274 $this->response_bad_request( __( 'Empty Content Not Allowed!', 'tutor' ) );
275 }
276
277 $data = array(
278 'comment_content' => $qna_text,
279 );
280
281 global $wpdb;
282 $wpdb->update( $wpdb->comments, $data, array( 'comment_ID' => $question_id ) );
283
284 $this->json_response( __( 'Comment edited successfully', 'tutor' ) );
285 }
286
287 /**
288 * Delete question [frontend dashboard]
289 *
290 * @since 1.6.4
291 */
292 public function tutor_delete_dashboard_question() {
293 tutor_utils()->checking_nonce();
294
295 $question_id = Input::post( 'question_id', 0, Input::TYPE_INT );
296 if ( ! $question_id || ! tutor_utils()->can_user_manage( 'qa_question', $question_id ) ) {
297 $this->response_bad_request( tutor_utils()->error_message( 'authorization' ) );
298 }
299
300 $this->delete_qna_permanently( array( $question_id ) );
301
302 $this->json_response( __( 'Comment deleted successfully.', 'tutor' ) );
303 }
304
305 /**
306 * Delete question permanently
307 *
308 * @since 1.6.4
309 *
310 * @param array $question_ids question ids.
311 *
312 * @return void
313 */
314 public function delete_qna_permanently( $question_ids ) {
315 if ( is_array( $question_ids ) && count( $question_ids ) ) {
316 global $wpdb;
317 // Prepare in clause.
318 $question_ids = QueryHelper::prepare_in_clause( $question_ids );
319
320 // Deleting question (comment), child question and question meta (comment meta).
321 // phpcs:disable -- variable $question_ids is escaped.
322 $wpdb->query(
323 $wpdb->prepare(
324 "DELETE
325 FROM {$wpdb->comments}
326 WHERE {$wpdb->comments}.comment_ID IN ($question_ids)
327 AND 1 = %d
328 ",
329 1
330 )
331 );
332
333 $wpdb->query(
334 $wpdb->prepare(
335 "DELETE
336 FROM {$wpdb->comments}
337 WHERE {$wpdb->comments}.comment_parent IN ($question_ids)
338 AND 1 = %d
339 ",
340 1
341 )
342 );
343
344 $wpdb->query(
345 $wpdb->prepare(
346 "DELETE
347 FROM {$wpdb->commentmeta}
348 WHERE {$wpdb->commentmeta}.comment_id IN ($question_ids)
349 AND 1 = %d
350 ",
351 1
352 )
353 );
354 // phpcs:enable
355 }
356 }
357
358 /**
359 * Process bulk delete
360 *
361 * @since v1.0.0
362 *
363 * @return void send wp_json response
364 */
365 public function process_bulk_action() {
366 tutor_utils()->checking_nonce();
367
368 $user_id = get_current_user_id();
369 $action = Input::post( 'bulk-action' );
370
371 switch ( $action ) {
372 case 'delete':
373 $qa_ids = Input::post( 'bulk-ids', '' );
374 $qa_ids = explode( ',', $qa_ids );
375 $qa_ids = array_filter(
376 $qa_ids,
377 function ( $id ) use ( $user_id ) {
378 return is_numeric( $id ) && tutor_utils()->can_user_manage( 'qa_question', $id, $user_id );
379 }
380 );
381
382 $this->delete_qna_permanently( $qa_ids );
383 break;
384 }
385
386 wp_send_json_success();
387 }
388
389 /**
390 * Get user id who asked
391 *
392 * @param int $question_id question id.
393 *
394 * @return string author id
395 */
396 private function get_asker_id( $question_id ) {
397 global $wpdb;
398 $author_id = $wpdb->get_var(
399 $wpdb->prepare(
400 "SELECT user_id
401 FROM {$wpdb->comments}
402 WHERE comment_ID = %d
403 ",
404 $question_id
405 )
406 );
407 return $author_id;
408 }
409
410 /**
411 * Update comment meta function
412 *
413 * @return void send wp_json response
414 */
415 public function tutor_qna_single_action() {
416 tutor_utils()->checking_nonce();
417
418 $question_id = Input::post( 'question_id', 0, Input::TYPE_INT );
419
420 if ( ! tutor_utils()->can_user_manage( 'qa_question', $question_id ) ) {
421 $this->response_bad_request( tutor_utils()->error_message( 'authorization' ) );
422 }
423
424 // Get who asked the question.
425 $context = Input::post( 'context', '' );
426 $user_id = get_current_user_id();
427
428 // Get the existing value from meta.
429 $action = Input::post( 'qna_action', '' );
430
431 $new_value = $this->trigger_qna_action( $question_id, $action, $context, $user_id );
432
433 // Transfer the new status.
434 wp_send_json_success( array( 'new_value' => $new_value ) );
435 }
436
437 /**
438 * Function to update Q&A action
439 *
440 * @since 2.6.2
441 *
442 * @param int $question_id question id.
443 * @param string $action action name.
444 * @param string $context context name.
445 * @param int $user_id user id.
446 *
447 * @return int
448 */
449 public function trigger_qna_action( $question_id, $action, $context, $user_id ) {
450 $asker_prefix = 'frontend-dashboard-qna-table-student' === $context ? '_' . $user_id : '';
451
452 // If current user asker, then make it unread for self.
453 // If it is instructor, then make unread for instructor side.
454 $meta_key = 'tutor_qna_' . $action . $asker_prefix;
455
456 $current_value = (int) get_comment_meta( $question_id, $meta_key, true );
457
458 $new_value = 1 === $current_value ? 0 : 1;
459
460 // Update the reverted value.
461 update_comment_meta( $question_id, $meta_key, $new_value );
462
463 return $new_value;
464 }
465
466 /**
467 * Available tabs that will visible on the right side of page navbar
468 *
469 * @since v2.0.0
470 *
471 * @param mixed $asker_id asker id.
472 *
473 * @return array
474 */
475 public static function tabs_key_value( $asker_id = null ) {
476
477 $args = Input::has( 'course-id' ) ? array( 'course_id' => Input::get( 'course-id', 0, Input::TYPE_INT ) ) : array();
478 $stats = array();
479
480 // Loop through all predefined Q&A statuses to retrieve corresponding question statistics.
481 foreach ( self::STATUS_LIST as $status ) {
482
483 $label = 'all' === $status ? null : $status;
484 $stats[ $status ] = tutor_utils()->get_qa_questions( 0, 99999, '', null, null, $asker_id, $label, true, $args );
485 }
486
487 // Assign value, url etc to the tab array.
488 $tabs = array_map(
489 function ( $tab ) use ( $stats ) {
490 return array(
491 'key' => 'all' === $tab ? '' : $tab,
492 'title' => tutor_utils()->translate_dynamic_text( $tab ),
493 'value' => $stats[ $tab ],
494 'url' => add_query_arg( array( 'data' => $tab ), remove_query_arg( 'data' ) ),
495 );
496 },
497 array_keys( $stats )
498 );
499
500 return $tabs;
501 }
502
503 /**
504 * Load more q & a
505 *
506 * @since v2.0.6
507 *
508 * @return void send wp_json response
509 */
510 public static function load_more() {
511 tutor_utils()->checking_nonce();
512 ob_start();
513 tutor_load_template( 'single.course.enrolled.question_and_answer' );
514 $html = ob_get_clean();
515 wp_send_json_success( array( 'html' => $html ) );
516 }
517
518 /**
519 * Load replies
520 *
521 * @since 4.0.0
522 *
523 * @return void send wp_json response
524 */
525 public function load_replies() {
526 tutor_utils()->checking_nonce();
527
528 $comment_id = Input::post( 'comment_id', 0, Input::TYPE_INT );
529 $replies_order = QueryHelper::get_valid_sort_order( Input::post( 'order', 'DESC' ) );
530 $context = Input::post( 'context', 'dashboard' );
531
532 if ( ! $comment_id ) {
533 $this->response_bad_request( __( 'Invalid comment ID', 'tutor' ) );
534 }
535
536 $user_id = get_current_user_id();
537 $replies = tutor_utils()->get_qa_answer_by_question( $comment_id, $replies_order, 'frontend' );
538
539 $template = 'dashboard.discussions.qna-replies';
540 if ( 'learning-area' === $context ) {
541 $template = 'learning-area.subpages.qna.replies';
542 }
543
544 ob_start();
545 tutor_load_template(
546 $template,
547 array(
548 'replies' => $replies,
549 'replies_order' => $replies_order,
550 'user_id' => $user_id,
551 )
552 );
553 $html = ob_get_clean();
554
555 $this->json_response( '', array( 'html' => $html ) );
556 }
557 }
558