Issues (896)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/class-sensei-utils.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 14 and the first side effect is on line 2.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
4
/**
5
 * Sensei Utilities Class
6
 *
7
 * Common utility functions for Sensei.
8
 *
9
 * @package Core
10
 * @author Automattic
11
 *
12
 * @since 1.0.0
13
 */
14
class Sensei_Utils {
15
	/**
16
	 * Get the placeholder thumbnail image.
17
	 * @access  public
18
	 * @since   1.0.0
19
	 * @return  string The URL to the placeholder thumbnail image.
20
	 */
21
	public static function get_placeholder_image () {
22
23
		return esc_url( apply_filters( 'sensei_placeholder_thumbnail', Sensei()->plugin_url . 'assets/images/placeholder.png' ) );
24
	} // End get_placeholder_image()
25
26
	/**
27
	 * Check if WooCommerce is present.
28
     *
29
     * @deprecated since 1.9.0 use Sensei_WC::is_woocommerce_present()
30
	 * @access public
31
	 * @since  1.0.2
32
	 * @static
33
	 * @return bool
34
	 */
35
	public static function sensei_is_woocommerce_present () {
36
37
        return Sensei_WC::is_woocommerce_present();
38
39
	} // End sensei_is_woocommerce_present()
40
41
	/**
42
	 * Check if WooCommerce is active.
43
     *
44
     * @deprecated since 1.9.0 use Sensei_WC::is_woocommerce_active
45
	 * @access public
46
	 * @since  1.0.2
47
	 * @static
48
	 * @return boolean
49
	 */
50
	public static function sensei_is_woocommerce_activated () {
51
52
		return  Sensei_WC::is_woocommerce_active();
53
54
	} // End sensei_is_woocommerce_activated()
55
56
	/**
57
	 * Log an activity item.
58
	 * @access public
59
	 * @since  1.0.0
60
	 * @param  array $args (default: array())
61
	 * @return bool | int
62
	 */
63
	public static function sensei_log_activity ( $args = array() ) {
64
		global $wpdb;
65
66
		// Args, minimum data required for WP
67
		$data = array(
68
					'comment_post_ID' => intval( $args['post_id'] ),
69
					'comment_author' => '', // Not needed
70
					'comment_author_email' => '', // Not needed
71
					'comment_author_url' => '', // Not needed
72
					'comment_content' => !empty($args['data']) ? esc_html( $args['data'] ) : '',
73
					'comment_type' => esc_attr( $args['type'] ),
74
					'user_id' => intval( $args['user_id'] ),
75
					'comment_approved' => !empty($args['status']) ? esc_html( $args['status'] ) : 'log', // 'log' == 'sensei_user_answer'
76
				);
77
		// Allow extra data
78
		if ( !empty($args['username']) ) {
79
			$data['comment_author'] = sanitize_user( $args['username'] );
80
		}
81
		if ( !empty($args['user_email']) ) {
82
			$data['comment_author_email'] = sanitize_email( $args['user_email'] );
83
		}
84
		if ( !empty($args['user_url']) ) {
85
			$data['comment_author_url'] = esc_url( $args['user_url'] );
86
		}
87
		if ( !empty($args['parent']) ) {
88
			$data['comment_parent'] = $args['parent'];
89
		}
90
		// Sanity check
91
		if ( empty($args['user_id']) ) {
92
			_deprecated_argument( __FUNCTION__, '1.0', __('At no point should user_id be equal to 0.', 'woothemes-sensei') );
93
			return false;
94
		}
95
96
		do_action( 'sensei_log_activity_before', $args, $data );
97
98
		$flush_cache = false;
99
100
		// Custom Logic
101
		// Check if comment exists first
102
		$comment_id = $wpdb->get_var( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d AND user_id = %d AND comment_type = %s ", $args['post_id'], $args['user_id'], $args['type'] ) );
103
		if ( ! $comment_id ) {
104
			// Add the comment
105
			$comment_id = wp_insert_comment( $data );
106
107
			$flush_cache = true;
108
		} elseif ( isset( $args['action'] ) && 'update' == $args['action'] ) {
109
			// Update the comment if an update was requested
110
			$data['comment_ID'] = $comment_id;
111
			// By default update the timestamp of the comment
112
			if ( empty($args['keep_time']) ) {
113
				$data['comment_date'] = current_time('mysql');
114
			}
115
			wp_update_comment( $data );
116
			$flush_cache = true;
117
		} // End If Statement
118
119
		// Manually Flush the Cache
120
		if ( $flush_cache ) {
121
			wp_cache_flush();
122
		}
123
124
		do_action( 'sensei_log_activity_after', $args, $data,  $comment_id );
125
126
		if ( 0 < $comment_id ) {
127
			// Return the ID so that it can be used for meta data storage
128
			return $comment_id;
129
		} else {
130
			return false;
131
		} // End If Statement
132
	} // End sensei_log_activity()
133
134
135
	/**
136
	 * Check for Sensei activity.
137
	 * @access public
138
	 * @since  1.0.0
139
	 * @param  array $args (default: array())
140
	 * @param  bool $return_comments (default: false)
141
	 * @return mixed | int
142
	 */
143
	public static function sensei_check_for_activity ( $args = array(), $return_comments = false ) {
144
145
		global  $wp_version;
146
		if ( !$return_comments ) {
147
			$args['count'] = true;
148
		}
149
150
		// Are we only retrieving a single entry, or not care about the order...
151
		if ( isset( $args['count'] ) || isset( $args['post_id'] ) ){
152
153
			// ...then we don't need to ask the db to order the results, this overrides WP default behaviour
154
			if ( version_compare( $wp_version, '4.1', '>=' ) ) {
155
				$args['order'] = false;
156
				$args['orderby'] = false;
157
			}
158
		}
159
160
		// A user ID of 0 is in valid, so shortcut this
161
		if ( isset( $args['user_id'] ) && 0 == intval ( $args['user_id'] ) ) {
162
			_deprecated_argument( __FUNCTION__, '1.0', __('At no point should user_id be equal to 0.', 'woothemes-sensei') );
163
			return false;
164
		}
165
		// Check for legacy code
166
		if ( isset($args['type']) && in_array($args['type'], array('sensei_course_start', 'sensei_course_end', 'sensei_lesson_start', 'sensei_lesson_end', 'sensei_quiz_asked', 'sensei_user_grade', 'sensei_quiz_grade', 'sense_answer_notes') ) ) {
167
			_deprecated_argument( __FUNCTION__, '1.7', sprintf( __('Sensei activity type %s is no longer used.', 'woothemes-sensei'), $args['type'] ) );
168
			return false;
169
		}
170
		// Are we checking for specific comment_approved statuses?
171
		if ( isset($args['status']) ) {
172
			// Temporarily store as a custom status if requesting an array...
173 View Code Duplication
			if ( is_array( $args['status'] ) && version_compare($wp_version, '4.1', '<') ) {
174
				// Encode now, decode later
175
				$args['status'] = implode( ",", $args['status'] );
176
				// ...use a filter to switch the encoding back
177
				add_filter( 'comments_clauses', array( __CLASS__, 'comment_multiple_status_filter' ) );
178
			}
179
		}
180
		else {
181
			$args['status'] = 'any'; // 'log' == 'sensei_user_answer'
182
		}
183
184
		// Take into account WP < 4.1 will automatically add ' comment_approved = 1 OR comment_approved = 0 '
185 View Code Duplication
		if ( ( is_array( $args['status'] ) || 'any' == $args['status'] ) && version_compare($wp_version, '4.1', '<') ) {
186
			add_filter( 'comments_clauses', array( __CLASS__, 'comment_any_status_filter' ) );
187
		}
188
189
        //Get the comments
190
        /**
191
         * This filter runs inside Sensei_Utils::sensei_check_for_activity
192
         *
193
         * It runs while getting the comments for the given request.
194
         *
195
         * @param int|array $comments
196
         */
197
        $comments = apply_filters('sensei_check_for_activity', get_comments( $args ) );
198
199
		remove_filter( 'comments_clauses', array( __CLASS__, 'comment_multiple_status_filter' ) );
200
		remove_filter( 'comments_clauses', array( __CLASS__, 'comment_any_status_filter' ) );
201
		// Return comments
202
		if ( $return_comments ) {
203
			// Could check for array of 1 and just return the 1 item?
204
			if ( is_array($comments) && 1 == count($comments) ) {
205
				$comments = array_shift($comments);
206
			}
207
208
			return $comments;
209
		} // End If Statement
210
		// Count comments
211
		return intval($comments); // This is the count, check the return from WP_Comment_Query
212
	} // End sensei_check_for_activity()
213
214
215
	/**
216
	 * Get IDs of Sensei activity items.
217
	 * @access public
218
	 * @since  1.0.0
219
	 * @param  array $args (default: array())
220
	 * @return array
221
	 */
222
	public static function sensei_activity_ids ( $args = array() ) {
223
224
225
		$comments = Sensei_Utils::sensei_check_for_activity( $args, true );
226
		// Need to always use an array, even with only 1 item
227
		if ( !is_array($comments) ) {
228
			$comments = array( $comments );
229
		}
230
231
		$post_ids = array();
232
		// Count comments
233
		if ( is_array( $comments ) && ( 0 < intval( count( $comments ) ) ) ) {
234
			foreach ( $comments as $key => $value  ) {
235
				// Add matches to id array
236
				if ( isset( $args['field'] ) && 'comment' == $args['field'] ) {
237
					array_push( $post_ids, $value->comment_ID );
238
				} elseif( isset( $args['field'] ) && 'user_id' == $args['field'] ) {
239
					array_push( $post_ids, $value->user_id );
240
				} else {
241
					array_push( $post_ids, $value->comment_post_ID );
242
				} // End If Statement
243
			} // End For Loop
244
			// Reset array indexes
245
			$post_ids = array_unique( $post_ids );
246
			$post_ids = array_values( $post_ids );
247
		} // End If Statement
248
249
		return $post_ids;
250
	} // End sensei_activity_ids()
251
252
253
	/**
254
	 * Delete Sensei activities.
255
	 * @access public
256
	 * @since  1.0.0
257
	 * @param  array $args (default: array())
258
	 * @return boolean
259
	 */
260
	public static function sensei_delete_activities ( $args = array() ) {
261
262
		$dataset_changes = false;
263
264
		// If activity exists remove activity from log
265
		$comments = Sensei_Utils::sensei_check_for_activity( array( 'post_id' => intval( $args['post_id'] ), 'user_id' => intval( $args['user_id'] ), 'type' => esc_attr( $args['type'] ) ), true );
266
		if( $comments ) {
267
			// Need to always return an array, even with only 1 item
268
			if ( !is_array( $comments ) ) {
269
				$comments = array( $comments );
270
			}
271
			foreach ( $comments as $key => $value  ) {
272
				if ( isset( $value->comment_ID ) && 0 < $value->comment_ID ) {
273
					$dataset_changes = wp_delete_comment( intval( $value->comment_ID ), true );
274
				} // End If Statement
275
			} // End For Loop
276
			// Manually flush the cache
277
			wp_cache_flush();
278
		} // End If Statement
279
		return $dataset_changes;
280
	} // End sensei_delete_activities()
281
282
    /**
283
     * Delete all activity for specified user
284
     * @access public
285
	 * @since  1.5.0
286
     * @param  integer $user_id User ID
287
     * @return boolean
288
     */
289
    public static function delete_all_user_activity( $user_id = 0 ) {
290
291
    	$dataset_changes = false;
292
293
    	if( $user_id ) {
294
295
			$activities = Sensei_Utils::sensei_check_for_activity( array( 'user_id' => $user_id ), true );
296
297
			if( $activities ) {
298
299
				// Need to always return an array, even with only 1 item
300
				if ( ! is_array( $activities ) ) {
301
					$activities = array( $activities );
302
				}
303
304
				foreach( $activities as $activity ) {
305
					if( '' == $activity->comment_type ) continue;
306
					if( strpos( 'sensei_', $activity->comment_type ) != 0 ) continue;
307
					$dataset_changes = wp_delete_comment( intval( $activity->comment_ID ), true );
308
					wp_cache_flush();
309
				}
310
			}
311
		}
312
313
		return $dataset_changes;
314
	} // Edn delete_all_user_activity()
315
316
317
	/**
318
	 * Get value for a specified activity.
319
	 * @access public
320
	 * @since  1.0.0
321
	 * @param  array $args (default: array())
322
	 * @return string
323
	 */
324
	public static function sensei_get_activity_value ( $args = array() ) {
325
326
		$activity_value = false;
327
		if ( !empty($args['field']) ) {
328
			$comment = Sensei_Utils::sensei_check_for_activity( $args, true );
329
330
			if ( isset( $comment->{$args['field']} ) && '' != $comment->{$args['field']} ) {
331
				$activity_value = $comment->{$args['field']};
332
			} // End If Statement
333
		}
334
		return $activity_value;
335
	} // End sensei_get_activity_value()
336
337
    /**
338
     * Checks if a user (by email) has bought an item.
339
     *
340
     * @deprecated since 1.9.0 use Sensei_WC::has_customer_bought_product($user_id, $product_id)
341
     * @access public
342
     * @since  1.0.0
343
     * @param  string $customer_email
344
     * @param  int $user_id
345
     * @param  int $product_id
346
     * @return bool
347
     */
348
    public static function sensei_customer_bought_product ( $customer_email, $user_id, $product_id ) {
349
350
        $emails = array();
351
352
        if ( $user_id ) {
353
            $user = get_user_by( 'id', intval( $user_id ) );
354
            $emails[] = $user->user_email;
355
        }
356
357
        if ( is_email( $customer_email ) )
358
            $emails[] = $customer_email;
359
360
        if ( sizeof( $emails ) == 0 )
361
            return false;
362
363
        return Sensei_WC::has_customer_bought_product( $user_id, $product_id );
364
365
    } // End sensei_customer_bought_product()
366
367
	/**
368
	 * Load the WordPress rich text editor
369
	 * @param  string $content    Initial content for editor
370
	 * @param  string $editor_id  ID of editor (only lower case characters - no spaces, underscores, hyphens, etc.)
371
	 * @param  string $input_name Name for text area form element
372
	 * @return void
373
	 */
374
	public static function sensei_text_editor( $content = '', $editor_id = 'senseitexteditor', $input_name = '' ) {
375
376
		if( ! $input_name ) $input_name = $editor_id;
377
378
		$buttons = 'bold,italic,underline,strikethrough,blockquote,bullist,numlist,justifyleft,justifycenter,justifyright,undo,redo,pastetext';
379
380
		$settings = array(
381
			'media_buttons' => false,
382
			'wpautop' => true,
383
			'textarea_name' => $input_name,
384
			'editor_class' => 'sensei_text_editor',
385
			'teeny' => false,
386
			'dfw' => false,
387
			'tinymce' => array(
388
				'theme_advanced_buttons1' => $buttons,
389
				'theme_advanced_buttons2' => ''
390
			),
391
			'quicktags' => false
392
		);
393
394
		wp_editor( $content, $editor_id, $settings );
395
396
	} // End sensei_text_editor()
397
398
	/**
399
	 * Save quiz answers submitted by users
400
	 * @param  array $submitted User's quiz answers
401
     * @param int $user_id
402
	 * @return boolean            Whether the answers were saved or not
403
	 */
404
	public static function sensei_save_quiz_answers( $submitted = array(), $user_id = 0 ) {
405
406
		if( intval( $user_id ) == 0 ) {
407
			$user_id = get_current_user_id();
408
		}
409
410
		$answers_saved = false;
411
412
		if( $submitted && intval( $user_id ) > 0 ) {
413
414
			foreach( $submitted as $question_id => $answer ) {
415
416
				// Get question type
417
				$question_type = Sensei()->question->get_question_type( $question_id );
418
419
				// Sanitise answer
420
				if( 0 == get_magic_quotes_gpc() ) {
421
					$answer = wp_unslash( $answer );
422
				}
423
				switch( $question_type ) {
424
					case 'multi-line': $answer = nl2br( $answer ); break;
425
					case 'single-line': break;
426
					case 'gap-fill': break;
427
					default: $answer = maybe_serialize( $answer ); break;
428
				}
429
				$args = array(
430
							'post_id' => $question_id,
431
							'data' => base64_encode( $answer ),
432
							'type' => 'sensei_user_answer', /* FIELD SIZE 20 */
433
							'user_id' => $user_id,
434
							'action' => 'update'
435
						);
436
				$answers_saved = Sensei_Utils::sensei_log_activity( $args );
437
			}
438
439
			// Handle file upload questions
440
			if( isset( $_FILES ) ) {
441
				foreach( $_FILES as $field => $file ) {
442
					if( strpos( $field, 'file_upload_' ) !== false ) {
443
						$question_id = str_replace( 'file_upload_', '', $field );
444
						if( $file && $question_id ) {
445
							$attachment_id = self::upload_file( $file );
446
							if( $attachment_id ) {
447
								$args = array(
448
									'post_id' => $question_id,
449
									'data' => base64_encode( $attachment_id ),
450
									'type' => 'sensei_user_answer', /* FIELD SIZE 20 */
451
									'user_id' => $user_id,
452
									'action' => 'update'
453
								);
454
								$answers_saved = Sensei_Utils::sensei_log_activity( $args );
455
							}
456
						}
457
					}
458
				}
459
			}
460
		}
461
462
		return $answers_saved;
463
464
	} // End sensei_save_quiz_answers()
465
466
	public static function upload_file( $file = array() ) {
467
468
		require_once( ABSPATH . 'wp-admin/includes/admin.php' );
469
470
        /**
471
         * Filter the data array for the Sensei wp_handle_upload function call
472
         *
473
         * This filter was mainly added for Unit Testing purposes.
474
         *
475
         * @since 1.7.4
476
         *
477
         * @param array  $file_upload_args {
478
         *      array of current values
479
         *
480
         *     @type string test_form set to false by default
481
         * }
482
         */
483
        $file_upload_args = apply_filters( 'sensei_file_upload_args', array('test_form' => false ) );
484
485
        $file_return = wp_handle_upload( $file, $file_upload_args );
486
487
        if( isset( $file_return['error'] ) || isset( $file_return['upload_error_handler'] ) ) {
488
            return false;
489
        } else {
490
491
            $filename = $file_return['file'];
492
493
            $attachment = array(
494
                'post_mime_type' => $file_return['type'],
495
                'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $filename ) ),
496
                'post_content' => '',
497
                'post_status' => 'inherit',
498
                'guid' => $file_return['url']
499
            );
500
501
            $attachment_id = wp_insert_attachment( $attachment, $filename );
502
503
            require_once(ABSPATH . 'wp-admin/includes/image.php');
504
            $attachment_data = wp_generate_attachment_metadata( $attachment_id, $filename );
505
            wp_update_attachment_metadata( $attachment_id, $attachment_data );
506
507
            if( 0 < intval( $attachment_id ) ) {
508
            	return $attachment_id;
509
            }
510
        }
511
512
        return false;
513
	}
514
515
	/**
516
	 * Grade quiz automatically
517
     *
518
     * This function grades each question automatically if the are auto gradable.
519
     * It store all question grades.
520
     *
521
     * @deprecated since 1.7.4 use WooThemes_Sensei_Grading::grade_quiz_auto instead
522
     *
523
	 * @param  integer $quiz_id         ID of quiz
524
	 * @param  array $submitted questions id ans answers {
525
     *          @type int $question_id
526
     *          @type mixed $answer
527
     * }
528
	 * @param  integer $total_questions Total questions in quiz (not used)
529
     * @param string $quiz_grade_type Optional defaults to auto
530
     *
531
	 * @return int $quiz_grade total sum of all question grades
532
	 */
533
	public static function sensei_grade_quiz_auto( $quiz_id = 0, $submitted = array(), $total_questions = 0, $quiz_grade_type = 'auto' ) {
534
535
        return Sensei_Grading::grade_quiz_auto( $quiz_id, $submitted, $total_questions, $quiz_grade_type );
536
537
	} // End sensei_grade_quiz_auto()
538
539
	/**
540
	 * Grade quiz
541
	 * @param  integer $quiz_id ID of quiz
542
	 * @param  integer $grade   Grade received
543
	 * @param  integer $user_id ID of user being graded
544
     * @param  string $quiz_grade_type default 'auto'
545
	 * @return boolean
546
	 */
547
	public static function sensei_grade_quiz( $quiz_id = 0, $grade = 0, $user_id = 0, $quiz_grade_type = 'auto' ) {
548
		if( intval( $user_id ) == 0 ) {
549
			$user_id = get_current_user_id();
550
		}
551
552
		$activity_logged = false;
553
		if( intval( $quiz_id ) > 0 && intval( $user_id ) > 0 ) {
554
			$lesson_id = get_post_meta( $quiz_id, '_quiz_lesson', true );
555
			$user_lesson_status = Sensei_Utils::user_lesson_status( $lesson_id, $user_id );
556
			$activity_logged = update_comment_meta( $user_lesson_status->comment_ID, 'grade', $grade );
557
558
			$quiz_passmark = absint( get_post_meta( $quiz_id, '_quiz_passmark', true ) );
559
560
			do_action( 'sensei_user_quiz_grade', $user_id, $quiz_id, $grade, $quiz_passmark, $quiz_grade_type );
561
		}
562
563
		return $activity_logged;
564
	}
565
566
	/**
567
	 * Grade question automatically
568
     *
569
     * This function checks the question typ and then grades it accordingly.
570
     *
571
     * @deprecated since 1.7.4 use WooThemes_Sensei_Grading::grade_question_auto instead
572
     *
573
	 * @param integer $question_id
574
     * @param string $question_type of the standard Sensei question types
575
	 * @param string $answer
576
     * @param int $user_id
577
     *
578
	 * @return int $question_grade
579
	 */
580
	public static function sensei_grade_question_auto( $question_id = 0, $question_type = '', $answer = '', $user_id = 0 ) {
581
582
       return  WooThemes_Sensei_Grading::grade_question_auto( $question_id, $question_type, $answer, $user_id  );
583
584
	} // end sensei_grade_question_auto
585
586
	/**
587
	 * Grade question
588
	 * @param  integer $question_id ID of question
589
	 * @param  integer $grade       Grade received
590
     * @param int $user_id
591
	 * @return boolean
592
	 */
593
	public static function sensei_grade_question( $question_id = 0, $grade = 0, $user_id = 0 ) {
594
		if( intval( $user_id ) == 0 ) {
595
			$user_id = get_current_user_id();
596
		}
597
598
		$activity_logged = false;
599
		if( intval( $question_id ) > 0 && intval( $user_id ) > 0 ) {
600
601
			$user_answer_id = Sensei_Utils::sensei_get_activity_value( array( 'post_id' => $question_id, 'user_id' => $user_id, 'type' => 'sensei_user_answer', 'field' => 'comment_ID' ) );
602
			$activity_logged = update_comment_meta( $user_answer_id, 'user_grade', $grade );
603
604
			$answer_notes = get_post_meta( $question_id, '_answer_feedback', true );
605
			if ( !empty($answer_notes) ) {
606
				update_comment_meta( $user_answer_id, 'answer_note', base64_encode( $answer_notes ) );
607
			}
608
609
		}
610
611
		return $activity_logged;
612
	}
613
614
	public static function sensei_delete_question_grade( $question_id = 0, $user_id = 0 ) {
615
		if( intval( $user_id ) == 0 ) {
616
			$user_id = get_current_user_id();
617
		}
618
619
		$activity_logged = false;
620
		if( intval( $question_id ) > 0 ) {
621
			$user_answer_id = Sensei_Utils::sensei_get_activity_value( array( 'post_id' => $question_id, 'user_id' => $user_id, 'type' => 'sensei_user_answer', 'field' => 'comment_ID' ) );
622
			$activity_logged = delete_comment_meta( $user_answer_id, 'user_grade' );
623
		}
624
625
		return $activity_logged;
626
	}
627
628
629
    /**
630
     * Alias to Woothemes_Sensei_Utils::sensei_start_lesson
631
     *
632
     * @since 1.7.4
633
     *
634
     * @param integer $user_id
635
     * @param integer $lesson_id
636
     * @param bool $complete
637
     *
638
     * @return mixed boolean or comment_ID
639
     */
640
    public static function user_start_lesson(  $user_id = 0, $lesson_id = 0, $complete = false ) {
641
642
        return self::sensei_start_lesson( $lesson_id, $user_id, $complete );
643
644
    }// end user_start_lesson()
645
646
	/**
647
	 * Mark a lesson as started for user
648
     *
649
     * Will also start the lesson course for the user if the user hans't started taking it already.
650
     *
651
     * @since 1.6.0
652
     *
653
	 * @param  integer $lesson_id ID of lesson
654
	 * @param int| string $user_id default 0
655
     * @param bool $complete default false
656
     *
657
     * @return mixed boolean or comment_ID
658
	 */
659
	public static function sensei_start_lesson( $lesson_id = 0, $user_id = 0, $complete = false ) {
660
661
662
		if( intval( $user_id ) == 0 ) {
663
			$user_id = get_current_user_id();
664
		}
665
666
		$activity_logged = false;
667
668
		if( intval( $lesson_id ) > 0 ) {
669
670
			$course_id = get_post_meta( $lesson_id, '_lesson_course', true );
671
			if( $course_id ) {
672
				$is_user_taking_course = Sensei_Utils::user_started_course( $course_id, $user_id );
673
				if( ! $is_user_taking_course ) {
674
					Sensei_Utils::user_start_course( $user_id, $course_id );
675
				}
676
			}
677
678
			$metadata = array();
679
			$status = 'in-progress';
680
681
			// Note: When this action runs the lesson status may not yet exist
682
			do_action( 'sensei_user_lesson_start', $user_id, $lesson_id );
683
684
			if( $complete ) {
685
686
				$has_questions = get_post_meta( $lesson_id, '_quiz_has_questions', true );
687
				if ( $has_questions ) {
688
					$status = 'passed'; // Force a pass
689
					$metadata['grade'] = 0;
690
				}
691
				else {
692
					$status = 'complete';
693
				}
694
			}
695
696
			// Check if user is already taking the lesson
697
			$activity_logged = Sensei_Utils::user_started_lesson( $lesson_id, $user_id );
698
			if( ! $activity_logged ) {
699
700
				$metadata['start'] = current_time('mysql');
701
				$activity_logged = Sensei_Utils::update_lesson_status( $user_id, $lesson_id, $status, $metadata );
702
703
            } else {
704
705
                // if users is already taking the lesson  and the status changes to complete update it
706
                $current_user_activity = get_comment($activity_logged);
707
                if( $status=='complete' &&
708
                    $status != $current_user_activity->comment_approved  ){
709
710
                    $comment = array();
711
                    $comment['comment_ID'] = $activity_logged;
712
                    $comment['comment_approved'] = $status;
713
                    wp_update_comment( $comment );
714
715
                }
716
717
            }
718
719
			if ( $complete ) {
720
				// Run this *after* the lesson status has been created/updated
721
				do_action( 'sensei_user_lesson_end', $user_id, $lesson_id );
722
			}
723
724
		}
725
726
		return $activity_logged;
727
	}
728
729
	/**
730
	 * Remove user from lesson, deleting all data from the corresponding quiz
731
	 *
732
	 * @param int $lesson_id
733
	 * @param int $user_id
734
	 * @return boolean
735
	 */
736
	public static function sensei_remove_user_from_lesson( $lesson_id = 0, $user_id = 0, $from_course = false ) {
737
738
		if( ! $lesson_id ) return false;
739
740
		if( intval( $user_id ) == 0 ) {
741
			$user_id = get_current_user_id();
742
		}
743
744
		// Process quiz
745
		$lesson_quiz_id = Sensei()->lesson->lesson_quizzes( $lesson_id );
746
747
		// Delete quiz answers, this auto deletes the corresponding meta data, such as the question/answer grade
748
		Sensei_Utils::sensei_delete_quiz_answers( $lesson_quiz_id, $user_id );
749
750
751
		// Delete quiz saved answers
752
		Sensei()->quiz->reset_user_lesson_data( $lesson_id, $user_id );
753
754
		// Delete lesson status
755
		$args = array(
756
			'post_id' => $lesson_id,
757
			'type' => 'sensei_lesson_status',
758
			'user_id' => $user_id,
759
		);
760
		// This auto deletes the corresponding meta data, such as the quiz grade, and questions asked
761
		Sensei_Utils::sensei_delete_activities( $args );
762
763
		if( ! $from_course ) {
764
			do_action( 'sensei_user_lesson_reset', $user_id, $lesson_id );
765
		}
766
767
		return true;
768
	}
769
770
	/**
771
	 * Remove a user from a course, deleting all activities across all lessons
772
	 *
773
	 * @param int $course_id
774
	 * @param int $user_id
775
	 * @return boolean
776
	 */
777
	public static function sensei_remove_user_from_course( $course_id = 0, $user_id = 0 ) {
778
779
780
		if( ! $course_id ) return false;
781
782
		if( intval( $user_id ) == 0 ) {
783
			$user_id = get_current_user_id();
784
		}
785
786
		$lesson_ids = Sensei()->course->course_lessons( $course_id, 'any', 'ids' );
787
788
		foreach( $lesson_ids as $lesson_id ) {
789
			Sensei_Utils::sensei_remove_user_from_lesson( $lesson_id, $user_id, true );
790
		}
791
792
		// Delete course status
793
		$args = array(
794
			'post_id' => $course_id,
795
			'type' => 'sensei_course_status',
796
			'user_id' => $user_id,
797
		);
798
799
		Sensei_Utils::sensei_delete_activities( $args );
800
801
		do_action( 'sensei_user_course_reset', $user_id, $course_id );
802
803
		return true;
804
	}
805
806
	public static function sensei_get_quiz_questions( $quiz_id = 0 ) {
807
808
809
		$questions = array();
810
811
		if( intval( $quiz_id ) > 0 ) {
812
			$questions = Sensei()->lesson->lesson_quiz_questions( $quiz_id );
813
			$questions = Sensei_Utils::array_sort_reorder( $questions );
814
		}
815
816
		return $questions;
817
	}
818
819
	public static function sensei_get_quiz_total( $quiz_id = 0 ) {
820
821
822
		$quiz_total = 0;
823
824
		if( $quiz_id > 0 ) {
825
			$questions = Sensei_Utils::sensei_get_quiz_questions( $quiz_id );
826
			$question_grade = 0;
827
			foreach( $questions as $question ) {
828
				$question_grade = Sensei()->question->get_question_grade( $question->ID );
829
				$quiz_total += $question_grade;
830
			}
831
		}
832
833
		return $quiz_total;
834
	}
835
836
	/**
837
	 * Returns the user_grade for a specific question and user, or sensei_user_answer entry
838
	 *
839
	 * @param mixed $question
840
	 * @param int $user_id
841
	 * @return string
842
	 */
843 View Code Duplication
	public static function sensei_get_user_question_grade( $question = 0, $user_id = 0 ) {
844
		$question_grade = false;
845
		if( $question ) {
846
			if ( is_object( $question ) ) {
847
				$user_answer_id = $question->comment_ID;
848
			}
849
			else {
850
				if( intval( $user_id ) == 0 ) {
851
					$user_id = get_current_user_id();
852
				}
853
				$user_answer_id = Sensei_Utils::sensei_get_activity_value( array( 'post_id' => intval($question), 'user_id' => $user_id, 'type' => 'sensei_user_answer', 'field' => 'comment_ID' ) );
854
			}
855
			if ( $user_answer_id ) {
856
				$question_grade = get_comment_meta( $user_answer_id, 'user_grade', true );
857
			}
858
		}
859
860
		return $question_grade;
861
	}
862
863
	/**
864
	 * Returns the answer_notes for a specific question and user, or sensei_user_answer entry
865
	 *
866
     * @deprecated since 1.7.5 use Sensei()->quiz->get_user_question_feedback instead
867
	 * @param mixed $question
868
	 * @param int $user_id
869
	 * @return string
870
	 */
871 View Code Duplication
	public static function sensei_get_user_question_answer_notes( $question = 0, $user_id = 0 ) {
872
		$answer_notes = false;
873
		if( $question ) {
874
			if ( is_object( $question ) ) {
875
				$user_answer_id = $question->comment_ID;
876
			}
877
			else {
878
				if( intval( $user_id ) == 0 ) {
879
					$user_id = get_current_user_id();
880
				}
881
				$user_answer_id = Sensei_Utils::sensei_get_activity_value( array( 'post_id' => intval($question), 'user_id' => $user_id, 'type' => 'sensei_user_answer', 'field' => 'comment_ID' ) );
882
			}
883
			if ( $user_answer_id ) {
884
				$answer_notes = base64_decode( get_comment_meta( $user_answer_id, 'answer_note', true ) );
885
			}
886
		}
887
888
		return $answer_notes;
889
	}
890
891
	public static function sensei_delete_quiz_answers( $quiz_id = 0, $user_id = 0 ) {
892
		if( intval( $user_id ) == 0 ) {
893
			$user_id = get_current_user_id();
894
		}
895
896
		$delete_answers = false;
897
		if( intval( $quiz_id ) > 0 ) {
898
			$questions = Sensei_Utils::sensei_get_quiz_questions( $quiz_id );
899
			foreach( $questions as $question ) {
900
				$delete_answers = Sensei_Utils::sensei_delete_activities( array( 'post_id' => $question->ID, 'user_id' => $user_id, 'type' => 'sensei_user_answer' ) );
901
			}
902
		}
903
904
		return $delete_answers;
905
	}
906
907
	public static function sensei_delete_quiz_grade( $quiz_id = 0, $user_id = 0 ) {
908
		if( intval( $user_id ) == 0 ) {
909
			$user_id = get_current_user_id();
910
		}
911
912
		$delete_grade = false;
913
		if( intval( $quiz_id ) > 0 ) {
914
			$lesson_id = get_post_meta( $quiz_id, '_quiz_lesson', true );
915
			$user_lesson_status = Sensei_Utils::user_lesson_status( $lesson_id, $user_id );
916
			$delete_grade = delete_comment_meta( $user_lesson_status->comment_ID, 'grade' );
917
		}
918
919
		return $delete_grade;
920
	}
921
922
	/**
923
	 * Add answer notes to question
924
	 * @param  integer $question_id ID of question
925
	 * @param  integer $user_id     ID of user
926
     * @param string $notes
927
	 * @return boolean
928
	 */
929
	public static function sensei_add_answer_notes( $question_id = 0, $user_id = 0, $notes = '' ) {
930
		if( intval( $user_id ) == 0 ) {
931
			$user_id = get_current_user_id();
932
		}
933
934
		$activity_logged = false;
935
936
		if( intval( $question_id ) > 0 ) {
937
			$notes = base64_encode( $notes );
938
939
			// Don't store empty values, no point
940
			if ( !empty($notes) ) {
941
				$user_lesson_id = Sensei_Utils::sensei_get_activity_value( array( 'post_id' => $question_id, 'user_id' => $user_id, 'type' => 'sensei_user_answer', 'field' => 'comment_ID' ) );
942
				$activity_logged = update_comment_meta( $user_lesson_id, 'answer_note', $notes );
943
			}
944
			else {
945
				$activity_logged = true;
946
			}
947
		}
948
949
		return $activity_logged;
950
	}
951
952
	/**
953
	 * array_sort_reorder handle sorting of table data
954
	 * @since  1.3.0
955
	 * @param  array $return_array data to be ordered
956
	 * @return array $return_array ordered data
957
	 */
958
	public static function array_sort_reorder( $return_array ) {
959
		if ( isset( $_GET['orderby'] ) && '' != esc_html( $_GET['orderby'] ) ) {
960
			$sort_key = '';
961
			// if ( array_key_exists( esc_html( $_GET['orderby'] ), $this->sortable_columns ) ) {
962
			// 	$sort_key = esc_html( $_GET['orderby'] );
963
			// } // End If Statement
964
			if ( '' != $sort_key ) {
965
					Sensei_Utils::sort_array_by_key($return_array,$sort_key);
966
				if ( isset( $_GET['order'] ) && 'desc' == esc_html( $_GET['order'] ) ) {
967
					$return_array = array_reverse( $return_array, true );
968
				} // End If Statement
969
			} // End If Statement
970
			return $return_array;
971
		} else {
972
			return $return_array;
973
		} // End If Statement
974
	} // End array_sort_reorder()
975
976
	/**
977
	 * sort_array_by_key sorts array by key
978
	 * @since  1.3.0
979
	 * @param  array $array by ref
980
	 * @param  $key string column name in array
981
	 * @return void
982
	 */
983
	public static function sort_array_by_key( $array, $key ) {
984
	    $sorter = array();
985
	    $ret = array();
986
	    reset( $array );
987
	    foreach ( $array as $ii => $va ) {
988
	        $sorter[$ii] = $va[$key];
989
	    } // End For Loop
990
	    asort( $sorter );
991
	    foreach ( $sorter as $ii => $va ) {
992
	        $ret[$ii] = $array[$ii];
993
	    } // End For Loop
994
	    $array = $ret;
995
	} // End sort_array_by_key()
996
997
	/**
998
	 * This function returns an array of lesson quiz questions
999
	 * @since  1.3.2
1000
	 * @param  integer $quiz_id
1001
	 * @return array of quiz questions
1002
	 */
1003 View Code Duplication
	public static function lesson_quiz_questions( $quiz_id = 0 ) {
1004
		$questions_array = array();
1005
		if ( 0 < $quiz_id ) {
1006
			$question_args = array( 'post_type'         => 'question',
1007
                                    'posts_per_page'       => -1,
1008
                                    'orderby'           => 'ID',
1009
                                    'order'             => 'ASC',
1010
                                    'meta_query'		=> array(
1011
										array(
1012
											'key'       => '_quiz_id',
1013
											'value'     => $quiz_id,
1014
										)
1015
									),
1016
                                    'post_status'       => 'any',
1017
                                    'suppress_filters'  => 0
1018
                                );
1019
            $questions_array = get_posts( $question_args );
1020
        } // End If Statement
1021
        return $questions_array;
1022
	} // End lesson_quiz_questions()
1023
1024
	/**
1025
	 * Get pass mark for course
1026
	 * @param  integer $course_id ID of course
1027
	 * @return integer            Pass mark for course
1028
	 */
1029
	public static function sensei_course_pass_grade( $course_id = 0 ) {
1030
1031
1032
		$course_passmark = 0;
1033
1034
		if( $course_id > 0 ) {
1035
			$lessons = Sensei()->course->course_lessons( $course_id );
1036
			$lesson_count = 0;
1037
			$total_passmark = 0;
1038
			foreach( $lessons as $lesson ) {
1039
1040
				// Get Quiz ID
1041
				$quiz_id = Sensei()->lesson->lesson_quizzes( $lesson->ID );
1042
1043
				// Check for a pass being required
1044
				$pass_required = get_post_meta( $quiz_id, '_pass_required', true );
1045
				if ( $pass_required ) {
1046
					// Get quiz passmark
1047
					$quiz_passmark = absint( get_post_meta( $quiz_id, '_quiz_passmark', true ) );
1048
1049
					// Add up total passmark
1050
					$total_passmark += $quiz_passmark;
1051
1052
					++$lesson_count;
1053
				}
1054
			}
1055
			// Might be a case of no required lessons
1056
			if ( $lesson_count ) {
1057
				$course_passmark = ( $total_passmark / $lesson_count );
1058
			}
1059
		}
1060
1061
		return Sensei_Utils::round( $course_passmark );
1062
	}
1063
1064
	/**
1065
	 * Get user total grade for course
1066
	 * @param  integer $course_id ID of course
1067
	 * @param  integer $user_id   ID of user
1068
	 * @return integer            User's total grade
1069
	 */
1070
	public static function sensei_course_user_grade( $course_id = 0, $user_id = 0 ) {
1071
1072
1073
		if( intval( $user_id ) == 0 ) {
1074
			$user_id = get_current_user_id();
1075
		}
1076
1077
		$total_grade = 0;
1078
1079
		if( $course_id > 0 && $user_id > 0 ) {
1080
			$lessons = Sensei()->course->course_lessons( $course_id );
1081
			$lesson_count = 0;
1082
			$total_grade = 0;
1083
			foreach( $lessons as $lesson ) {
1084
1085
				// Check for lesson having questions, thus a quiz, thus having a grade
1086
				$has_questions = get_post_meta( $lesson->ID, '_quiz_has_questions', true );
1087
				if ( $has_questions ) {
1088
					$user_lesson_status = Sensei_Utils::user_lesson_status( $lesson->ID, $user_id );
1089
1090
					if(  empty( $user_lesson_status ) ){
1091
						continue;
1092
					}
1093
					// Get user quiz grade
1094
					$quiz_grade = get_comment_meta( $user_lesson_status->comment_ID, 'grade', true );
1095
1096
					// Add up total grade
1097
					$total_grade += $quiz_grade;
1098
1099
					++$lesson_count;
1100
				}
1101
			}
1102
1103
			// Might be a case of no lessons with quizzes
1104
			if ( $lesson_count ) {
1105
				$total_grade = ( $total_grade / $lesson_count );
1106
			}
1107
1108
		}
1109
1110
		return Sensei_Utils::round( $total_grade );
1111
	}
1112
1113
	/**
1114
	 * Check if user has passed a course
1115
	 * @param  integer $course_id ID of course
1116
	 * @param  integer $user_id   ID of user
1117
	 * @return boolean
1118
	 */
1119
	public static function sensei_user_passed_course( $course_id = 0, $user_id = 0 ) {
1120
		if( intval( $user_id ) == 0 ) {
1121
			$user_id = get_current_user_id();
1122
		}
1123
1124
		$pass = false;
1125
1126
		if( $course_id > 0 && $user_id > 0 ) {
1127
			$passmark = Sensei_Utils::sensei_course_pass_grade( $course_id );
1128
			$user_grade = Sensei_Utils::sensei_course_user_grade( $course_id, $user_id );
1129
1130
			if( $user_grade >= $passmark ) {
1131
				$pass = true;
1132
			}
1133
		}
1134
1135
		return $pass; // Should add the $passmark and $user_grade as part of the return!
1136
1137
	}
1138
1139
	/**
1140
	 * Set the status message displayed to the user for a course
1141
	 * @param  integer $course_id ID of course
1142
	 * @param  integer $user_id   ID of user
1143
	 */
1144
	public static function sensei_user_course_status_message( $course_id = 0, $user_id = 0 ) {
1145
		if( intval( $user_id ) == 0 ) {
1146
			$user_id = get_current_user_id();
1147
		}
1148
1149
		$status = 'not_started';
1150
		$box_class = 'info';
1151
		$message = __( 'You have not started this course yet.', 'woothemes-sensei' );
1152
1153
		if( $course_id > 0 && $user_id > 0 ) {
1154
1155
			$started_course = Sensei_Utils::user_started_course( $course_id, $user_id );
1156
1157
			if( $started_course ) {
1158
				$passmark = Sensei_Utils::sensei_course_pass_grade( $course_id ); // This happens inside sensei_user_passed_course()!
1159
				$user_grade = Sensei_Utils::sensei_course_user_grade( $course_id, $user_id ); // This happens inside sensei_user_passed_course()!
1160
				if( $user_grade >= $passmark ) {
1161
					$status = 'passed';
1162
					$box_class = 'tick';
1163
					$message = sprintf( __( 'You have passed this course with a grade of %1$d%%.', 'woothemes-sensei' ), $user_grade );
1164
				} else {
1165
					$status = 'failed';
1166
					$box_class = 'alert';
1167
					$message = sprintf( __( 'You require %1$d%% to pass this course. Your grade is %2$s%%.', 'woothemes-sensei' ), $passmark, $user_grade );
1168
				}
1169
			}
1170
1171
		}
1172
1173
		$message = apply_filters( 'sensei_user_course_status_' . $status, $message );
1174
		Sensei()->notices->add_notice( $message, $box_class   );
1175
	}
1176
1177
	/**
1178
	 * Set the status message displayed to the user for a quiz
1179
	 * @param  integer $lesson_id ID of quiz lesson
1180
	 * @param  integer $user_id   ID of user
1181
     * @param  bool $is_lesson
1182
	 * @return array              Status code and message
1183
	 */
1184
	public static function sensei_user_quiz_status_message( $lesson_id = 0, $user_id = 0, $is_lesson = false ) {
1185
		global  $current_user;
1186
		if( intval( $user_id ) == 0 ) {
1187
			$user_id = $current_user->ID;
1188
		}
1189
1190
		$status = 'not_started';
1191
		$box_class = 'info';
1192
		$message = __( "You have not taken this lesson's quiz yet", 'woothemes-sensei' );
1193
		$extra = '';
1194
1195
		if( $lesson_id > 0 && $user_id > 0 ) {
1196
1197
			// Prerequisite lesson
1198
			$prerequisite = get_post_meta( $lesson_id, '_lesson_prerequisite', true );
1199
1200
			// Course ID
1201
			$course_id = absint( get_post_meta( $lesson_id, '_lesson_course', true ) );
1202
1203
			// Has user started course
1204
			$started_course = Sensei_Utils::user_started_course( $course_id, $user_id );
1205
1206
			// Has user completed lesson
1207
			$user_lesson_status = Sensei_Utils::user_lesson_status( $lesson_id, $user_id );
1208
			$lesson_complete = Sensei_Utils::user_completed_lesson( $user_lesson_status );
1209
1210
			// Quiz ID
1211
			$quiz_id = Sensei()->lesson->lesson_quizzes( $lesson_id );
1212
1213
			// Quiz grade
1214
			$quiz_grade = 0;
1215
			if ( $user_lesson_status ) {
1216
				$quiz_grade = get_comment_meta( $user_lesson_status->comment_ID, 'grade', true );
1217
			}
1218
1219
			// Quiz passmark
1220
			$quiz_passmark = absint( get_post_meta( $quiz_id, '_quiz_passmark', true ) );
1221
			$quiz_passmark_float = (float) $quiz_passmark;
1222
1223
			// Pass required
1224
			$pass_required = get_post_meta( $quiz_id, '_pass_required', true );
1225
1226
			// Quiz questions
1227
			$has_quiz_questions = get_post_meta( $lesson_id, '_quiz_has_questions', true );
1228
1229
			if ( ! $started_course ) {
1230
1231
				$status = 'not_started_course';
1232
				$box_class = 'info';
1233
				$message = sprintf( __( 'Please sign up for %1$sthe course%2$s before taking this quiz', 'woothemes-sensei' ), '<a href="' . esc_url( get_permalink( $course_id ) ) . '" title="' . esc_attr( __( 'Sign Up', 'woothemes-sensei' ) ) . '">', '</a>' );
1234
1235
			} elseif ( ! is_user_logged_in() ) {
1236
1237
				$status = 'login_required';
1238
				$box_class = 'info';
1239
				$message = __( 'You must be logged in to take this quiz', 'woothemes-sensei' );
1240
1241
			}
1242
			// Lesson/Quiz is marked as complete thus passing any quiz restrictions
1243
			elseif ( $lesson_complete ) {
1244
1245
				$status = 'passed';
1246
				$box_class = 'tick';
1247
				// Lesson status will be "complete" (has no Quiz)
1248
				if ( ! $has_quiz_questions ) {
1249
					$message = sprintf( __( 'Congratulations! You have passed this lesson.', 'woothemes-sensei' ) );
1250
				}
1251
				// Lesson status will be "graded" (no passmark required so might have failed all the questions)
1252
				elseif ( empty( $quiz_grade ) ) {
1253
					$message = sprintf( __( 'Congratulations! You have completed this lesson.', 'woothemes-sensei' ) );
1254
				}
1255
				// Lesson status will be "passed" (passmark reached)
1256
				elseif ( ! empty( $quiz_grade ) && abs( $quiz_grade ) >= 0 ) {
1257 View Code Duplication
					if( $is_lesson ) {
1258
						$message = sprintf( __( 'Congratulations! You have passed this lesson\'s quiz achieving %s%%', 'woothemes-sensei' ), Sensei_Utils::round( $quiz_grade ) );
1259
					} else {
1260
						$message = sprintf( __( 'Congratulations! You have passed this quiz achieving %s%%', 'woothemes-sensei' ),  Sensei_Utils::round( $quiz_grade ) );
1261
					}
1262
				}
1263
1264
                // add next lesson button
1265
                $nav_id_array = sensei_get_prev_next_lessons( $lesson_id );
1266
                $next_lesson_id = absint( $nav_id_array['next_lesson'] );
1267
1268
                // Output HTML
1269
                if ( ( 0 < $next_lesson_id ) ) {
1270
                    $message .= ' ' . '<a class="next-lesson" href="' . esc_url( get_permalink( $next_lesson_id ) )
1271
                                . '" rel="next"><span class="meta-nav"></span>'. __( 'Next Lesson' ,'woothemes-sensei')
1272
                                .'</a>';
1273
1274
                }
1275
1276
			}
1277
            // Lesson/Quiz not complete
1278
			else {
1279
				// Lesson/Quiz isn't "complete" instead it's ungraded (previously this "state" meant that it *was* complete)
1280
				if ( isset( $user_lesson_status->comment_approved ) && 'ungraded' == $user_lesson_status->comment_approved ) {
1281
					$status = 'complete';
1282
					$box_class = 'info';
1283
					if( $is_lesson ) {
1284
						$message = sprintf( __( 'You have completed this lesson\'s quiz and it will be graded soon. %1$sView the lesson quiz%2$s', 'woothemes-sensei' ), '<a href="' . esc_url( get_permalink( $quiz_id ) ) . '" title="' . esc_attr( get_the_title( $quiz_id ) ) . '">', '</a>' );
1285
					} else {
1286
						$message = sprintf( __( 'You have completed this quiz and it will be graded soon. You require %1$s%% to pass.', 'woothemes-sensei' ),  Sensei_Utils::round( $quiz_passmark ) );
1287
					}
1288
				}
1289
				// Lesson status must be "failed"
1290
				elseif ( isset( $user_lesson_status->comment_approved ) && 'failed' == $user_lesson_status->comment_approved ) {
1291
					$status = 'failed';
1292
					$box_class = 'alert';
1293
					if( $is_lesson ) {
1294
						$message = sprintf( __( 'You require %1$d%% to pass this lesson\'s quiz. Your grade is %2$s%%', 'woothemes-sensei' ),  Sensei_Utils::round( $quiz_passmark ),  Sensei_Utils::round( $quiz_grade ) );
1295
					} else {
1296
						$message = sprintf( __( 'You require %1$d%% to pass this quiz. Your grade is %2$s%%', 'woothemes-sensei' ),  Sensei_Utils::round( $quiz_passmark ),  Sensei_Utils::round( $quiz_grade ) );
1297
					}
1298
				}
1299
				// Lesson/Quiz requires a pass
1300
				elseif( $pass_required ) {
1301
					$status = 'not_started';
1302
					$box_class = 'info';
1303 View Code Duplication
					if( $is_lesson ) {
1304
						$message = sprintf( __( 'You require %1$d%% to pass this lesson\'s quiz.', 'woothemes-sensei' ),  Sensei_Utils::round( $quiz_passmark ) );
1305
					} else {
1306
						$message = sprintf( __( 'You require %1$d%% to pass this quiz.', 'woothemes-sensei' ),  Sensei_Utils::round( $quiz_passmark ) );
1307
					}
1308
				}
1309
			}
1310
1311
		}else{
1312
1313
			$course_id = Sensei()->lesson->get_course_id( $lesson_id );
1314
			$a_element = '<a href="' . esc_url( get_permalink( $course_id ) ) . '" title="' . __( 'Sign Up', 'woothemes-sensei' )  . '">';
1315
			$a_element .= __( 'course', 'woothemes-sensei' );
1316
			$a_element .= '</a>';
1317
1318
			if ( Sensei_WC::is_course_purchasable( $course_id ) ){
1319
1320
				$message = sprintf( __( 'Please purchase the %1$s before taking this quiz.', 'woothemes-sensei' ), $a_element );
1321
1322
			} else {
1323
1324
				$message = sprintf( __( 'Please sign up for the %1$s before taking this quiz.', 'woothemes-sensei' ), $a_element );
1325
1326
			}
1327
1328
1329
		}
1330
1331
		// Legacy filter
1332
		$message = apply_filters( 'sensei_user_quiz_status_' . $status, $message );
1333
1334
		if( $is_lesson && ! in_array( $status, array( 'login_required', 'not_started_course' ) ) ) {
1335
            $quiz_id = Sensei()->lesson->lesson_quizzes( $lesson_id );
1336
			$extra = '<p><a class="button" href="' . esc_url( get_permalink( $quiz_id ) ) . '" title="' .  __( 'View the lesson quiz', 'woothemes-sensei' ) . '">' .  __( 'View the lesson quiz', 'woothemes-sensei' )  . '</a></p>';
1337
		}
1338
1339
		// Filter of all messages
1340
		return apply_filters( 'sensei_user_quiz_status', array( 'status' => $status, 'box_class' => $box_class, 'message' => $message, 'extra' => $extra ), $lesson_id, $user_id, $is_lesson );
1341
	}
1342
1343
	/**
1344
	 * Start course for user
1345
	 * @since  1.4.8
1346
	 * @param  integer $user_id   User ID
1347
	 * @param  integer $course_id Course ID
1348
	 * @return mixed boolean or comment_ID
1349
	 */
1350
	public static function user_start_course( $user_id = 0, $course_id = 0 ) {
1351
1352
		$activity_logged = false;
1353
1354
		if( $user_id && $course_id ) {
1355
			// Check if user is already on the Course
1356
			$activity_logged = Sensei_Utils::user_started_course( $course_id, $user_id );
1357
			if ( ! $activity_logged ) {
1358
1359
				// Add user to course
1360
				$course_metadata = array(
1361
					'start' => current_time('mysql'),
1362
					'percent' => 0, // No completed lessons yet
1363
					'complete' => 0,
1364
				);
1365
1366
				$activity_logged = Sensei_Utils::update_course_status( $user_id, $course_id, $course_status = 'in-progress', $course_metadata );
1367
1368
				// Allow further actions
1369
				if ( $activity_logged ) {
1370
					do_action( 'sensei_user_course_start', $user_id, $course_id );
1371
				}
1372
			}
1373
		}
1374
1375
		return $activity_logged;
1376
	}
1377
1378
	/**
1379
	 * Check if a user has started a course or not
1380
	 *
1381
	 * @since  1.7.0
1382
	 * @param int $course_id
1383
	 * @param int $user_id
1384
	 * @return mixed false or comment_ID
1385
	 */
1386
	public static function user_started_course( $course_id = 0, $user_id = 0 ) {
1387
1388
		$user_started_course = false;
1389
1390
		if( $course_id ) {
1391
1392
			if( ! $user_id ) {
1393
				$user_id = get_current_user_id();
1394
			}
1395
1396 View Code Duplication
            if ( ! $user_id > 0 ) {
1397
1398
	            $user_started_course =  false;
1399
1400
            } else {
1401
1402
	            $activity_args = array(
1403
		            'post_id' => $course_id,
1404
		            'user_id' => $user_id,
1405
		            'type' => 'sensei_course_status',
1406
		            'field' => 'comment_ID'
1407
	            );
1408
1409
				$user_course_status_id = Sensei_Utils::sensei_get_activity_value( $activity_args );
1410
1411
				if ( $user_course_status_id ) {
1412
1413
					$user_started_course = $user_course_status_id;
1414
1415
				}
1416
            }
1417
		}
1418
1419
		/**
1420
		 * Filter the user started course value
1421
		 *
1422
		 * @since 1.9.3
1423
		 *
1424
		 * @param bool $user_started_course
1425
		 * @param integer $course_id
1426
		 */
1427
		return apply_filters( 'sensei_user_started_course', $user_started_course, $course_id, $user_id );
1428
1429
	}
1430
1431
	/**
1432
	 * Checks if a user has completed a course by checking every lesson status
1433
	 *
1434
	 * @since  1.7.0
1435
	 * @param  integer $course_id Course ID
1436
	 * @param  integer $user_id   User ID
1437
	 * @return int
1438
	 */
1439
	public static function user_complete_course( $course_id = 0, $user_id = 0 ) {
1440
		global  $wp_version;
1441
1442
		if( $course_id ) {
1443
			if( ! $user_id ) {
1444
				$user_id = get_current_user_id();
1445
			}
1446
1447
			$course_status = 'in-progress';
1448
			$course_metadata = array();
1449
			$course_completion = Sensei()->settings->settings[ 'course_completion' ];
1450
			$lessons_completed = $total_lessons = 0;
1451
			$lesson_status_args = array(
1452
					'user_id' => $user_id,
1453
					'status' => 'any',
1454
					'type' => 'sensei_lesson_status', /* FIELD SIZE 20 */
1455
				);
1456
1457
			// Grab all of this Courses' lessons, looping through each...
1458
			$lesson_ids = Sensei()->course->course_lessons( $course_id, 'any', 'ids' );
1459
			$total_lessons = count( $lesson_ids );
1460
				// ...if course completion not set to 'passed', and all lessons are complete or graded,
1461
				// ......then all lessons are 'passed'
1462
				// ...else if course completion is set to 'passed', check if each lesson has questions...
1463
				// ......if no questions yet the status is 'complete'
1464
				// .........then the lesson is 'passed'
1465
				// ......else if questions check the lesson status has a grade and that the grade is greater than the lesson passmark
1466
				// .........then the lesson is 'passed'
1467
				// ...if all lessons 'passed' then update the course status to complete
1468
1469
			// The below checks if a lesson is fully completed, though maybe should be Utils::user_completed_lesson()
1470
			$all_lesson_statuses = array();
1471
			// In WordPress 4.1 get_comments() allows a single query to cover multiple comment_post_IDs
1472
			if ( version_compare($wp_version, '4.1', '>=') ) {
1473
				$lesson_status_args['post__in'] = $lesson_ids;
1474
				$all_lesson_statuses = Sensei_Utils::sensei_check_for_activity( $lesson_status_args, true );
1475
				// Need to always return an array, even with only 1 item
1476
				if ( !is_array($all_lesson_statuses) ) {
1477
					$all_lesson_statuses = array( $all_lesson_statuses );
1478
				}
1479
			}
1480
			// ...otherwise check each one
1481
			else {
1482
				foreach( $lesson_ids as $lesson_id ) {
1483
					$lesson_status_args['post_id'] = $lesson_id;
1484
					$each_lesson_status = Sensei_Utils::sensei_check_for_activity( $lesson_status_args, true );
1485
					// Check for valid return before using
1486
					if ( !empty($each_lesson_status->comment_approved) ) {
1487
						$all_lesson_statuses[] = $each_lesson_status;
1488
					}
1489
				}
1490
			}
1491
			foreach( $all_lesson_statuses as $lesson_status ) {
1492
				// If lessons are complete without needing quizzes to be passed
1493 View Code Duplication
				if ( 'passed' != $course_completion ) {
1494
					switch ( $lesson_status->comment_approved ) {
1495
						// A user cannot 'complete' a course if a lesson...
1496
						case 'in-progress': // ...is still in progress
1497
						case 'ungraded': // ...hasn't yet been graded
1498
							break;
1499
1500
						default:
1501
							$lessons_completed++;
1502
							break;
1503
					}
1504
				}
1505
				else {
1506
					switch ( $lesson_status->comment_approved ) {
1507
						case 'complete': // Lesson has no quiz/questions
1508
						case 'graded': // Lesson has quiz, but it's not important what the grade was
1509
						case 'passed': // Lesson has quiz and the user passed
1510
							$lessons_completed++;
1511
							break;
1512
1513
						// A user cannot 'complete' a course if on a lesson...
1514
						case 'failed': // ...a user failed the passmark on a quiz
1515
						default:
1516
							break;
1517
					}
1518
				}
1519
			} // Each lesson
1520
			if ( $lessons_completed == $total_lessons ) {
1521
				$course_status = 'complete';
1522
			}
1523
1524
			// Update meta data on how many lessons have been completed
1525
			$course_metadata['complete'] = $lessons_completed;
1526
			// update the overall percentage of the course lessons complete (or graded) compared to 'in-progress' regardless of the above
1527
			$course_metadata['percent'] = abs( round( ( doubleval( $lessons_completed ) * 100 ) / ( $total_lessons ), 0 ) );
1528
1529
			$activity_logged = Sensei_Utils::update_course_status( $user_id, $course_id, $course_status, $course_metadata );
1530
1531
			// Allow further actions
1532
			if ( 'complete' == $course_status ) {
1533
				do_action( 'sensei_user_course_end', $user_id, $course_id );
1534
			}
1535
			return $activity_logged;
1536
		}
1537
1538
		return false;
1539
	}
1540
1541
	/**
1542
	 * Check if a user has completed a course or not
1543
	 *
1544
	 * @param int | WP_Post | WP_Comment $course course_id or sensei_course_status entry
1545
     *
1546
	 * @param int $user_id
1547
	 * @return boolean
1548
	 */
1549
	public static function user_completed_course( $course , $user_id = 0 ) {
1550
1551
		if( $course ) {
1552
			if ( is_object( $course ) && is_a( $course,'WP_Comment') ) {
1553
				$user_course_status = $course->comment_approved;
1554
			}
1555
			elseif ( !is_numeric( $course ) && ! is_a( $course,'WP_Post') ) {
1556
				$user_course_status = $course;
1557
			}
1558
			else {
1559
1560
				// check the user_id
1561
				if( ! $user_id ) {
1562
1563
					$user_id = get_current_user_id();
1564
1565
					if( empty( $user_id ) ){
1566
1567
						return false;
1568
1569
					}
1570
				}
1571
1572
                if( is_a( $course, 'WP_Post' ) ){
1573
                    $course =   $course->ID;
1574
                }
1575
1576
				$user_course_status = Sensei_Utils::user_course_status( $course , $user_id );
1577
				if( isset( $user_course_status->comment_approved ) ){
1578
                    $user_course_status = $user_course_status->comment_approved;
1579
                }
1580
1581
			}
1582
			if( $user_course_status && 'complete' == $user_course_status ) {
1583
				return true;
1584
			}
1585
		}
1586
		return false;
1587
	}
1588
1589
	/**
1590
	 * Check if a user has started a lesson or not
1591
	 *
1592
	 * @since  1.7.0
1593
	 * @param int $lesson_id
1594
	 * @param int $user_id
1595
	 * @return mixed false or comment_ID
1596
	 */
1597
	public static function user_started_lesson( $lesson_id = 0, $user_id = 0 ) {
1598
1599 View Code Duplication
		if( $lesson_id ) {
1600
			if( ! $user_id ) {
1601
				$user_id = get_current_user_id();
1602
			}
1603
1604
            $activity_args = array(
1605
                'post_id' => $lesson_id,
1606
                'user_id' => $user_id,
1607
                'type' => 'sensei_lesson_status',
1608
                'field' => 'comment_ID' );
1609
1610
			$user_lesson_status_id = Sensei_Utils::sensei_get_activity_value( $activity_args );
1611
			if( $user_lesson_status_id ) {
1612
				return $user_lesson_status_id;
1613
			}
1614
		}
1615
		return false;
1616
	}
1617
1618
	/**
1619
	 * Check if a user has completed a lesson or not
1620
	 *
1621
     * @uses  Sensei()
1622
	 * @param mixed $lesson lesson_id or sensei_lesson_status entry
1623
	 * @param int $user_id
1624
	 * @return boolean
1625
	 */
1626
	public static function user_completed_lesson( $lesson = 0, $user_id = 0 ) {
1627
1628
		if( $lesson ) {
1629
			$lesson_id = 0;
1630
			if ( is_object( $lesson ) ) {
1631
				$user_lesson_status = $lesson->comment_approved;
1632
				$lesson_id = $lesson->comment_post_ID;
1633
			}
1634
			elseif ( ! is_numeric( $lesson ) ) {
1635
				$user_lesson_status = $lesson;
1636
			}
1637
			else {
1638
				if( ! $user_id ) {
1639
					$user_id = get_current_user_id();
1640
				}
1641
1642
                // the user is not logged in
1643
                if( ! $user_id > 0 ){
1644
                    return false;
1645
                }
1646
				$_user_lesson_status = Sensei_Utils::user_lesson_status( $lesson, $user_id );
1647
1648
				if ( $_user_lesson_status ) {
1649
					$user_lesson_status = $_user_lesson_status->comment_approved;
1650
				}
1651
				else {
1652
					return false; // No status means not complete
1653
				}
1654
				$lesson_id = $lesson;
1655
			}
1656
			if ( 'in-progress' != $user_lesson_status ) {
1657
				// Check for Passed or Completed Setting
1658
				// Should we be checking for the Course completion setting? Surely that should only affect the Course completion, not bypass each Lesson setting
1659
//				$course_completion = Sensei()->settings->settings[ 'course_completion' ];
1660
//				if ( 'passed' == $course_completion ) {
1661
					switch( $user_lesson_status ) {
1662
						case 'complete':
1663
						case 'graded':
1664
						case 'passed':
1665
							return true;
1666
							break;
1667
1668
						case 'failed':
1669
							// This may be 'completed' depending on...
1670
							if ( $lesson_id ) {
1671
								// Get Quiz ID, this won't be needed once all Quiz meta fields are stored on the Lesson
1672
								$lesson_quiz_id = Sensei()->lesson->lesson_quizzes( $lesson_id );
1673
								if ( $lesson_quiz_id ) {
1674
									// ...the quiz pass setting
1675
									$pass_required = get_post_meta( $lesson_quiz_id, '_pass_required', true );
1676
									if ( empty($pass_required) ) {
1677
										// We just require the user to have done the quiz, not to have passed
1678
										return true;
1679
									}
1680
								}
1681
							}
1682
							return false;
1683
							break;
1684
					}
1685
			} // End If Statement
1686
		}
1687
1688
		return false;
1689
	}
1690
1691
	/**
1692
	 * Returns the requested course status
1693
	 *
1694
	 * @since 1.7.0
1695
	 * @param int $course_id
1696
	 * @param int $user_id
1697
	 * @return object
1698
	 */
1699
	public static function user_course_status( $course_id = 0, $user_id = 0 ) {
1700
1701
1702
		if( $course_id ) {
1703
			if( ! $user_id ) {
1704
				$user_id = get_current_user_id();
1705
			}
1706
1707
			$user_course_status = Sensei_Utils::sensei_check_for_activity( array( 'post_id' => $course_id, 'user_id' => $user_id, 'type' => 'sensei_course_status' ), true );
1708
			return $user_course_status;
1709
		}
1710
1711
		return false;
1712
	}
1713
1714
	/**
1715
	 * Returns the requested lesson status
1716
	 *
1717
	 * @since 1.7.0
1718
	 * @param int $lesson_id
1719
	 * @param int $user_id
1720
	 * @return object | bool
1721
	 */
1722
	public static function user_lesson_status( $lesson_id = 0, $user_id = 0 ) {
1723
1724
        if( ! $user_id ) {
1725
            $user_id = get_current_user_id();
1726
        }
1727
1728
		if( $lesson_id > 0 && $user_id > 0 ) {
1729
1730
			$user_lesson_status = Sensei_Utils::sensei_check_for_activity( array( 'post_id' => $lesson_id, 'user_id' => $user_id, 'type' => 'sensei_lesson_status' ), true );
1731
			return $user_lesson_status;
1732
		}
1733
1734
		return false;
1735
	}
1736
1737
	public static function is_preview_lesson( $lesson_id ) {
1738
		$is_preview = false;
1739
1740
		if( 'lesson' == get_post_type( $lesson_id ) ) {
1741
			$lesson_preview = get_post_meta( $lesson_id, '_lesson_preview', true );
1742
			if ( isset( $lesson_preview ) && '' != $lesson_preview ) {
1743
				$is_preview = true;
1744
			}
1745
		}
1746
1747
		return $is_preview;
1748
	}
1749
1750
	public static function user_passed_quiz( $quiz_id = 0, $user_id = 0 ) {
1751
1752
		if( ! $quiz_id  ) return false;
1753
1754
		if( ! $user_id ) {
1755
			$user_id = get_current_user_id();
1756
		}
1757
		$lesson_id = get_post_meta( $quiz_id, '_quiz_lesson', true );
1758
1759
		// Quiz Grade
1760
		$lesson_status = Sensei_Utils::user_lesson_status( $lesson_id, $user_id );
1761
		$quiz_grade = get_comment_meta( $lesson_status->comment_ID, 'grade', true );
1762
1763
		// Check if Grade is greater than or equal to pass percentage
1764
		$quiz_passmark = abs( round( doubleval( get_post_meta( $quiz_id, '_quiz_passmark', true ) ), 2 ) );
1765
		if ( $quiz_passmark <= intval( $quiz_grade ) ) {
1766
			return true;
1767
		}
1768
1769
		return false;
1770
1771
	}
1772
1773
	/**
1774
	 * Sets the status for the lesson
1775
	 *
1776
	 * @since  1.7.0
1777
     *
1778
	 * @param int|string $user_id
1779
	 * @param int|string $lesson_id
1780
	 * @param string $status
1781
	 * @param array $metadata
1782
     *
1783
	 * @return mixed false or comment_ID
1784
	 */
1785 View Code Duplication
	public static function update_lesson_status( $user_id, $lesson_id, $status = 'in-progress', $metadata = array() ) {
1786
		$comment_id = false;
1787
		if ( !empty($status) ) {
1788
			$args = array(
1789
					'user_id'   => $user_id,
1790
					'post_id'   => $lesson_id,
1791
					'status'    => $status,
1792
					'type'      => 'sensei_lesson_status', /* FIELD SIZE 20 */
1793
					'action'    => 'update', // Update the existing status...
1794
					'keep_time' => true, // ...but don't change the existing timestamp
1795
				);
1796
			if( 'in-progress' == $status ) {
1797
				unset( $args['keep_time'] ); // Keep updating what's happened
1798
			}
1799
1800
			$comment_id = Sensei_Utils::sensei_log_activity( $args );
1801
			if ( $comment_id && !empty($metadata) ) {
1802
				foreach( $metadata as $key => $value ) {
1803
					update_comment_meta( $comment_id, $key, $value );
1804
				}
1805
			}
1806
1807
			do_action( 'sensei_lesson_status_updated', $status, $user_id, $lesson_id, $comment_id );
1808
		}
1809
		return $comment_id;
1810
	}
1811
1812
	/**
1813
	 * Sets the statuses for the Course
1814
	 *
1815
	 * @access public
1816
	 * @since  1.7.0
1817
	 * @param int $user_id
1818
	 * @param int $course_id
1819
	 * @param string $status
1820
	 * @param array $metadata
1821
	 * @return mixed false or comment_ID
1822
	 */
1823 View Code Duplication
	public static function update_course_status( $user_id, $course_id, $status = 'in-progress', $metadata = array() ) {
1824
		$comment_id = false;
1825
		if ( !empty($status) ) {
1826
			$args = array(
1827
					'user_id'   => $user_id,
1828
					'post_id'   => $course_id,
1829
					'status'    => $status,
1830
					'type'      => 'sensei_course_status', /* FIELD SIZE 20 */
1831
					'action'    => 'update', // Update the existing status...
1832
					'keep_time' => true, // ...but don't change the existing timestamp
1833
				);
1834
			if( 'in-progress' == $status ) {
1835
				unset( $args['keep_time'] ); // Keep updating what's happened
1836
			}
1837
1838
			$comment_id = Sensei_Utils::sensei_log_activity( $args );
1839
			if ( $comment_id && !empty($metadata) ) {
1840
				foreach( $metadata as $key => $value ) {
1841
					update_comment_meta( $comment_id, $key, $value );
1842
				}
1843
			}
1844
			do_action( 'sensei_course_status_updated', $status, $user_id, $course_id, $comment_id );
1845
		}
1846
		return $comment_id;
1847
	}
1848
1849
	/**
1850
	 * Remove the orderby for comments
1851
	 * @access public
1852
	 * @since  1.7.0
1853
	 * @param  array $pieces (default: array())
1854
	 * @return array
1855
	 */
1856
	public static function single_comment_filter( $pieces ) {
1857
		unset( $pieces['orderby'] );
1858
		unset( $pieces['order'] );
1859
1860
		return $pieces;
1861
	}
1862
1863
	/**
1864
	 * Allow retrieving comments with any comment_approved status, little bypass to WP_Comment. Required only for WP < 4.1
1865
	 * @access public
1866
	 * @since  1.7.0
1867
	 * @param  array $pieces (default: array())
1868
	 * @return array
1869
	 */
1870
	public static function comment_any_status_filter( $pieces ) {
1871
1872
		$pieces['where'] = str_replace( array( "( comment_approved = '0' OR comment_approved = '1' ) AND", "comment_approved = 'any' AND" ), '', $pieces['where'] );
1873
1874
		return $pieces;
1875
	}
1876
1877
	/**
1878
	 * Allow retrieving comments within multiple statuses, little bypass to WP_Comment. Required only for WP < 4.1
1879
	 * @access public
1880
	 * @since  1.7.0
1881
	 * @param  array $pieces (default: array())
1882
	 * @return array
1883
	 */
1884
	public static function comment_multiple_status_filter( $pieces ) {
1885
1886
		preg_match( "/^comment_approved = '([a-z\-\,]+)'/", $pieces['where'], $placeholder );
1887
		if ( !empty($placeholder[1]) ) {
1888
			$statuses = explode( ',', $placeholder[1] );
1889
			$pieces['where'] = str_replace( "comment_approved = '" . $placeholder[1] . "'", "comment_approved IN ('". implode( "', '", $statuses ) . "')", $pieces['where'] );
1890
		}
1891
1892
		return $pieces;
1893
	}
1894
1895
	/**
1896
	 * Adjust the comment query to be faster on the database, used by Analysis admin
1897
	 * @since  1.7.0
1898
     * @param array $pieces
1899
	 * @return array $pieces
1900
	 */
1901 View Code Duplication
	public static function comment_total_sum_meta_value_filter( $pieces ) {
1902
		global $wpdb, $wp_version;
1903
1904
		$pieces['fields'] = " COUNT(*) AS total, SUM($wpdb->commentmeta.meta_value) AS meta_sum ";
1905
		unset( $pieces['groupby'] );
1906
		if ( version_compare($wp_version, '4.1', '>=') ) {
1907
			$args['order'] = false;
1908
			$args['orderby'] = false;
1909
		}
1910
1911
		return $pieces;
1912
	}
1913
1914
	/**
1915
	 * Shifts counting of posts to the database where it should be. Likely not to be used due to knock on issues.
1916
	 * @access public
1917
	 * @since  1.7.0
1918
	 * @param  array $pieces (default: array())
1919
	 * @return array
1920
	 */
1921 View Code Duplication
	public static function get_posts_count_only_filter( $pieces ) {
1922
		global $wp_version;
1923
1924
		$pieces['fields'] = " COUNT(*) AS total ";
1925
		unset( $pieces['groupby'] );
1926
		if ( version_compare($wp_version, '4.1', '>=') ) {
1927
			$args['order'] = false;
1928
			$args['orderby'] = false;
1929
		}
1930
		return $pieces;
1931
	}
1932
1933
    /**
1934
     *
1935
     * Alias to Woothemes_Sensei_Utils::update_user_data
1936
     * @since 1.7.4
1937
     *
1938
     * @param string $data_key maximum 39 characters allowed
1939
     * @param int $post_id
1940
     * @param mixed $value
1941
     * @param int $user_id
1942
     *
1943
     * @return bool $success
1944
     */
1945
    public static function add_user_data( $data_key, $post_id , $value = '' , $user_id = 0  ){
1946
1947
        return self::update_user_data( $data_key, $post_id, $value , $user_id );
1948
1949
    }// end add_user_data
1950
1951
    /**
1952
     * add user specific data to the passed in sensei post type id
1953
     *
1954
     * This function saves comment meta on the users current status. If no status is available
1955
     * status will be created. It only operates on the available sensei Post types: course, lesson, quiz.
1956
     *
1957
     * @since 1.7.4
1958
     *
1959
     * @param string $data_key maximum 39 characters allowed
1960
     * @param int $post_id
1961
     * @param mixed $value
1962
     * @param int $user_id
1963
     *
1964
     * @return bool $success
1965
     */
1966
    public static function update_user_data( $data_key, $post_id, $value = '' , $user_id = 0  ){
1967
1968
        if( ! ( $user_id > 0 ) ){
1969
            $user_id = get_current_user_id();
1970
        }
1971
1972
        $supported_post_types = array( 'course', 'lesson' );
1973
        $post_type = get_post_type( $post_id );
1974
        if( empty( $post_id ) || empty( $data_key )
1975
            || ! is_int( $post_id ) || ! ( intval( $post_id ) > 0 ) || ! ( intval( $user_id ) > 0 )
1976
            || !get_userdata( $user_id )
1977
            || ! in_array( $post_type, $supported_post_types )  ){
1978
1979
            return false;
1980
        }
1981
1982
        // check if there and existing Sensei status on this post type if not create it
1983
        // and get the  activity ID
1984
        $status_function = 'user_'.$post_type.'_status';
1985
        $sensei_user_status = self::$status_function( $post_id ,$user_id  );
1986
        if( ! isset( $sensei_user_status->comment_ID ) ){
1987
1988
            $start_function = 'user_start_'.$post_type;
1989
            $sensei_user_activity_id = self::$start_function( $user_id, $post_id );
1990
1991
        }else{
1992
1993
            $sensei_user_activity_id = $sensei_user_status->comment_ID;
1994
1995
        }
1996
1997
        // store the data
1998
        $success = update_comment_meta( $sensei_user_activity_id, $data_key, $value );
1999
2000
       return $success;
2001
2002
    }//update_user_data
2003
2004
    /**
2005
     * Get the user data stored on the passed in post type
2006
     *
2007
     * This function gets the comment meta on the lesson or course status
2008
     *
2009
     * @since 1.7.4
2010
     *
2011
     * @param $data_key
2012
     * @param $post_id
2013
     * @param int $user_id
2014
     *
2015
     * @return mixed $user_data_value
2016
     */
2017 View Code Duplication
    public static function get_user_data( $data_key, $post_id, $user_id = 0  ){
2018
2019
        $user_data_value = true;
2020
2021
        if( ! ( $user_id > 0 ) ){
2022
            $user_id = get_current_user_id();
2023
        }
2024
2025
        $supported_post_types = array( 'course', 'lesson' );
2026
        $post_type = get_post_type( $post_id );
2027
        if( empty( $post_id ) || empty( $data_key )
2028
            || ! ( intval( $post_id ) > 0 ) || ! ( intval( $user_id ) > 0 )
2029
            || ! get_userdata( $user_id )
2030
            || !in_array( $post_type, $supported_post_types )  ){
2031
2032
            return false;
2033
        }
2034
2035
        // check if there and existing Sensei status on this post type if not create it
2036
        // and get the  activity ID
2037
        $status_function = 'user_'.$post_type.'_status';
2038
        $sensei_user_status = self::$status_function( $post_id ,$user_id  );
2039
        if( ! isset( $sensei_user_status->comment_ID ) ){
2040
            return false;
2041
        }
2042
2043
        $sensei_user_activity_id = $sensei_user_status->comment_ID;
2044
        $user_data_value = get_comment_meta( $sensei_user_activity_id , $data_key, true );
2045
2046
        return $user_data_value;
2047
2048
    }// end get_user_data
2049
2050
    /**
2051
     * Delete the Sensei user data for the given key, Sensei post type and user combination.
2052
     *
2053
     * @param int $data_key
2054
     * @param int $post_id
2055
     * @param int $user_id
2056
     *
2057
     * @return bool $deleted
2058
     */
2059 View Code Duplication
    public static function delete_user_data( $data_key, $post_id , $user_id ){
2060
        $deleted = true;
2061
2062
        if( ! ( $user_id > 0 ) ){
2063
            $user_id = get_current_user_id();
2064
        }
2065
2066
        $supported_post_types = array( 'course', 'lesson' );
2067
        $post_type = get_post_type( $post_id );
2068
        if( empty( $post_id ) || empty( $data_key )
2069
            || ! is_int( $post_id ) || ! ( intval( $post_id ) > 0 ) || ! ( intval( $user_id ) > 0 )
2070
            || ! get_userdata( $user_id )
2071
            || !in_array( $post_type, $supported_post_types )  ){
2072
2073
            return false;
2074
        }
2075
2076
        // check if there and existing Sensei status on this post type if not create it
2077
        // and get the  activity ID
2078
        $status_function = 'user_'.$post_type.'_status';
2079
        $sensei_user_status = self::$status_function( $post_id ,$user_id  );
2080
        if( ! isset( $sensei_user_status->comment_ID ) ){
2081
            return false;
2082
        }
2083
2084
        $sensei_user_activity_id = $sensei_user_status->comment_ID;
2085
        $deleted = delete_comment_meta( $sensei_user_activity_id , $data_key );
2086
2087
        return $deleted;
2088
2089
    }// end delete_user_data
2090
2091
2092
    /**
2093
     * The function creates a drop down. Never write up a Sensei select statement again.
2094
     *
2095
     * @since 1.8.0
2096
     *
2097
     * @param string $selected_value
2098
     * @param $options{
2099
     *    @type string $value the value saved in the database
2100
     *    @type string $option what the user will see in the list of items
2101
     * }
2102
     * @param array $attributes{
2103
     *   @type string $attribute  type such name or id etc.
2104
     *  @type string $value
2105
     * }
2106
     * @param bool $enable_none_option
2107
     *
2108
     * @return string $drop_down_element
2109
     */
2110
    public static function generate_drop_down( $selected_value, $options = array() , $attributes = array(), $enable_none_option = true ) {
2111
2112
        $drop_down_element = '';
2113
2114
        // setup the basic attributes
2115 View Code Duplication
        if( !isset( $attributes['name'] ) || empty( $attributes['name']  ) ) {
2116
2117
            $attributes['name'] = 'sensei-options';
2118
2119
        }
2120
2121 View Code Duplication
        if( !isset( $attributes['id'] ) || empty( $attributes['id']  ) ) {
2122
2123
            $attributes['id'] = 'sensei-options';
2124
2125
        }
2126
2127
        if( !isset( $attributes['class'] ) || empty( $attributes['class']  ) ) {
2128
2129
            $attributes['class'] ='chosen_select widefat';
2130
2131
        }
2132
2133
        // create element attributes
2134
        $combined_attributes = '';
2135
        foreach( $attributes as $attribute => $value ){
2136
2137
            $combined_attributes .= $attribute . '="'.$value.'"' . ' ';
2138
2139
        }// end for each
2140
2141
2142
        // create the select element
2143
        $drop_down_element .= '<select '. $combined_attributes . ' >' . "\n";
2144
2145
        // show the none option if the client requested
2146
        if( $enable_none_option ) {
2147
            $drop_down_element .= '<option value="">' . __('None', 'woothemes-sensei') . '</option>';
2148
        }
2149
2150
        if ( count( $options ) > 0 ) {
2151
2152
            foreach ($options as $value => $option ){
2153
2154
                $element = '';
2155
                $element.= '<option value="' . esc_attr( $value ) . '"';
2156
                $element .= selected( $value, $selected_value, false ) . '>';
2157
                $element .= esc_html(  $option ) . '</option>' . "\n";
2158
2159
                // add the element to the select html
2160
                $drop_down_element.= $element;
2161
            } // End For Loop
2162
2163
        } // End If Statement
2164
2165
        $drop_down_element .= '</select>' . "\n";
2166
2167
        return $drop_down_element;
2168
2169
    }// generate_drop_down
2170
2171
    /**
2172
     * Wrapper for the default php round() function.
2173
     * This allows us to give more control to a user on how they can round Sensei
2174
     * decimals passed through this function.
2175
     *
2176
     * @since 1.8.5
2177
     *
2178
     * @param double $val
2179
     * @param int $precision
2180
     * @param $mode
2181
     * @param string $context
2182
     *
2183
     * @return double $val
2184
     */
2185
    public static function round( $val, $precision = 0, $mode = PHP_ROUND_HALF_UP, $context = ''  ){
2186
2187
        /**Ã¥
2188
         * Change the precision for the Sensei_Utils::round function.
2189
         * the precision given will be passed into the php round function
2190
         * @since 1.8.5
2191
         */
2192
        $precision = apply_filters( 'sensei_round_precision', $precision , $val, $context, $mode );
2193
2194
        /**
2195
         * Change the mode for the Sensei_Utils::round function.
2196
         * the mode given will be passed into the php round function
2197
         *
2198
         * This applies only to PHP version 5.3.0 and greater
2199
         *
2200
         * @since 1.8.5
2201
         */
2202
        $mode = apply_filters( 'sensei_round_mode', $mode , $val, $context, $precision   );
2203
2204
        if ( version_compare(PHP_VERSION, '5.3.0') >= 0 ) {
2205
2206
            return round( $val, $precision, $mode );
2207
2208
        }else{
2209
2210
            return round( $val, $precision );
2211
2212
        }
2213
2214
    }
2215
2216
    /**
2217
     * Returns the current url with all the query vars
2218
     *
2219
     * @since 1.9.0
2220
     * @return string $url
2221
     */
2222
    public static function get_current_url(){
2223
2224
        global $wp;
2225
        $current_url = trailingslashit( home_url( $wp->request ) );
2226
        if ( isset( $_GET ) ) {
2227
2228
            foreach ($_GET as $param => $val ) {
2229
2230
                $current_url = add_query_arg( $param, $val , $current_url );
2231
2232
            }
2233
        }
2234
2235
        return $current_url;
2236
    }
2237
2238
    /**
2239
     * Restore the global WP_Query
2240
     *
2241
     * @since 1.9.0
2242
     */
2243
    public static function restore_wp_query() {
2244
2245
        wp_reset_query();
2246
2247
    }
2248
2249
    /**
2250
     * Merge two arrays in a zip like fashion.
2251
     * If one array is longer than the other the elements will be apended
2252
     * to the end of the resulting array.
2253
     *
2254
     * @since 1.9.0
2255
     *
2256
     * @param array $array_a
2257
     * @param array $array_b
2258
     * @return array $merged_array
2259
     */
2260
    public static function array_zip_merge( $array_a, $array_b ){
2261
2262
        if( ! is_array( $array_a ) || ! is_array( $array_b )  ){
2263
            trigger_error('array_zip_merge requires both arrays to be indexed arrays ');
2264
        }
2265
2266
        $merged_array = array();
2267
        $total_elements = count( $array_a )  + count( $array_b );
2268
2269
        // Zip arrays
2270
        for ( $i = 0; $i < $total_elements; $i++) {
2271
2272
            // if has an element at current index push a on top
2273
            if( isset( $array_a[ $i ] ) ){
2274
                $merged_array[] = $array_a[ $i ]  ;
2275
            }
2276
2277
            // next if $array_b has an element at current index push a on top of the element
2278
            // from a if there was one, if not the element before that.
2279
            if( isset( $array_b[ $i ] ) ){
2280
                $merged_array[] = $array_b[ $i ]  ;
2281
            }
2282
2283
        }
2284
2285
        return $merged_array;
2286
    }
2287
2288
} // End Class
2289
2290
/**
2291
 * Class WooThemes_Sensei_Utils
2292
 * @ignore only for backward compatibility
2293
 * @since 1.9.0
2294
 */
2295
class WooThemes_Sensei_Utils extends Sensei_Utils{}