PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 3.7.4
Tutor LMS – eLearning and online course solution v3.7.4
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 / Quiz_Attempts_List.php
tutor / classes Last commit date
Addons.php 11 months ago Admin.php 11 months ago Ajax.php 1 year ago Announcements.php 1 year ago Assets.php 10 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 11 months ago Course_Embed.php 3 years ago Course_Filter.php 1 year ago Course_List.php 11 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 1 year ago FormHandler.php 2 years ago Frontend.php 1 year ago Gutenberg.php 1 year ago Icon.php 10 months ago Input.php 1 year ago Instructor.php 1 year ago Instructors_List.php 11 months ago Lesson.php 11 months ago Options_V2.php 11 months ago Permalink.php 2 years ago Post_types.php 1 year ago Private_Course_Access.php 1 year ago Q_And_A.php 11 months ago Question_Answers_List.php 11 months ago Quiz.php 10 months ago QuizBuilder.php 11 months ago Quiz_Attempts_List.php 11 months ago RestAPI.php 2 years ago Reviews.php 11 months 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 11 months ago Theme_Compatibility.php 3 years ago Tools.php 1 year ago Tools_V2.php 1 year ago Tutor.php 11 months ago TutorEDD.php 1 year ago Tutor_Base.php 2 years ago Tutor_Setup.php 1 year ago Upgrader.php 11 months ago User.php 1 year ago Utils.php 11 months ago Video_Stream.php 3 years ago WhatsNew.php 2 years ago Withdraw.php 1 year ago Withdraw_Requests_List.php 11 months ago WooCommerce.php 1 year ago
Quiz_Attempts_List.php
371 lines
1 <?php
2 /**
3 * Quiz attempt list management
4 *
5 * @package Tutor\QuestionAnswer
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\Cache\QuizAttempts;
18 use Tutor\Models\QuizModel;
19
20 /**
21 * Quiz attempt class
22 *
23 * @since 1.0.0
24 */
25 class Quiz_Attempts_List {
26
27 const QUIZ_ATTEMPT_PAGE = 'tutor_quiz_attempts';
28
29 /**
30 * Trait for utilities
31 *
32 * @var $page_title
33 */
34
35 use Backend_Page_Trait;
36
37 /**
38 * Bulk Action
39 *
40 * @var $bulk_action
41 */
42 public $bulk_action = true;
43
44 /**
45 * Handle dependencies
46 *
47 * @since 1.0.0
48 *
49 * @param boolean $register_hook should register hook or not.
50 */
51 public function __construct( $register_hook = true ) {
52 if ( ! $register_hook ) {
53 return;
54 }
55
56 /**
57 * Handle bulk action
58 *
59 * @since 2.0.0
60 */
61 add_action( 'wp_ajax_tutor_quiz_attempts_bulk_action', array( $this, 'quiz_attempts_bulk_action' ) );
62 add_action( 'wp_ajax_tutor_quiz_attempts_count', array( $this, 'get_quiz_attempts_stat' ) );
63
64 /**
65 * Delete quiz attempt cache
66 *
67 * @since 2.1.0
68 */
69 add_action( 'tutor_quiz/attempt_ended', array( new QuizAttempts(), 'delete_cache' ) );
70 add_action( 'tutor_quiz/attempt_deleted', array( new QuizAttempts(), 'delete_cache' ) );
71 add_action( 'tutor_quiz/answer/review/after', array( new QuizAttempts(), 'delete_cache' ) );
72 }
73
74 /**
75 * Page title fallback
76 *
77 * @since 3.5.0
78 *
79 * @param string $name Property name.
80 *
81 * @return string
82 */
83 public function __get( $name ) {
84 if ( 'page_title' === $name ) {
85 return esc_html__( 'Quiz Attempts', 'tutor' );
86 }
87 }
88
89 /**
90 * Get the attempts stat from specific instructor context
91 *
92 * @since 2.0.0
93 *
94 * @return array
95 */
96 public function get_quiz_attempts_stat() {
97 global $wpdb;
98
99 if ( wp_doing_ajax() ) {
100 tutor_utils()->checking_nonce();
101 }
102
103 /**
104 * Parse `passing_grade` value from `attempt_info` serialized data.
105 *
106 * Data Format : "passing_grade";s:2:"80" or "passing_grade";s:3:"100"
107 * Expected Output : 80 or 100
108 *
109 * TODO: Optimize SQL query.
110 */
111 $pass_mark = "((( SUBSTRING_INDEX(
112 SUBSTRING_INDEX(
113 attempt_info,
114 CONCAT(
115 '\"passing_grade\";s:',
116 SUBSTRING_INDEX(SUBSTRING_INDEX(attempt_info, '\"passing_grade\";s:', -1), ':\"', 1),
117 ':\"'
118 ),
119 -1
120 ),
121 '\"',
122 1
123 ))/100) * quiz_attempts.total_marks)";
124
125 $pending_count = "(SELECT COUNT(DISTINCT attempt_answer_id) FROM {$wpdb->prefix}tutor_quiz_attempt_answers WHERE quiz_attempt_id=quiz_attempts.attempt_id AND is_correct IS NULL)";
126 $pass_clause = " AND quiz_attempts.earned_marks >= {$pass_mark} ";
127 $fail_clause = " AND quiz_attempts.earned_marks < {$pass_mark} AND {$pending_count} < 1 ";
128 $pending_clause = " AND {$pending_count} > 0 ";
129
130 $user_id = get_current_user_id();
131 $user_clause = '';
132 if ( ! current_user_can( 'administrator' ) ) {
133 $user_clause = "AND quiz.post_author = {$user_id}";
134 }
135
136 $count_obj = (object) array(
137 'pass' => 0,
138 'fail' => 0,
139 'pending' => 0,
140 );
141
142 $is_ajax_action = 'tutor_quiz_attempts_count' === Input::post( 'action' );
143 $course_id = Input::post( 'course_id', '' );
144 $date = Input::post( 'date', '' );
145 $search = Input::post( 'search', '' );
146
147 $course_filter = '' !== $course_id ? $wpdb->prepare( ' AND quiz_attempts.course_id = %d', $course_id ) : '';
148 $date_filter = '' !== $date ? $wpdb->prepare( ' AND DATE(quiz_attempts.attempt_started_at) = %s ', $date ) : '';
149 $search_term_raw = $search;
150 $search_filter = '%' . $wpdb->esc_like( $search ) . '%';
151
152 if ( $is_ajax_action ) {
153 $current_params = compact( 'course_id', 'date', 'search' );
154 $attempt_cache = new QuizAttempts( $current_params );
155
156 $cached_attempts = $attempt_cache->get_cache();
157 if ( $attempt_cache->has_cache() && $attempt_cache->is_same_query() && isset( $cached_attempts['result'] ) ) {
158 $count_obj = $cached_attempts['result'];
159 } else {
160 $select_stmt = "SELECT COUNT( DISTINCT attempt_id)
161 FROM {$wpdb->prefix}tutor_quiz_attempts quiz_attempts
162 INNER JOIN {$wpdb->posts} quiz ON quiz_attempts.quiz_id = quiz.ID
163 INNER JOIN {$wpdb->users} AS users ON quiz_attempts.user_id = users.ID
164 INNER JOIN {$wpdb->posts} AS course ON course.ID = quiz_attempts.course_id";
165
166 $count_obj->pass = (int) $wpdb->get_var(
167 $wpdb->prepare(
168 "{$select_stmt}
169 WHERE attempt_status != %s
170 AND (
171 users.user_email = %s
172 OR users.display_name LIKE %s
173 OR quiz.post_title LIKE %s
174 OR course.post_title LIKE %s
175 )
176 {$pass_clause}
177 {$user_clause}
178 {$course_filter}
179 {$date_filter}
180 ",
181 'attempt_started',
182 $search_term_raw,
183 $search_filter,
184 $search_filter,
185 $search_filter
186 )
187 );
188
189 $count_obj->fail = (int) $wpdb->get_var(
190 $wpdb->prepare(
191 "{$select_stmt}
192 WHERE attempt_status != %s
193 AND (
194 users.user_email = %s
195 OR users.display_name LIKE %s
196 OR quiz.post_title LIKE %s
197 OR course.post_title LIKE %s
198 )
199 {$fail_clause}
200 {$user_clause}
201 {$course_filter}
202 {$date_filter}
203 ",
204 'attempt_started',
205 $search_term_raw,
206 $search_filter,
207 $search_filter,
208 $search_filter
209 )
210 );
211
212 $count_obj->pending = (int) $wpdb->get_var(
213 $wpdb->prepare(
214 "{$select_stmt}
215 WHERE attempt_status != %s
216 AND (
217 users.user_email = %s
218 OR users.display_name LIKE %s
219 OR quiz.post_title LIKE %s
220 OR course.post_title LIKE %s
221 )
222 {$pending_clause}
223 {$user_clause}
224 {$course_filter}
225 {$date_filter}
226 ",
227 'attempt_started',
228 $search_term_raw,
229 $search_filter,
230 $search_filter,
231 $search_filter
232 )
233 );
234
235 $attempt_cache->data = $count_obj;
236 $attempt_cache->set_cache();
237 }
238 }
239
240 $all = $count_obj->pass + $count_obj->fail + $count_obj->pending;
241 $pass = $count_obj->pass;
242 $fail = $count_obj->fail;
243 $pending = $count_obj->pending;
244 $response = compact( 'all', 'pass', 'fail', 'pending' );
245
246 return $is_ajax_action ? wp_send_json_success( $response ) : $response;
247 }
248
249 /**
250 * Available tabs that will visible on the right side of page navbar
251 *
252 * @since 2.0.0
253 *
254 * @param string $user_id selected quiz_attempts id | optional.
255 * @param int $course_id selected quiz_attempts id | optional.
256 * @param string $date selected date | optional.
257 * @param string $search search by user name or email | optional.
258 *
259 * @return array
260 */
261 public function tabs_key_value( $user_id, $course_id, $date, $search ): array {
262 $url = apply_filters( 'tutor_data_tab_base_url', get_pagenum_link() );
263 $stats = $this->get_quiz_attempts_stat();
264
265 $tabs = array(
266 array(
267 'key' => '',
268 'title' => __( 'All', 'tutor' ),
269 'value' => $stats['all'],
270 'url' => $url . '&data=all',
271 ),
272 array(
273 'key' => 'pass',
274 'title' => __( 'Pass', 'tutor' ),
275 'value' => $stats['pass'],
276 'url' => $url . '&data=pass',
277 ),
278 array(
279 'key' => 'fail',
280 'title' => __( 'Fail', 'tutor' ),
281 'value' => $stats['fail'],
282 'url' => $url . '&data=fail',
283 ),
284 array(
285 'key' => 'pending',
286 'title' => __( 'Pending', 'tutor' ),
287 'value' => $stats['pending'],
288 'url' => $url . '&data=pending',
289 ),
290 );
291
292 return $tabs;
293 }
294
295 /**
296 * Prepare bulk actions that will show on dropdown options
297 *
298 * @since 2.0.0
299 *
300 * @return array
301 */
302 public function prpare_bulk_actions(): array {
303 $actions = array(
304 $this->bulk_action_default(),
305 $this->bulk_action_delete(),
306 );
307 return $actions;
308 }
309
310
311 /**
312 * Handle bulk action for instructor delete
313 *
314 * @since 2.0.0
315 *
316 * @return void send wp_json response
317 */
318 public function quiz_attempts_bulk_action() {
319 // check nonce.
320 tutor_utils()->checking_nonce();
321
322 // Check if user is privileged.
323 if ( ! User::has_any_role( array( User::ADMIN, User::INSTRUCTOR ) ) ) {
324 wp_send_json_error( tutor_utils()->error_message() );
325 }
326
327 $bulk_action = Input::post( 'bulk-action', '' );
328 $bulk_ids = Input::post( 'bulk-ids', '' );
329 $bulk_ids = explode( ',', $bulk_ids );
330 $bulk_ids = array_map(
331 function( $id ) {
332 return (int) trim( $id );
333 },
334 $bulk_ids
335 );
336
337 // prevent instructor to remove quiz attempt from admin.
338 $bulk_ids = array_filter(
339 $bulk_ids,
340 function ( $attempt_id ) {
341 $attempt = tutor_utils()->get_attempt( $attempt_id );
342 $user_id = get_current_user_id();
343 $course_id = $attempt && is_object( $attempt ) ? $attempt->course_id : 0;
344 return $course_id && tutor_utils()->can_user_edit_course( $user_id, $course_id );
345 }
346 );
347
348 switch ( $bulk_action ) {
349 case 'delete':
350 QuizModel::delete_quiz_attempt( $bulk_ids );
351 break;
352 }
353
354 wp_send_json_success();
355 }
356
357 /**
358 * Get bulk action as an array
359 *
360 * @since 2.0.0
361 *
362 * @return array
363 */
364 public function get_bulk_actions() {
365 $actions = array(
366 'delete' => 'Delete',
367 );
368 return $actions;
369 }
370 }
371