Completed
Push — master ( 7d92ba...201c1b )
by Dwain
05:05
created

Sensei_Utils   D

Complexity

Total Complexity 354

Size/Duplication

Total Lines 2207
Duplicated Lines 15.22 %

Coupling/Cohesion

Components 1
Dependencies 9
Metric Value
wmc 354
lcom 1
cbo 9
dl 336
loc 2207
rs 4.4103

63 Methods

Rating   Name   Duplication   Size   Complexity  
A get_placeholder_image() 0 4 1
A sensei_is_woocommerce_present() 0 12 4
A sensei_is_woocommerce_activated() 0 5 3
F sensei_log_activity() 0 70 14
C sensei_check_for_activity() 9 70 18
D sensei_activity_ids() 0 29 9
B sensei_delete_activities() 0 21 6
C delete_all_user_activity() 0 26 7
A sensei_get_activity_value() 0 13 4
A sensei_customer_bought_product() 0 18 4
A sensei_text_editor() 0 23 2
C sensei_save_quiz_answers() 0 60 15
B upload_file() 0 48 4
A sensei_grade_quiz_auto() 0 5 1
A sensei_grade_quiz() 0 18 4
A sensei_grade_question_auto() 0 5 1
B sensei_grade_question() 0 20 5
A sensei_delete_question_grade() 0 13 3
A user_start_lesson() 0 5 1
C sensei_start_lesson() 0 69 11
B sensei_remove_user_from_lesson() 30 30 4
B sensei_remove_user_from_course() 28 28 4
A sensei_get_quiz_questions() 0 12 2
A sensei_get_quiz_total() 0 16 3
B sensei_get_user_question_grade() 19 19 5
B sensei_get_user_question_answer_notes() 19 19 5
A sensei_delete_quiz_answers() 0 15 4
A sensei_delete_quiz_grade() 0 14 3
B sensei_add_answer_notes() 0 22 4
B array_sort_reorder() 0 17 6
A sort_array_by_key() 0 13 3
A lesson_quiz_questions() 20 20 2
B sensei_course_pass_grade() 0 34 5
C sensei_course_user_grade() 0 38 7
B sensei_user_passed_course() 0 19 5
B sensei_user_course_status_message() 0 33 6
D sensei_user_quiz_status_message() 10 139 24
B user_start_course() 0 27 5
B user_started_course() 0 18 5
F user_complete_course() 26 101 17
C user_completed_course() 0 30 11
A user_started_lesson() 14 14 4
C user_completed_lesson() 0 64 15
A user_course_status() 0 14 3
A user_lesson_status() 14 14 4
A is_preview_lesson() 0 12 4
B user_passed_quiz() 0 22 4
B update_lesson_status() 26 26 6
B update_course_status() 25 25 6
A single_comment_filter() 0 6 1
A comment_any_status_filter() 0 6 1
A comment_multiple_status_filter() 0 10 2
A comment_total_sum_meta_value_filter() 12 12 2
A get_posts_count_only_filter() 11 11 2
A add_user_data() 0 5 1
D update_user_data() 0 37 10
D get_user_data() 32 32 9
D delete_user_data() 31 31 10
C generate_drop_down() 10 60 11
B round() 0 30 2
A get_current_url() 0 15 3
A restore_wp_query() 0 5 1
B array_zip_merge() 0 27 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Sensei_Utils often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Sensei_Utils, and based on these observations, apply Extract Interface, too.

1
<?php
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 WordPress
10
 * @subpackage Sensei
11
 * @category Utilities
12
 * @author WooThemes
13
 * @since 1.0.0
14
 */
15
class Sensei_Utils {
16
	/**
17
	 * Get the placeholder thumbnail image.
18
	 * @access  public
19
	 * @since   1.0.0
20
	 * @return  string The URL to the placeholder thumbnail image.
21
	 */
22
	public static function get_placeholder_image () {
23
24
		return esc_url( apply_filters( 'sensei_placeholder_thumbnail', Sensei()->plugin_url . 'assets/images/placeholder.png' ) );
25
	} // End get_placeholder_image()
26
27
	/**
28
	 * Check if WooCommerce is present.
29
	 * @access public
30
	 * @since  1.0.2
31
	 * @static
32
	 * @return void
33
	 */
34
	public static function sensei_is_woocommerce_present () {
35
		if ( class_exists( 'Woocommerce' ) ) {
36
			return true;
37
		} else {
38
			$active_plugins = apply_filters( 'active_plugins', get_option('active_plugins' ) );
39
			if ( is_array( $active_plugins ) && in_array( 'woocommerce/woocommerce.php', $active_plugins ) ) {
40
				return true;
41
			} else {
42
				return false;
43
			} // End If Statement
44
		} // End If Statement
45
	} // End sensei_is_woocommerce_present()
46
47
	/**
48
	 * Check if WooCommerce is active.
49
     *
50
	 * @access public
51
	 * @since  1.0.2
52
	 * @static
53
	 * @return boolean
54
	 */
55
	public static function sensei_is_woocommerce_activated () {
56
57
		return  WooThemes_Sensei_Utils::sensei_is_woocommerce_present() && isset( Sensei()->settings->settings['woocommerce_enabled'] ) && Sensei()->settings->settings['woocommerce_enabled'];
58
59
	} // End sensei_is_woocommerce_activated()
60
61
	/**
62
	 * Log an activity item.
63
	 * @access public
64
	 * @since  1.0.0
65
	 * @param  array $args (default: array())
66
	 * @return void
67
	 */
68
	public static function sensei_log_activity ( $args = array() ) {
69
		global $wpdb;
70
71
		// Args, minimum data required for WP
72
		$data = array(
73
					'comment_post_ID' => intval( $args['post_id'] ),
74
					'comment_author' => '', // Not needed
75
					'comment_author_email' => '', // Not needed
76
					'comment_author_url' => '', // Not needed
77
					'comment_content' => !empty($args['data']) ? esc_html( $args['data'] ) : '',
78
					'comment_type' => esc_attr( $args['type'] ),
79
					'user_id' => intval( $args['user_id'] ),
80
					'comment_approved' => !empty($args['status']) ? esc_html( $args['status'] ) : 'log', // 'log' == 'sensei_user_answer'
81
				);
82
		// Allow extra data
83
		if ( !empty($args['username']) ) {
84
			$data['comment_author'] = sanitize_user( $args['username'] );
85
		}
86
		if ( !empty($args['user_email']) ) {
87
			$data['comment_author_email'] = sanitize_email( $args['user_email'] );
88
		}
89
		if ( !empty($args['user_url']) ) {
90
			$data['comment_author_url'] = esc_url( $args['user_url'] );
91
		}
92
		if ( !empty($args['parent']) ) {
93
			$data['comment_parent'] = $args['parent'];
94
		}
95
		// Sanity check
96
		if ( empty($args['user_id']) ) {
97
			_deprecated_argument( __FUNCTION__, '1.0', __('At no point should user_id be equal to 0.', 'woothemes-sensei') );
98
			return false;
99
		}
100
101
		do_action( 'sensei_log_activity_before', $args, $data );
102
103
		$flush_cache = false;
104
105
		// Custom Logic
106
		// Check if comment exists first
107
		$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'] ) );
108
		if ( ! $comment_id ) {
109
			// Add the comment
110
			$comment_id = wp_insert_comment( $data );
111
112
			$flush_cache = true;
113
		} elseif ( isset( $args['action'] ) && 'update' == $args['action'] ) {
114
			// Update the comment if an update was requested
115
			$data['comment_ID'] = $comment_id;
116
			// By default update the timestamp of the comment
117
			if ( empty($args['keep_time']) ) {
118
				$data['comment_date'] = current_time('mysql');
119
			}
120
			wp_update_comment( $data );
121
			$flush_cache = true;
122
		} // End If Statement
123
124
		// Manually Flush the Cache
125
		if ( $flush_cache ) {
126
			wp_cache_flush();
127
		}
128
129
		do_action( 'sensei_log_activity_after', $args, $data,  $comment_id );
130
131
		if ( 0 < $comment_id ) {
132
			// Return the ID so that it can be used for meta data storage
133
			return $comment_id;
134
		} else {
135
			return false;
136
		} // End If Statement
137
	} // End sensei_log_activity()
138
139
140
	/**
141
	 * Check for Sensei activity.
142
	 * @access public
143
	 * @since  1.0.0
144
	 * @param  array $args (default: array())
145
	 * @param  bool $return_comments (default: false)
146
	 * @return mixed | int
147
	 */
148
	public static function sensei_check_for_activity ( $args = array(), $return_comments = false ) {
149
150
		global  $wp_version;
151
		if ( !$return_comments ) {
152
			$args['count'] = true;
153
		}
154
155
		// Are we only retrieving a single entry, or not care about the order...
156
		if ( isset( $args['count'] ) || isset( $args['post_id'] ) ){
157
158
			// ...then we don't need to ask the db to order the results, this overrides WP default behaviour
159
			if ( version_compare( $wp_version, '4.1', '>=' ) ) {
160
				$args['order'] = false;
161
				$args['orderby'] = false;
162
			}
163
		}
164
165
		// A user ID of 0 is in valid, so shortcut this
166
		if ( isset( $args['user_id'] ) && 0 == intval ( $args['user_id'] ) ) {
167
			_deprecated_argument( __FUNCTION__, '1.0', __('At no point should user_id be equal to 0.', 'woothemes-sensei') );
168
			return false;
169
		}
170
		// Check for legacy code
171
		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') ) ) {
172
			_deprecated_argument( __FUNCTION__, '1.7', sprintf( __('Sensei activity type %s is no longer used.', 'woothemes-sensei'), $args['type'] ) );
173
			return false;
174
		}
175
		// Are we checking for specific comment_approved statuses?
176
		if ( isset($args['status']) ) {
177
			// Temporarily store as a custom status if requesting an array...
178 View Code Duplication
			if ( is_array( $args['status'] ) && version_compare($wp_version, '4.1', '<') ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
179
				// Encode now, decode later
180
				$args['status'] = implode( ",", $args['status'] );
181
				// ...use a filter to switch the encoding back
182
				add_filter( 'comments_clauses', array( __CLASS__, 'comment_multiple_status_filter' ) );
183
			}
184
		}
185
		else {
186
			$args['status'] = 'any'; // 'log' == 'sensei_user_answer'
187
		}
188
189
		// Take into account WP < 4.1 will automatically add ' comment_approved = 1 OR comment_approved = 0 '
190 View Code Duplication
		if ( ( is_array( $args['status'] ) || 'any' == $args['status'] ) && version_compare($wp_version, '4.1', '<') ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
191
			add_filter( 'comments_clauses', array( __CLASS__, 'comment_any_status_filter' ) );
192
		}
193
194
        //Get the comments
195
        /**
196
         * This filter runs inside Sensei_Utils::sensei_check_for_activity
197
         *
198
         * It runs while getting the comments for the given request.
199
         *
200
         * @param int|array $comments
201
         */
202
        $comments = apply_filters('sensei_check_for_activity', get_comments( $args ) );
203
204
		remove_filter( 'comments_clauses', array( __CLASS__, 'comment_multiple_status_filter' ) );
205
		remove_filter( 'comments_clauses', array( __CLASS__, 'comment_any_status_filter' ) );
206
		// Return comments
207
		if ( $return_comments ) {
208
			// Could check for array of 1 and just return the 1 item?
209
			if ( is_array($comments) && 1 == count($comments) ) {
210
				$comments = array_shift($comments);
211
			}
212
213
			return $comments;
214
		} // End If Statement
215
		// Count comments
216
		return intval($comments); // This is the count, check the return from WP_Comment_Query
217
	} // End sensei_check_for_activity()
218
219
220
	/**
221
	 * Get IDs of Sensei activity items.
222
	 * @access public
223
	 * @since  1.0.0
224
	 * @param  array $args (default: array())
225
	 * @return void
226
	 */
227
	public static function sensei_activity_ids ( $args = array() ) {
228
229
230
		$comments = WooThemes_Sensei_Utils::sensei_check_for_activity( $args, true );
231
		// Need to always use an array, even with only 1 item
232
		if ( !is_array($comments) ) {
233
			$comments = array( $comments );
234
		}
235
236
		$post_ids = array();
237
		// Count comments
238
		if ( is_array( $comments ) && ( 0 < intval( count( $comments ) ) ) ) {
239
			foreach ( $comments as $key => $value  ) {
240
				// Add matches to id array
241
				if ( isset( $args['field'] ) && 'comment' == $args['field'] ) {
242
					array_push( $post_ids, $value->comment_ID );
243
				} elseif( isset( $args['field'] ) && 'user_id' == $args['field'] ) {
244
					array_push( $post_ids, $value->user_id );
245
				} else {
246
					array_push( $post_ids, $value->comment_post_ID );
247
				} // End If Statement
248
			} // End For Loop
249
			// Reset array indexes
250
			$post_ids = array_unique( $post_ids );
251
			$post_ids = array_values( $post_ids );
252
		} // End If Statement
253
254
		return $post_ids;
255
	} // End sensei_activity_ids()
256
257
258
	/**
259
	 * Delete Sensei activities.
260
	 * @access public
261
	 * @since  1.0.0
262
	 * @param  array $args (default: array())
263
	 * @return boolean
264
	 */
265
	public static function sensei_delete_activities ( $args = array() ) {
266
267
		$dataset_changes = false;
268
269
		// If activity exists remove activity from log
270
		$comments = WooThemes_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 );
271
		if( $comments ) {
272
			// Need to always return an array, even with only 1 item
273
			if ( !is_array( $comments ) ) {
274
				$comments = array( $comments );
275
			}
276
			foreach ( $comments as $key => $value  ) {
277
				if ( isset( $value->comment_ID ) && 0 < $value->comment_ID ) {
278
					$dataset_changes = wp_delete_comment( intval( $value->comment_ID ), true );
279
				} // End If Statement
280
			} // End For Loop
281
			// Manually flush the cache
282
			wp_cache_flush();
283
		} // End If Statement
284
		return $dataset_changes;
285
	} // End sensei_delete_activities()
286
287
    /**
288
     * Delete all activity for specified user
289
     * @access public
290
	 * @since  1.5.0
291
     * @param  integer $user_id User ID
292
     * @return boolean
293
     */
294
    public static function delete_all_user_activity( $user_id = 0 ) {
295
296
    	$dataset_changes = false;
297
298
    	if( $user_id ) {
299
300
			$activities = WooThemes_Sensei_Utils::sensei_check_for_activity( array( 'user_id' => $user_id ), true );
301
302
			if( $activities ) {
303
304
				// Need to always return an array, even with only 1 item
305
				if ( ! is_array( $activities ) ) {
306
					$activities = array( $activities );
307
				}
308
309
				foreach( $activities as $activity ) {
310
					if( '' == $activity->comment_type ) continue;
311
					if( strpos( 'sensei_', $activity->comment_type ) != 0 ) continue;
312
					$dataset_changes = wp_delete_comment( intval( $activity->comment_ID ), true );
313
					wp_cache_flush();
314
				}
315
			}
316
		}
317
318
		return $dataset_changes;
319
	} // Edn delete_all_user_activity()
320
321
322
	/**
323
	 * Get value for a specified activity.
324
	 * @access public
325
	 * @since  1.0.0
326
	 * @param  array $args (default: array())
327
	 * @return string
328
	 */
329
	public static function sensei_get_activity_value ( $args = array() ) {
330
331
332
		$activity_value = false;
333
		if ( !empty($args['field']) ) {
334
			$comment = WooThemes_Sensei_Utils::sensei_check_for_activity( $args, true );
335
336
			if ( isset( $comment->{$args['field']} ) && '' != $comment->{$args['field']} ) {
337
				$activity_value = $comment->{$args['field']};
338
			} // End If Statement
339
		}
340
		return $activity_value;
341
	} // End sensei_get_activity_value()
342
343
    /**
344
     * Checks if a user (by email) has bought an item.
345
     *
346
     * @deprecated since 1.9.0 use Sensei_WC::has_customer_bought_product($user_id, $product_id)
347
     * @access public
348
     * @since  1.0.0
349
     * @param  string $customer_email
350
     * @param  int $user_id
351
     * @param  int $product_id
352
     * @return bool
353
     */
354
    public static function sensei_customer_bought_product ( $customer_email, $user_id, $product_id ) {
355
356
        $emails = array();
357
358
        if ( $user_id ) {
359
            $user = get_user_by( 'id', intval( $user_id ) );
360
            $emails[] = $user->user_email;
361
        }
362
363
        if ( is_email( $customer_email ) )
364
            $emails[] = $customer_email;
365
366
        if ( sizeof( $emails ) == 0 )
367
            return false;
368
369
        return Sensei_WC::has_customer_bought_product( $user_id, $product_id );
370
371
    } // End sensei_customer_bought_product()
372
373
	/**
374
	 * Load the WordPress rich text editor
375
	 * @param  string $content    Initial content for editor
376
	 * @param  string $editor_id  ID of editor (only lower case characters - no spaces, underscores, hyphens, etc.)
377
	 * @param  string $input_name Name for textarea form element
378
	 * @return void
379
	 */
380
	public static function sensei_text_editor( $content = '', $editor_id = 'senseitexteditor', $input_name = '' ) {
381
382
		if( ! $input_name ) $input_name = $editor_id;
383
384
		$buttons = 'bold,italic,underline,strikethrough,blockquote,bullist,numlist,justifyleft,justifycenter,justifyright,undo,redo,pastetext';
385
386
		$settings = array(
387
			'media_buttons' => false,
388
			'wpautop' => true,
389
			'textarea_name' => $input_name,
390
			'editor_class' => 'sensei_text_editor',
391
			'teeny' => false,
392
			'dfw' => false,
393
			'tinymce' => array(
394
				'theme_advanced_buttons1' => $buttons,
395
				'theme_advanced_buttons2' => ''
396
			),
397
			'quicktags' => false
398
		);
399
400
		wp_editor( $content, $editor_id, $settings );
401
402
	} // End sensei_text_editor()
403
404
	/**
405
	 * Save quiz answers submitted by users
406
	 * @param  boolean $submitted User's quiz answers
407
	 * @return boolean            Whether the answers were saved or not
408
	 */
409
	public static function sensei_save_quiz_answers( $submitted = false, $user_id = 0 ) {
410
		if( intval( $user_id ) == 0 ) {
411
			$user_id = get_current_user_id();
412
		}
413
414
		$answers_saved = false;
415
416
		if( $submitted && intval( $user_id ) > 0 ) {
417
418
			foreach( $submitted as $question_id => $answer ) {
0 ignored issues
show
Bug introduced by
The expression $submitted of type boolean is not traversable.
Loading history...
419
420
				// Get question type
421
				$question_type = Sensei()->question->get_question_type( $question_id );
422
423
				// Sanitise answer
424
				if( 0 == get_magic_quotes_gpc() ) {
425
					$answer = wp_unslash( $answer );
426
				}
427
				switch( $question_type ) {
428
					case 'multi-line': $answer = nl2br( $answer ); break;
429
					case 'single-line': break;
430
					case 'gap-fill': break;
431
					default: $answer = maybe_serialize( $answer ); break;
432
				}
433
				$args = array(
434
							'post_id' => $question_id,
435
							'data' => base64_encode( $answer ),
436
							'type' => 'sensei_user_answer', /* FIELD SIZE 20 */
437
							'user_id' => $user_id,
438
							'action' => 'update'
439
						);
440
				$answers_saved = WooThemes_Sensei_Utils::sensei_log_activity( $args );
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $answers_saved is correct as \WooThemes_Sensei_Utils:...sei_log_activity($args) (which targets Sensei_Utils::sensei_log_activity()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
441
			}
442
443
			// Handle file upload questions
444
			if( isset( $_FILES ) ) {
445
				foreach( $_FILES as $field => $file ) {
446
					if( strpos( $field, 'file_upload_' ) !== false ) {
447
						$question_id = str_replace( 'file_upload_', '', $field );
448
						if( $file && $question_id ) {
449
							$attachment_id = self::upload_file( $file );
450
							if( $attachment_id ) {
451
								$args = array(
452
									'post_id' => $question_id,
453
									'data' => base64_encode( $attachment_id ),
454
									'type' => 'sensei_user_answer', /* FIELD SIZE 20 */
455
									'user_id' => $user_id,
456
									'action' => 'update'
457
								);
458
								$answers_saved = WooThemes_Sensei_Utils::sensei_log_activity( $args );
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $answers_saved is correct as \WooThemes_Sensei_Utils:...sei_log_activity($args) (which targets Sensei_Utils::sensei_log_activity()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
459
							}
460
						}
461
					}
462
				}
463
			}
464
		}
465
466
		return $answers_saved;
467
468
	} // End sensei_save_quiz_answers()
469
470
	public static function upload_file( $file = array() ) {
471
472
		require_once( ABSPATH . 'wp-admin/includes/admin.php' );
473
474
        /**
475
         * Filter the data array for the Sensei wp_handle_upload function call
476
         *
477
         * This filter was mainly added for Unit Testing purposes.
478
         *
479
         * @since 1.7.4
480
         *
481
         * @param array  $file_upload_args {
482
         *      array of current values
483
         *
484
         *     @type string test_form set to false by default
485
         * }
486
         */
487
        $file_upload_args = apply_filters( 'sensei_file_upload_args', array('test_form' => false ) );
488
489
        $file_return = wp_handle_upload( $file, $file_upload_args );
490
491
        if( isset( $file_return['error'] ) || isset( $file_return['upload_error_handler'] ) ) {
492
            return false;
493
        } else {
494
495
            $filename = $file_return['file'];
496
497
            $attachment = array(
498
                'post_mime_type' => $file_return['type'],
499
                'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $filename ) ),
500
                'post_content' => '',
501
                'post_status' => 'inherit',
502
                'guid' => $file_return['url']
503
            );
504
505
            $attachment_id = wp_insert_attachment( $attachment, $filename );
506
507
            require_once(ABSPATH . 'wp-admin/includes/image.php');
508
            $attachment_data = wp_generate_attachment_metadata( $attachment_id, $filename );
509
            wp_update_attachment_metadata( $attachment_id, $attachment_data );
510
511
            if( 0 < intval( $attachment_id ) ) {
512
            	return $attachment_id;
513
            }
514
        }
515
516
        return false;
517
	}
518
519
	/**
520
	 * Grade quiz automatically
521
     *
522
     * This function grades each question automatically if the are auto gradable.
523
     * It store all question grades.
524
     *
525
     * @deprecated since 1.7.4 use WooThemes_Sensei_Grading::grade_quiz_auto instead
526
     *
527
	 * @param  integer $quiz_id         ID of quiz
528
	 * @param  array $submitted questions id ans answers {
529
     *          @type int $question_id
530
     *          @type mixed $answer
531
     * }
532
	 * @param  integer $total_questions Total questions in quiz (not used)
533
     * @param string $quiz_grade_type Optional defaults to auto
534
     *
535
	 * @return int $quiz_grade total sum of all question grades
536
	 */
537
	public static function sensei_grade_quiz_auto( $quiz_id = 0, $submitted = array(), $total_questions = 0, $quiz_grade_type = 'auto' ) {
538
539
        return Sensei_Grading::grade_quiz_auto( $quiz_id, $submitted, $total_questions, $quiz_grade_type );
540
541
	} // End sensei_grade_quiz_auto()
542
543
	/**
544
	 * Grade quiz
545
	 * @param  integer $quiz_id ID of quiz
546
	 * @param  integer $grade   Grade received
547
	 * @param  integer $user_id ID of user being graded
548
	 * @return boolean
549
	 */
550
	public static function sensei_grade_quiz( $quiz_id = 0, $grade = 0, $user_id = 0, $quiz_grade_type = 'auto' ) {
551
		if( intval( $user_id ) == 0 ) {
552
			$user_id = get_current_user_id();
553
		}
554
555
		$activity_logged = false;
556
		if( intval( $quiz_id ) > 0 && intval( $user_id ) > 0 ) {
557
			$lesson_id = get_post_meta( $quiz_id, '_quiz_lesson', true );
558
			$user_lesson_status = WooThemes_Sensei_Utils::user_lesson_status( $lesson_id, $user_id );
559
			$activity_logged = update_comment_meta( $user_lesson_status->comment_ID, 'grade', $grade );
560
561
			$quiz_passmark = absint( get_post_meta( $quiz_id, '_quiz_passmark', true ) );
562
563
			do_action( 'sensei_user_quiz_grade', $user_id, $quiz_id, $grade, $quiz_passmark, $quiz_grade_type );
564
		}
565
566
		return $activity_logged;
567
	}
568
569
	/**
570
	 * Grade question automatically
571
     *
572
     * This function checks the question typ and then grades it accordingly.
573
     *
574
     * @deprecated since 1.7.4 use WooThemes_Sensei_Grading::grade_question_auto instead
575
     *
576
	 * @param integer $question_id
577
     * @param string $question_type of the standard Sensei question types
578
	 * @param string $answer
579
     * @param int $user_id
580
     *
581
	 * @return int $question_grade
582
	 */
583
	public static function sensei_grade_question_auto( $question_id = 0, $question_type = '', $answer = '', $user_id = 0 ) {
584
585
       return  WooThemes_Sensei_Grading::grade_question_auto( $question_id, $question_type, $answer, $user_id  );
586
587
	} // end sensei_grade_question_auto
588
589
	/**
590
	 * Grade question
591
	 * @param  integer $question_id ID of question
592
	 * @param  integer $grade       Grade received
593
	 * @return boolean
594
	 */
595
	public static function sensei_grade_question( $question_id = 0, $grade = 0, $user_id = 0 ) {
596
		if( intval( $user_id ) == 0 ) {
597
			$user_id = get_current_user_id();
598
		}
599
600
		$activity_logged = false;
601
		if( intval( $question_id ) > 0 && intval( $user_id ) > 0 ) {
602
603
			$user_answer_id = WooThemes_Sensei_Utils::sensei_get_activity_value( array( 'post_id' => $question_id, 'user_id' => $user_id, 'type' => 'sensei_user_answer', 'field' => 'comment_ID' ) );
604
			$activity_logged = update_comment_meta( $user_answer_id, 'user_grade', $grade );
605
606
			$answer_notes = get_post_meta( $question_id, '_answer_feedback', true );
607
			if ( !empty($answer_notes) ) {
608
				update_comment_meta( $user_answer_id, 'answer_note', base64_encode( $answer_notes ) );
609
			}
610
611
		}
612
613
		return $activity_logged;
614
	}
615
616
	public static function sensei_delete_question_grade( $question_id = 0, $user_id = 0 ) {
617
		if( intval( $user_id ) == 0 ) {
618
			$user_id = get_current_user_id();
619
		}
620
621
		$activity_logged = false;
622
		if( intval( $question_id ) > 0 ) {
623
			$user_answer_id = WooThemes_Sensei_Utils::sensei_get_activity_value( array( 'post_id' => $question_id, 'user_id' => $user_id, 'type' => 'sensei_user_answer', 'field' => 'comment_ID' ) );
624
			$activity_logged = delete_comment_meta( $user_answer_id, 'user_grade' );
625
		}
626
627
		return $activity_logged;
628
	}
629
630
631
    /**
632
     * Alias to Woothemes_Sensei_Utils::sensei_start_lesson
633
     *
634
     * @since 1.7.4
635
     *
636
     * @param integer $user_id
637
     * @param integer $lesson_id
638
     * @param bool $complete
639
     *
640
     * @return mixed boolean or comment_ID
641
     */
642
    public static function user_start_lesson(  $user_id = 0, $lesson_id = 0, $complete = false ) {
643
644
        return self::sensei_start_lesson( $lesson_id, $user_id, $complete );
645
646
    }// end user_start_lesson()
647
648
	/**
649
	 * Mark a lesson as started for user
650
     *
651
     * Will also start the lesson course for the user if the user hans't started taking it already.
652
     *
653
     * @since 1.6.0
654
     *
655
	 * @param  integer $lesson_id ID of lesson
656
	 * @param int| string $user_id default 0
657
     * @param bool $complete default false
658
     *
659
     * @return mixed boolean or comment_ID
660
	 */
661
	public static function sensei_start_lesson( $lesson_id = 0, $user_id = 0, $complete = false ) {
662
663
664
		if( intval( $user_id ) == 0 ) {
665
			$user_id = get_current_user_id();
666
		}
667
668
		$activity_logged = false;
669
670
		if( intval( $lesson_id ) > 0 ) {
671
672
			$course_id = get_post_meta( $lesson_id, '_lesson_course', true );
673
			if( $course_id ) {
674
				$is_user_taking_course = WooThemes_Sensei_Utils::user_started_course( $course_id, $user_id );
675
				if( ! $is_user_taking_course ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $is_user_taking_course of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
676
					WooThemes_Sensei_Utils::user_start_course( $user_id, $course_id );
677
				}
678
			}
679
680
			$metadata = array();
681
			$status = 'in-progress';
682
683
			// Note: When this action runs the lesson status may not yet exist
684
			do_action( 'sensei_user_lesson_start', $user_id, $lesson_id );
685
686
			if( $complete ) {
687
688
				$has_questions = get_post_meta( $lesson_id, '_quiz_has_questions', true );
689
				if ( $has_questions ) {
690
					$status = 'passed'; // Force a pass
691
					$metadata['grade'] = 0;
692
				}
693
				else {
694
					$status = 'complete';
695
				}
696
			}
697
698
			// Check if user is already taking the lesson
699
			$activity_logged = WooThemes_Sensei_Utils::user_started_lesson( $lesson_id, $user_id );
700
			if( ! $activity_logged ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $activity_logged of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
701
702
				$metadata['start'] = current_time('mysql');
703
				$activity_logged = WooThemes_Sensei_Utils::update_lesson_status( $user_id, $lesson_id, $status, $metadata );
704
705
            } else {
706
707
                // if users is already taking the lesson  and the status changes to complete update it
708
                $current_user_activity = get_comment($activity_logged);
709
                if( $status=='complete' &&
710
                    $status != $current_user_activity->comment_approved  ){
711
712
                    $comment = array();
713
                    $comment['comment_ID'] = $activity_logged;
714
                    $comment['comment_approved'] = $status;
715
                    wp_update_comment( $comment );
716
717
                }
718
719
            }
720
721
			if ( $complete ) {
722
				// Run this *after* the lesson status has been created/updated
723
				do_action( 'sensei_user_lesson_end', $user_id, $lesson_id );
724
			}
725
726
		}
727
728
		return $activity_logged;
729
	}
730
731
	/**
732
	 * Remove user from lesson, deleting all data from the corresponding quiz
733
	 *
734
	 * @param type $lesson_id
735
	 * @param type $user_id
736
	 * @return boolean
737
	 */
738 View Code Duplication
	public static function sensei_remove_user_from_lesson( $lesson_id = 0, $user_id = 0, $from_course = false ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
739
740
741
		if( ! $lesson_id ) return false;
742
743
		if( intval( $user_id ) == 0 ) {
744
			$user_id = get_current_user_id();
745
		}
746
747
		// Process quiz
748
		$lesson_quiz_id = Sensei()->lesson->lesson_quizzes( $lesson_id );
749
750
		// Delete quiz answers, this auto deletes the corresponding meta data, such as the question/answer grade
751
		WooThemes_Sensei_Utils::sensei_delete_quiz_answers( $lesson_quiz_id, $user_id );
752
753
		// Delete lesson status
754
		$args = array(
755
			'post_id' => $lesson_id,
756
			'type' => 'sensei_lesson_status',
757
			'user_id' => $user_id,
758
		);
759
		// This auto deletes the corresponding meta data, such as the quiz grade, and questions asked
760
		WooThemes_Sensei_Utils::sensei_delete_activities( $args );
761
762
		if( ! $from_course ) {
763
			do_action( 'sensei_user_lesson_reset', $user_id, $lesson_id );
764
		}
765
766
		return true;
767
	}
768
769
	/**
770
	 * Remove a user from a course, deleting all activities across all lessons
771
	 *
772
	 * @param type $course_id
773
	 * @param type $user_id
774
	 * @return boolean
775
	 */
776 View Code Duplication
	public static function sensei_remove_user_from_course( $course_id = 0, $user_id = 0 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
777
778
779
		if( ! $course_id ) return false;
780
781
		if( intval( $user_id ) == 0 ) {
782
			$user_id = get_current_user_id();
783
		}
784
785
		$lesson_ids = Sensei()->course->course_lessons( $course_id, 'any', 'ids' );
786
787
		foreach( $lesson_ids as $lesson_id ) {
788
			WooThemes_Sensei_Utils::sensei_remove_user_from_lesson( $lesson_id, $user_id, true );
789
		}
790
791
		// Delete course status
792
		$args = array(
793
			'post_id' => $course_id,
794
			'type' => 'sensei_course_status',
795
			'user_id' => $user_id,
796
		);
797
798
		WooThemes_Sensei_Utils::sensei_delete_activities( $args );
799
800
		do_action( 'sensei_user_course_reset', $user_id, $course_id );
801
802
		return true;
803
	}
804
805
	public static function sensei_get_quiz_questions( $quiz_id = 0 ) {
806
807
808
		$questions = array();
809
810
		if( intval( $quiz_id ) > 0 ) {
811
			$questions = Sensei()->lesson->lesson_quiz_questions( $quiz_id );
812
			$questions = WooThemes_Sensei_Utils::array_sort_reorder( $questions );
813
		}
814
815
		return $questions;
816
	}
817
818
	public static function sensei_get_quiz_total( $quiz_id = 0 ) {
819
820
821
		$quiz_total = 0;
822
823
		if( $quiz_id > 0 ) {
824
			$questions = WooThemes_Sensei_Utils::sensei_get_quiz_questions( $quiz_id );
825
			$question_grade = 0;
0 ignored issues
show
Unused Code introduced by
$question_grade is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
826
			foreach( $questions as $question ) {
827
				$question_grade = Sensei()->question->get_question_grade( $question->ID );
828
				$quiz_total += $question_grade;
829
			}
830
		}
831
832
		return $quiz_total;
833
	}
834
835
	/**
836
	 * Returns the user_grade for a specific question and user, or sensei_user_answer entry
837
	 *
838
	 * @param mixed $question
839
	 * @param int $user_id
840
	 * @return string
841
	 */
842 View Code Duplication
	public static function sensei_get_user_question_grade( $question = 0, $user_id = 0 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
843
		$question_grade = false;
844
		if( $question ) {
845
			if ( is_object( $question ) ) {
846
				$user_answer_id = $question->comment_ID;
847
			}
848
			else {
849
				if( intval( $user_id ) == 0 ) {
850
					$user_id = get_current_user_id();
851
				}
852
				$user_answer_id = WooThemes_Sensei_Utils::sensei_get_activity_value( array( 'post_id' => intval($question), 'user_id' => $user_id, 'type' => 'sensei_user_answer', 'field' => 'comment_ID' ) );
853
			}
854
			if ( $user_answer_id ) {
855
				$question_grade = get_comment_meta( $user_answer_id, 'user_grade', true );
856
			}
857
		}
858
859
		return $question_grade;
860
	}
861
862
	/**
863
	 * Returns the answer_notes for a specific question and user, or sensei_user_answer entry
864
	 *
865
     * @deprecated since 1.7.5 use Sensei()->quiz->get_user_question_feedback instead
866
	 * @param mixed $question
867
	 * @param int $user_id
868
	 * @return string
869
	 */
870 View Code Duplication
	public static function sensei_get_user_question_answer_notes( $question = 0, $user_id = 0 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
871
		$answer_notes = false;
872
		if( $question ) {
873
			if ( is_object( $question ) ) {
874
				$user_answer_id = $question->comment_ID;
875
			}
876
			else {
877
				if( intval( $user_id ) == 0 ) {
878
					$user_id = get_current_user_id();
879
				}
880
				$user_answer_id = WooThemes_Sensei_Utils::sensei_get_activity_value( array( 'post_id' => intval($question), 'user_id' => $user_id, 'type' => 'sensei_user_answer', 'field' => 'comment_ID' ) );
881
			}
882
			if ( $user_answer_id ) {
883
				$answer_notes = base64_decode( get_comment_meta( $user_answer_id, 'answer_note', true ) );
884
			}
885
		}
886
887
		return $answer_notes;
888
	}
889
890
	public static function sensei_delete_quiz_answers( $quiz_id = 0, $user_id = 0 ) {
891
		if( intval( $user_id ) == 0 ) {
892
			$user_id = get_current_user_id();
893
		}
894
895
		$delete_answers = false;
896
		if( intval( $quiz_id ) > 0 ) {
897
			$questions = WooThemes_Sensei_Utils::sensei_get_quiz_questions( $quiz_id );
898
			foreach( $questions as $question ) {
899
				$delete_answers = WooThemes_Sensei_Utils::sensei_delete_activities( array( 'post_id' => $question->ID, 'user_id' => $user_id, 'type' => 'sensei_user_answer' ) );
900
			}
901
		}
902
903
		return $delete_answers;
904
	}
905
906
	public static function sensei_delete_quiz_grade( $quiz_id = 0, $user_id = 0 ) {
907
		if( intval( $user_id ) == 0 ) {
908
			$user_id = get_current_user_id();
909
		}
910
911
		$delete_grade = false;
912
		if( intval( $quiz_id ) > 0 ) {
913
			$lesson_id = get_post_meta( $quiz_id, '_quiz_lesson', true );
914
			$user_lesson_status = WooThemes_Sensei_Utils::user_lesson_status( $lesson_id, $user_id );
915
			$delete_grade = delete_comment_meta( $user_lesson_status->comment_ID, 'grade' );
916
		}
917
918
		return $delete_grade;
919
	}
920
921
	/**
922
	 * Add answer notes to question
923
	 * @param  integer $question_id ID of question
924
	 * @param  integer $user_id     ID of user
925
	 * @return boolean
926
	 */
927
	public static function sensei_add_answer_notes( $question_id = 0, $user_id = 0, $notes = '' ) {
928
		if( intval( $user_id ) == 0 ) {
929
			$user_id = get_current_user_id();
930
		}
931
932
		$activity_logged = false;
933
934
		if( intval( $question_id ) > 0 ) {
935
			$notes = base64_encode( $notes );
936
937
			// Don't store empty values, no point
938
			if ( !empty($notes) ) {
939
				$user_lesson_id = WooThemes_Sensei_Utils::sensei_get_activity_value( array( 'post_id' => $question_id, 'user_id' => $user_id, 'type' => 'sensei_user_answer', 'field' => 'comment_ID' ) );
940
				$activity_logged = update_comment_meta( $user_lesson_id, 'answer_note', $notes );
941
			}
942
			else {
943
				$activity_logged = true;
944
			}
945
		}
946
947
		return $activity_logged;
948
	}
949
950
	/**
951
	 * array_sort_reorder handle sorting of table data
952
	 * @since  1.3.0
953
	 * @param  array $return_array data to be ordered
954
	 * @return array $return_array ordered data
955
	 */
956
	public static function array_sort_reorder( $return_array ) {
957
		if ( isset( $_GET['orderby'] ) && '' != esc_html( $_GET['orderby'] ) ) {
958
			$sort_key = '';
959
			// if ( array_key_exists( esc_html( $_GET['orderby'] ), $this->sortable_columns ) ) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
960
			// 	$sort_key = esc_html( $_GET['orderby'] );
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
961
			// } // End If Statement
962
			if ( '' != $sort_key ) {
963
					WooThemes_Sensei_Utils::sort_array_by_key($return_array,$sort_key);
964
				if ( isset( $_GET['order'] ) && 'desc' == esc_html( $_GET['order'] ) ) {
965
					$return_array = array_reverse( $return_array, true );
966
				} // End If Statement
967
			} // End If Statement
968
			return $return_array;
969
		} else {
970
			return $return_array;
971
		} // End If Statement
972
	} // End array_sort_reorder()
973
974
	/**
975
	 * sort_array_by_key sorts array by key
976
	 * @since  1.3.0
977
	 * @param  $array by ref
978
	 * @param  $key string column name in array
979
	 * @return void
980
	 */
981
	public static function sort_array_by_key( $array, $key ) {
982
	    $sorter = array();
983
	    $ret = array();
984
	    reset( $array );
985
	    foreach ( $array as $ii => $va ) {
986
	        $sorter[$ii] = $va[$key];
987
	    } // End For Loop
988
	    asort( $sorter );
989
	    foreach ( $sorter as $ii => $va ) {
990
	        $ret[$ii] = $array[$ii];
991
	    } // End For Loop
992
	    $array = $ret;
0 ignored issues
show
Unused Code introduced by
$array is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
993
	} // End sort_array_by_key()
994
995
	/**
996
	 * This function returns an array of lesson quiz questions
997
	 * @since  1.3.2
998
	 * @param  integer $quiz_id
999
	 * @return array of quiz questions
1000
	 */
1001 View Code Duplication
	public static function lesson_quiz_questions( $quiz_id = 0 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1002
		$questions_array = array();
1003
		if ( 0 < $quiz_id ) {
1004
			$question_args = array( 'post_type'         => 'question',
1005
                                    'posts_per_page'       => -1,
1006
                                    'orderby'           => 'ID',
1007
                                    'order'             => 'ASC',
1008
                                    'meta_query'		=> array(
1009
										array(
1010
											'key'       => '_quiz_id',
1011
											'value'     => $quiz_id,
1012
										)
1013
									),
1014
                                    'post_status'       => 'any',
1015
                                    'suppress_filters'  => 0
1016
                                );
1017
            $questions_array = get_posts( $question_args );
1018
        } // End If Statement
1019
        return $questions_array;
1020
	} // End lesson_quiz_questions()
1021
1022
	/**
1023
	 * Get pass mark for course
1024
	 * @param  integer $course_id ID of course
1025
	 * @return integer            Pass mark for course
1026
	 */
1027
	public static function sensei_course_pass_grade( $course_id = 0 ) {
1028
1029
1030
		$course_passmark = 0;
1031
1032
		if( $course_id > 0 ) {
1033
			$lessons = Sensei()->course->course_lessons( $course_id );
1034
			$lesson_count = 0;
1035
			$total_passmark = 0;
1036
			foreach( $lessons as $lesson ) {
1037
1038
				// Get Quiz ID
1039
				$quiz_id = Sensei()->lesson->lesson_quizzes( $lesson->ID );
1040
1041
				// Check for a pass being required
1042
				$pass_required = get_post_meta( $quiz_id, '_pass_required', true );
1043
				if ( $pass_required ) {
1044
					// Get quiz passmark
1045
					$quiz_passmark = absint( get_post_meta( $quiz_id, '_quiz_passmark', true ) );
1046
1047
					// Add up total passmark
1048
					$total_passmark += $quiz_passmark;
1049
1050
					++$lesson_count;
1051
				}
1052
			}
1053
			// Might be a case of no required lessons
1054
			if ( $lesson_count ) {
1055
				$course_passmark = ( $total_passmark / $lesson_count );
1056
			}
1057
		}
1058
1059
		return Woothemes_Sensei_Utils::round( $course_passmark );
1060
	}
1061
1062
	/**
1063
	 * Get user total grade for course
1064
	 * @param  integer $course_id ID of course
1065
	 * @param  integer $user_id   ID of user
1066
	 * @return integer            User's total grade
1067
	 */
1068
	public static function sensei_course_user_grade( $course_id = 0, $user_id = 0 ) {
1069
1070
1071
		if( intval( $user_id ) == 0 ) {
1072
			$user_id = get_current_user_id();
1073
		}
1074
1075
		$total_grade = 0;
1076
1077
		if( $course_id > 0 && $user_id > 0 ) {
1078
			$lessons = Sensei()->course->course_lessons( $course_id );
1079
			$lesson_count = 0;
1080
			$total_grade = 0;
1081
			foreach( $lessons as $lesson ) {
1082
1083
				// Check for lesson having questions, thus a quiz, thus having a grade
1084
				$has_questions = get_post_meta( $lesson->ID, '_quiz_has_questions', true );
1085
				if ( $has_questions ) {
1086
					$user_lesson_status = WooThemes_Sensei_Utils::user_lesson_status( $lesson->ID, $user_id );
1087
					// Get user quiz grade
1088
					$quiz_grade = get_comment_meta( $user_lesson_status->comment_ID, 'grade', true );
1089
1090
					// Add up total grade
1091
					$total_grade += $quiz_grade;
1092
1093
					++$lesson_count;
1094
				}
1095
			}
1096
1097
			// Might be a case of no lessons with quizzes
1098
			if ( $lesson_count ) {
1099
				$total_grade = ( $total_grade / $lesson_count );
1100
			}
1101
1102
		}
1103
1104
		return WooThemes_Sensei_Utils::round( $total_grade );
1105
	}
1106
1107
	/**
1108
	 * Check if user has passed a course
1109
	 * @param  integer $course_id ID of course
1110
	 * @param  integer $user_id   ID of user
1111
	 * @return boolean
1112
	 */
1113
	public static function sensei_user_passed_course( $course_id = 0, $user_id = 0 ) {
1114
		if( intval( $user_id ) == 0 ) {
1115
			$user_id = get_current_user_id();
1116
		}
1117
1118
		$pass = false;
1119
1120
		if( $course_id > 0 && $user_id > 0 ) {
1121
			$passmark = WooThemes_Sensei_Utils::sensei_course_pass_grade( $course_id );
1122
			$user_grade = WooThemes_Sensei_Utils::sensei_course_user_grade( $course_id, $user_id );
1123
1124
			if( $user_grade >= $passmark ) {
1125
				$pass = true;
1126
			}
1127
		}
1128
1129
		return $pass; // Should add the $passmark and $user_grade as part of the return!
1130
1131
	}
1132
1133
	/**
1134
	 * Set the status message displayed to the user for a course
1135
	 * @param  integer $course_id ID of course
1136
	 * @param  integer $user_id   ID of user
1137
	 * @return array              Status code and message
1138
	 */
1139
	public static function sensei_user_course_status_message( $course_id = 0, $user_id = 0 ) {
1140
		if( intval( $user_id ) == 0 ) {
1141
			$user_id = get_current_user_id();
1142
		}
1143
1144
		$status = 'not_started';
1145
		$box_class = 'info';
1146
		$message = __( 'You have not started this course yet.', 'woothemes-sensei' );
1147
1148
		if( $course_id > 0 && $user_id > 0 ) {
1149
1150
			$started_course = WooThemes_Sensei_Utils::user_started_course( $course_id, $user_id );
1151
1152
			if( $started_course ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $started_course of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1153
				$passmark = WooThemes_Sensei_Utils::sensei_course_pass_grade( $course_id ); // This happens inside sensei_user_passed_course()!
1154
				$user_grade = WooThemes_Sensei_Utils::sensei_course_user_grade( $course_id, $user_id ); // This happens inside sensei_user_passed_course()!
1155
				if( $user_grade >= $passmark ) {
1156
					$status = 'passed';
1157
					$box_class = 'tick';
1158
					$message = sprintf( __( 'You have passed this course with a grade of %1$d%%.', 'woothemes-sensei' ), $user_grade );
1159
				} else {
1160
					$status = 'failed';
1161
					$box_class = 'alert';
1162
					$message = sprintf( __( 'You require %1$d%% to pass this course. Your grade is %2$s%%.', 'woothemes-sensei' ), $passmark, $user_grade );
1163
				}
1164
			}
1165
1166
		}
1167
1168
		$message = apply_filters( 'sensei_user_course_status_' . $status, $message );
1169
1170
		return array( 'status' => $status, 'box_class' => $box_class, 'message' => $message );
1171
	}
1172
1173
	/**
1174
	 * Set the status message displayed to the user for a quiz
1175
	 * @param  integer $lesson_id ID of quiz lesson
1176
	 * @param  integer $user_id   ID of user
1177
	 * @return array              Status code and message
1178
	 */
1179
	public static function sensei_user_quiz_status_message( $lesson_id = 0, $user_id = 0, $is_lesson = false ) {
1180
		global  $current_user;
1181
		if( intval( $user_id ) == 0 ) {
1182
			$user_id = $current_user->ID;
1183
		}
1184
1185
		$status = 'not_started';
1186
		$box_class = 'info';
1187
		$message = __( "You have not taken this lesson's quiz yet", 'woothemes-sensei' );
1188
		$extra = '';
1189
1190
		if( $lesson_id > 0 && $user_id > 0 ) {
1191
1192
			// Prerequisite lesson
1193
			$prerequisite = get_post_meta( $lesson_id, '_lesson_prerequisite', true );
0 ignored issues
show
Unused Code introduced by
$prerequisite is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1194
1195
			// Course ID
1196
			$course_id = absint( get_post_meta( $lesson_id, '_lesson_course', true ) );
1197
1198
			// Has user started course
1199
			$started_course = WooThemes_Sensei_Utils::user_started_course( $course_id, $user_id );
1200
1201
			// Has user completed lesson
1202
			$user_lesson_status = WooThemes_Sensei_Utils::user_lesson_status( $lesson_id, $user_id );
1203
			$lesson_complete = WooThemes_Sensei_Utils::user_completed_lesson( $user_lesson_status );
0 ignored issues
show
Documentation introduced by
$user_lesson_status is of type object, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1204
1205
			// Quiz ID
1206
			$quiz_id = Sensei()->lesson->lesson_quizzes( $lesson_id );
1207
1208
			// Quiz grade
1209
			$quiz_grade = 0;
1210
			if ( $user_lesson_status ) {
1211
				$quiz_grade = get_comment_meta( $user_lesson_status->comment_ID, 'grade', true );
1212
			}
1213
1214
			// Quiz passmark
1215
			$quiz_passmark = absint( get_post_meta( $quiz_id, '_quiz_passmark', true ) );
1216
			$quiz_passmark_float = (float) $quiz_passmark;
0 ignored issues
show
Unused Code introduced by
$quiz_passmark_float is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1217
1218
			// Pass required
1219
			$pass_required = get_post_meta( $quiz_id, '_pass_required', true );
1220
1221
			// Quiz questions
1222
			$has_quiz_questions = get_post_meta( $lesson_id, '_quiz_has_questions', true );
1223
1224
			if ( ! $started_course ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $started_course of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1225
1226
				$status = 'not_started_course';
1227
				$box_class = 'info';
1228
				$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>' );
1229
1230
			} elseif ( ! is_user_logged_in() ) {
1231
1232
				$status = 'login_required';
1233
				$box_class = 'info';
1234
				$message = __( 'You must be logged in to take this quiz', 'woothemes-sensei' );
1235
1236
			}
1237
			// Lesson/Quiz is marked as complete thus passing any quiz restrictions
1238
			elseif ( $lesson_complete ) {
1239
1240
				$status = 'passed';
1241
				$box_class = 'tick';
1242
				// Lesson status will be "complete" (has no Quiz)
1243
				if ( ! $has_quiz_questions ) {
1244
					$message = sprintf( __( 'Congratulations! You have passed this lesson.', 'woothemes-sensei' ) );
1245
				}
1246
				// Lesson status will be "graded" (no passmark required so might have failed all the questions)
1247
				elseif ( empty( $quiz_grade ) ) {
1248
					$message = sprintf( __( 'Congratulations! You have completed this lesson.', 'woothemes-sensei' ) );
1249
				}
1250
				// Lesson status will be "passed" (passmark reached)
1251
				elseif ( ! empty( $quiz_grade ) && abs( $quiz_grade ) >= 0 ) {
1252 View Code Duplication
					if( $is_lesson ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1253
						$message = sprintf( __( 'Congratulations! You have passed this lesson\'s quiz achieving %s%%', 'woothemes-sensei' ), WooThemes_Sensei_Utils::round( $quiz_grade ) );
1254
					} else {
1255
						$message = sprintf( __( 'Congratulations! You have passed this quiz achieving %s%%', 'woothemes-sensei' ),  WooThemes_Sensei_Utils::round( $quiz_grade ) );
1256
					}
1257
				}
1258
1259
                // add next lesson button
1260
                $nav_id_array = sensei_get_prev_next_lessons( $lesson_id );
1261
                $next_lesson_id = absint( $nav_id_array['next_lesson'] );
1262
1263
                // Output HTML
1264
                if ( ( 0 < $next_lesson_id ) ) {
1265
                    $message .= ' ' . '<a class="next-lesson" href="' . esc_url( get_permalink( $next_lesson_id ) )
1266
                                . '" rel="next"><span class="meta-nav"></span>'. __( 'Next Lesson' ,'woothemes-sensei')
1267
                                .'</a>';
1268
1269
                }
1270
1271
			}
1272
            // Lesson/Quiz not complete
1273
			else {
1274
				// Lesson/Quiz isn't "complete" instead it's ungraded (previously this "state" meant that it *was* complete)
1275
				if ( isset( $user_lesson_status->comment_approved ) && 'ungraded' == $user_lesson_status->comment_approved ) {
1276
					$status = 'complete';
1277
					$box_class = 'info';
1278
					if( $is_lesson ) {
1279
						$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>' );
1280
					} else {
1281
						$message = sprintf( __( 'You have completed this quiz and it will be graded soon. You require %1$s%% to pass.', 'woothemes-sensei' ),  WooThemes_Sensei_Utils::round( $quiz_passmark ) );
1282
					}
1283
				}
1284
				// Lesson status must be "failed"
1285
				elseif ( isset( $user_lesson_status->comment_approved ) && 'failed' == $user_lesson_status->comment_approved ) {
1286
					$status = 'failed';
1287
					$box_class = 'alert';
1288
					if( $is_lesson ) {
1289
						$message = sprintf( __( 'You require %1$d%% to pass this lesson\'s quiz. Your grade is %2$s%%', 'woothemes-sensei' ),  WooThemes_Sensei_Utils::round( $quiz_passmark ),  WooThemes_Sensei_Utils::round( $quiz_grade ) );
1290
					} else {
1291
						$message = sprintf( __( 'You require %1$d%% to pass this quiz. Your grade is %2$s%%', 'woothemes-sensei' ),  WooThemes_Sensei_Utils::round( $quiz_passmark ),  WooThemes_Sensei_Utils::round( $quiz_grade ) );
1292
					}
1293
				}
1294
				// Lesson/Quiz requires a pass
1295
				elseif( $pass_required ) {
1296
					$status = 'not_started';
1297
					$box_class = 'info';
1298 View Code Duplication
					if( $is_lesson ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1299
						$message = sprintf( __( 'You require %1$d%% to pass this lesson\'s quiz.', 'woothemes-sensei' ),  WooThemes_Sensei_Utils::round( $quiz_passmark ) );
1300
					} else {
1301
						$message = sprintf( __( 'You require %1$d%% to pass this quiz.', 'woothemes-sensei' ),  WooThemes_Sensei_Utils::round( $quiz_passmark ) );
1302
					}
1303
				}
1304
			}
1305
1306
		}
1307
1308
		// Legacy filter
1309
		$message = apply_filters( 'sensei_user_quiz_status_' . $status, $message );
1310
1311
		if( $is_lesson && ! in_array( $status, array( 'login_required', 'not_started_course' ) ) ) {
1312
			$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>';
0 ignored issues
show
Bug introduced by
The variable $quiz_id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1313
		}
1314
1315
		// Filter of all messages
1316
		return apply_filters( 'sensei_user_quiz_status', array( 'status' => $status, 'box_class' => $box_class, 'message' => $message, 'extra' => $extra ), $lesson_id, $user_id, $is_lesson );
1317
	}
1318
1319
	/**
1320
	 * Start course for user
1321
	 * @since  1.4.8
1322
	 * @param  integer $user_id   User ID
1323
	 * @param  integer $course_id Course ID
1324
	 * @return mixed boolean or comment_ID
1325
	 */
1326
	public static function user_start_course( $user_id = 0, $course_id = 0 ) {
1327
1328
		$activity_logged = false;
1329
1330
		if( $user_id && $course_id ) {
1331
			// Check if user is already on the Course
1332
			$activity_logged = WooThemes_Sensei_Utils::user_started_course( $course_id, $user_id );
1333
			if ( ! $activity_logged ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $activity_logged of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1334
1335
				// Add user to course
1336
				$course_metadata = array(
1337
					'start' => current_time('mysql'),
1338
					'percent' => 0, // No completed lessons yet
1339
					'complete' => 0,
1340
				);
1341
1342
				$activity_logged = WooThemes_Sensei_Utils::update_course_status( $user_id, $course_id, $course_status = 'in-progress', $course_metadata );
0 ignored issues
show
Documentation introduced by
$user_id is of type integer, but the function expects a object<type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$course_id is of type integer, but the function expects a object<type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1343
1344
				// Allow further actions
1345
				if ( $activity_logged ) {
1346
					do_action( 'sensei_user_course_start', $user_id, $course_id );
1347
				}
1348
			}
1349
		}
1350
1351
		return $activity_logged;
1352
	}
1353
1354
	/**
1355
	 * Check if a user has started a course or not
1356
	 *
1357
	 * @since  1.7.0
1358
	 * @param type $course_id
1359
	 * @param type $user_id
1360
	 * @return mixed false or comment_ID
1361
	 */
1362
	public static function user_started_course( $course_id = 0, $user_id = 0 ) {
1363
1364
		if( $course_id ) {
1365
			if( ! $user_id ) {
1366
				$user_id = get_current_user_id();
1367
			}
1368
1369
            if( ! $user_id > 0 ){
1370
                return false;
1371
            }
1372
1373
			$user_course_status_id = WooThemes_Sensei_Utils::sensei_get_activity_value( array( 'post_id' => $course_id, 'user_id' => $user_id, 'type' => 'sensei_course_status', 'field' => 'comment_ID' ) );
1374
			if( $user_course_status_id ) {
1375
				return $user_course_status_id;
1376
			}
1377
		}
1378
		return false;
1379
	}
1380
1381
	/**
1382
	 * Checks if a user has completed a course by checking every lesson status
1383
	 *
1384
	 * @since  1.7.0
1385
	 * @param  integer $course_id Course ID
1386
	 * @param  integer $user_id   User ID
1387
	 * @return int
1388
	 */
1389
	public static function user_complete_course( $course_id = 0, $user_id = 0 ) {
1390
		global  $wp_version;
1391
1392
		if( $course_id ) {
1393
			if( ! $user_id ) {
1394
				$user_id = get_current_user_id();
1395
			}
1396
1397
			$course_status = 'in-progress';
1398
			$course_metadata = array();
1399
			$course_completion = Sensei()->settings->settings[ 'course_completion' ];
1400
			$lessons_completed = $total_lessons = 0;
0 ignored issues
show
Unused Code introduced by
$total_lessons is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1401
			$lesson_status_args = array(
1402
					'user_id' => $user_id,
1403
					'status' => 'any',
1404
					'type' => 'sensei_lesson_status', /* FIELD SIZE 20 */
1405
				);
1406
1407
			// Grab all of this Courses' lessons, looping through each...
1408
			$lesson_ids = Sensei()->course->course_lessons( $course_id, 'any', 'ids' );
1409
			$total_lessons = count( $lesson_ids );
1410
				// ...if course completion not set to 'passed', and all lessons are complete or graded,
1411
				// ......then all lessons are 'passed'
1412
				// ...else if course completion is set to 'passed', check if each lesson has questions...
1413
				// ......if no questions yet the status is 'complete'
1414
				// .........then the lesson is 'passed'
1415
				// ......else if questions check the lesson status has a grade and that the grade is greater than the lesson passmark
1416
				// .........then the lesson is 'passed'
1417
				// ...if all lessons 'passed' then update the course status to complete
1418
1419
			// The below checks if a lesson is fully completed, though maybe should be Utils::user_completed_lesson()
1420
			$all_lesson_statuses = array();
1421
			// In WordPress 4.1 get_comments() allows a single query to cover multiple comment_post_IDs
1422
			if ( version_compare($wp_version, '4.1', '>=') ) {
1423
				$lesson_status_args['post__in'] = $lesson_ids;
1424
				$all_lesson_statuses = WooThemes_Sensei_Utils::sensei_check_for_activity( $lesson_status_args, true );
1425
				// Need to always return an array, even with only 1 item
1426
				if ( !is_array($all_lesson_statuses) ) {
1427
					$all_lesson_statuses = array( $all_lesson_statuses );
1428
				}
1429
			}
1430
			// ...otherwise check each one
1431
			else {
1432
				foreach( $lesson_ids as $lesson_id ) {
1433
					$lesson_status_args['post_id'] = $lesson_id;
1434
					$each_lesson_status = WooThemes_Sensei_Utils::sensei_check_for_activity( $lesson_status_args, true );
1435
					// Check for valid return before using
1436
					if ( !empty($each_lesson_status->comment_approved) ) {
1437
						$all_lesson_statuses[] = $each_lesson_status;
1438
					}
1439
				}
1440
			}
1441
			foreach( $all_lesson_statuses as $lesson_status ) {
1442
				// If lessons are complete without needing quizzes to be passed
1443 View Code Duplication
				if ( 'passed' != $course_completion ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1444
					switch ( $lesson_status->comment_approved ) {
1445
						// A user cannot 'complete' a course if a lesson...
1446
						case 'in-progress': // ...is still in progress
1447
						case 'ungraded': // ...hasn't yet been graded
1448
							break;
1449
1450
						default:
1451
							$lessons_completed++;
1452
							break;
1453
					}
1454
				}
1455
				else {
1456
					switch ( $lesson_status->comment_approved ) {
1457
						case 'complete': // Lesson has no quiz/questions
1458
						case 'graded': // Lesson has quiz, but it's not important what the grade was
1459
						case 'passed': // Lesson has quiz and the user passed
1460
							$lessons_completed++;
1461
							break;
1462
1463
						// A user cannot 'complete' a course if on a lesson...
1464
						case 'failed': // ...a user failed the passmark on a quiz
1465
						default:
1466
							break;
1467
					}
1468
				}
1469
			} // Each lesson
1470
			if ( $lessons_completed == $total_lessons ) {
1471
				$course_status = 'complete';
1472
			}
1473
1474
			// Update meta data on how many lessons have been completed
1475
			$course_metadata['complete'] = $lessons_completed;
1476
			// update the overall percentage of the course lessons complete (or graded) compared to 'in-progress' regardless of the above
1477
			$course_metadata['percent'] = abs( round( ( doubleval( $lessons_completed ) * 100 ) / ( $total_lessons ), 0 ) );
1478
1479
			$activity_logged = WooThemes_Sensei_Utils::update_course_status( $user_id, $course_id, $course_status, $course_metadata );
0 ignored issues
show
Documentation introduced by
$course_id is of type integer, but the function expects a object<type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1480
1481
			// Allow further actions
1482
			if ( 'complete' == $course_status ) {
1483
				do_action( 'sensei_user_course_end', $user_id, $course_id );
1484
			}
1485
			return $activity_logged;
1486
		}
1487
1488
		return false;
1489
	}
1490
1491
	/**
1492
	 * Check if a user has completed a course or not
1493
	 *
1494
	 * @param mixed $course course_id or sensei_course_status entry
1495
	 * @param int $user_id
1496
	 * @return boolean
1497
	 */
1498
	public static function user_completed_course( $course = 0, $user_id = 0 ) {
1499
1500
		if( $course ) {
1501
			if ( is_object( $course ) && is_a( $course,'WP_Comment') ) {
1502
				$user_course_status = $course->comment_approved;
1503
			}
1504
			elseif ( !is_numeric( $course ) && ! is_a( $course,'WP_Post') ) {
1505
				$user_course_status = $course;
1506
			}
1507
			else {
1508
				if( ! $user_id ) {
1509
					$user_id = get_current_user_id();
1510
				}
1511
1512
                if( is_a( $course, 'WP_Post' ) ){
1513
                    $course =   $course->ID;
1514
                }
1515
1516
				$user_course_status = WooThemes_Sensei_Utils::user_course_status( $course , $user_id );
1517
				if( isset( $user_course_status->comment_approved ) ){
1518
                    $user_course_status = $user_course_status->comment_approved;
1519
                }
1520
1521
			}
1522
			if( $user_course_status && 'complete' == $user_course_status ) {
1523
				return true;
1524
			}
1525
		}
1526
		return false;
1527
	}
1528
1529
	/**
1530
	 * Check if a user has started a lesson or not
1531
	 *
1532
	 * @since  1.7.0
1533
	 * @param type $lesson_id
1534
	 * @param type $user_id
1535
	 * @return mixed false or comment_ID
1536
	 */
1537 View Code Duplication
	public static function user_started_lesson( $lesson_id = 0, $user_id = 0 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1538
1539
		if( $lesson_id ) {
1540
			if( ! $user_id ) {
1541
				$user_id = get_current_user_id();
1542
			}
1543
1544
			$user_lesson_status_id = WooThemes_Sensei_Utils::sensei_get_activity_value( array( 'post_id' => $lesson_id, 'user_id' => $user_id, 'type' => 'sensei_lesson_status', 'field' => 'comment_ID' ) );
1545
			if( $user_lesson_status_id ) {
1546
				return $user_lesson_status_id;
1547
			}
1548
		}
1549
		return false;
1550
	}
1551
1552
	/**
1553
	 * Check if a user has completed a lesson or not
1554
	 *
1555
     * @uses  Sensei()
1556
	 * @param mixed $lesson lesson_id or sensei_lesson_status entry
1557
	 * @param int $user_id
1558
	 * @return boolean
1559
	 */
1560
	public static function user_completed_lesson( $lesson = 0, $user_id = 0 ) {
1561
1562
		if( $lesson ) {
1563
			$lesson_id = 0;
1564
			if ( is_object( $lesson ) ) {
1565
				$user_lesson_status = $lesson->comment_approved;
1566
				$lesson_id = $lesson->comment_post_ID;
1567
			}
1568
			elseif ( ! is_numeric( $lesson ) ) {
1569
				$user_lesson_status = $lesson;
1570
			}
1571
			else {
1572
				if( ! $user_id ) {
1573
					$user_id = get_current_user_id();
1574
				}
1575
1576
                // the user is not logged in
1577
                if( ! $user_id > 0 ){
1578
                    return false;
1579
                }
1580
				$_user_lesson_status = WooThemes_Sensei_Utils::user_lesson_status( $lesson, $user_id );
1581
1582
				if ( $_user_lesson_status ) {
1583
					$user_lesson_status = $_user_lesson_status->comment_approved;
1584
				}
1585
				else {
1586
					return false; // No status means not complete
1587
				}
1588
				$lesson_id = $lesson;
1589
			}
1590
			if ( 'in-progress' != $user_lesson_status ) {
1591
				// Check for Passed or Completed Setting
1592
				// Should we be checking for the Course completion setting? Surely that should only affect the Course completion, not bypass each Lesson setting
1593
//				$course_completion = Sensei()->settings->settings[ 'course_completion' ];
1594
//				if ( 'passed' == $course_completion ) {
1595
					switch( $user_lesson_status ) {
1596
						case 'complete':
1597
						case 'graded':
1598
						case 'passed':
1599
							return true;
1600
							break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1601
1602
						case 'failed':
1603
							// This may be 'completed' depending on...
1604
							if ( $lesson_id ) {
1605
								// Get Quiz ID, this won't be needed once all Quiz meta fields are stored on the Lesson
1606
								$lesson_quiz_id = Sensei()->lesson->lesson_quizzes( $lesson_id );
1607
								if ( $lesson_quiz_id ) {
1608
									// ...the quiz pass setting
1609
									$pass_required = get_post_meta( $lesson_quiz_id, '_pass_required', true );
1610
									if ( empty($pass_required) ) {
1611
										// We just require the user to have done the quiz, not to have passed
1612
										return true;
1613
									}
1614
								}
1615
							}
1616
							return false;
1617
							break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1618
					}
1619
			} // End If Statement
1620
		}
1621
1622
		return false;
1623
	}
1624
1625
	/**
1626
	 * Returns the requested course status
1627
	 *
1628
	 * @since 1.7.0
1629
	 * @param type $course_id
1630
	 * @param type $user_id
1631
	 * @return object
1632
	 */
1633
	public static function user_course_status( $course_id = 0, $user_id = 0 ) {
1634
1635
1636
		if( $course_id ) {
1637
			if( ! $user_id ) {
1638
				$user_id = get_current_user_id();
1639
			}
1640
1641
			$user_course_status = WooThemes_Sensei_Utils::sensei_check_for_activity( array( 'post_id' => $course_id, 'user_id' => $user_id, 'type' => 'sensei_course_status' ), true );
1642
			return $user_course_status;
1643
		}
1644
1645
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Sensei_Utils::user_course_status of type object.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1646
	}
1647
1648
	/**
1649
	 * Returns the requested lesson status
1650
	 *
1651
	 * @since 1.7.0
1652
	 * @param int $lesson_id
1653
	 * @param int $user_id
1654
	 * @return object | bool
1655
	 */
1656 View Code Duplication
	public static function user_lesson_status( $lesson_id = 0, $user_id = 0 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1657
1658
        if( ! $user_id ) {
1659
            $user_id = get_current_user_id();
1660
        }
1661
1662
		if( $lesson_id > 0 && $user_id > 0 ) {
1663
1664
			$user_lesson_status = WooThemes_Sensei_Utils::sensei_check_for_activity( array( 'post_id' => $lesson_id, 'user_id' => $user_id, 'type' => 'sensei_lesson_status' ), true );
1665
			return $user_lesson_status;
1666
		}
1667
1668
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Sensei_Utils::user_lesson_status of type object.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1669
	}
1670
1671
	public static function is_preview_lesson( $lesson_id ) {
1672
		$is_preview = false;
1673
1674
		if( 'lesson' == get_post_type( $lesson_id ) ) {
1675
			$lesson_preview = get_post_meta( $lesson_id, '_lesson_preview', true );
1676
			if ( isset( $lesson_preview ) && '' != $lesson_preview ) {
1677
				$is_preview = true;
1678
			}
1679
		}
1680
1681
		return $is_preview;
1682
	}
1683
1684
	public static function user_passed_quiz( $quiz_id = 0, $user_id = 0 ) {
1685
1686
		if( ! $quiz_id  ) return false;
1687
1688
		if( ! $user_id ) {
1689
			$user_id = get_current_user_id();
1690
		}
1691
		$lesson_id = get_post_meta( $quiz_id, '_quiz_lesson', true );
1692
1693
		// Quiz Grade
1694
		$lesson_status = WooThemes_Sensei_Utils::user_lesson_status( $lesson_id, $user_id );
1695
		$quiz_grade = get_comment_meta( $lesson_status->comment_ID, 'grade', true );
1696
1697
		// Check if Grade is greater than or equal to pass percentage
1698
		$quiz_passmark = abs( round( doubleval( get_post_meta( $quiz_id, '_quiz_passmark', true ) ), 2 ) );
1699
		if ( $quiz_passmark <= intval( $quiz_grade ) ) {
1700
			return true;
1701
		}
1702
1703
		return false;
1704
1705
	}
1706
1707
	/**
1708
	 * Sets the status for the lesson
1709
	 *
1710
	 * @since  1.7.0
1711
     *
1712
	 * @param int|string $user_id
1713
	 * @param int|string $lesson_id
1714
	 * @param string $status
1715
	 * @param array $metadata
1716
     *
1717
	 * @return mixed false or comment_ID
1718
	 */
1719 View Code Duplication
	public static function update_lesson_status( $user_id, $lesson_id, $status = 'in-progress', $metadata = array() ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1720
		$comment_id = false;
1721
		if ( !empty($status) ) {
1722
			$args = array(
1723
					'user_id'   => $user_id,
1724
					'post_id'   => $lesson_id,
1725
					'status'    => $status,
1726
					'type'      => 'sensei_lesson_status', /* FIELD SIZE 20 */
1727
					'action'    => 'update', // Update the existing status...
1728
					'keep_time' => true, // ...but don't change the existing timestamp
1729
				);
1730
			if( 'in-progress' == $status ) {
1731
				unset( $args['keep_time'] ); // Keep updating what's happened
1732
			}
1733
1734
			$comment_id = WooThemes_Sensei_Utils::sensei_log_activity( $args );
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $comment_id is correct as \WooThemes_Sensei_Utils:...sei_log_activity($args) (which targets Sensei_Utils::sensei_log_activity()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1735
			if ( $comment_id && !empty($metadata) ) {
1736
				foreach( $metadata as $key => $value ) {
1737
					update_comment_meta( $comment_id, $key, $value );
1738
				}
1739
			}
1740
1741
			do_action( 'sensei_lesson_status_updated', $status, $user_id, $lesson_id, $comment_id );
1742
		}
1743
		return $comment_id;
1744
	}
1745
1746
	/**
1747
	 * Sets the statuses for the Course
1748
	 *
1749
	 * @access public
1750
	 * @since  1.7.0
1751
	 * @param type $user_id
1752
	 * @param type $course_id
1753
	 * @param type $status
1754
	 * @param type $metadata
1755
	 * @return mixed false or comment_ID
1756
	 */
1757 View Code Duplication
	public static function update_course_status( $user_id, $course_id, $status = 'in-progress', $metadata = array() ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1758
		$comment_id = false;
1759
		if ( !empty($status) ) {
1760
			$args = array(
1761
					'user_id'   => $user_id,
1762
					'post_id'   => $course_id,
1763
					'status'    => $status,
1764
					'type'      => 'sensei_course_status', /* FIELD SIZE 20 */
1765
					'action'    => 'update', // Update the existing status...
1766
					'keep_time' => true, // ...but don't change the existing timestamp
1767
				);
1768
			if( 'in-progress' == $status ) {
1769
				unset( $args['keep_time'] ); // Keep updating what's happened
1770
			}
1771
1772
			$comment_id = WooThemes_Sensei_Utils::sensei_log_activity( $args );
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $comment_id is correct as \WooThemes_Sensei_Utils:...sei_log_activity($args) (which targets Sensei_Utils::sensei_log_activity()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1773
			if ( $comment_id && !empty($metadata) ) {
1774
				foreach( $metadata as $key => $value ) {
1775
					update_comment_meta( $comment_id, $key, $value );
1776
				}
1777
			}
1778
			do_action( 'sensei_course_status_updated', $status, $user_id, $course_id, $comment_id );
1779
		}
1780
		return $comment_id;
1781
	}
1782
1783
	/**
1784
	 * Remove the orderby for comments
1785
	 * @access public
1786
	 * @since  1.7.0
1787
	 * @param  array $pieces (default: array())
1788
	 * @return array
1789
	 */
1790
	public static function single_comment_filter( $pieces ) {
1791
		unset( $pieces['orderby'] );
1792
		unset( $pieces['order'] );
1793
1794
		return $pieces;
1795
	}
1796
1797
	/**
1798
	 * Allow retrieving comments with any comment_approved status, little bypass to WP_Comment. Required only for WP < 4.1
1799
	 * @access public
1800
	 * @since  1.7.0
1801
	 * @param  array $pieces (default: array())
1802
	 * @return array
1803
	 */
1804
	public static function comment_any_status_filter( $pieces ) {
1805
1806
		$pieces['where'] = str_replace( array( "( comment_approved = '0' OR comment_approved = '1' ) AND", "comment_approved = 'any' AND" ), '', $pieces['where'] );
1807
1808
		return $pieces;
1809
	}
1810
1811
	/**
1812
	 * Allow retrieving comments within multiple statuses, little bypass to WP_Comment. Required only for WP < 4.1
1813
	 * @access public
1814
	 * @since  1.7.0
1815
	 * @param  array $pieces (default: array())
1816
	 * @return array
1817
	 */
1818
	public static function comment_multiple_status_filter( $pieces ) {
1819
1820
		preg_match( "/^comment_approved = '([a-z\-\,]+)'/", $pieces['where'], $placeholder );
1821
		if ( !empty($placeholder[1]) ) {
1822
			$statuses = explode( ',', $placeholder[1] );
1823
			$pieces['where'] = str_replace( "comment_approved = '" . $placeholder[1] . "'", "comment_approved IN ('". implode( "', '", $statuses ) . "')", $pieces['where'] );
1824
		}
1825
1826
		return $pieces;
1827
	}
1828
1829
	/**
1830
	 * Adjust the comment query to be faster on the database, used by Analysis admin
1831
	 * @since  1.7.0
1832
	 * @return array
1833
	 */
1834 View Code Duplication
	public static function comment_total_sum_meta_value_filter( $pieces ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1835
		global $wpdb, $wp_version;
1836
1837
		$pieces['fields'] = " COUNT(*) AS total, SUM($wpdb->commentmeta.meta_value) AS meta_sum ";
1838
		unset( $pieces['groupby'] );
1839
		if ( version_compare($wp_version, '4.1', '>=') ) {
1840
			$args['order'] = false;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$args was never initialized. Although not strictly required by PHP, it is generally a good practice to add $args = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1841
			$args['orderby'] = false;
1842
		}
1843
1844
		return $pieces;
1845
	}
1846
1847
	/**
1848
	 * Shifts counting of posts to the database where it should be. Likely not to be used due to knock on issues.
1849
	 * @access public
1850
	 * @since  1.7.0
1851
	 * @param  array $pieces (default: array())
1852
	 * @return array
1853
	 */
1854 View Code Duplication
	public static function get_posts_count_only_filter( $pieces ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1855
		global $wp_version;
1856
1857
		$pieces['fields'] = " COUNT(*) AS total ";
1858
		unset( $pieces['groupby'] );
1859
		if ( version_compare($wp_version, '4.1', '>=') ) {
1860
			$args['order'] = false;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$args was never initialized. Although not strictly required by PHP, it is generally a good practice to add $args = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1861
			$args['orderby'] = false;
1862
		}
1863
		return $pieces;
1864
	}
1865
1866
    /**
1867
     *
1868
     * Alias to Woothemes_Sensei_Utils::update_user_data
1869
     * @since 1.7.4
1870
     *
1871
     * @param string $data_key maximum 39 characters allowed
1872
     * @param int $post_id
1873
     * @param mixed $value
1874
     * @param int $user_id
1875
     *
1876
     * @return bool $success
1877
     */
1878
    public static function add_user_data( $data_key, $post_id , $value = '' , $user_id = 0  ){
1879
1880
        return self::update_user_data( $data_key, $post_id, $value , $user_id );
1881
1882
    }// end add_user_data
1883
1884
    /**
1885
     * add user specific data to the passed in sensei post type id
1886
     *
1887
     * This function saves comment meta on the users current status. If no status is available
1888
     * status will be created. It only operates on the available sensei Post types: course, lesson, quiz.
1889
     *
1890
     * @since 1.7.4
1891
     *
1892
     * @param string $data_key maximum 39 characters allowed
1893
     * @param int $post_id
1894
     * @param mixed $value
1895
     * @param int $user_id
1896
     *
1897
     * @return bool $success
1898
     */
1899
    public static function update_user_data( $data_key, $post_id, $value = '' , $user_id = 0  ){
1900
1901
        if( ! ( $user_id > 0 ) ){
1902
            $user_id = get_current_user_id();
1903
        }
1904
1905
        $supported_post_types = array( 'course', 'lesson' );
1906
        $post_type = get_post_type( $post_id );
1907
        if( empty( $post_id ) || empty( $data_key )
1908
            || ! is_int( $post_id ) || ! ( intval( $post_id ) > 0 ) || ! ( intval( $user_id ) > 0 )
1909
            || !get_userdata( $user_id )
1910
            || ! in_array( $post_type, $supported_post_types )  ){
1911
1912
            return false;
1913
        }
1914
1915
        // check if there and existing Sensei status on this post type if not create it
1916
        // and get the  activity ID
1917
        $status_function = 'user_'.$post_type.'_status';
1918
        $sensei_user_status = self::$status_function( $post_id ,$user_id  );
1919
        if( ! isset( $sensei_user_status->comment_ID ) ){
1920
1921
            $start_function = 'user_start_'.$post_type;
1922
            $sensei_user_activity_id = self::$start_function( $user_id, $post_id );
1923
1924
        }else{
1925
1926
            $sensei_user_activity_id = $sensei_user_status->comment_ID;
1927
1928
        }
1929
1930
        // store the data
1931
        $success = update_comment_meta( $sensei_user_activity_id, $data_key, $value );
1932
1933
       return $success;
1934
1935
    }//update_user_data
1936
1937
    /**
1938
     * Get the user data stored on the passed in post type
1939
     *
1940
     * This function gets the comment meta on the lesson or course status
1941
     *
1942
     * @since 1.7.4
1943
     *
1944
     * @param $data_key
1945
     * @param $post_id
1946
     * @param int $user_id
1947
     *
1948
     * @return mixed $user_data_value
1949
     */
1950 View Code Duplication
    public static function get_user_data( $data_key, $post_id, $user_id = 0  ){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1951
1952
        $user_data_value = true;
0 ignored issues
show
Unused Code introduced by
$user_data_value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1953
1954
        if( ! ( $user_id > 0 ) ){
1955
            $user_id = get_current_user_id();
1956
        }
1957
1958
        $supported_post_types = array( 'course', 'lesson' );
1959
        $post_type = get_post_type( $post_id );
1960
        if( empty( $post_id ) || empty( $data_key )
1961
            || ! ( intval( $post_id ) > 0 ) || ! ( intval( $user_id ) > 0 )
1962
            || ! get_userdata( $user_id )
1963
            || !in_array( $post_type, $supported_post_types )  ){
1964
1965
            return false;
1966
        }
1967
1968
        // check if there and existing Sensei status on this post type if not create it
1969
        // and get the  activity ID
1970
        $status_function = 'user_'.$post_type.'_status';
1971
        $sensei_user_status = self::$status_function( $post_id ,$user_id  );
1972
        if( ! isset( $sensei_user_status->comment_ID ) ){
1973
            return false;
1974
        }
1975
1976
        $sensei_user_activity_id = $sensei_user_status->comment_ID;
1977
        $user_data_value = get_comment_meta( $sensei_user_activity_id , $data_key, true );
1978
1979
        return $user_data_value;
1980
1981
    }// end get_user_data
1982
1983
    /**
1984
     * Delete the Sensei user data for the given key, Sensei post type and user combination.
1985
     *
1986
     * @param int $data_key
1987
     * @param int $post_id
1988
     * @param int $user_id
1989
     *
1990
     * @return bool $deleted
1991
     */
1992 View Code Duplication
    public static function delete_user_data( $data_key, $post_id , $user_id ){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1993
        $deleted = true;
0 ignored issues
show
Unused Code introduced by
$deleted is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1994
1995
        if( ! ( $user_id > 0 ) ){
1996
            $user_id = get_current_user_id();
1997
        }
1998
1999
        $supported_post_types = array( 'course', 'lesson' );
2000
        $post_type = get_post_type( $post_id );
2001
        if( empty( $post_id ) || empty( $data_key )
2002
            || ! is_int( $post_id ) || ! ( intval( $post_id ) > 0 ) || ! ( intval( $user_id ) > 0 )
2003
            || ! get_userdata( $user_id )
2004
            || !in_array( $post_type, $supported_post_types )  ){
2005
2006
            return false;
2007
        }
2008
2009
        // check if there and existing Sensei status on this post type if not create it
2010
        // and get the  activity ID
2011
        $status_function = 'user_'.$post_type.'_status';
2012
        $sensei_user_status = self::$status_function( $post_id ,$user_id  );
2013
        if( ! isset( $sensei_user_status->comment_ID ) ){
2014
            return false;
2015
        }
2016
2017
        $sensei_user_activity_id = $sensei_user_status->comment_ID;
2018
        $deleted = delete_comment_meta( $sensei_user_activity_id , $data_key );
2019
2020
        return $deleted;
2021
2022
    }// end delete_user_data
2023
2024
2025
    /**
2026
     * The function creates a drop down. Never write up a Sensei select statement again.
2027
     *
2028
     * @since 1.8.0
2029
     *
2030
     * @param string $selected_value
2031
     * @param $options{
2032
     *    @type string $value the value saved in the database
2033
     *    @type string $option what the user will see in the list of items
2034
     * }
2035
     * @param array $attributes{
0 ignored issues
show
Documentation introduced by
There is no parameter named $attributes{. Did you maybe mean $attributes?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
2036
     *   @type string $attribute  type such name or id etc.
2037
     *  @type string $value
2038
     * }
2039
     * @param bool $enable_none_option
2040
     *
2041
     * @return string $drop_down_element
2042
     */
2043
    public static function generate_drop_down( $selected_value, $options = array() , $attributes = array(), $enable_none_option = true ) {
2044
2045
        $drop_down_element = '';
2046
2047
        // setup the basic attributes
2048 View Code Duplication
        if( !isset( $attributes['name'] ) || empty( $attributes['name']  ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2049
2050
            $attributes['name'] = 'sensei-options';
2051
2052
        }
2053
2054 View Code Duplication
        if( !isset( $attributes['id'] ) || empty( $attributes['id']  ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2055
2056
            $attributes['id'] = 'sensei-options';
2057
2058
        }
2059
2060
        if( !isset( $attributes['class'] ) || empty( $attributes['class']  ) ) {
2061
2062
            $attributes['class'] ='chosen_select widefat';
2063
2064
        }
2065
2066
        // create element attributes
2067
        $combined_attributes = '';
2068
        foreach( $attributes as $attribute => $value ){
2069
2070
            $combined_attributes .= $attribute . '="'.$value.'"' . ' ';
2071
2072
        }// end for each
2073
2074
2075
        // create the select element
2076
        $drop_down_element .= '<select '. $combined_attributes . ' >' . "\n";
2077
2078
        // show the none option if the client requested
2079
        if( $enable_none_option ) {
2080
            $drop_down_element .= '<option value="">' . __('None', 'woothemes-sensei') . '</option>';
2081
        }
2082
2083
        if ( count( $options ) > 0 ) {
2084
2085
            foreach ($options as $value => $option ){
2086
2087
                $element = '';
2088
                $element.= '<option value="' . esc_attr( $value ) . '"';
2089
                $element .= selected( $value, $selected_value, false ) . '>';
2090
                $element .= esc_html(  $option ) . '</option>' . "\n";
2091
2092
                // add the element to the select html
2093
                $drop_down_element.= $element;
2094
            } // End For Loop
2095
2096
        } // End If Statement
2097
2098
        $drop_down_element .= '</select>' . "\n";
2099
2100
        return $drop_down_element;
2101
2102
    }// generate_drop_down
2103
2104
    /**
2105
     * Wrapper for the default php round() function.
2106
     * This allows us to give more control to a user on how they can round Sensei
2107
     * decimals passed through this function.
2108
     *
2109
     * @since 1.8.5
2110
     *
2111
     * @param double $val
2112
     * @param int $precision
2113
     * @param $mode
2114
     * @param string $context
2115
     *
2116
     * @return double $val
2117
     */
2118
    public static function round( $val, $precision = 0, $mode = PHP_ROUND_HALF_UP, $context = ''  ){
2119
2120
        /**å
2121
         * Change the precision for the Sensei_Utils::round function.
2122
         * the precision given will be passed into the php round function
2123
         * @since 1.8.5
2124
         */
2125
        $precision = apply_filters( 'sensei_round_precision', $precision , $val, $context, $mode );
2126
2127
        /**
2128
         * Change the mode for the Sensei_Utils::round function.
2129
         * the mode given will be passed into the php round function
2130
         *
2131
         * This applies only to PHP version 5.3.0 and greater
2132
         *
2133
         * @since 1.8.5
2134
         */
2135
        $mode = apply_filters( 'sensei_round_mode', $mode , $val, $context, $precision   );
2136
2137
        if ( version_compare(PHP_VERSION, '5.3.0') >= 0 ) {
2138
2139
            return round( $val, $precision, $mode );
2140
2141
        }else{
2142
2143
            return round( $val, $precision );
2144
2145
        }
2146
2147
    }
2148
2149
    /**
2150
     * Returns the current url with all the query vars
2151
     *
2152
     * @since 1.9.0
2153
     * @return string $url
2154
     */
2155
    public static function get_current_url(){
2156
2157
        global $wp;
2158
        $current_url = trailingslashit( home_url( $wp->request ) );
2159
        if ( isset( $_GET ) ) {
2160
2161
            foreach ($_GET as $param => $val ) {
2162
2163
                $current_url = add_query_arg( $param, $val , $current_url );
2164
2165
            }
2166
        }
2167
2168
        return $current_url;
2169
    }
2170
2171
    /**
2172
     * Restore the global WP_Query
2173
     *
2174
     * @since 1.9.0
2175
     */
2176
    public static function restore_wp_query() {
2177
2178
        wp_reset_query();
2179
2180
    }
2181
2182
    /**
2183
     * Merge two arrays in a zip like fashion.
2184
     * If one array is longer than the other the elements will be apended
2185
     * to the end of the resulting array.
2186
     *
2187
     * @since 1.9.0
2188
     *
2189
     * @param array $array_a
2190
     * @param array $array_b
2191
     * @return array $merged_array
2192
     */
2193
    public static function array_zip_merge( $array_a, $array_b ){
2194
2195
        if( ! isset( $array_a[0]  ) || ! isset( $array_b[0] )  ){
2196
            trigger_error('array_zip_merge requires both arrays to be indexed arrays ');
2197
        }
2198
2199
        $merged_array = array();
2200
        $total_elements = count( $array_a )  + count( $array_b );
2201
2202
        // Zip arrays
2203
        for ( $i = 0; $i < $total_elements; $i++) {
2204
2205
            // if has an element at current index push a on top
2206
            if( isset( $array_a[ $i ] ) ){
2207
                $merged_array[] = $array_a[ $i ]  ;
2208
            }
2209
2210
            // next if $array_b has an element at current index push a on top of the element
2211
            // from a if there was one, if not the element before that.
2212
            if( isset( $array_b[ $i ] ) ){
2213
                $merged_array[] = $array_b[ $i ]  ;
2214
            }
2215
2216
        }
2217
2218
        return $merged_array;
2219
    }
2220
2221
} // End Class
2222
2223
/**
2224
 * Class WooThemes_Sensei_Utils
2225
 * for backward compatibility
2226
 * @since 1.9.0
2227
 */
2228
class WooThemes_Sensei_Utils extends Sensei_Utils{}
2229