Issues (896)

Security Analysis    not enabled

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

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

includes/admin/class-sensei-learner-management.php (11 issues)

Upgrade to new PHP Analysis Engine

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

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

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

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

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

Loading history...
2
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
4
/**
5
 * Sensei Learners Class
6
 *
7
 * All functionality pertaining to the Admin Learners in Sensei.
8
 *
9
 * @package Users
10
 * @author Automattic
11
 *
12
 * @since 1.3.0
13
 */
14
class Sensei_Learner_Management {
15
16
	public $name;
17
	public $file;
18
	public $page_slug;
19
20
	/**
21
	 * Constructor
22
	 * @since  1.6.0
23
	 * @return  void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
24
	 */
25
	public function __construct ( $file ) {
26
		$this->name = __( 'Learner Management', 'woothemes-sensei' );;
27
		$this->file = $file;
28
		$this->page_slug = 'sensei_learners';
29
30
		// Admin functions
31 View Code Duplication
		if ( is_admin() ) {
0 ignored issues
show
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...
32
			add_action( 'admin_menu', array( $this, 'learners_admin_menu' ), 30);
33
			add_action( 'learners_wrapper_container', array( $this, 'wrapper_container'  ) );
34
			if ( isset( $_GET['page'] ) && ( $_GET['page'] == $this->page_slug ) ) {
35
				add_action( 'admin_print_scripts', array( $this, 'enqueue_scripts' ) );
36
				add_action( 'admin_print_styles', array( $this, 'enqueue_styles' ) );
37
			}
38
39
			add_action( 'admin_init', array( $this, 'add_new_learners' ) );
40
41
			add_action( 'admin_notices', array( $this, 'add_learner_notices' ) );
42
		} // End If Statement
43
44
		// Ajax functions
45
		if ( is_admin() ) {
46
			add_action( 'wp_ajax_get_redirect_url_learners', array( $this, 'get_redirect_url' ) );
47
			add_action( 'wp_ajax_remove_user_from_post', array( $this, 'remove_user_from_post' ) );
48
			add_action( 'wp_ajax_sensei_json_search_users', array( $this, 'json_search_users' ) );
49
		}
50
	} // End __construct()
51
52
	/**
53
	 * learners_admin_menu function.
54
	 * @since  1.6.0
55
	 * @access public
56
	 * @return void
57
	 */
58
	public function learners_admin_menu() {
59
		global $menu;
60
61 View Code Duplication
		if ( current_user_can( 'manage_sensei_grades' ) ) {
0 ignored issues
show
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...
62
			$learners_page = add_submenu_page( 'sensei', $this->name, $this->name, 'manage_sensei_grades', $this->page_slug, array( $this, 'learners_page' ) );
0 ignored issues
show
$learners_page 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...
63
		}
64
65
	} // End learners_admin_menu()
66
67
	/**
68
	 * enqueue_scripts function.
69
	 *
70
	 * @description Load in JavaScripts where necessary.
71
	 * @access public
72
	 * @since 1.6.0
73
	 * @return void
74
	 */
75
	public function enqueue_scripts () {
76
77
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
78
79
		// Load Learners JS
80
		wp_enqueue_script( 'sensei-learners-general',
81
            Sensei()->plugin_url . 'assets/js/learners-general' . $suffix . '.js',
82
                            array('jquery','sensei-core-select2','sensei-chosen-ajax' ), Sensei()->version, true );
83
84
		$data = array(
85
			'remove_generic_confirm' => __( 'Are you sure you want to remove this user?', 'woothemes-sensei' ),
86
			'remove_from_lesson_confirm' => __( 'Are you sure you want to remove the user from this lesson?', 'woothemes-sensei' ),
87
			'remove_from_course_confirm' => __( 'Are you sure you want to remove the user from this course?', 'woothemes-sensei' ),
88
			'remove_user_from_post_nonce' => wp_create_nonce( 'remove_user_from_post_nonce' ),
89
            'search_users_nonce' => wp_create_nonce( 'search-users' ),
90
            'selectplaceholder'=> __( 'Select Learner', 'woothemes-sensei' )
91
		);
92
93
		wp_localize_script( 'sensei-learners-general', 'woo_learners_general_data', $data );
94
95
	} // End enqueue_scripts()
96
97
	/**
98
	 * enqueue_styles function.
99
	 *
100
	 * @description Load in CSS styles where necessary.
101
	 * @access public
102
	 * @since 1.6.0
103
	 * @return void
104
	 */
105
	public function enqueue_styles () {
106
107
		wp_enqueue_style( 'woothemes-sensei-admin' );
108
109
	} // End enqueue_styles()
110
111
	/**
112
	 * load_data_table_files loads required files for Learners
113
	 * @since  1.6.0
114
	 * @return void
115
	 */
116
	public function load_data_table_files() {
117
118
		// Load Learners Classes
119
		$classes_to_load = array(	'list-table',
120
									'learners-main',
121
									);
122
		foreach ( $classes_to_load as $class_file ) {
123
			Sensei()->load_class( $class_file );
124
		} // End For Loop
125
126
	} // End load_data_table_files()
127
128
	/**
129
	 * load_data_object creates new instance of class
130
	 * @since  1.6.0
131
	 * @param  string  $name          Name of class
132
	 * @param  integer $data          constructor arguments
133
	 * @param  undefined  $optional_data optional constructor arguments
134
	 * @return object                 class instance object
135
	 */
136 View Code Duplication
	public function load_data_object( $name = '', $data = 0, $optional_data = null ) {
0 ignored issues
show
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...
137
		// Load Analysis data
138
		$object_name = 'WooThemes_Sensei_Learners_' . $name;
139
		if ( is_null($optional_data) ) {
140
			$sensei_learners_object = new $object_name( $data );
141
		} else {
142
			$sensei_learners_object = new $object_name( $data, $optional_data );
143
		} // End If Statement
144
		if ( 'Main' == $name ) {
145
			$sensei_learners_object->prepare_items();
146
		} // End If Statement
147
		return $sensei_learners_object;
148
	} // End load_data_object()
149
150
	/**
151
	 * learners_page function.
152
	 * @since 1.6.0
153
	 * @access public
154
	 * @return void
155
	 */
156 View Code Duplication
	public function learners_page() {
0 ignored issues
show
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...
157
158
		// Load Learners data
159
		$course_id = 0;
160
		$lesson_id = 0;
161
		if( isset( $_GET['course_id'] ) ) {
162
			$course_id = intval( $_GET['course_id'] );
163
		}
164
		if( isset( $_GET['lesson_id'] ) ) {
165
			$lesson_id = intval( $_GET['lesson_id'] );
166
		}
167
		$sensei_learners_main = $this->load_data_object( 'Main', $course_id, $lesson_id );
0 ignored issues
show
$lesson_id is of type integer, but the function expects a object<undefined>|null.

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...
168
		// Wrappers
169
		do_action( 'learners_before_container' );
170
		do_action( 'learners_wrapper_container', 'top' );
171
		$this->learners_headers();
172
		?>
173
		<div id="poststuff" class="sensei-learners-wrap">
174
			<div class="sensei-learners-main">
175
				<?php $sensei_learners_main->display(); ?>
176
			</div>
177
			<div class="sensei-learners-extra">
178
				<?php do_action( 'sensei_learners_extra' ); ?>
179
			</div>
180
		</div>
181
		<?php
182
		do_action( 'learners_wrapper_container', 'bottom' );
183
		do_action( 'learners_after_container' );
184
	} // End learners_default_view()
185
186
	/**
187
	 * learners_headers outputs Learners general headers
188
	 * @since  1.6.0
189
     * @param array $args
190
	 * @return void
191
	 */
192 View Code Duplication
	public function learners_headers( $args = array( 'nav' => 'default' ) ) {
0 ignored issues
show
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...
193
194
		$function = 'learners_' . $args['nav'] . '_nav';
195
		$this->$function();
196
		?>
197
			<p class="powered-by-woo"><?php _e( 'Powered by', 'woothemes-sensei' ); ?><a href="http://www.woothemes.com/" title="WooThemes"><img src="<?php echo Sensei()->plugin_url; ?>assets/images/woothemes.png" alt="WooThemes" /></a></p>
198
		<?php
199
		do_action( 'sensei_learners_after_headers' );
200
201
	} // End learners_headers()
202
203
	/**
204
	 * wrapper_container wrapper for Learners area
205
	 * @since  1.6.0
206
	 * @param $which string
207
	 * @return void
208
	 */
209
	public function wrapper_container( $which ) {
210
		if ( 'top' == $which ) {
211
			?><div id="woothemes-sensei" class="wrap woothemes-sensei"><?php
212
		} elseif ( 'bottom' == $which ) {
213
			?></div><!--/#woothemes-sensei--><?php
214
		} // End If Statement
215
	} // End wrapper_container()
216
217
	/**
218
	 * learners_default_nav default nav area for Learners
219
	 * @since  1.6.0
220
	 * @return void
221
	 */
222
	public function learners_default_nav() {
223
		$title = sprintf( '<a href="%s">%s</a>', esc_url( add_query_arg( array( 'page' => $this->page_slug ), admin_url( 'admin.php' ) ) ), esc_html( $this->name ) );
224
		if ( isset( $_GET['course_id'] ) ) { 
225
			$course_id = intval( $_GET['course_id'] );
226
			$url = add_query_arg( array( 'page' => $this->page_slug, 'course_id' => $course_id, 'view' => 'learners' ), admin_url( 'admin.php' ) );
227
			$title .= sprintf( '&nbsp;&nbsp;<span class="course-title">&gt;&nbsp;&nbsp;<a href="%s">%s</a></span>', esc_url( $url ), get_the_title( $course_id ) );
228
		}
229 View Code Duplication
		if ( isset( $_GET['lesson_id'] ) ) { 
0 ignored issues
show
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...
230
			$lesson_id = intval( $_GET['lesson_id'] );
231
			$title .= '&nbsp;&nbsp;<span class="lesson-title">&gt;&nbsp;&nbsp;' . get_the_title( intval( $lesson_id ) ) . '</span>'; 
232
		}
233
		?>
234
			<h2><?php echo apply_filters( 'sensei_learners_nav_title', $title ); ?></h2>
235
		<?php
236
	} // End learners_default_nav()
237
238
	public function get_redirect_url() {
239
240
		// Parse POST data
241
		$data = $_POST['data'];
242
		$course_data = array();
243
		parse_str( $data, $course_data );
244
245
		$course_cat = intval( $course_data['course_cat'] );
246
247
		$redirect_url = apply_filters( 'sensei_ajax_redirect_url', add_query_arg( array( 'page' => $this->page_slug, 'course_cat' => $course_cat ), admin_url( 'admin.php' ) ) );
248
249
		echo esc_url_raw( $redirect_url );
250
		die();
251
	}
252
253
	public function remove_user_from_post() {
254
255
        // Parse POST data
256
        $data = sanitize_text_field( $_POST['data'] );
257
        $action_data = array();
258
        parse_str( $data, $action_data );
259
260
		// Security checks
261
        // ensure the current user may remove users from post
262
        // only teacher or admin can remove users
263
264
        // check the nonce, valid post
265
		$nonce = '';
266
		if ( isset($_POST['remove_user_from_post_nonce']) ) {
267
			$nonce = esc_html( $_POST['remove_user_from_post_nonce'] );
268
		}
269
        $post =  get_post( intval( $action_data[ 'post_id' ] ) );
270
271
        // validate the user
272
        $may_remove_user = false;
273
        if( current_user_can('manage_sensei')
274
            ||  $post->post_author == get_current_user_id() ){
275
276
            $may_remove_user = true;
277
278
        }
279
280
        if( ! wp_verify_nonce( $nonce, 'remove_user_from_post_nonce' )
281
            || ! is_a( $post ,'WP_Post' )
282
            || ! $may_remove_user ){
283
284
            die('');
285
286
        }
287
288
		if( $action_data['user_id'] && $action_data['post_id'] && $action_data['post_type'] ) {
289
290
			$user_id = intval( $action_data['user_id'] );
291
			$post_id = intval( $action_data['post_id'] );
292
			$post_type = sanitize_text_field( $action_data['post_type'] );
293
294
			$user = get_userdata( $user_id );
0 ignored issues
show
$user 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...
295
296
			switch( $post_type ) {
297
298
				case 'course':
299
300
                    $removed = Sensei_Utils::sensei_remove_user_from_course( $post_id, $user_id );
301
302
				break;
303
304
				case 'lesson':
305
306
					$removed = Sensei_Utils::sensei_remove_user_from_lesson( $post_id, $user_id );
307
308
				break;
309
310
			}
311
312
			if( $removed ) {
313
				die( 'removed' );
314
			}
315
316
		}
317
318
		die('');
319
	}
320
321
	public function json_search_users() {
322
323
324
		check_ajax_referer( 'search-users', 'security' );
325
326
		$term = sanitize_text_field( stripslashes( $_GET['term'] ) );
327
328
		if ( empty( $term ) ) {
329
			die();
330
		}
331
332
		$default = isset( $_GET['default'] ) ? $_GET['default'] : __( 'None', 'woocommerce' );
333
334
		$found_users = array( '' => $default );
335
336
		$users_query = new WP_User_Query( apply_filters( 'sensei_json_search_users_query', array(
337
			'fields'         => 'all',
338
			'orderby'        => 'display_name',
339
			'search'         => '*' . $term . '*',
340
			'search_columns' => array( 'ID', 'user_login', 'user_email', 'user_nicename','user_firstname','user_lastname' )
341
		), $term ) );
342
343
		$users = $users_query->get_results();
344
345
		if ( $users ) {
346
			foreach ( $users as $user ) {
347
                $full_name = Sensei_Learner::get_full_name( $user->ID );
348
349
                if( trim($user->display_name ) == trim( $full_name ) ){
350
351
                    $name = $full_name;
352
353
                }else{
354
355
                    $name = $full_name . ' ['. $user->display_name .']';
356
357
                }
358
359
                $found_users[ $user->ID ] = $name  . ' (#' . $user->ID . ' &ndash; ' . sanitize_email( $user->user_email ) . ')';
360
			}
361
		}
362
363
		wp_send_json( $found_users );
364
	}
365
366
	public function add_new_learners() {
367
368
		$result = false;
369
370
		if( ! isset( $_POST['add_learner_submit'] ) ) return $result;
371
372
		if ( ! isset( $_POST['add_learner_nonce'] ) || ! wp_verify_nonce( $_POST['add_learner_nonce'], 'add_learner_to_sensei' ) ) return $result;
373
374
		if( ( ! isset( $_POST['add_user_id'] ) || '' ==  $_POST['add_user_id'] ) || ! isset( $_POST['add_post_type'] ) || ! isset( $_POST['add_course_id'] ) || ! isset( $_POST['add_lesson_id'] ) ) return $result;
375
376
		$post_type = $_POST['add_post_type'];
377
		$user_id = absint( $_POST['add_user_id'] );
378
		$course_id = absint( $_POST['add_course_id'] );
379
380
		switch( $post_type ) {
381
			case 'course':
382
383
				$result = Sensei_Utils::user_start_course( $user_id, $course_id );
384
385
				// Complete each lesson if course is set to be completed
386
				if( isset( $_POST['add_complete_course'] ) && 'yes' == $_POST['add_complete_course'] ) {
387
388
					$lesson_ids = Sensei()->course->course_lessons( $course_id, 'any', 'ids' );
389
390
					foreach( $lesson_ids as $id ) {
391
						Sensei_Utils::sensei_start_lesson( $id, $user_id, true );
392
					}
393
394
					// Updates the Course status and it's meta data
395
					Sensei_Utils::user_complete_course( $course_id, $user_id );
396
397
					do_action( 'sensei_user_course_end', $user_id, $course_id );
398
				}
399
400
			break;
401
402
			case 'lesson':
403
                $lesson_id = absint( $_POST['add_lesson_id'] );
404
				$complete = false;
405
				if( isset( $_POST['add_complete_lesson'] ) && 'yes' == $_POST['add_complete_lesson'] ) {
406
					$complete = true;
407
				}
408
409
				$result = Sensei_Utils::sensei_start_lesson( $lesson_id, $user_id, $complete );
410
411
				// Updates the Course status and it's meta data
412
				Sensei_Utils::user_complete_course( $course_id, $user_id );
413
414
			break;
415
		}
416
417
		// Set redirect URL after adding user to course/lesson
418
		$query_args = array( 'page' => $this->page_slug, 'view' => 'learners' );
419
420
		if( $result ) {
421
422
			if( $course_id ) {
423
				$query_args['course_id'] = $course_id;
424
			}
425
426
			if( $lesson_id ) {
427
				$query_args['lesson_id'] = $lesson_id;
428
			}
429
430
			$query_args['message'] = 'success';
431
432
		} else {
433
			$query_args['message'] = 'error';
434
		}
435
436
		$redirect_url = apply_filters( 'sensei_learners_add_learner_redirect_url', add_query_arg( $query_args, admin_url( 'admin.php' ) ) );
437
438
		wp_safe_redirect( esc_url_raw( $redirect_url ) );
439
		exit;
440
	}
441
442
	public function add_learner_notices() {
443
		if( isset( $_GET['page'] ) && $this->page_slug == $_GET['page'] && isset( $_GET['message'] ) && $_GET['message'] ) {
444
			if( 'success' == $_GET['message'] ) {
445
				$msg = array(
446
					'updated',
447
					__( 'Learner added successfully!', 'woothemes-sensei' ),
448
				);
449
			} else {
450
				$msg = array(
451
					'error',
452
					__( 'Error adding learner.', 'woothemes-sensei' ),
453
				);
454
			}
455
			?>
456
			<div class="learners-notice <?php echo $msg[0]; ?>">
457
				<p><?php echo $msg[1]; ?></p>
458
			</div>
459
			<?php
460
		}
461
	}
462
463
    /**
464
     * Return the full name and surname or the display name of the user.
465
     *
466
     * The user must have both name and surname otherwise display name will be returned.
467
     *
468
     * @deprecated since 1.9.0 use Se
469
     * @since 1.8.0
470
     *
471
     * @param int $user_id | bool false for an invalid $user_id
472
     *
473
     * @return string $full_name
474
     */
475
    public function get_learner_full_name( $user_id ){
476
477
        return Sensei_Learner::get_full_name( $user_id );
478
479
    } // end get_learner_full_name
480
481
} // End Class
482
483
/**
484
 * Class WooThemes_Sensei_Learners
485
 * @ignore only for backward compatibility
486
 * @since 1.9.0
487
 */
488
class WooThemes_Sensei_Learners extends Sensei_Learner_Management{}
489