PluginProbe
Watu Quiz / 3.4.7
Watu Quiz v3.4.7
3.4.8 trunk 3.4.2 3.4.3 3.4.4 3.4.5 3.4.5.1 3.4.5.2 3.4.5.3 3.4.6 3.4.7
watu / controllers / takings.php

takings.php in Watu Quiz 3.4.7, at controllers/takings.php

414 lines 16.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
4 // select taking records for an exam
5 function watu_takings($in_shortcode = false, $atts = null) {
6 global $wpdb, $post;
7
8 if($in_shortcode) {
9 $_GET['exam_id'] = intval($atts['quiz_id'] ?? 0);
10 }
11
12 $is_preview_request = ( is_preview() || isset( $_GET['preview'] ) || isset( $_GET['_ppp'] ) );
13
14 // block preview for unauthorized
15 if ( $in_shortcode && $is_preview_request && ! current_user_can( 'manage_options' ) && ! current_user_can( 'watu_manage' ) ) {
16 return '';
17 }
18
19 // if Namaste! LMS is installed we'll also select courses
20 if(class_exists('NamasteLMSCourseModel')) {
21 $_course = new NamasteLMSCourseModel();
22 $namaste_courses = $_course->select();
23 }
24
25 // select exam
26 $exam = $wpdb->get_row($wpdb->prepare("SELECT * FROM ".WATU_EXAMS." WHERE ID=%d", intval($_GET['exam_id'])));
27 $grades = $wpdb->get_results($wpdb->prepare("SELECT * FROM ".WATU_GRADES." WHERE exam_id=%d order by gtitle ", $exam->ID) );
28
29 // delete a taking
30 if(!empty($_GET['del_taking']) and check_admin_referer('watu_del_taking')) {
31 $wpdb->query($wpdb->prepare("DELETE FROM ".WATU_TAKINGS." WHERE ID=%d", intval($_GET['id'])));
32 watu_redirect("admin.php?page=watu_takings&exam_id=".$exam->ID);
33 }
34
35 // mass cleanup
36 if(!empty($_POST['delete_all_takings']) and check_admin_referer('watu_delete_all')) {
37 $wpdb->query($wpdb->prepare("DELETE FROM ".WATU_TAKINGS." WHERE exam_id=%d", $exam->ID));
38 }
39
40 // mass delete selected takings
41 if(!empty($_POST['del_takings']) and check_admin_referer('watu_del_takings') and !empty($_POST['ids'])) {
42 $ids = array_map('intval', $_POST['ids']);
43
44 $wpdb->query("DELETE FROM ".WATU_TAKINGS." WHERE ID IN (". implode(', ', $ids) .")");
45 }
46
47 // select taking records
48 $ob = empty($_GET['ob'])? "tT.id" : sanitize_text_field($_GET['ob']);
49 if(!in_array($ob, ['tT.id', 'tT.date', 'tU.user_login', 'tT.points', 'tT.percent_correct'])) $ob = 'tT.id';
50 $dir = !empty($_GET['dir'])? $_GET['dir'] : "DESC";
51
52
53 $offset = empty($_GET['offset']) ? 0 : intval($_GET['offset']);
54 $limit_sql = empty($_GET['watu_export']) ? "LIMIT $offset, 10" : "";
55
56 // ob, dir and limit can be overwritten by shortcode atts. When limit is passed we will not show pagination (leaderboard)
57 if(!empty($atts['ob']) and in_array($atts['ob'], ['points', 'percent_correct', 'date'] ) ) $ob = 'tT.'.esc_attr($atts['ob']);
58 if(!empty($atts['dir'])) $dir = esc_attr($atts['dir']);
59 if(!empty($atts['num'])) $limit_sql = $wpdb->prepare(" LIMIT %d ", intval($atts['num']));
60
61 if(strtoupper($dir) != 'ASC' and strtoupper($dir) != 'DESC') $dir = 'DESC';
62 $odir = ($dir=='ASC')?'DESC':'ASC';
63
64 // filter / search?
65 $filters = $joins = array();
66 $filter_sql = $left_join_sql = $role_join_sql = $group_join_sql = $left_join = "";
67 $join_sql="LEFT JOIN {$wpdb->users} tU ON tU.ID=tT.user_id";
68
69 // display name
70 if(!empty($_GET['dn'])) {
71 $_GET['dn'] = sanitize_text_field($_GET['dn']);
72 switch($_GET['dnf']) {
73 case 'contains': $like="%$_GET[dn]%"; break;
74 case 'starts': $like="$_GET[dn]%"; break;
75 case 'ends': $like="%$_GET[dn]"; break;
76 case 'equals':
77 default: $like=$_GET['dn']; break;
78 }
79
80 $joins[]= " display_name LIKE '$like' ";
81 }
82
83 // email
84 if(!empty($_GET['email'])) {
85 $_GET['email'] = sanitize_email($_GET['email']);
86 switch($_GET['emailf']) {
87 case 'contains': $like="%$_GET[email]%"; break;
88 case 'starts': $like="$_GET[email]%"; break;
89 case 'ends': $like="%$_GET[email]"; break;
90 case 'equals':
91 default: $like=$_GET['email']; break;
92 }
93
94 $joins[]=$wpdb->prepare(" user_email LIKE %s ", $like);
95 $filters[]=$wpdb->prepare(" ((user_id=0 AND email LIKE %s) OR (user_id!=0 AND user_email LIKE %s)) ", $like, $like);
96 $left_join = 'LEFT'; // when email is selected, do left join because it might be without logged user
97 }
98
99 // IP
100 if(!empty($_GET['ip'])) {
101 $_GET['ip'] = filter_var($_GET['ip'], FILTER_VALIDATE_IP);
102 switch($_GET['ipf']) {
103 case 'contains': $like="%$_GET[ip]%"; break;
104 case 'starts': $like="$_GET[ip]%"; break;
105 case 'ends': $like="%$_GET[ip]"; break;
106 case 'equals':
107 default: $like=$_GET['ip']; break;
108 }
109
110 $filters[]=$wpdb->prepare(" ip LIKE %s ", $like);
111 }
112
113 // Date
114 if(!empty($_GET['date'])) {
115 $_GET['date'] = sanitize_text_field($_GET['date']);
116 switch($_GET['datef']) {
117 case 'after': $filters[]=$wpdb->prepare(" date>%s ", $_GET['date']); break;
118 case 'before': $filters[]=$wpdb->prepare(" date<%s ", $_GET['date']); break;
119 case 'equals':
120 default: $filters[]=$wpdb->prepare(" date=%s ", $_GET['date']); break;
121 }
122 }
123
124 // Points
125 if(!empty($_GET['points'])) {
126 $_GET['points'] = floatval($_GET['points']);
127 switch($_GET['pointsf']) {
128 case 'less': $filters[]=$wpdb->prepare(" points < %f ", $_GET['points']); break;
129 case 'more': $filters[]=$wpdb->prepare(" points > %f ", $_GET['points']); break;
130 case 'equals':
131 default: $filters[]=$wpdb->prepare(" points=%d ", $_GET['points']); break;
132 }
133 }
134
135 // grade
136 if(!empty($_GET['grade_id'])) {
137 $filters[] = $wpdb->prepare(" grade_id=%d ", intval($_GET['grade_id']));
138 }
139
140 // source URL
141 if(!empty($_GET['source_url'])) {
142 $filters[] = $wpdb->prepare(" source_url=%s ", esc_url_raw($_GET['source_url']));
143 }
144
145 // Namaste! LMS Course
146 if(!empty($_GET['namaste_course_id']) and !empty($namaste_courses)) {
147 // let's select here as a subquery might be slower (is it?)
148 $namaste_uids = array(-1);
149 $namaste_uids1 = $wpdb->get_results($wpdb->prepare("SELECT user_id FROM ".NAMASTE_STUDENT_COURSES."
150 WHERE course_id=%d AND (status='enrolled' OR status='completed')", intval($_GET['namaste_course_id'])));
151 foreach($namaste_uids1 as $nu) $namaste_uids[] = intval($nu->user_id);
152
153 // Use prepared statement for IN clause to prevent SQL injection
154 $namaste_placeholders = implode(',', array_fill(0, count($namaste_uids), '%d'));
155 $filters[] = $wpdb->prepare(" tT.user_id IN ($namaste_placeholders) ", $namaste_uids);
156 }
157
158 // construct filter & join SQLs
159 if(count($filters)) {
160 $filter_sql=" AND ".implode(" AND ", $filters);
161 }
162
163 if(count($joins)) {
164 $join_sql=" $left_join JOIN {$wpdb->users} tU ON tU.ID=tT.user_id AND "
165 .implode(" AND ", $joins);
166 }
167
168 // select unique source URLs in this quiz
169 $source_urls = $wpdb->get_results($wpdb->prepare("SELECT DISTINCT(source_url) FROM ".WATU_TAKINGS."
170 WHERE exam_id=%d AND source_url != ''", intval($_GET['exam_id'])));
171
172 $takings = $wpdb->get_results("SELECT SQL_CALC_FOUND_ROWS tT.*, tU.user_login as user_login, tU.user_email as user_email,
173 tG.gtitle as grade_title
174 FROM ".WATU_TAKINGS." tT
175 LEFT JOIN ".WATU_GRADES." tG ON tG.ID = tT.grade_id
176 $join_sql
177 WHERE tT.exam_id={$exam->ID} $filter_sql
178 ORDER BY $ob $dir $limit_sql");
179
180 $count = $wpdb->get_var("SELECT FOUND_ROWS()");
181
182 $delim = get_option('watu_csv_delim');
183 if(empty($delim)) $delim = ',';
184
185 // export CSV
186 if(!empty($_GET['watu_export'])) {
187 $newline=watu_define_newline();
188
189 $rows=array();
190
191 if($delim == 'tab') $delim = "\t";
192
193 $quotes = get_option('watu_csv_quotes');
194 $quote = ($quotes === '0') ? '' : '"';
195
196 $rows[]=__('User or IP Address', 'watu').$delim.__('Email address', 'watu').$delim.__('Date', 'watu').$delim.__('Points', 'watu').$delim.__('% Correct Answers', 'watu').
197 $delim.__('Num Correct Answers', 'watu').$delim.__('Num Wrong Answers', 'watu').$delim.__('Num Unanswered Questions', 'watu').
198 $delim.__('Result/Grade', 'watu');
199 foreach($takings as $taking) {
200 if(empty($taking->email) and !empty($taking->user_email)) $taking->email = $taking->user_email;
201 $row = ($taking->user_id ? $taking->user_login : $taking->ip).$delim.$taking->email.$delim.$quote.date(get_option('date_format'), strtotime($taking->date)).$quote.$delim.
202 $taking->points.$delim.$taking->percent_correct.$delim.$taking->num_correct.$delim
203 .$taking->num_wrong.$delim.$taking->num_empty.$delim.$quote.$taking->result.$quote;
204 $rows[] = $row;
205 } // end foreach taking
206 $csv=implode($newline,$rows);
207
208 $now = gmdate('D, d M Y H:i:s') . ' GMT';
209 $filename = 'exam-'.$exam->ID.'-results.csv';
210 header('Content-Type: ' . watu_get_mime_type());
211 header('Expires: ' . $now);
212 header('Content-Disposition: attachment; filename="'.$filename.'"');
213 header('Pragma: no-cache');
214 echo $csv;
215 exit;
216 }
217
218 // this var will be added to links at the view
219 $filters_url="dn=".esc_attr($_GET['dn'] ?? '')."&dnf=".esc_attr($_GET['dnf'] ?? '')."&email=".esc_attr($_GET['email'] ?? '')
220 ."&emailf=".esc_attr($_GET['emailf'] ?? '')."&ip=".esc_attr($_GET['ip'] ?? '' )."&ipf=".esc_attr($_GET['ipf'] ?? '')."&date="
221 .esc_attr($_GET['date'] ?? '')."&datef=".esc_attr($_GET['datef'] ?? '')."&points=".esc_attr($_GET['points'] ?? '')
222 ."&pointsf=".esc_attr($_GET['pointsf'] ?? '')."&grade_id=".esc_attr($_GET['grade_id'] ?? '')."&source_url=".esc_attr($_GET['source_url'] ?? '');
223
224 if(!empty($namaste_courses) and !empty($_GET['namaste_course_id'])) {
225 $filters_url .= "&namaste_course_id=".intval($_GET['namaste_course_id']);
226 }
227
228 // if in shortcode prepare the target URL
229 if($in_shortcode) {
230 $permalink = get_permalink($post->ID);
231 $params = array('exam_id' => $exam->ID);
232 $target_url = add_query_arg( $params, $permalink );
233 }
234 else $target_url = "?page=watu_takings&exam_id=" . $exam->ID;
235
236 $display_filters=(!sizeof($filters) and !sizeof($joins)) ? false : true;
237
238 // shortcode params
239 $show_email = isset($atts['show_email']) ? intval($atts['show_email']) : 1;
240 $show_points = isset($atts['show_points']) ? intval($atts['show_points']) : 1;
241 $show_percent = isset($atts['show_percent']) ? intval($atts['show_percent']) : 1;
242
243 wp_enqueue_script('thickbox',null,array('jquery'));
244 wp_enqueue_style('thickbox.css', '/'.WPINC.'/js/thickbox/thickbox.css', null, '1.0');
245 if(@file_exists(get_stylesheet_directory().'/watu/takings.php')) include get_stylesheet_directory().'/watu/takings.php';
246 else include(WATU_PATH . '/views/takings.php');
247 }
248
249 // display taking details by ajax
250 function watu_taking_details() {
251 global $wpdb, $user_ID;
252
253 $view_level = current_user_can('watu_manage') ? 'watu_manage' : 'manage_options';
254
255 // select taking
256 $taking=$wpdb->get_row($wpdb->prepare("SELECT * FROM ".WATU_TAKINGS."
257 WHERE id=%d", intval($_REQUEST['id'])));
258
259 // select user
260 $student=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->users}
261 WHERE id=%d", $taking->user_id));
262
263 if(empty($taking->email) and !empty($student->ID)) $taking->email = $student->user_email;
264
265 // make sure I'm admin or that's me
266 if(!current_user_can($view_level) and $student->ID != $user_ID) {
267 wp_die( __('You do not have sufficient permissions to access this page', 'watu') );
268 }
269
270 // select exam
271 $exam=$wpdb->get_row($wpdb->prepare("SELECT * FROM ".WATU_EXAMS." WHERE id=%d", $taking->exam_id));
272
273 if(@file_exists(get_stylesheet_directory().'/watu/taking_details.html.php')) include get_stylesheet_directory().'/watu/taking_details.html.php';
274 else include(WATU_PATH . '/views/taking_details.html.php');
275 exit;
276 }
277
278 // shortcode for showing the basic barchart included in the core WatuPRO
279 // call this ONLY in the Final Screen of the quiz
280 function watu_basic_chart($atts) {
281 $taking_id = intval($GLOBALS['watu_taking_id']);
282 $content = watu_barchart($taking_id, $atts);
283 return $content;
284 }
285
286 // basic barchart your points vs avg points, your % vs avg %
287 // this chart will be loaded by variable or shortcode in the Final screen
288 // this function uses globals so it will work properly only when called on controllers/show_exam.php or a shortcode on the Final screen
289 function watu_barchart($taking_id, $atts) {
290 global $wpdb, $achieved;
291
292 // normalize params
293 $show = empty($atts['show']) ? 'both' : $atts['show'];
294 if(!in_array($show, array('both', 'points', 'percent'))) $show = 'both';
295 $your_color = empty($atts['your_color']) ? "blue" : esc_attr($atts['your_color']);
296 $avg_color = empty($atts['avg_color']) ? "gray" : esc_attr($atts['avg_color']);
297 $your_percent_text = empty($atts['your_percent_text']) ? __('You: %d%% correct', 'watu') : esc_attr($atts['your_percent_text']);
298 $avg_percent_text = empty($atts['avg_percent_text']) ? __('Avg. %d%% correct', 'watu') : esc_attr($atts['your_percent_text']);
299 $your_points_text = empty($atts['your_points_text']) ? __('Your points: %s', 'watu') : esc_attr($atts['your_points_text']);
300 $avg_points_text = empty($atts['avg_points_text']) ? __('Avg. points: %s', 'watu') : esc_attr($atts['avg_points_text']);
301 $step = 2;
302
303 // select taking
304 $taking = $wpdb->get_row($wpdb->prepare("SELECT * FROM ".WATU_TAKINGS." WHERE ID=%d", $taking_id));
305
306 // get average points
307 $all_point_rows = $wpdb->get_results($wpdb->prepare("SELECT points FROM ".WATU_TAKINGS." WHERE exam_id=%d", $taking->exam_id));
308 $all_points = 0;
309 foreach($all_point_rows as $r) $all_points += $r->points;
310 $all_points += $achieved;
311 $avg_points = round($all_points / ($wpdb->num_rows + 1), 1);
312
313 // the points step should rougly make the higher points bar 200px high
314 $more_points = ($avg_points > $taking->points) ? $avg_points : $taking->points;
315 if(!$more_points) $more_points = 1; // set to non-zero for division
316 $points_step = round(200 / $more_points, 2);
317
318 // create & return the chart HTML
319 $content = '<table class="watu-basic-chart"><tr>';
320
321 if($show == 'points' or $show == 'both') {
322 $your_points_text = sprintf($your_points_text, $taking->points);
323 $avg_points_text = sprintf($avg_points_text, $avg_points);
324
325 // normalize points here, shouldn't be less than zero when calculating the bar height
326 if($taking->points < 0) $taking->points = 0;
327
328 $content .= '<td style="vertical-align:bottom;"><table class="watu-basic-chart-points"><tr><td align="center" style="vertical-align:bottom;">';
329 $content .= '<div style="background-color:'.$your_color.';width:100px;height:'.round($points_step * $taking->points). 'px;">&nbsp;</div>';
330 $content .='</td><td align="center" style="vertical-align:bottom;">';
331 $content .= '<div style="background-color:'.$avg_color.';width:100px;height:'.round($points_step * $avg_points). 'px;">&nbsp;</div>';
332 $content .='</td></tr><tr><td align="center">' . $your_points_text . '</td><td align="center">'. $avg_points_text .'</td></tr>';
333 $content .= '</table></td>';
334 }
335 $content .= '</tr></table>';
336
337 return $content;
338 }
339
340 function watu_shortcode_takings($atts) {
341 ob_start();
342 watu_takings(true, $atts);
343 $content = ob_get_clean();
344 return $content;
345 }
346
347 // handle MoolaMojo integration if enabled
348 function watu_taking_transfer_moola($taking_id, $exam, $user_id, $points, $grade_id) {
349 global $wpdb;
350
351 if(empty($user_id)) return false;
352 if(empty($points) and empty($grade_id)) return false;
353 if(get_option('watu_integrate_moolamojo') != 1) return false;
354
355 $advanced_settings = unserialize(stripslashes($exam->advanced_settings));
356 if(empty($advanced_settings['transfer_moola'])) return false;
357
358
359 if($advanced_settings['transfer_moola_mode'] == 'equal') $credits = $points;
360 else {
361 // select grade points
362 if($grade_id == 0) return false;
363 $credits = $wpdb->get_var($wpdb->prepare("SELECT moola FROM ".WATU_GRADES." WHERE ID=%d", $grade_id));
364 }
365
366 if($credits == 0) return false;
367
368 // actually transfer the moola
369 if($credits > 0 ) $reward = true;
370 else {
371 $reward = false;
372 $credits = abs($credits);
373 }
374 do_action("moolamojo_transaction", $reward, $credits, __('submitted test', 'watu'), $user_id, WATU_TAKINGS, $taking_id);
375
376 } // end transfer_moola
377
378 // register personal data eraser
379 function watu_register_eraser($erasers) {
380 $erasers['watu'] = array(
381 'eraser_friendly_name' => __( 'Watu Quiz', 'watu' ),
382 'callback' => 'watu_erase_data'
383 );
384
385 return $erasers;
386 }
387
388 // again deleting user data but this time when called from the WP erase data hook
389 function watu_erase_data($email_address, $page = 1) {
390 global $wpdb;
391
392 $number = 200; // Limit us to avoid timing out
393 $page = (int) $page;
394 $email_address = sanitize_email($email_address);
395
396 // find student
397 $user = get_user_by('email', $email_address);
398
399 if(empty($user->ID)) {
400 // delete exam results
401 $wpdb->query($wpdb->prepare("DELETE FROM ".WATU_TAKINGS." WHERE email=%s", $email_address));
402 }
403 else {
404 $wpdb->query($wpdb->prepare("DELETE FROM ".WATU_TAKINGS." WHERE user_id=%d", $user->ID));
405 }
406
407 return array( 'items_removed' => true,
408 'items_retained' => false,
409 'messages' => array(), // no messages
410 'done' => true,
411 );
412 } // end data eraser
413
414