Issues (896)

Security Analysis    not enabled

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

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

includes/class-sensei-admin.php (8 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 11 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
 * Handles all admin views, assets and navigation.
6
 *
7
 * @package Views
8
 * @author Automattic
9
 * @since 1.0.0
10
 */
11
class Sensei_Admin {
12
13
	/**
14
	 * Constructor.
15
	 * @since  1.0.0
16
	 */
17
	public function __construct () {
18
19
        //register admin styles
20
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_styles_global' ) );
21
22
        //register admin scripts
23
        add_action( 'admin_enqueue_scripts', array( $this, 'register_scripts' ) );
24
25
		add_action( 'admin_print_styles', array( $this, 'admin_notices_styles' ) );
26
		add_action( 'settings_before_form', array( $this, 'install_pages_output' ) );
27
		add_action( 'admin_menu', array( $this, 'admin_menu' ), 10 );
28
		add_action( 'menu_order', array( $this, 'admin_menu_order' ) );
29
		add_action( 'admin_head', array( $this, 'admin_menu_highlight' ) );
30
		add_action( 'admin_init', array( $this, 'page_redirect' ) );
31
		add_action( 'admin_init', array( $this, 'sensei_add_custom_menu_items' ) );
32
        add_action( 'admin_init', array( __CLASS__, 'install_pages' ));
33
34
		// Duplicate lesson & courses
35
		add_filter( 'post_row_actions', array( $this, 'duplicate_action_link' ), 10, 2 );
36
		add_action( 'admin_action_duplicate_lesson', array( $this, 'duplicate_lesson_action' ) );
37
		add_action( 'admin_action_duplicate_course', array( $this, 'duplicate_course_action' ) );
38
		add_action( 'admin_action_duplicate_course_with_lessons', array( $this, 'duplicate_course_with_lessons_action' ) );
39
40
		// Handle lessons list table filtering
41
		add_action( 'restrict_manage_posts', array( $this, 'lesson_filter_options' ) );
42
		add_filter( 'request', array( $this, 'lesson_filter_actions' ) );
43
44
		// Add Sensei items to 'at a glance' widget
45
		add_filter( 'dashboard_glance_items', array( $this, 'glance_items' ), 10, 1 );
46
47
		// Handle course and lesson deletions
48
		add_action( 'trash_course', array( $this, 'delete_content' ), 10, 2 );
49
		add_action( 'trash_lesson', array( $this, 'delete_content' ), 10, 2 );
50
51
		// Delete user activity when user is deleted
52
		add_action( 'deleted_user', array( $this, 'delete_user_activity' ), 10, 1 );
53
54
		// Add notices to WP dashboard
55
		add_action( 'admin_notices', array( $this, 'theme_compatibility_notices' ) );
56
57
		// Reset theme notices when switching themes
58
		add_action( 'switch_theme', array( $this, 'reset_theme_check_notices' ) );
59
60
		// Allow Teacher access the admin area
61
		add_filter( 'woocommerce_prevent_admin_access', array( $this, 'admin_access' ) );
62
63
	} // End __construct()
64
65
	/**
66
	 * Add items to admin menu
67
	 * @since  1.4.0
68
	 * @return void
69
	 */
70
	public function admin_menu() {
71
		global $menu;
72
		$menu_cap = '';
73
		if( current_user_can( 'manage_sensei' ) ) {
74
			$menu_cap = 'manage_sensei';
75
		} else {
76
			if( current_user_can( 'manage_sensei_grades' ) ) {
77
				$menu_cap = 'manage_sensei_grades';
78
			}
79
		}
80
81
		if( $menu_cap ) {
82
			$menu[] = array( '', 'read', 'separator-sensei', '', 'wp-menu-separator sensei' );
83
            add_menu_page( 'Sensei', 'Sensei', $menu_cap, 'sensei' , array( Sensei()->analysis, 'analysis_page' ) , '', '50' );
84
		}
85
86
		add_submenu_page( 'edit.php?post_type=course', __( 'Order Courses', 'woothemes-sensei' ), __( 'Order Courses', 'woothemes-sensei' ), 'manage_sensei', 'course-order', array( $this, 'course_order_screen' ) );
87
		add_submenu_page( 'edit.php?post_type=lesson', __( 'Order Lessons', 'woothemes-sensei' ), __( 'Order Lessons', 'woothemes-sensei' ), 'edit_lessons', 'lesson-order', array( $this, 'lesson_order_screen' ) );
88
	}
89
90
	/**
91
	 * [admin_menu_order description]
92
	 * @since  1.4.0
93
	 * @param  array $menu_order Existing menu order
94
	 * @return array 			 Modified menu order for Sensei
95
	 */
96
	public function admin_menu_order( $menu_order ) {
97
98
		// Initialize our custom order array
99
		$sensei_menu_order = array();
100
101
		// Get the index of our custom separator
102
		$sensei_separator = array_search( 'separator-sensei', $menu_order );
103
104
		// Loop through menu order and do some rearranging
105
		foreach ( $menu_order as $index => $item ) :
106
107
			if ( ( ( 'sensei' ) == $item ) ) :
108
				$sensei_menu_order[] = 'separator-sensei';
109
				$sensei_menu_order[] = $item;
110
				unset( $menu_order[$sensei_separator] );
111
			elseif ( !in_array( $item, array( 'separator-sensei' ) ) ) :
112
				$sensei_menu_order[] = $item;
113
			endif;
114
115
		endforeach;
116
117
		// Return order
118
		return $sensei_menu_order;
119
	}
120
121
	/**
122
	 * Handle highlighting of admin menu items
123
	 * @since 1.4.0
124
	 * @return void
125
	 */
126
	public function admin_menu_highlight() {
127
		global $menu, $submenu, $parent_file, $submenu_file, $self, $post_type, $taxonomy;
128
129
		$screen = get_current_screen();
130
131
        if( empty( $screen ) ){
132
            return;
133
        }
134
135
		if ( $screen->base == 'post' && $post_type == 'course' ) {
136
137
			$parent_file  = 'edit.php?post_type=course';
138
139
		} elseif ( $screen->base == 'edit-tags' && $taxonomy == 'course-category' ) {
140
141
			$submenu_file = 'edit-tags.php?taxonomy=course-category&amp;post_type=course';
142
			$parent_file  = 'edit.php?post_type=course';
143
144
        } elseif ( $screen->base == 'edit-tags' && $taxonomy == 'module' ) {
145
146
            $submenu_file = 'edit-tags.php?taxonomy=module';
147
            $parent_file  = 'edit.php?post_type=course';
148
149
		} elseif ( in_array( $screen->id, array( 'sensei_message', 'edit-sensei_message' ) ) ) {
150
151
            $submenu_file = 'edit.php?post_type=sensei_message';
152
			$parent_file  = 'sensei';
153
154
		}
155
	}
156
157
	/**
158
	 * Redirect Sensei menu item to Analysis page
159
	 * @since  1.4.0
160
	 * @return void
161
	 */
162
	public function page_redirect() {
163
		if( isset( $_GET['page'] ) && $_GET['page'] == 'sensei' ) {
164
			wp_safe_redirect( 'admin.php?page=sensei_analysis' );
165
			exit;
166
		}
167
	}
168
169
	/**
170
	 * install_pages_output function.
171
	 *
172
	 * Handles installation of the 2 pages needs for courses and my courses
173
	 *
174
	 * @access public
175
	 * @return void
176
	 */
177
	function install_pages_output() {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
178
179
        if( isset($_GET['sensei_install_complete']) && 'true' == $_GET['sensei_install_complete']) {
180
181
            ?>
182
            <div id="message" class="updated sensei-message sensei-connect">
183
                <p><?php _e( '<strong>Congratulations!</strong> &#8211; Sensei has been installed and set up.', 'woothemes-sensei' ); ?></p>
184
                <p><a href="https://twitter.com/share" class="twitter-share-button" data-url="http://www.woothemes.com/sensei/" data-text="A premium Learning Management plugin for #WordPress that helps you create courses. Beautifully." data-via="WooThemes" data-size="large" data-hashtags="Sensei">Tweet</a>
185
                <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script></p>
186
            </div>
187
            <?php
188
189
        }
190
191
	} // End install_pages_output()
192
193
194
	/**
195
	 * create_page function.
196
	 *
197
	 * @access public
198
	 * @param mixed $slug
199
	 * @param mixed $option
0 ignored issues
show
There is no parameter named $option. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

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

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

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

Loading history...
200
	 * @param string $page_title (default: '')
201
	 * @param string $page_content (default: '')
202
	 * @param int $post_parent (default: 0)
203
	 * @return integer $page_id
204
	 */
205
	function create_page( $slug, $page_title = '', $page_content = '', $post_parent = 0 ) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
206
		global $wpdb;
207
208
        $page_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM " . $wpdb->posts . " WHERE post_name = %s LIMIT 1;", $slug ) );
209
		if ( $page_id ) :
210
			return $page_id;
211
		endif;
212
213
		$page_data = array(
214
	        'post_status' 		=> 'publish',
215
	        'post_type' 		=> 'page',
216
	        'post_author' 		=> 1,
217
	        'post_name' 		=> $slug,
218
	        'post_title' 		=> $page_title,
219
	        'post_content' 		=> $page_content,
220
	        'post_parent' 		=> $post_parent,
221
	        'comment_status' 	=> 'closed'
222
	    );
223
224
	    $page_id = wp_insert_post( $page_data );
225
226
	    return $page_id;
227
228
	} // End create_page()
229
230
231
	/**
232
	 * create_pages function.
233
	 *
234
	 * @access public
235
	 * @return void
236
	 */
237
	function create_pages() {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
238
239
		// Courses page
240
	    $new_course_page_id = $this->create_page( esc_sql( _x('courses-overview', 'page_slug', 'woothemes-sensei') ),  __('Courses', 'woothemes-sensei'), '' );
241
        Sensei()->settings->set( 'course_page', $new_course_page_id );
242
243
        // User Dashboard page
244
	    $new_my_course_page_id = $this->create_page( esc_sql( _x('my-courses', 'page_slug', 'woothemes-sensei') ), __('My Courses', 'woothemes-sensei'), '[sensei_user_courses]' );
245
        Sensei()->settings->set( 'my_course_page',$new_my_course_page_id  );
246
247
	} // End create_pages()
248
249
	/**
250
	 * Load the global admin styles for the menu icon and the relevant page icon.
251
	 * @access public
252
	 * @since 1.0.0
253
	 * @return void
254
	 */
255
	public function admin_styles_global ( $hook ) {
256
		global $post_type;
257
258
		$allowed_post_types = apply_filters( 'sensei_scripts_allowed_post_types', array( 'lesson', 'course', 'question' ) );
259
		$allowed_post_type_pages = apply_filters( 'sensei_scripts_allowed_post_type_pages', array( 'edit.php', 'post-new.php', 'post.php', 'edit-tags.php' ) );
260
		$allowed_pages = apply_filters( 'sensei_scripts_allowed_pages', array( 'sensei_grading', 'sensei_analysis', 'sensei_learners', 'sensei_updates', 'woothemes-sensei-settings', 'lesson-order', 'course-order' ) );
261
262
		// Global Styles for icons and menu items
263
		wp_register_style( 'woothemes-sensei-global', Sensei()->plugin_url . 'assets/css/global.css', '', Sensei()->version, 'screen' );
264
		wp_enqueue_style( 'woothemes-sensei-global' );
265
266
        // Select 2 styles
267
        wp_enqueue_style( 'sensei-core-select2', Sensei()->plugin_url . 'assets/css/select2/select2.css', '', Sensei()->version, 'screen' );
268
269
		// Test for Write Panel Pages
270 View Code Duplication
		if ( ( ( isset( $post_type ) && in_array( $post_type, $allowed_post_types ) ) && ( isset( $hook ) && in_array( $hook, $allowed_post_type_pages ) ) ) || ( isset( $_GET['page'] ) && in_array( $_GET['page'], $allowed_pages ) ) ) {
271
272
			wp_register_style( 'woothemes-sensei-admin-custom', Sensei()->plugin_url . 'assets/css/admin-custom.css', '', Sensei()->version, 'screen' );
273
			wp_enqueue_style( 'woothemes-sensei-admin-custom' );
274
275
		}
276
277
	} // End admin_styles_global()
278
279
280
    /**
281
     * Globally register all scripts needed in admin.
282
     *
283
     * The script users should enqueue the script when needed.
284
     *
285
     * @since 1.8.2
286
     * @access public
287
     */
288
    public function register_scripts( $hook ){
289
290
        $screen = get_current_screen();
291
292
        // Allow developers to load non-minified versions of scripts
293
        $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
294
295
        // Select2 script used to enhance all select boxes
296
        wp_register_script( 'sensei-core-select2', Sensei()->plugin_url . '/assets/js/select2/select2' . $suffix . '.js', array( 'jquery' ), Sensei()->version );
297
298
        // load edit module scripts
299
        if( 'edit-module' ==  $screen->id ){
300
301
            wp_enqueue_script( 'sensei-chosen-ajax', Sensei()->plugin_url . 'assets/chosen/ajax-chosen.jquery.min.js', array( 'jquery', 'sensei-chosen' ), Sensei()->version, true );
302
303
        }
304
305
    }
306
307
308
	/**
309
	 * admin_install_notice function.
310
	 *
311
	 * @access public
312
	 * @return void
313
	 */
314
	function admin_install_notice() {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
315
	    ?>
316
	    <div id="message" class="updated sensei-message sensei-connect">
317
318
            <p>
319
                <?php _e( '<strong>Welcome to Sensei</strong> &#8211; You\'re almost ready to create some courses!', 'woothemes-sensei' ); ?>
320
            </p>
321
322
            <p class="submit">
323
324
                <a href="<?php echo esc_url( add_query_arg('install_sensei_pages', 'true', admin_url('admin.php?page=woothemes-sensei-settings') ) ); ?>"
325
                   class="button-primary">
326
327
                    <?php _e( 'Install Sensei Pages', 'woothemes-sensei' ); ?>
328
329
                </a>
330
331
                <a class="skip button" href="<?php echo esc_url( add_query_arg( 'skip_install_sensei_pages', 'true', admin_url('admin.php?page=woothemes-sensei-settings' ) ) ); ?>">
332
333
                    <?php _e('Skip setup', 'woothemes-sensei'); ?>
334
335
                </a>
336
337
            </p>
338
	    </div>
339
	    <?php
340
	} // End admin_install_notice()
341
342
343
	/**
344
	 * admin_installed_notice function.
345
	 *
346
	 * @access public
347
	 * @return void
348
	 */
349
	function admin_installed_notice() {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
350
	    ?>
351
	    <div id="message" class="updated sensei-message sensei-connect">
352
353
	    	<p>
354
                <?php _e( '<strong>Sensei has been installed</strong> &#8211; You\'re ready to start creating courses!', 'woothemes-sensei' ); ?>
355
            </p>
356
357
			<p class="submit">
358
                <a href="<?php echo admin_url('admin.php?page=woothemes-sensei-settings'); ?>" class="button-primary"><?php _e( 'Settings', 'woothemes-sensei' ); ?></a> <a class="docs button" href="http://www.woothemes.com/sensei-docs/">
359
                    <?php _e('Documentation', 'woothemes-sensei'); ?>
360
                </a>
361
            </p>
362
363
            <p>
364
365
                <a href="https://twitter.com/share" class="twitter-share-button" data-url="http://www.woothemes.com/sensei/" data-text="A premium Learning Management plugin for #WordPress that helps you teach courses online. Beautifully." data-via="WooThemes" data-size="large" data-hashtags="Sensei">
366
                    <?php _e('Tweet', 'woothemes-sensei'); ?>
367
                </a>
368
369
                <script>
370
                    !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");
371
                </script>
372
373
            </p>
374
375
	    </div>
376
	    <?php
377
378
	    // Set installed option
379
	    update_option('sensei_installed', 0);
380
	} // End admin_installed_notice()
381
382
383
	/**
384
	 * Language pack install notice.
385
	 *
386
	 * @since 1.9.0
387
	 */
388
	public function language_pack_install_notice() {
389
		?>
390
		<div id="message" class="updated sensei-message sensei-connect">
391
				<p><?php _e( '<strong>Sensei in your language</strong> &#8211; There is a translation available for your language.', 'woothemes-sensei' ); ?><p>
392
393
				<p class="submit">
394
					<a href="<?php echo esc_url( Sensei_Language_Pack_Manager::get_install_uri() ); ?>" class="button-primary"><?php _e( 'Install', 'woothemes-sensei' ); ?></a>
395
					<a href="<?php echo esc_url( Sensei_Language_Pack_Manager::get_dismiss_uri() ) ?>" class="docs button"><?php _e( 'Hide this notice', 'woothemes-sensei' ); ?></a>
396
				</p>
397
		</div>
398
		<?php
399
	}
400
401
402
	/**
403
	 * admin_notices_styles function.
404
	 *
405
	 * @access public
406
	 * @return void
407
	 */
408
	function admin_notices_styles() {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
409
410
		// Installed notices
411
	    if ( 1 == get_option( 'sensei_installed' ) ) {
412
413
	    	wp_enqueue_style( 'sensei-activation', plugins_url(  '/assets/css/activation.css', dirname( __FILE__ ) ), '', Sensei()->version );
414
415
	    	if (get_option('skip_install_sensei_pages')!=1 && Sensei()->get_page_id('course')<1 && !isset($_GET['install_sensei_pages']) && !isset($_GET['skip_install_sensei_pages'])) {
416
	    		add_action( 'admin_notices', array( $this, 'admin_install_notice' ) );
417
	    	} elseif ( !isset($_GET['page']) || $_GET['page']!='woothemes-sensei-settings' ) {
418
	    		add_action( 'admin_notices', array( $this, 'admin_installed_notice' ) );
419
	    	} // End If Statement
420
421
	    } // End If Statement
422
423
	    if ( Sensei_Language_Pack_Manager::has_language_pack_available() ) {
424
	    	add_action( 'admin_notices', array( $this, 'language_pack_install_notice' ) );
425
	    }
426
427
	} // End admin_notices_styles()
428
429
	/**
430
	 * Add links for duplicating lessons & courses
431
	 * @param  array  $actions Default actions
432
	 * @param  object $post    Current post
433
	 * @return array           Modified actions
434
	 */
435
	public function duplicate_action_link( $actions, $post ) {
436
		switch( $post->post_type ) {
437
			case 'lesson':
438
				$confirm = __( 'This will duplicate the lesson quiz and all of its questions. Are you sure you want to do this?', 'woothemes-sensei' );
439
				$actions['duplicate'] = "<a onclick='return confirm(\"" . $confirm . "\");' href='" . $this->get_duplicate_link( $post->ID ) . "' title='" . esc_attr(__( 'Duplicate this lesson', 'woothemes-sensei' ) ) . "'>" .  __('Duplicate', 'woothemes-sensei' ) . "</a>";
440
			break;
441
442
			case 'course':
443
				$confirm = __( 'This will duplicate the course lessons along with all of their quizzes and questions. Are you sure you want to do this?', 'woothemes-sensei' );
444
				$actions['duplicate'] = '<a href="' . $this->get_duplicate_link( $post->ID ) . '" title="' . esc_attr(__( 'Duplicate this course', 'woothemes-sensei' ) ) . '">' .  __('Duplicate', 'woothemes-sensei' ) . '</a>';
445
				$actions['duplicate_with_lessons'] = '<a onclick="return confirm(\'' . $confirm . '\');" href="' . $this->get_duplicate_link( $post->ID, true ) . '" title="' . esc_attr(__( 'Duplicate this course with its lessons', 'woothemes-sensei' ) ) . '">' .  __('Duplicate (with lessons)', 'woothemes-sensei' ) . '</a>';
446
			break;
447
		}
448
449
		return $actions;
450
	}
451
452
	/**
453
	 * Generate duplicationlink
454
	 * @param  integer $post_id      Post ID
455
	 * @param  boolean $with_lessons Include lessons or not
456
	 * @return string                Duplication link
457
	 */
458
	private function get_duplicate_link( $post_id = 0, $with_lessons = false ) {
459
460
		$post = get_post( $post_id );
461
462
		$action = 'duplicate_' . $post->post_type;
463
464
		if( 'course' == $post->post_type && $with_lessons ) {
465
			$action .= '_with_lessons';
466
		}
467
468
		return apply_filters( $action . '_link', admin_url( 'admin.php?action=' . $action . '&post=' . $post_id ), $post_id );
469
	}
470
471
	/**
472
	 * Duplicate lesson
473
	 * @return void
474
	 */
475
	public function duplicate_lesson_action() {
476
		$this->duplicate_content( 'lesson' );
477
	}
478
479
	/**
480
	 * Duplicate course
481
	 * @return void
482
	 */
483
	public function duplicate_course_action() {
484
		$this->duplicate_content( 'course' );
485
	}
486
487
	/**
488
	 * Duplicate course with lessons
489
	 * @return void
490
	 */
491
	public function duplicate_course_with_lessons_action() {
492
		$this->duplicate_content( 'course', true );
493
	}
494
495
	/**
496
	 * Duplicate content
497
	 * @param  string  $post_type    Post type being duplicated
498
	 * @param  boolean $with_lessons Include lessons or not
499
	 * @return void
500
	 */
501
	private function duplicate_content( $post_type = 'lesson', $with_lessons = false ) {
502
		if ( ! isset( $_GET['post'] ) ) {
503
			wp_die( sprintf( __( 'Please supply a %1$s ID.', 'woothemes-sensei' ) ), $post_type );
504
		}
505
506
		$post_id = $_GET['post'];
507
		$post = get_post( $post_id );
508
509
		if( ! is_wp_error( $post ) ) {
510
511
			$new_post = $this->duplicate_post( $post );
512
513
			if( $new_post && ! is_wp_error( $new_post ) ) {
514
515
				if( 'lesson' == $new_post->post_type ) {
516
					$this->duplicate_lesson_quizzes( $post_id, $new_post->ID );
517
				}
518
519
				if( 'course' == $new_post->post_type && $with_lessons ) {
520
					$this->duplicate_course_lessons( $post_id, $new_post->ID );
521
				}
522
523
				$redirect_url = admin_url( 'post.php?post=' . $new_post->ID . '&action=edit' );
524
			} else {
525
				$redirect_url = admin_url( 'edit.php?post_type=' . $post->post_type . '&message=duplicate_failed' );
526
			}
527
528
			wp_safe_redirect( esc_url_raw( $redirect_url ) );
529
			exit;
530
		}
531
	}
532
533
	/**
534
	 * Duplicate quizzes inside lessons
535
	 * @param  integer $old_lesson_id ID of original lesson
536
	 * @param  integer $new_lesson_id ID of duplicate lesson
537
	 * @return void
538
	 */
539
	private function duplicate_lesson_quizzes( $old_lesson_id, $new_lesson_id ) {
540
541
        $old_quiz_id = Sensei()->lesson->lesson_quizzes( $old_lesson_id );
542
        $old_quiz_questions = Sensei()->lesson->lesson_quiz_questions( $old_quiz_id );
543
544
        // duplicate the generic wp post information
545
		$new_quiz = $this->duplicate_post( get_post( $old_quiz_id ), '' );
546
547
		//update the new lesson data
548
        add_post_meta( $new_lesson_id, '_lesson_quiz', $new_quiz->ID );
549
550
		//update the new quiz data
551
        add_post_meta( $new_quiz->ID, '_quiz_lesson', $new_lesson_id );
552
        wp_update_post(
553
            array(
554
                'ID' => $new_quiz->ID,
555
                'post_parent' => $new_lesson_id
556
            )
557
        );
558
559
		foreach( $old_quiz_questions as $question ) {
560
561
			// copy the question order over to the new quiz
562
			$old_question_order = get_post_meta( $question->ID, '_quiz_question_order'. $old_quiz_id, true );
563
            $new_question_order = str_ireplace( $old_quiz_id, $new_quiz->ID , $old_question_order );
564
            add_post_meta( $question->ID, '_quiz_question_order' . $new_quiz->ID, $new_question_order );
565
566
			// Add question to quiz
567
			add_post_meta( $question->ID, '_quiz_id', $new_quiz->ID, false );
568
569
		}
570
	}
571
572
	/**
573
	 * Duplicate lessons inside a course
574
	 * @param  integer $old_course_id ID of original course
575
	 * @param  integer $new_course_id ID of duplicated course
576
	 * @return void
577
	 */
578
	private function duplicate_course_lessons( $old_course_id, $new_course_id ) {
579
		$lesson_args = array(
580
			'post_type' => 'lesson',
581
			'posts_per_page' => -1,
582
			'meta_key' => '_lesson_course',
583
			'meta_value' => $old_course_id,
584
			'suppress_filters' 	=> 0
585
		);
586
		$lessons = get_posts( $lesson_args );
587
588
		foreach( $lessons as $lesson ) {
589
			$new_lesson = $this->duplicate_post( $lesson, '', true );
590
			add_post_meta( $new_lesson->ID, '_lesson_course', $new_course_id );
591
592
			$this->duplicate_lesson_quizzes( $lesson->ID, $new_lesson->ID );
593
		}
594
	}
595
596
	/**
597
	 * Duplicate post
598
	 * @param  object  $post          Post to be duplicated
599
	 * @param  string  $suffix        Suffix for duplicated post title
600
	 * @param  boolean $ignore_course Ignore lesson course when dulicating
601
	 * @return object                 Duplicate post object
602
	 */
603
	private function duplicate_post( $post, $suffix = ' (Duplicate)', $ignore_course = false ) {
604
605
		$new_post = array();
606
607
		foreach( $post as $k => $v ) {
608
			if( ! in_array( $k, array( 'ID', 'post_status', 'post_date', 'post_date_gmt', 'post_name', 'post_modified', 'post_modified_gmt', 'guid', 'comment_count' ) ) ) {
609
				$new_post[ $k ] = $v;
610
			}
611
		}
612
613
		$new_post['post_title'] .= __( $suffix, 'woothemes-sensei' );
614
615
		$new_post['post_date'] = current_time( 'mysql' );
616
		$new_post['post_date_gmt'] = get_gmt_from_date( $new_post['post_date'] );
617
		$new_post['post_modified'] = $new_post['post_date'];
618
		$new_post['post_modified_gmt'] = $new_post['post_date_gmt'];
619
620
		switch( $post->post_type ) {
621
			case 'course': $new_post['post_status'] = 'draft'; break;
622
			case 'lesson': $new_post['post_status'] = 'draft'; break;
623
			case 'quiz': $new_post['post_status'] = 'publish'; break;
624
			case 'question': $new_post['post_status'] = 'publish'; break;
625
		}
626
627
		// As per wp_update_post() we need to escape the data from the db.
628
		$new_post = wp_slash( $new_post );
629
630
		$new_post_id = wp_insert_post( $new_post );
631
632
		if( ! is_wp_error( $new_post_id ) ) {
633
634
			$post_meta = get_post_custom( $post->ID );
635
			if( $post_meta && count( $post_meta ) > 0 ) {
636
637
				$ignore_meta = array( '_quiz_lesson', '_quiz_id', '_lesson_quiz' );
638
639
				if( $ignore_course ) {
640
					$ignore_meta[] = '_lesson_course';
641
				}
642
643
				foreach( $post_meta as $key => $meta ) {
644
					foreach( $meta as $value ) {
645
						$value = maybe_unserialize( $value );
646
						if( ! in_array( $key, $ignore_meta ) ) {
647
							add_post_meta( $new_post_id, $key, $value );
648
						}
649
					}
650
				}
651
			}
652
653
			add_post_meta( $new_post_id, '_duplicate', $post->ID );
654
655
			$taxonomies = get_object_taxonomies( $post->post_type, 'objects' );
656
657
			foreach ( $taxonomies as $slug => $tax ) {
658
				$terms = get_the_terms( $post->ID, $slug );
659
				if( isset( $terms ) && is_array( $terms ) && 0 < count( $terms ) ) {
660
					foreach( $terms as $term ) {
661
						wp_set_object_terms( $new_post_id, $term->term_id, $slug, true );
662
					}
663
				}
664
			}
665
666
			$new_post = get_post( $new_post_id );
667
668
			return $new_post;
669
		}
670
671
		return false;
672
	}
673
674
	/**
675
	 * Add options to filter lessons
676
	 * @return void
677
	 */
678
	public function lesson_filter_options() {
679
		global $typenow;
680
681
		if( is_admin() && 'lesson' == $typenow ) {
682
683
			$args = array(
684
				'post_type' => 'course',
685
				'post_status' => array('publish', 'pending', 'draft', 'future', 'private'),
686
				'posts_per_page' => -1,
687
				'suppress_filters' => 0,
688
				'orderby' => 'menu_order date',
689
				'order' => 'ASC',
690
			);
691
			$courses = get_posts( $args );
692
693
			$selected = isset( $_GET['lesson_course'] ) ? $_GET['lesson_course'] : '';
694
			$course_options = '';
695
			foreach( $courses as $course ) {
696
				$course_options .= '<option value="' . esc_attr( $course->ID ) . '" ' . selected( $selected, $course->ID, false ) . '>' . get_the_title( $course->ID ) . '</option>';
697
			}
698
699
			$output = '<select name="lesson_course" id="dropdown_lesson_course">';
700
			$output .= '<option value="">'.__( 'Show all courses', 'woothemes-sensei' ).'</option>';
701
			$output .= $course_options;
702
			$output .= '</select>';
703
704
			echo $output;
705
		}
706
	}
707
708
	/**
709
	 * Filter lessons
710
	 * @param  array $request Current request
711
	 * @return array          Modified request
712
	 */
713
	public function lesson_filter_actions( $request ) {
714
		global $typenow;
715
716
		if( is_admin() && 'lesson' == $typenow ) {
717
			$lesson_course = isset( $_GET['lesson_course'] ) ? $_GET['lesson_course'] : '';
718
719
			if( $lesson_course ) {
720
				$request['meta_key'] = '_lesson_course';
721
				$request['meta_value'] = $lesson_course;
722
				$request['meta_compare'] = '=';
723
			}
724
		}
725
726
		return $request;
727
	}
728
729
	/**
730
	 * Adding Sensei items to 'At a glance' dashboard widget
731
	 * @param  array $items Existing items
732
	 * @return array        Updated items
733
	 */
734
	public function glance_items( $items = array() ) {
735
736
		$types = array( 'course', 'lesson', 'question' );
737
738
		foreach( $types as $type ) {
739
			if( ! post_type_exists( $type ) ) continue;
740
741
			$num_posts = wp_count_posts( $type );
742
743
			if( $num_posts ) {
744
745
				$published = intval( $num_posts->publish );
746
				$post_type = get_post_type_object( $type );
747
748
				$text = _n( '%s ' . $post_type->labels->singular_name, '%s ' . $post_type->labels->name, $published, 'woothemes-sensei' );
749
				$text = sprintf( $text, number_format_i18n( $published ) );
750
751
				if ( current_user_can( $post_type->cap->edit_posts ) ) {
752
					$items[] = sprintf( '<a class="%1$s-count" href="edit.php?post_type=%1$s">%2$s</a>', $type, $text ) . "\n";
753
				} else {
754
					$items[] = sprintf( '<span class="%1$s-count">%2$s</span>', $type, $text ) . "\n";
755
				}
756
			}
757
		}
758
759
		return $items;
760
	}
761
762
	/**
763
	 * Process lesson and course deletion
764
	 * @param  integer $post_id Post ID
765
	 * @param  object  $post    Post object
766
	 * @return void
767
	 */
768
	public function delete_content( $post_id, $post ) {
769
770
		$type = $post->post_type;
771
772
		if( in_array( $type, array( 'lesson', 'course' ) ) ) {
773
774
			$meta_key = '_' . $type . '_prerequisite';
775
776
			$args = array(
777
				'post_type' => $type,
778
				'post_status' => 'any',
779
				'posts_per_page' => -1,
780
				'meta_key' => $meta_key,
781
				'meta_value' => $post_id
782
			);
783
784
			$posts = get_posts( $args );
785
786
			foreach( $posts as $post ) {
787
				delete_post_meta( $post->ID, $meta_key );
788
			}
789
		}
790
	}
791
792
	/**
793
	 * Delete all user activity when user is deleted
794
	 * @param  integer $user_id User ID
795
	 * @return void
796
	 */
797
	public function delete_user_activity( $user_id = 0 ) {
798
		if( $user_id ) {
799
			Sensei_Utils::delete_all_user_activity( $user_id );
800
		}
801
	}
802
803
	public function render_settings( $settings = array(), $post_id = 0, $group_id = '' ) {
804
805
		$html = '';
806
807
		if( 0 == count( $settings ) ) return $html;
808
809
		$html .= '<div class="sensei-options-panel">' . "\n";
810
811
			$html .= '<div class="options_group" id="' . esc_attr( $group_id ) . '">' . "\n";
812
813
				foreach( $settings as $field ) {
814
815
					$data = '';
816
817
					if( $post_id ) {
818
						$data = get_post_meta( $post_id, '_' . $field['id'], true );
819
						if( ! $data && isset( $field['default'] ) ) {
820
							$data = $field['default'];
821
						}
822
					} else {
823
						$option = get_option( $field['id'] );
824
						if( isset( $field['default'] ) ) {
825
							$data = $field['default'];
826
							if( $option ) {
827
								$data = $option;
828
							}
829
						}
830
					}
831
832
					$disabled = '';
833
					if( isset( $field['disabled'] ) && $field['disabled'] ) {
834
						$disabled = disabled( $field['disabled'], true, false );
835
					}
836
837
					if( 'hidden' != $field['type'] ) {
838
839
						$class_tail = '';
840
841
						if( isset( $field['class'] ) ) {
842
							$class_tail .= $field['class'];
843
						}
844
845
						if( isset( $field['disabled'] ) && $field['disabled'] ) {
846
							$class_tail .= ' disabled';
847
						}
848
849
						$html .= '<p class="form-field ' . esc_attr( $field['id'] ) . ' ' . esc_attr( $class_tail ) . '">' . "\n";
850
					}
851
852
						if( ! in_array( $field['type'], array( 'hidden', 'checkbox_multi', 'radio' ) ) ) {
853
							$html .= '<label for="' . esc_attr( $field['id'] ) . '">' . "\n";
854
						}
855
856
							if( $field['label'] ) {
857
								$html .= '<span class="label">' . esc_html( $field['label'] ) . '</span>';
858
							}
859
860
							switch( $field['type'] ) {
861
								case 'text':
862
								case 'password':
863
									$html .= '<input id="' . esc_attr( $field['id'] ) . '" type="' . $field['type'] . '" name="' . esc_attr( $field['id'] ) . '" placeholder="' . esc_attr( $field['placeholder'] ) . '" value="' . $data . '" ' . $disabled . ' />' . "\n";
864
								break;
865
866
								case 'number':
867
868
									$min = '';
869 View Code Duplication
									if( isset( $field['min'] ) ) {
870
										$min = 'min="' . esc_attr( $field['min'] ) . '"';
871
									}
872
873
									$max = '';
874 View Code Duplication
									if( isset( $field['max'] ) ) {
875
										$max = 'max="' . esc_attr( $field['max'] ) . '"';
876
									}
877
878
									$html .= '<input id="' . esc_attr( $field['id'] ) . '" type="' . $field['type'] . '" name="' . esc_attr( $field['id'] ) . '" placeholder="' . esc_attr( $field['placeholder'] ) . '" value="' . $data . '" ' . $min . '  ' . $max . ' class="small-text" ' . $disabled . ' />' . "\n";
879
								break;
880
881 View Code Duplication
								case 'textarea':
882
									$html .= '<textarea id="' . esc_attr( $field['id'] ) . '" rows="5" cols="50" name="' . esc_attr( $field['id'] ) . '" placeholder="' . esc_attr( $field['placeholder'] ) . '" ' . $disabled . '>' . $data . '</textarea><br/>'. "\n";
883
								break;
884
885
								case 'checkbox':
886
                                    //backwards compatibility
887
                                    if( empty( $data ) || 'on' == $data ){
888
                                        $checked_value = 'on';
889
                                    }elseif( 'yes' == $data  ) {
890
891
                                        $checked_value = 'yes';
892
893
                                    }elseif( 'auto' == $data  ) {
894
895
                                        $checked_value = 'auto';
896
897
                                    } else {
898
                                        $checked_value = 1;
899
                                        $data = intval( $data );
900
                                    }
901
									$checked = checked( $checked_value, $data, false );
902
									$html .= '<input id="' . esc_attr( $field['id'] ) . '" type="' . $field['type'] . '" name="' . esc_attr( $field['id'] ) . '" ' . $checked . ' ' . $disabled . '/>' . "\n";
903
								break;
904
905 View Code Duplication
								case 'checkbox_multi':
906
									foreach( $field['options'] as $k => $v ) {
907
										$checked = false;
908
										if( in_array( $k, $data ) ) {
909
											$checked = true;
910
										}
911
										$html .= '<label for="' . esc_attr( $field['id'] . '_' . $k ) . '"><input type="checkbox" ' . checked( $checked, true, false ) . ' name="' . esc_attr( $field['id'] ) . '[]" value="' . esc_attr( $k ) . '" id="' . esc_attr( $field['id'] . '_' . $k ) . '" ' . $disabled . ' /> ' . $v . '</label> ' . "\n";
912
									}
913
								break;
914
915 View Code Duplication
								case 'radio':
916
									foreach( $field['options'] as $k => $v ) {
917
										$checked = false;
918
										if( $k == $data ) {
919
											$checked = true;
920
										}
921
										$html .= '<label for="' . esc_attr( $field['id'] . '_' . $k ) . '"><input type="radio" ' . checked( $checked, true, false ) . ' name="' . esc_attr( $field['id'] ) . '" value="' . esc_attr( $k ) . '" id="' . esc_attr( $field['id'] . '_' . $k ) . '" ' . $disabled . ' /> ' . $v . '</label> ' . "\n";
922
									}
923
								break;
924
925 View Code Duplication
								case 'select':
926
									$html .= '<select name="' . esc_attr( $field['id'] ) . '" id="' . esc_attr( $field['id'] ) . '" ' . $disabled . '>' . "\n";
927
									foreach( $field['options'] as $k => $v ) {
928
										$selected = false;
929
										if( $k == $data ) {
930
											$selected = true;
931
										}
932
										$html .= '<option ' . selected( $selected, true, false ) . ' value="' . esc_attr( $k ) . '">' . $v . '</option>' . "\n";
933
									}
934
									$html .= '</select><br/>' . "\n";
935
								break;
936
937 View Code Duplication
								case 'select_multi':
938
									$html .= '<select name="' . esc_attr( $field['id'] ) . '[]" id="' . esc_attr( $field['id'] ) . '" multiple="multiple" ' . $disabled . '>' . "\n";
939
									foreach( $field['options'] as $k => $v ) {
940
										$selected = false;
941
										if( in_array( $k, $data ) ) {
942
											$selected = true;
943
										}
944
										$html .= '<option ' . selected( $selected, true, false ) . ' value="' . esc_attr( $k ) . '" />' . $v . '</option>' . "\n";
945
									}
946
									$html .= '</select> . "\n"';
947
								break;
948
949 View Code Duplication
								case 'hidden':
950
									$html .= '<input id="' . esc_attr( $field['id'] ) . '" type="' . $field['type'] . '" name="' . esc_attr( $field['id'] ) . '" value="' . $data . '" ' . $disabled . '/>' . "\n";
951
								break;
952
953
							}
954
955
							if( $field['description'] ) {
956
								$html .= ' <span class="description">' . esc_html( $field['description'] ) . '</span>' . "\n";
957
							}
958
959
						if( ! in_array( $field['type'], array( 'hidden', 'checkbox_multi', 'radio' ) ) ) {
960
							$html .= '</label>' . "\n";
961
						}
962
963
					if( 'hidden' != $field['type'] ) {
964
						$html .= '</p>' . "\n";
965
					}
966
967
				}
968
969
			$html .= '</div>' . "\n";
970
971
		$html .= '</div>' . "\n";
972
973
		return $html;
974
	}
975
976
	/**
977
	 * Dsplay Course Order screen
978
	 * @return void
979
	 */
980
	public function course_order_screen() {
981
982
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
983
		wp_enqueue_script( 'woothemes-sensei-settings', esc_url( Sensei()->plugin_url . 'assets/js/settings' . $suffix . '.js' ), array( 'jquery', 'jquery-ui-sortable' ), Sensei()->version );
984
985
		?><div id="course-order" class="wrap course-order">
986
		<h2><?php _e( 'Order Courses', 'woothemes-sensei' ); ?></h2><?php
987
988
		$html = '';
989
990 View Code Duplication
		if( isset( $_POST['course-order'] ) && 0 < strlen( $_POST['course-order'] ) ) {
991
			$ordered = $this->save_course_order( esc_attr( $_POST['course-order'] ) );
992
993
			if( $ordered ) {
994
				$html .= '<div class="updated fade">' . "\n";
995
				$html .= '<p>' . __( 'The course order has been saved.', 'woothemes-sensei' ) . '</p>' . "\n";
996
				$html .= '</div>' . "\n";
997
			}
998
		}
999
1000
		$courses = Sensei()->course->get_all_courses();
1001
1002
		if( 0 < count( $courses ) ) {
1003
1004
            // order the courses as set by the users
1005
            $all_course_ids = array();
1006
            foreach( $courses as $course ){
1007
1008
                $all_course_ids[] = (string)$course->ID;
1009
1010
            }
1011
            $order_string = $this->get_course_order();
1012
1013
            if( !empty( $order_string ) ){
1014
                $ordered_course_ids = explode(',' , $order_string );
1015
                $all_course_ids = array_unique( array_merge( $ordered_course_ids , $all_course_ids ) );
1016
            }
1017
1018
1019
			$html .= '<form id="editgrouping" method="post" action="" class="validate">' . "\n";
1020
			$html .= '<ul class="sortable-course-list">' . "\n";
1021
			$count = 0;
1022
			foreach ( $all_course_ids as $course_id ) {
1023
                $course = get_post( $course_id );
1024
				$count++;
1025
				$class = 'course';
1026
				if ( $count == 1 ) { $class .= ' first'; }
1027
				if ( $count == count( $course ) ) { $class .= ' last'; }
1028
				if ( $count % 2 != 0 ) {
1029
					$class .= ' alternate';
1030
				}
1031
				$html .= '<li class="' . esc_attr( $class ) . '"><span rel="' . esc_attr( $course->ID ) . '" style="width: 100%;"> ' . $course->post_title . '</span></li>' . "\n";
1032
			}
1033
			$html .= '</ul>' . "\n";
1034
1035
			$html .= '<input type="hidden" name="course-order" value="' . esc_attr( $order_string ) . '" />' . "\n";
1036
			$html .= '<input type="submit" class="button-primary" value="' . __( 'Save course order', 'woothemes-sensei' ) . '" />' . "\n";
1037
		}
1038
1039
		echo $html;
1040
1041
		?></div><?php
1042
	}
1043
1044
	public function get_course_order() {
1045
		return get_option( 'sensei_course_order', '' );
1046
	}
1047
1048
	public function save_course_order( $order_string = '' ) {
1049
		$order = explode( ',', $order_string );
1050
1051
		update_option( 'sensei_course_order', $order_string );
1052
1053
		$i = 1;
1054
		foreach( $order as $course_id ) {
1055
1056
			if( $course_id ) {
1057
1058
				$update_args = array(
1059
					'ID' => $course_id,
1060
					'menu_order' => $i,
1061
				);
1062
1063
				wp_update_post( $update_args );
1064
1065
				++$i;
1066
			}
1067
		}
1068
1069
		return true;
1070
	}
1071
1072
	/**
1073
	 * Dsplay Lesson Order screen
1074
	 * @return void
1075
	 */
1076
	public function lesson_order_screen() {
1077
1078
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
1079
		wp_enqueue_script( 'woothemes-sensei-settings', esc_url( Sensei()->plugin_url . 'assets/js/settings' . $suffix . '.js' ), array( 'jquery', 'jquery-ui-sortable' ), Sensei()->version );
1080
1081
		?><div id="lesson-order" class="wrap lesson-order">
1082
		<h2><?php _e( 'Order Lessons', 'woothemes-sensei' ); ?></h2><?php
1083
1084
		$html = '';
1085
1086 View Code Duplication
		if( isset( $_POST['lesson-order'] ) ) {
1087
1088
			$ordered = $this->save_lesson_order( esc_attr( $_POST['lesson-order'] ), esc_attr( $_POST['course_id'] ) );
1089
1090
			if( $ordered ) {
1091
				$html .= '<div class="updated fade">' . "\n";
1092
				$html .= '<p>' . __( 'The lesson order has been saved.', 'woothemes-sensei' ) . '</p>' . "\n";
1093
				$html .= '</div>' . "\n";
1094
			}
1095
		}
1096
1097
		$args = array(
1098
			'post_type' => 'course',
1099
			'post_status' => array('publish', 'draft', 'future', 'private'),
1100
			'posts_per_page' => -1,
1101
			'orderby' => 'name',
1102
			'order' => 'ASC',
1103
		);
1104
		$courses = get_posts( $args );
1105
1106
		$html .= '<form action="' . admin_url( 'edit.php' ) . '" method="get">' . "\n";
1107
		$html .= '<input type="hidden" name="post_type" value="lesson" />' . "\n";
1108
		$html .= '<input type="hidden" name="page" value="lesson-order" />' . "\n";
1109
		$html .= '<select id="lesson-order-course" name="course_id">' . "\n";
1110
		$html .= '<option value="">' . __( 'Select a course', 'woothemes-sensei' ) . '</option>' . "\n";
1111
1112
		foreach( $courses as $course ) {
1113
			$course_id = '';
1114
			if( isset( $_GET['course_id'] ) ) {
1115
				$course_id = intval( $_GET['course_id'] );
1116
			}
1117
			$html .= '<option value="' . esc_attr( intval( $course->ID ) ) . '" ' . selected( $course->ID, $course_id, false ) .'>' . get_the_title( $course->ID ) . '</option>' . "\n";
1118
		}
1119
1120
		$html .= '</select>' . "\n";
1121
		$html .= '<input type="submit" class="button-primary lesson-order-select-course-submit" value="' . __( 'Select', 'woothemes-sensei' ) . '" />' . "\n";
1122
		$html .= '</form>' . "\n";
1123
1124
		$html .= '<script type="text/javascript">' . "\n";
1125
		$html .= 'jQuery( \'#lesson-order-course\' ).select2({width:"resolve"});' . "\n";
1126
		$html .= '</script>' . "\n";
1127
1128
		if( isset( $_GET['course_id'] ) ) {
1129
			$course_id = intval( $_GET['course_id'] );
1130
			if( $course_id > 0 ) {
1131
1132
				$order_string = $this->get_lesson_order( $course_id );
1133
1134
				$html .= '<form id="editgrouping" method="post" action="" class="validate">' . "\n";
1135
1136
				$displayed_lessons = array();
1137
1138
                $modules = Sensei()->modules->get_course_modules( intval( $course_id ) );
1139
1140
                foreach( $modules as $module ) {
1141
1142
                    $args = array(
1143
                        'post_type' => 'lesson',
1144
                        'post_status' => 'publish',
1145
                        'posts_per_page' => -1,
1146
                        'meta_query' => array(
1147
                            array(
1148
                                'key' => '_lesson_course',
1149
                                'value' => intval( $course_id ),
1150
                                'compare' => '='
1151
                            )
1152
                        ),
1153
                        'tax_query' => array(
1154
                            array(
1155
                                'taxonomy' => Sensei()->modules->taxonomy,
1156
                                'field' => 'id',
1157
                                'terms' => intval( $module->term_id )
1158
                            )
1159
                        ),
1160
                        'meta_key' => '_order_module_' . $module->term_id,
1161
                        'orderby' => 'meta_value_num date',
1162
                        'order' => 'ASC',
1163
                        'suppress_filters' => 0
1164
                    );
1165
1166
                    $lessons = get_posts( $args );
1167
1168
                    if( count( $lessons ) > 0 ) {
1169
                        $html .= '<h3>' . $module->name . '</h3>' . "\n";
1170
                        $html .= '<ul class="sortable-lesson-list" data-module_id="' . $module->term_id . '">' . "\n";
1171
1172
                        $count = 0;
1173 View Code Duplication
                        foreach( $lessons as $lesson ) {
1174
                            $count++;
1175
                            $class = 'lesson';
1176
                            if ( $count == 1 ) { $class .= ' first'; }
1177
                            if ( $count == count( $lesson ) ) { $class .= ' last'; }
1178
                            if ( $count % 2 != 0 ) {
1179
                                $class .= ' alternate';
1180
                            }
1181
1182
                            $html .= '<li class="' . esc_attr( $class ) . '"><span rel="' . esc_attr( $lesson->ID ) . '" style="width: 100%;"> ' . $lesson->post_title . '</span></li>' . "\n";
1183
1184
                            $displayed_lessons[] = $lesson->ID;
1185
                        }
1186
1187
                        $html .= '</ul>' . "\n";
1188
1189
                        $html .= '<input type="hidden" name="lesson-order-module-' . $module->term_id . '" value="" />' . "\n";
1190
                    }
1191
                }
1192
1193
1194
                $lessons = Sensei()->course->course_lessons( $course_id );
1195
1196
				if( 0 < count( $lessons ) ) {
1197
1198
                    //get module term ids, will be used to exclude lessons
1199
                    $module_items_ids = array();
1200
                    if( ! empty( $modules ) ) {
1201
                        foreach ($modules as $module) {
1202
                            $module_items_ids[] = $module->term_id;
1203
                        }
1204
                    }
1205
1206 View Code Duplication
					if( 0 < count( $displayed_lessons ) ) {
1207
						$html .= '<h3>' . __( 'Other Lessons', 'woothemes-sensei' ) . '</h3>' . "\n";
1208
					}
1209
1210
					$html .= '<ul class="sortable-lesson-list" data-module_id="0">' . "\n";
1211
					$count = 0;
1212
					foreach ( $lessons as $lesson ) {
1213
1214
                        // if lesson belongs to one fo the course modules then exclude it here
1215
                        // as it is listed above
1216
                        if( has_term( $module_items_ids, 'module', $lesson->ID )  ){
1217
1218
                            continue;
1219
1220
                        }
1221
1222
						$count++;
1223
						$class = 'lesson';
1224
						if ( $count == 1 ) { $class .= ' first'; }
1225
						if ( $count == count( $lesson ) ) { $class .= ' last'; }
1226
						if ( $count % 2 != 0 ) {
1227
1228
							$class .= ' alternate';
1229
1230
						}
1231
						$html .= '<li class="' . esc_attr( $class ) . '"><span rel="' . esc_attr( $lesson->ID ) . '" style="width: 100%;"> ' . $lesson->post_title . '</span></li>' . "\n";
1232
1233
						$displayed_lessons[] = $lesson->ID;
1234
					}
1235
					$html .= '</ul>' . "\n";
1236 View Code Duplication
				} else {
1237
					if( 0 == count( $displayed_lessons ) ) {
1238
						$html .= '<p><em>' . __( 'There are no lessons in this course.', 'woothemes-sensei' ) . '</em></p>';
1239
					}
1240
				}
1241
1242
				if( 0 < count( $displayed_lessons ) ) {
1243
					$html .= '<input type="hidden" name="lesson-order" value="' . esc_attr( $order_string ) . '" />' . "\n";
1244
					$html .= '<input type="hidden" name="course_id" value="' . $course_id . '" />' . "\n";
1245
					$html .= '<input type="submit" class="button-primary" value="' . __( 'Save lesson order', 'woothemes-sensei' ) . '" />' . "\n";
1246
				}
1247
			}
1248
		}
1249
1250
		echo $html;
1251
1252
		?></div><?php
1253
	}
1254
1255
	public function get_lesson_order( $course_id = 0 ) {
1256
		$order_string = get_post_meta( $course_id, '_lesson_order', true );
1257
		return $order_string;
1258
	}
1259
1260
	public function save_lesson_order( $order_string = '', $course_id = 0 ) {
1261
1262
		if( $course_id ) {
1263
1264
            $modules = Sensei()->modules->get_course_modules( intval( $course_id ) );
1265
1266
            foreach( $modules as $module ) {
1267
1268
1269
                if( isset( $_POST[ 'lesson-order-module-' . $module->term_id ] )
1270
                    && $_POST[ 'lesson-order-module-' . $module->term_id ] ) {
1271
1272
                    $order = explode( ',', $_POST[ 'lesson-order-module-' . $module->term_id ] );
1273
                    $i = 1;
1274
                    foreach( $order as $lesson_id ) {
1275
1276
                        if( $lesson_id ) {
1277
                            update_post_meta( $lesson_id, '_order_module_' . $module->term_id, $i );
1278
                            ++$i;
1279
                        }
1280
1281
                    }// end for each order
1282
1283
                }// end if
1284
1285
            } // end for each modules
1286
1287
1288
			if( $order_string ) {
1289
				update_post_meta( $course_id, '_lesson_order', $order_string );
1290
1291
				$order = explode( ',', $order_string );
1292
1293
				$i = 1;
1294
				foreach( $order as $lesson_id ) {
1295
					if( $lesson_id ) {
1296
						update_post_meta( $lesson_id, '_order_' . $course_id, $i );
1297
						++$i;
1298
					}
1299
				}
1300
			}
1301
1302
			return true;
1303
		}
1304
1305
		return false;
1306
	}
1307
1308
	function sensei_add_custom_menu_items() {
1309
		global $pagenow;
1310
1311
		if( 'nav-menus.php' == $pagenow ) {
1312
			add_meta_box( 'add-sensei-links', 'Sensei', array( $this, 'wp_nav_menu_item_sensei_links_meta_box' ), 'nav-menus', 'side', 'low' );
1313
		}
1314
	}
1315
1316
	function wp_nav_menu_item_sensei_links_meta_box( $object ) {
1317
		global $nav_menu_selected_id;
1318
1319
		$menu_items = array(
1320
			'#senseicourses' => __( 'Courses', 'woothemes-sensei' ),
1321
			'#senseilessons' => __( 'Lessons', 'woothemes-sensei' ),
1322
			'#senseimycourses' => __( 'My Courses', 'woothemes-sensei' ),
1323
			'#senseilearnerprofile' => __( 'My Profile', 'woothemes-sensei' ),
1324
			'#senseimymessages' => __( 'My Messages', 'woothemes-sensei' ),
1325
			'#senseiloginlogout' => __( 'Login', 'woothemes-sensei' ) . '|' . __( 'Logout', 'woothemes-sensei' )
1326
		);
1327
1328
		$menu_items_obj = array();
1329
		foreach ( $menu_items as $value => $title ) {
1330
			$menu_items_obj[$title] = new stdClass;
1331
			$menu_items_obj[$title]->object_id			= esc_attr( $value );
1332
			$menu_items_obj[$title]->title				= esc_attr( $title );
1333
			$menu_items_obj[$title]->url				= esc_attr( $value );
1334
			$menu_items_obj[$title]->description 		= 'description';
1335
			$menu_items_obj[$title]->db_id 				= 0;
1336
			$menu_items_obj[$title]->object 			= 'sensei';
1337
			$menu_items_obj[$title]->menu_item_parent 	= 0;
1338
			$menu_items_obj[$title]->type 				= 'custom';
1339
			$menu_items_obj[$title]->target 			= '';
1340
			$menu_items_obj[$title]->attr_title 		= '';
1341
			$menu_items_obj[$title]->classes 			= array();
1342
			$menu_items_obj[$title]->xfn 				= '';
1343
		}
1344
1345
		$walker = new Walker_Nav_Menu_Checklist( array() );
1346
		?>
1347
1348
		<div id="sensei-links" class="senseidiv taxonomydiv">
1349
			<div id="tabs-panel-sensei-links-all" class="tabs-panel tabs-panel-view-all tabs-panel-active">
1350
1351
				<ul id="sensei-linkschecklist" class="list:sensei-links categorychecklist form-no-clear">
1352
					<?php echo walk_nav_menu_tree( array_map( 'wp_setup_nav_menu_item', $menu_items_obj ), 0, (object)array( 'walker' => $walker ) ); ?>
1353
				</ul>
1354
1355
			</div>
1356
			<p class="button-controls">
1357
				<span class="add-to-menu">
1358
					<input type="submit"<?php disabled( $nav_menu_selected_id, 0 ); ?> class="button-secondary submit-add-to-menu right" value="<?php esc_attr_e( 'Add to Menu', 'woothemes-sensei' ); ?>" name="add-sensei-links-menu-item" id="submit-sensei-links" />
1359
					<span class="spinner"></span>
1360
				</span>
1361
			</p>
1362
		</div><!-- .senseidiv -->
1363
		<?php
1364
	}
1365
1366
	/**
1367
	 * Adding admin notice if the current
1368
     * installed theme is not compatible
1369
     *
1370
	 * @return void
1371
	 */
1372
	public function theme_compatibility_notices() {
1373
1374
        if( isset( $_GET['sensei_hide_notice'] ) ) {
1375
        	switch( esc_attr( $_GET['sensei_hide_notice'] ) ) {
1376
				case 'menu_settings': add_user_meta( get_current_user_id(), 'sensei_hide_menu_settings_notice', true ); break;
1377
				case 'theme_check': add_user_meta( get_current_user_id(), 'sensei_hide_theme_check_notice', true ); break;
1378
			}
1379
        }
1380
1381
        // white list templates that are already support by default and do not show notice for them
1382
        $template = get_option( 'template' );
1383
1384
        $white_list = array(    'twentyeleven',
1385
                                'twentytwelve',
1386
                                'twentyfourteen',
1387
                                'twentyfifteen',
1388
                                'twentysixteen',
1389
                                'storefront',
1390
                                                );
1391
1392
        if ( in_array( $template, $white_list ) ) {
1393
1394
            return;
1395
1396
        }
1397
1398
        // don't show the notice if the user chose to hide it
1399
        $hide_theme_check_notice = get_user_meta( get_current_user_id(), 'sensei_hide_theme_check_notice', true );
1400
        if(  $hide_theme_check_notice ) {
1401
1402
            return;
1403
1404
        }
1405
1406
        // show the notice for themes not supporting sensei
1407
	    if ( ! current_theme_supports( 'sensei' ) ) {
1408
            ?>
1409
1410
            <div id="message" class="error sensei-message sensei-connect">
1411
                    <p>
1412
                        <strong>
1413
1414
                            <?php _e('Your theme does not declare Sensei support', 'woothemes-sensei' ); ?>
1415
1416
                        </strong> &#8211;
1417
1418
                        <?php _e( 'if you encounter layout issues please read our integration guide or choose a ', 'woothemes-sensei' ); ?>
1419
1420
                        <a href="http://www.woothemes.com/product-category/themes/sensei-themes/"> <?php  _e( 'Sensei theme', 'woothemes-sensei' ) ?> </a>
1421
1422
                        :)
1423
1424
                    </p>
1425
                    <p class="submit">
1426
                        <a href="<?php echo esc_url( apply_filters( 'sensei_docs_url', 'http://docs.woothemes.com/document/sensei-and-theme-compatibility/', 'theme-compatibility' ) ); ?>" class="button-primary">
1427
1428
                            <?php _e( 'Theme Integration Guide', 'woothemes-sensei' ); ?></a> <a class="skip button" href="<?php echo esc_url( add_query_arg( 'sensei_hide_notice', 'theme_check' ) ); ?>"><?php _e( 'Hide this notice', 'woothemes-sensei' ); ?>
1429
1430
                        </a>
1431
                    </p>
1432
            </div>
1433
            <?php
1434
		}
1435
	}
1436
1437
	/**
1438
	 * Reset theme check notice
1439
	 * @return void
1440
	 */
1441
	public function reset_theme_check_notices() {
1442
		global $current_user;
1443
		wp_get_current_user();
1444
        $user_id = $current_user->ID;
1445
1446
		delete_user_meta( $user_id, 'sensei_hide_theme_check_notice' );
1447
	}
1448
1449
	/**
1450
	 * Set Sensei users access to the admin area when WooCommerce is installed
1451
	 * Allow Teachers to access the admin area
1452
	 *
1453
	 * @param  bool $prevent_access
1454
	 * @return bool
1455
	 */
1456
	public function admin_access( $prevent_access ) {
1457
		if ( current_user_can( 'manage_sensei_grades' ) ) {
1458
			return false;
1459
		}
1460
1461
		return $prevent_access;
1462
	}
1463
1464
    /**
1465
     * Hooked onto admin_init. Listens for install_sensei_pages and skip_install_sensei_pages query args
1466
     * on the sensei settings page.
1467
     *
1468
     * The function
1469
     *
1470
     * @since 1.8.7
1471
     */
1472
    public  static function install_pages(){
1473
1474
        // only fire on the settings page
1475
        if( ! isset( $_GET['page'] )
1476
            || 'woothemes-sensei-settings' != $_GET['page']
1477
            || 1 == get_option('skip_install_sensei_pages') ){
1478
1479
            return;
1480
1481
        }
1482
1483
        // Install/page installer
1484
        $install_complete = false;
1485
1486
        // Add pages button
1487
        $settings_url = '';
1488
        if (isset($_GET['install_sensei_pages']) && $_GET['install_sensei_pages']) {
1489
1490
            Sensei()->admin->create_pages();
1491
1492
            update_option('skip_install_sensei_pages', 1);
1493
1494
            $install_complete = true;
1495
            $settings_url = remove_query_arg('install_sensei_pages');
1496
1497
            // Skip button
1498
        } elseif (isset($_GET['skip_install_sensei_pages']) && $_GET['skip_install_sensei_pages']) {
1499
1500
            update_option('skip_install_sensei_pages', 1);
1501
            $install_complete = true;
1502
            $settings_url = remove_query_arg('skip_install_sensei_pages');
1503
1504
        }
1505
1506
        if ($install_complete) {
1507
1508
            // refresh the rewrite rules on init
1509
            update_option('sensei_flush_rewrite_rules', '1');
1510
1511
            // Set installed option
1512
            update_option('sensei_installed', 0);
1513
1514
            $complete_url = add_query_arg( 'sensei_install_complete', 'true', $settings_url  );
1515
            wp_redirect( $complete_url );
1516
1517
        }
1518
1519
    }// end install_pages
1520
1521
} // End Class
1522
1523
/**
1524
 * Legacy Class WooThemes_Sensei_Admin
1525
 * @ignore only for backward compatibility
1526
 * @since 1.9.0
1527
 * @ignore
1528
 */
1529
class WooThemes_Sensei_Admin extends Sensei_Admin{ }
1530