PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 3.9.0
Tutor LMS – eLearning and online course solution v3.9.0
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 11 months ago Admin.php 9 months ago Ajax.php 9 months ago Announcements.php 1 year ago Assets.php 8 months ago Backend_Page_Trait.php 1 year ago BaseController.php 1 year ago Config.php 11 months ago Container.php 11 months ago Course.php 9 months ago Course_Embed.php 3 years ago Course_Filter.php 1 year ago Course_List.php 10 months ago Course_Settings_Tabs.php 1 year ago Course_Widget.php 1 year ago Custom_Validation.php 3 years ago Dashboard.php 1 year ago Earnings.php 9 months ago FormHandler.php 2 years ago Frontend.php 1 year ago Gutenberg.php 1 year ago Icon.php 8 months ago Input.php 1 year ago Instructor.php 1 year ago Instructors_List.php 11 months ago Lesson.php 8 months ago Options_V2.php 9 months ago Permalink.php 2 years ago Post_types.php 1 year ago Private_Course_Access.php 1 year ago Q_And_A.php 10 months ago Question_Answers_List.php 11 months ago Quiz.php 9 months ago QuizBuilder.php 11 months ago Quiz_Attempts_List.php 9 months ago RestAPI.php 2 years ago Reviews.php 9 months ago Rewrite_Rules.php 2 years ago Shortcode.php 9 months ago Singleton.php 1 year ago Student.php 1 year ago Students_List.php 1 year ago Taxonomies.php 1 year ago Template.php 9 months ago Theme_Compatibility.php 3 years ago Tools.php 1 year ago Tools_V2.php 1 year ago Tutor.php 9 months ago TutorEDD.php 1 year ago Tutor_Base.php 2 years ago Tutor_Setup.php 1 year ago Upgrader.php 9 months ago User.php 1 year ago Utils.php 8 months ago Video_Stream.php 3 years ago WhatsNew.php 9 months ago Withdraw.php 1 year ago Withdraw_Requests_List.php 11 months ago WooCommerce.php 9 months ago
Q_And_A.php
437 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 use Tutor\Helpers\QueryHelper;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18 /**
19 * Question answer management
20 *
21 * @since 1.0.0
22 */
23 class Q_And_A {
24
25 /**
26 * List of all possible Q&A question statuses.
27 *
28 * @since 3.7.2
29 *
30 * @var string[]
31 */
32 const STATUS_LIST = array(
33 'all',
34 'read',
35 'unread',
36 'important',
37 'archived',
38 );
39
40 /**
41 * Register hooks
42 *
43 * @param boolean $register_hooks true/false to execute the hooks.
44 */
45 public function __construct( $register_hooks = true ) {
46 if ( ! $register_hooks ) {
47 return;
48 }
49
50 add_action( 'wp_ajax_tutor_qna_create_update', array( $this, 'tutor_qna_create_update' ) );
51
52 /**
53 * Delete question
54 *
55 * @since v.1.6.4
56 */
57 add_action( 'wp_ajax_tutor_delete_dashboard_question', array( $this, 'tutor_delete_dashboard_question' ) );
58
59 /**
60 * Take action against single qna
61 *
62 * @since v2.0.0
63 */
64 add_action( 'wp_ajax_tutor_qna_single_action', array( $this, 'tutor_qna_single_action' ) );
65 add_action( 'wp_ajax_tutor_qna_bulk_action', array( $this, 'process_bulk_action' ) );
66 /**
67 * Q & A load more
68 *
69 * @since v2.0.6
70 */
71 add_action( 'wp_ajax_tutor_q_and_a_load_more', __CLASS__ . '::load_more' );
72 }
73
74 /**
75 * Check user has access to QnA.
76 *
77 * @since 2.6.1
78 *
79 * @param int $user_id user id.
80 * @param int $course_id course id.
81 *
82 * @return boolean
83 */
84 public static function has_qna_access( $user_id, $course_id ) {
85 $is_public_course = Course_List::is_public( $course_id );
86
87 $has_access = $is_public_course
88 || User::is_admin()
89 || tutor_utils()->is_instructor_of_this_course( $user_id, $course_id )
90 || tutor_utils()->is_enrolled( $course_id, $user_id );
91 return $has_access;
92 }
93
94 /**
95 * Undocumented function
96 *
97 * @since v1.0.0
98 *
99 * @return void
100 */
101 public function tutor_qna_create_update() {
102 tutor_utils()->checking_nonce();
103
104 $user_id = get_current_user_id();
105 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
106
107 if ( ! $this->has_qna_access( $user_id, $course_id ) ) {
108 wp_send_json_error( array( 'message' => tutor_utils()->error_message() ) );
109 }
110
111 $qna_text = Input::post( 'answer', '', tutor()->has_pro ? Input::TYPE_KSES_POST : Input::TYPE_TEXTAREA );
112
113 if ( ! $qna_text ) {
114 // Content validation.
115 wp_send_json_error( array( 'message' => __( 'Empty Content Not Allowed!', 'tutor' ) ) );
116 }
117
118 // Prepare course, question info.
119 $course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
120 $question_id = Input::post( 'question_id', 0, Input::TYPE_INT );
121 $context = Input::post( 'context' );
122
123 // Prepare user info.
124 $user = get_userdata( $user_id );
125 $date = gmdate( 'Y-m-d H:i:s', tutor_time() );
126
127 $qna_object = new \stdClass();
128 $qna_object->user_id = $user_id;
129 $qna_object->course_id = $course_id;
130 $qna_object->question_id = $question_id;
131 $qna_object->qna_text = $qna_text;
132 $qna_object->user = $user;
133 $qna_object->date = $date;
134
135 $question_id = $this->inset_qna( $qna_object );
136
137 // Provide the html now.
138 // phpcs:disable WordPress.Security.NonceVerification.Missing
139 ob_start();
140 tutor_load_template_from_custom_path(
141 tutor()->path . '/views/qna/qna-single.php',
142 array(
143 'question_id' => $question_id,
144 'back_url' => isset( $_POST['back_url'] ) ? esc_url_raw( wp_unslash( $_POST['back_url'] ) ) : '',
145 'context' => $context,
146 )
147 );
148 wp_send_json_success(
149 array(
150 'html' => ob_get_clean(),
151 'editor_id' => 'tutor_qna_reply_editor_' . $question_id,
152 )
153 );
154 }
155
156 /**
157 * Function to insert Q&A
158 *
159 * @param object $qna_object the object to insert.
160 * @return int
161 */
162 public function inset_qna( $qna_object ) {
163 $course_id = $qna_object->course_id;
164 $question_id = $qna_object->question_id;
165 $qna_text = $qna_object->qna_text;
166 $user_id = $qna_object->user_id;
167 $user = $qna_object->user;
168 $date = $qna_object->date;
169
170 // Insert data prepare.
171 $data = apply_filters(
172 'tutor_qna_insert_data',
173 array(
174 'comment_post_ID' => $course_id,
175 'comment_author' => $user->user_login,
176 'comment_date' => $date,
177 'comment_date_gmt' => get_gmt_from_date( $date ),
178 'comment_content' => $qna_text,
179 'comment_approved' => 'approved',
180 'comment_agent' => 'TutorLMSPlugin',
181 'comment_type' => 'tutor_q_and_a',
182 'comment_parent' => $question_id,
183 'user_id' => $user_id,
184 )
185 );
186
187 global $wpdb;
188
189 // Insert new question/answer.
190 $wpdb->insert( $wpdb->comments, $data );
191 ! $question_id ? $question_id = (int) $wpdb->insert_id : 0;
192
193 // Mark the question unseen if action made from student.
194 $asker_id = $this->get_asker_id( $question_id );
195 $self = $asker_id == $user_id;
196 update_comment_meta( $question_id, 'tutor_qna_read' . ( $self ? '' : '_' . $asker_id ), 0 );
197
198 do_action( 'tutor_after_asked_question', $data );
199
200 // question_id != 0 means it's a reply.
201 $reply_id = Input::post( 'question_id', 0, Input::TYPE_INT );
202 $answer_id = (int) $wpdb->insert_id;
203 if ( 0 !== $reply_id && ( current_user_can( 'administrator' ) || tutor_utils()->is_instructor_of_this_course( $user_id, $course_id ) ) ) {
204 do_action( 'tutor_after_answer_to_question', $answer_id );
205 }
206
207 return $question_id;
208 }
209
210 /**
211 * Delete question [frontend dashboard]
212 *
213 * @since v.1.6.4
214 */
215 public function tutor_delete_dashboard_question() {
216 tutor_utils()->checking_nonce();
217
218 $question_id = Input::post( 'question_id', 0, Input::TYPE_INT );
219 if ( ! $question_id || ! tutor_utils()->can_user_manage( 'qa_question', $question_id ) ) {
220 wp_send_json_error( array( 'message' => __( 'Access Denied', 'tutor' ) ) );
221 }
222
223 $this->delete_qna_permanently( array( $question_id ) );
224
225 wp_send_json_success();
226 }
227
228 /**
229 * Undocumented function
230 *
231 * @param array $question_ids question ids.
232 *
233 * @return void
234 */
235 public function delete_qna_permanently( $question_ids ) {
236 if ( is_array( $question_ids ) && count( $question_ids ) ) {
237 global $wpdb;
238 // Prepare in clause.
239 $question_ids = QueryHelper::prepare_in_clause( $question_ids );
240
241 // Deleting question (comment), child question and question meta (comment meta).
242 $wpdb->query(
243 $wpdb->prepare(
244 "DELETE
245 FROM {$wpdb->comments}
246 WHERE {$wpdb->comments}.comment_ID IN ($question_ids)
247 AND 1 = %d
248 ",
249 1
250 )
251 );
252
253 $wpdb->query(
254 $wpdb->prepare(
255 "DELETE
256 FROM {$wpdb->comments}
257 WHERE {$wpdb->comments}.comment_parent IN ($question_ids)
258 AND 1 = %d
259 ",
260 1
261 )
262 );
263
264 $wpdb->query(
265 $wpdb->prepare(
266 "DELETE
267 FROM {$wpdb->commentmeta}
268 WHERE {$wpdb->commentmeta}.comment_id IN ($question_ids)
269 AND 1 = %d
270 ",
271 1
272 )
273 );
274 }
275 }
276
277 /**
278 * Process bulk delete
279 *
280 * @since v1.0.0
281 *
282 * @return void send wp_json response
283 */
284 public function process_bulk_action() {
285 tutor_utils()->checking_nonce();
286
287 $user_id = get_current_user_id();
288 $action = Input::post( 'bulk-action' );
289
290 switch ( $action ) {
291 case 'delete':
292 $qa_ids = Input::post( 'bulk-ids', '' );
293 $qa_ids = explode( ',', $qa_ids );
294 $qa_ids = array_filter(
295 $qa_ids,
296 function ( $id ) use ( $user_id ) {
297 return is_numeric( $id ) && tutor_utils()->can_user_manage( 'qa_question', $id, $user_id );
298 }
299 );
300
301 $this->delete_qna_permanently( $qa_ids );
302 break;
303 }
304
305 wp_send_json_success();
306 }
307
308 /**
309 * Get user id who asked
310 *
311 * @param int $question_id question id.
312 *
313 * @return string author id
314 */
315 private function get_asker_id( $question_id ) {
316 global $wpdb;
317 $author_id = $wpdb->get_var(
318 $wpdb->prepare(
319 "SELECT user_id
320 FROM {$wpdb->comments}
321 WHERE comment_ID = %d
322 ",
323 $question_id
324 )
325 );
326 return $author_id;
327 }
328
329 /**
330 * Update comment meta function
331 *
332 * @return void send wp_json response
333 */
334 public function tutor_qna_single_action() {
335 tutor_utils()->checking_nonce();
336
337 $question_id = Input::post( 'question_id', 0, Input::TYPE_INT );
338
339 if ( ! tutor_utils()->can_user_manage( 'qa_question', $question_id ) ) {
340 wp_send_json_error( array( 'message' => __( 'Permission Denied!', 'tutor' ) ) );
341 }
342
343 // Get who asked the question.
344 $context = Input::post( 'context', '' );
345 $user_id = get_current_user_id();
346
347 // Get the existing value from meta.
348 $action = Input::post( 'qna_action', '' );
349
350 $new_value = $this->trigger_qna_action( $question_id, $action, $context, $user_id );
351
352 // Transfer the new status.
353 wp_send_json_success( array( 'new_value' => $new_value ) );
354 }
355
356 /**
357 * Function to update Q&A action
358 *
359 * @since 2.6.2
360 *
361 * @param int $question_id question id.
362 * @param string $action action name.
363 * @param string $context context name.
364 * @param int $user_id user id.
365 *
366 * @return int
367 */
368 public function trigger_qna_action( $question_id, $action, $context, $user_id ) {
369 $asker_prefix = 'frontend-dashboard-qna-table-student' === $context ? '_' . $user_id : '';
370
371 // If current user asker, then make it unread for self.
372 // If it is instructor, then make unread for instructor side.
373 $meta_key = 'tutor_qna_' . $action . $asker_prefix;
374
375 $current_value = (int) get_comment_meta( $question_id, $meta_key, true );
376
377 $new_value = 1 === $current_value ? 0 : 1;
378
379 // Update the reverted value.
380 update_comment_meta( $question_id, $meta_key, $new_value );
381
382 return $new_value;
383 }
384
385 /**
386 * Available tabs that will visible on the right side of page navbar
387 *
388 * @since v2.0.0
389 *
390 * @param mixed $asker_id asker id.
391 *
392 * @return array
393 */
394 public static function tabs_key_value( $asker_id = null ) {
395
396 $args = Input::has( 'course-id' ) ? array( 'course_id' => Input::get( 'course-id', 0, Input::TYPE_INT ) ) : array();
397 $stats = array();
398
399 // Loop through all predefined Q&A statuses to retrieve corresponding question statistics.
400 foreach ( self::STATUS_LIST as $status ) {
401
402 $label = 'all' === $status ? null : $status;
403 $stats[ $status ] = tutor_utils()->get_qa_questions( 0, 99999, '', null, null, $asker_id, $label, true, $args );
404 }
405
406 // Assign value, url etc to the tab array.
407 $tabs = array_map(
408 function ( $tab ) use ( $stats ) {
409 return array(
410 'key' => 'all' === $tab ? '' : $tab,
411 'title' => tutor_utils()->translate_dynamic_text( $tab ),
412 'value' => $stats[ $tab ],
413 'url' => add_query_arg( array( 'data' => $tab ), remove_query_arg( 'data' ) ),
414 );
415 },
416 array_keys( $stats )
417 );
418
419 return $tabs;
420 }
421
422 /**
423 * Load more q & a
424 *
425 * @since v2.0.6
426 *
427 * @return void send wp_json response
428 */
429 public static function load_more() {
430 tutor_utils()->checking_nonce();
431 ob_start();
432 tutor_load_template( 'single.course.enrolled.question_and_answer' );
433 $html = ob_get_clean();
434 wp_send_json_success( array( 'html' => $html ) );
435 }
436 }
437