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