Completed
Push — master ( d51ce1...80c0ca )
by Dwain
05:52
created

Sensei_Learner_Management::learners_page()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 29
Code Lines 20

Duplication

Lines 29
Ratio 100 %
Metric Value
dl 29
loc 29
rs 8.8571
cc 3
eloc 20
nc 4
nop 0
1
<?php
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 WordPress
10
 * @subpackage Sensei
11
 * @category Core
12
 * @author WooThemes
13
 * @since 1.3.0
14
 */
15
class Sensei_Learner_Management {
16
17
	public $name;
18
	public $file;
19
	public $page_slug;
20
21
	/**
22
	 * Constructor
23
	 * @since  1.6.0
24
	 * @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...
25
	 */
26
	public function __construct ( $file ) {
27
		$this->name = __( 'Learner Management', 'woothemes-sensei' );;
28
		$this->file = $file;
29
		$this->page_slug = 'sensei_learners';
30
31
		// Admin functions
32 View Code Duplication
		if ( is_admin() ) {
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...
33
			add_action( 'admin_menu', array( $this, 'learners_admin_menu' ), 30);
34
			add_action( 'learners_wrapper_container', array( $this, 'wrapper_container'  ) );
35
			if ( isset( $_GET['page'] ) && ( $_GET['page'] == $this->page_slug ) ) {
36
				add_action( 'admin_print_scripts', array( $this, 'enqueue_scripts' ) );
37
				add_action( 'admin_print_styles', array( $this, 'enqueue_styles' ) );
38
			}
39
40
			add_action( 'admin_init', array( $this, 'add_new_learners' ) );
41
42
			add_action( 'admin_notices', array( $this, 'add_learner_notices' ) );
43
		} // End If Statement
44
45
		// Ajax functions
46
		if ( is_admin() ) {
47
			add_action( 'wp_ajax_get_redirect_url_learners', array( $this, 'get_redirect_url' ) );
48
			add_action( 'wp_ajax_remove_user_from_post', array( $this, 'remove_user_from_post' ) );
49
			add_action( 'wp_ajax_sensei_json_search_users', array( $this, 'json_search_users' ) );
50
		}
51
	} // End __construct()
52
53
	/**
54
	 * learners_admin_menu function.
55
	 * @since  1.6.0
56
	 * @access public
57
	 * @return void
58
	 */
59
	public function learners_admin_menu() {
60
		global $menu;
61
62 View Code Duplication
		if ( current_user_can( 'manage_sensei_grades' ) ) {
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...
63
			$learners_page = add_submenu_page( 'sensei', $this->name, $this->name, 'manage_sensei_grades', $this->page_slug, array( $this, 'learners_page' ) );
0 ignored issues
show
Unused Code introduced by
$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...
64
		}
65
66
	} // End learners_admin_menu()
67
68
	/**
69
	 * enqueue_scripts function.
70
	 *
71
	 * @description Load in JavaScripts where necessary.
72
	 * @access public
73
	 * @since 1.6.0
74
	 * @return void
75
	 */
76
	public function enqueue_scripts () {
77
78
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
79
80
		// Load Learners JS
81
		wp_enqueue_script( 'sensei-learners-general',
82
            Sensei()->plugin_url . 'assets/js/learners-general' . $suffix . '.js',
83
                            array('jquery','select2','sensei-chosen-ajax' ), Sensei()->version, true );
84
85
		$data = array(
86
			'remove_generic_confirm' => __( 'Are you sure you want to remove this user?', 'woothemes-sensei' ),
87
			'remove_from_lesson_confirm' => __( 'Are you sure you want to remove the user from this lesson?', 'woothemes-sensei' ),
88
			'remove_from_course_confirm' => __( 'Are you sure you want to remove the user from this course?', 'woothemes-sensei' ),
89
            'remove_from_purchased_course_confirm' => __( 'Are you sure you want to remove the user from this course? This order associate with this will also be set to canceled.', 'woothemes-sensei' ),
90
			'remove_user_from_post_nonce' => wp_create_nonce( 'remove_user_from_post_nonce' ),
91
            'search_users_nonce' => wp_create_nonce( 'search-users' ),
92
            'selectplaceholder'=> __( 'Select Learner', 'woothemes-sensei' )
93
		);
94
95
		wp_localize_script( 'sensei-learners-general', 'woo_learners_general_data', $data );
96
97
	} // End enqueue_scripts()
98
99
	/**
100
	 * enqueue_styles function.
101
	 *
102
	 * @description Load in CSS styles where necessary.
103
	 * @access public
104
	 * @since 1.6.0
105
	 * @return void
106
	 */
107
	public function enqueue_styles () {
108
109
		wp_enqueue_style( 'woothemes-sensei-admin' );
110
111
	} // End enqueue_styles()
112
113
	/**
114
	 * load_data_table_files loads required files for Learners
115
	 * @since  1.6.0
116
	 * @return void
117
	 */
118
	public function load_data_table_files() {
119
120
		// Load Learners Classes
121
		$classes_to_load = array(	'list-table',
122
									'learners-main',
123
									);
124
		foreach ( $classes_to_load as $class_file ) {
125
			Sensei()->load_class( $class_file );
126
		} // End For Loop
127
	} // End load_data_table_files()
128
129
	/**
130
	 * load_data_object creates new instance of class
131
	 * @since  1.6.0
132
	 * @param  string  $name          Name of class
133
	 * @param  integer $data          constructor arguments
134
	 * @param  undefined  $optional_data optional constructor arguments
135
	 * @return object                 class instance object
136
	 */
137 View Code Duplication
	public function load_data_object( $name = '', $data = 0, $optional_data = null ) {
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...
138
		// Load Analysis data
139
		$object_name = 'WooThemes_Sensei_Learners_' . $name;
140
		if ( is_null($optional_data) ) {
141
			$sensei_learners_object = new $object_name( $data );
142
		} else {
143
			$sensei_learners_object = new $object_name( $data, $optional_data );
144
		} // End If Statement
145
		if ( 'Main' == $name ) {
146
			$sensei_learners_object->prepare_items();
147
		} // End If Statement
148
		return $sensei_learners_object;
149
	} // End load_data_object()
150
151
	/**
152
	 * learners_page function.
153
	 * @since 1.6.0
154
	 * @access public
155
	 * @return void
156
	 */
157 View Code Duplication
	public function learners_page() {
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...
158
159
		// Load Learners data
160
		$course_id = 0;
161
		$lesson_id = 0;
162
		if( isset( $_GET['course_id'] ) ) {
163
			$course_id = intval( $_GET['course_id'] );
164
		}
165
		if( isset( $_GET['lesson_id'] ) ) {
166
			$lesson_id = intval( $_GET['lesson_id'] );
167
		}
168
		$sensei_learners_main = $this->load_data_object( 'Main', $course_id, $lesson_id );
0 ignored issues
show
Documentation introduced by
$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...
169
		// Wrappers
170
		do_action( 'learners_before_container' );
171
		do_action( 'learners_wrapper_container', 'top' );
172
		$this->learners_headers();
173
		?>
174
		<div id="poststuff" class="sensei-learners-wrap">
175
			<div class="sensei-learners-main">
176
				<?php $sensei_learners_main->display(); ?>
177
			</div>
178
			<div class="sensei-learners-extra">
179
				<?php do_action( 'sensei_learners_extra' ); ?>
180
			</div>
181
		</div>
182
		<?php
183
		do_action( 'learners_wrapper_container', 'bottom' );
184
		do_action( 'learners_after_container' );
185
	} // End learners_default_view()
186
187
	/**
188
	 * learners_headers outputs Learners general headers
189
	 * @since  1.6.0
190
	 * @return void
191
	 */
192 View Code Duplication
	public function learners_headers( $args = array( 'nav' => 'default' ) ) {
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...
193
194
195
		$function = 'learners_' . $args['nav'] . '_nav';
196
		$this->$function();
197
		?>
198
			<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>
199
		<?php
200
		do_action( 'sensei_learners_after_headers' );
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
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...
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
256
		$return = '';
257
258
		// Security check
259
		$nonce = '';
260
		if ( isset($_POST['remove_user_from_post_nonce']) ) {
261
			$nonce = esc_html( $_POST['remove_user_from_post_nonce'] );
262
		}
263
		if ( ! wp_verify_nonce( $nonce, 'remove_user_from_post_nonce' ) ) {
264
			die( $return );
265
		}
266
267
		// Parse POST data
268
		$data = $_POST['data'];
269
		$action_data = array();
270
		parse_str( $data, $action_data );
271
272
		if( $action_data['user_id'] && $action_data['post_id'] && $action_data['post_type'] ) {
273
274
			$user_id = intval( $action_data['user_id'] );
275
			$post_id = intval( $action_data['post_id'] );
276
			$post_type = sanitize_text_field( $action_data['post_type'] );
277
            $order_id = sanitize_text_field( $action_data['order_id'] );
278
279
			$user = get_userdata( $user_id );
0 ignored issues
show
Unused Code introduced by
$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...
280
281
			switch( $post_type ) {
282
283
				case 'course':
284
					$removed = Sensei_Utils::sensei_remove_user_from_course( $post_id, $user_id );
285
286
                    if( ! empty( $order_id ) && Sensei_WC::is_woocommerce_active()  ){
287
288
                        $order = new WC_Order($order_id);
289
290
                        if (!empty($order)) {
291
                            $order->update_status( 'cancelled' );
292
                        }
293
294
                    }
295
296
				break;
297
298
				case 'lesson':
299
					$removed = Sensei_Utils::sensei_remove_user_from_lesson( $post_id, $user_id );
300
				break;
301
302
			}
303
304
			if( $removed ) {
0 ignored issues
show
Bug introduced by
The variable $removed 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...
305
				$return = 'removed';
306
			}
307
308
		}
309
310
		die( $return );
311
	}
312
313
	public function json_search_users() {
314
315
316
		check_ajax_referer( 'search-users', 'security' );
317
318
		$term = sanitize_text_field( stripslashes( $_GET['term'] ) );
319
320
		if ( empty( $term ) ) {
321
			die();
322
		}
323
324
		$default = isset( $_GET['default'] ) ? $_GET['default'] : __( 'None', 'woocommerce' );
325
326
		$found_users = array( '' => $default );
327
328
		$users_query = new WP_User_Query( apply_filters( 'sensei_json_search_users_query', array(
329
			'fields'         => 'all',
330
			'orderby'        => 'display_name',
331
			'search'         => '*' . $term . '*',
332
			'search_columns' => array( 'ID', 'user_login', 'user_email', 'user_nicename','user_firstname','user_lastname' )
333
		), $term ) );
334
335
		$users = $users_query->get_results();
336
337
		if ( $users ) {
338
			foreach ( $users as $user ) {
339
                $full_name = Sensei_Student::get_full_name( $user->ID );
340
341
                if( trim($user->display_name ) == trim( $full_name ) ){
342
343
                    $name = $full_name;
344
345
                }else{
346
347
                    $name = $full_name . ' ['. $user->display_name .']';
348
349
                }
350
351
                $found_users[ $user->ID ] = $name  . ' (#' . $user->ID . ' &ndash; ' . sanitize_email( $user->user_email ) . ')';
352
			}
353
		}
354
355
		wp_send_json( $found_users );
356
	}
357
358
	public function add_new_learners() {
359
360
		$result = false;
361
362
		if( ! isset( $_POST['add_learner_submit'] ) ) return $result;
363
364
		if ( ! isset( $_POST['add_learner_nonce'] ) || ! wp_verify_nonce( $_POST['add_learner_nonce'], 'add_learner_to_sensei' ) ) return $result;
365
366
		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;
367
368
		$post_type = $_POST['add_post_type'];
369
		$user_id = absint( $_POST['add_user_id'] );
370
		$course_id = absint( $_POST['add_course_id'] );
371
372
		switch( $post_type ) {
373
			case 'course':
374
375
				$result = Sensei_Utils::user_start_course( $user_id, $course_id );
376
377
				// Complete each lesson if course is set to be completed
378
				if( isset( $_POST['add_complete_course'] ) && 'yes' == $_POST['add_complete_course'] ) {
379
380
					$lesson_ids = Sensei()->course->course_lessons( $course_id, 'any', 'ids' );
381
382
					foreach( $lesson_ids as $id ) {
383
						Sensei_Utils::sensei_start_lesson( $id, $user_id, true );
384
					}
385
386
					// Updates the Course status and it's meta data
387
					Sensei_Utils::user_complete_course( $course_id, $user_id );
388
389
					do_action( 'sensei_user_course_end', $user_id, $course_id );
390
				}
391
392
			break;
393
394
			case 'lesson':
395
                $lesson_id = absint( $_POST['add_lesson_id'] );
396
				$complete = false;
397
				if( isset( $_POST['add_complete_lesson'] ) && 'yes' == $_POST['add_complete_lesson'] ) {
398
					$complete = true;
399
				}
400
401
				$result = Sensei_Utils::sensei_start_lesson( $lesson_id, $user_id, $complete );
402
403
				// Updates the Course status and it's meta data
404
				Sensei_Utils::user_complete_course( $course_id, $user_id );
405
406
			break;
407
		}
408
409
		// Set redirect URL after adding user to course/lesson
410
		$query_args = array( 'page' => $this->page_slug, 'view' => 'learners' );
411
412
		if( $result ) {
413
414
			if( $course_id ) {
415
				$query_args['course_id'] = $course_id;
416
			}
417
418
			if( $lesson_id ) {
419
				$query_args['lesson_id'] = $lesson_id;
0 ignored issues
show
Bug introduced by
The variable $lesson_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...
420
			}
421
422
			$query_args['message'] = 'success';
423
424
		} else {
425
			$query_args['message'] = 'error';
426
		}
427
428
		$redirect_url = apply_filters( 'sensei_learners_add_learner_redirect_url', add_query_arg( $query_args, admin_url( 'admin.php' ) ) );
429
430
		wp_safe_redirect( esc_url_raw( $redirect_url ) );
431
		exit;
432
	}
433
434
	public function add_learner_notices() {
435
		if( isset( $_GET['page'] ) && $this->page_slug == $_GET['page'] && isset( $_GET['message'] ) && $_GET['message'] ) {
436
			if( 'success' == $_GET['message'] ) {
437
				$msg = array(
438
					'updated',
439
					__( 'Learner added successfully!', 'woothemes-sensei' ),
440
				);
441
			} else {
442
				$msg = array(
443
					'error',
444
					__( 'Error adding learner.', 'woothemes-sensei' ),
445
				);
446
			}
447
			?>
448
			<div class="learners-notice <?php echo $msg[0]; ?>">
449
				<p><?php echo $msg[1]; ?></p>
450
			</div>
451
			<?php
452
		}
453
	}
454
455
    /**
456
     * Return the full name and surname or the display name of the user.
457
     *
458
     * The user must have both name and surname otherwise display name will be returned.
459
     *
460
     * @deprecated since 1.9.0 use Se
461
     * @since 1.8.0
462
     *
463
     * @param int $user_id | bool false for an invalid $user_id
464
     *
465
     * @return string $full_name
466
     */
467
    public function get_learner_full_name( $user_id ){
468
469
        return Sensei_Student::get_full_name( $user_id );
470
471
    } // end get_learner_full_name
472
473
} // End Class
474
475
/**
476
 * Class WooThemes_Sensei_Learners
477
 * for backward compatibility
478
 * @since 1.9.0
479
 */
480
class WooThemes_Sensei_Learners extends Sensei_Learner_Management{}
481