Completed
Push — master ( 723667...83164b )
by Dwain
04:46
created

Sensei_Admin::admin_installed_notice()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 32
Code Lines 12

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 32
rs 8.8571
cc 1
eloc 12
nc 1
nop 0
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
4
/**
5
 * Sensei Administration Class
6
 *
7
 * All functionality pertaining to the administration sections of Sensei.
8
 *
9
 * @package WordPress
10
 * @subpackage Sensei
11
 * @category Administration
12
 * @author WooThemes
13
 * @since 1.0.0
14
 */
15
class Sensei_Admin {
16
17
	public $token;
18
19
	/**
20
	 * Constructor.
21
	 * @since  1.0.0
22
	 * @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...
23
	 */
24
	public function __construct () {
25
26
        //register admin styles
27
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_styles_global' ) );
28
29
        //register admin scripts
30
        add_action( 'admin_enqueue_scripts', array( $this, 'register_scripts' ) );
31
32
		add_action( 'admin_print_styles', array( $this, 'admin_notices_styles' ) );
33
		add_action( 'settings_before_form', array( $this, 'install_pages_output' ) );
34
		add_action( 'admin_menu', array( $this, 'admin_menu' ), 10 );
35
		add_action( 'menu_order', array( $this, 'admin_menu_order' ) );
36
		add_action( 'admin_head', array( $this, 'admin_menu_highlight' ) );
37
		add_action( 'admin_init', array( $this, 'page_redirect' ) );
38
		add_action( 'admin_init', array( $this, 'sensei_add_custom_menu_items' ) );
39
        add_action( 'admin_init', array( __CLASS__, 'install_pages' ));
40
41
		// Duplicate lesson & courses
42
		add_filter( 'post_row_actions', array( $this, 'duplicate_action_link' ), 10, 2 );
43
		add_action( 'admin_action_duplicate_lesson', array( $this, 'duplicate_lesson_action' ) );
44
		add_action( 'admin_action_duplicate_course', array( $this, 'duplicate_course_action' ) );
45
		add_action( 'admin_action_duplicate_course_with_lessons', array( $this, 'duplicate_course_with_lessons_action' ) );
46
47
		// Handle lessons list table filtering
48
		add_action( 'restrict_manage_posts', array( $this, 'lesson_filter_options' ) );
49
		add_filter( 'request', array( $this, 'lesson_filter_actions' ) );
50
51
		// Add Sensei items to 'at a glance' widget
52
		add_filter( 'dashboard_glance_items', array( $this, 'glance_items' ), 10, 1 );
53
54
		// Handle course and lesson deletions
55
		add_action( 'trash_course', array( $this, 'delete_content' ), 10, 2 );
56
		add_action( 'trash_lesson', array( $this, 'delete_content' ), 10, 2 );
57
58
		// Delete user activity when user is deleted
59
		add_action( 'deleted_user', array( $this, 'delete_user_activity' ), 10, 1 );
60
61
		// Add notices to WP dashboard
62
		add_action( 'admin_notices', array( $this, 'theme_compatibility_notices' ) );
63
64
		// Reset theme notices when switching themes
65
		add_action( 'switch_theme', array( $this, 'reset_theme_check_notices' ) );
66
67
		// Allow Teacher access the admin area
68
		add_filter( 'woocommerce_prevent_admin_access', array( $this, 'admin_access' ) );
69
70
	} // End __construct()
71
72
	/**
73
	 * Add items to admin menu
74
	 * @since  1.4.0
75
	 * @return void
76
	 */
77
	public function admin_menu() {
78
		global $menu;
79
		$menu_cap = '';
80
		if( current_user_can( 'manage_sensei' ) ) {
81
			$menu_cap = 'manage_sensei';
82
		} else {
83
			if( current_user_can( 'manage_sensei_grades' ) ) {
84
				$menu_cap = 'manage_sensei_grades';
85
			}
86
		}
87
88
		if( $menu_cap ) {
89
			$menu[] = array( '', 'read', 'separator-sensei', '', 'wp-menu-separator sensei' );
90
            add_menu_page( 'Sensei', 'Sensei', $menu_cap, 'sensei' , array( Sensei()->analysis, 'analysis_page' ) , '', '50' );
91
		}
92
93
		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' ) );
94
		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' ) );
95
	}
96
97
	/**
98
	 * [admin_menu_order description]
99
	 * @since  1.4.0
100
	 * @param  array $menu_order Existing menu order
101
	 * @return array 			 Modified menu order for Sensei
102
	 */
103
	public function admin_menu_order( $menu_order ) {
104
105
		// Initialize our custom order array
106
		$sensei_menu_order = array();
107
108
		// Get the index of our custom separator
109
		$sensei_separator = array_search( 'separator-sensei', $menu_order );
110
111
		// Loop through menu order and do some rearranging
112
		foreach ( $menu_order as $index => $item ) :
113
114
			if ( ( ( 'sensei' ) == $item ) ) :
115
				$sensei_menu_order[] = 'separator-sensei';
116
				$sensei_menu_order[] = $item;
117
				unset( $menu_order[$sensei_separator] );
118
			elseif ( !in_array( $item, array( 'separator-sensei' ) ) ) :
119
				$sensei_menu_order[] = $item;
120
			endif;
121
122
		endforeach;
123
124
		// Return order
125
		return $sensei_menu_order;
126
	}
127
128
	/**
129
	 * Handle highlighting of admin menu items
130
	 * @since 1.4.0
131
	 * @return void
132
	 */
133
	public function admin_menu_highlight() {
134
		global $menu, $submenu, $parent_file, $submenu_file, $self, $post_type, $taxonomy;
135
136
		$screen = get_current_screen();
137
138
		if ( $screen->base == 'post' && $post_type == 'course' ) {
139
140
			$parent_file  = 'edit.php?post_type=course';
141
142
		} elseif ( $screen->base == 'edit-tags' && $taxonomy == 'course-category' ) {
143
144
			$submenu_file = 'edit-tags.php?taxonomy=course-category&amp;post_type=course';
145
			$parent_file  = 'edit.php?post_type=course';
146
147
        } elseif ( $screen->base == 'edit-tags' && $taxonomy == 'module' ) {
148
149
            $submenu_file = 'edit-tags.php?taxonomy=module';
150
            $parent_file  = 'edit.php?post_type=course';
151
152
		} elseif ( in_array( $screen->id, array( 'sensei_message', 'edit-sensei_message' ) ) ) {
153
154
            $submenu_file = 'edit.php?post_type=sensei_message';
155
			$parent_file  = 'sensei';
156
157
		}
158
	}
159
160
	/**
161
	 * Redirect Sensei menu item to Analysis page
162
	 * @since  1.4.0
163
	 * @return void
164
	 */
165
	public function page_redirect() {
166
		if( isset( $_GET['page'] ) && $_GET['page'] == 'sensei' ) {
167
			wp_safe_redirect( 'admin.php?page=sensei_analysis' );
168
			exit;
169
		}
170
	}
171
172
	/**
173
	 * install_pages_output function.
174
	 *
175
	 * Handles installation of the 2 pages needs for courses and my courses
176
	 *
177
	 * @access public
178
	 * @return void
179
	 */
180
	function install_pages_output() {
0 ignored issues
show
Best Practice introduced by
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...
181
182
        if( isset($_GET['sensei_install_complete']) && 'true' == $_GET['sensei_install_complete']) {
183
184
            ?>
185
            <div id="message" class="updated sensei-message sensei-connect">
186
                <p><?php _e( '<strong>Congratulations!</strong> &#8211; Sensei has been installed and set up.', 'woothemes-sensei' ); ?></p>
187
                <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>
188
                <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>
189
            </div>
190
            <?php
191
192
        }
193
194
	} // End install_pages_output()
195
196
197
	/**
198
	 * create_page function.
199
	 *
200
	 * @access public
201
	 * @param mixed $slug
202
	 * @param mixed $option
203
	 * @param string $page_title (default: '')
204
	 * @param string $page_content (default: '')
205
	 * @param int $post_parent (default: 0)
206
	 * @return void
207
	 */
208
	function create_page( $slug, $option, $page_title = '', $page_content = '', $post_parent = 0 ) {
0 ignored issues
show
Best Practice introduced by
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...
209
		global $wpdb;
210
211
		$option_value = get_option( $option );
212
213
		if ( $option_value > 0 && get_post( $option_value ) )
214
			return;
215
216
		$page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM " . $wpdb->posts . " WHERE post_name = %s LIMIT 1;", $slug ) );
217
		if ( $page_found ) :
218
			if ( ! $option_value )
219
				update_option( $option, $page_found );
220
			return;
221
		endif;
222
223
		$page_data = array(
224
	        'post_status' 		=> 'publish',
225
	        'post_type' 		=> 'page',
226
	        'post_author' 		=> 1,
227
	        'post_name' 		=> $slug,
228
	        'post_title' 		=> $page_title,
229
	        'post_content' 		=> $page_content,
230
	        'post_parent' 		=> $post_parent,
231
	        'comment_status' 	=> 'closed'
232
	    );
233
	    $page_id = wp_insert_post( $page_data );
234
235
	    update_option( $option, $page_id );
236
	} // End create_page()
237
238
239
	/**
240
	 * create_pages function.
241
	 *
242
	 * @access public
243
	 * @return void
244
	 */
245
	function create_pages() {
0 ignored issues
show
Best Practice introduced by
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...
246
247
		// Courses page
248
	    $this->create_page( esc_sql( _x('courses-overview', 'page_slug', 'woothemes-sensei') ), $this->token . '_courses_page_id', __('Courses', 'woothemes-sensei'), '[newcourses][featuredcourses][freecourses][paidcourses]' );
249
250
		// User Dashboard page
251
	    $this->create_page( esc_sql( _x('my-courses', 'page_slug', 'woothemes-sensei') ), $this->token . '_user_dashboard_page_id', __('My Courses', 'woothemes-sensei'), '[usercourses]' );
252
253
	} // End create_pages()
254
255
	/**
256
	 * Load the global admin styles for the menu icon and the relevant page icon.
257
	 * @access public
258
	 * @since 1.0.0
259
	 * @return void
260
	 */
261
	public function admin_styles_global ( $hook ) {
262
		global $post_type;
263
264
		$allowed_post_types = apply_filters( 'sensei_scripts_allowed_post_types', array( 'lesson', 'course', 'question' ) );
265
		$allowed_post_type_pages = apply_filters( 'sensei_scripts_allowed_post_type_pages', array( 'edit.php', 'post-new.php', 'post.php', 'edit-tags.php' ) );
266
		$allowed_pages = apply_filters( 'sensei_scripts_allowed_pages', array( 'sensei_grading', 'sensei_analysis', 'sensei_learners', 'sensei_updates', 'woothemes-sensei-settings', 'lesson-order', 'course-order' ) );
267
268
		// Global Styles for icons and menu items
269
		wp_register_style( Sensei()->token . '-global', Sensei()->plugin_url . 'assets/css/global.css', '', Sensei()->version, 'screen' );
270
		wp_enqueue_style( Sensei()->token . '-global' );
271
272
        // Select 2 styles
273
        wp_enqueue_style( 'select2', Sensei()->plugin_url . 'assets/css/select2/select2.css', '', Sensei()->version, 'screen' );
274
275
		// Test for Write Panel Pages
276
		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 ) ) ) {
277
278
			wp_register_style( Sensei()->token . '-admin-custom', Sensei()->plugin_url . 'assets/css/admin-custom.css', '', Sensei()->version, 'screen' );
279
			wp_enqueue_style( Sensei()->token . '-admin-custom' );
280
281
		}
282
283
	} // End admin_styles_global()
284
285
286
    /**
287
     * Globally register all scripts needed in admin.
288
     *
289
     * The script users should enqueue the script when needed.
290
     *
291
     * @since 1.8.2
292
     * @access public
293
     */
294
    public function register_scripts( $hook ){
0 ignored issues
show
Unused Code introduced by
The parameter $hook is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
295
296
        $screen = get_current_screen();
297
298
        // Allow developers to load non-minified versions of scripts
299
        $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
300
301
        // Select2 script used to enhance all select boxes
302
        wp_register_script( 'select2', Sensei()->plugin_url . '/assets/js/select2/select2' . $suffix . '.js', array( 'jquery' ), Sensei()->version );
303
304
        // load edit module scripts
305
        if( 'edit-module' ==  $screen->id ){
306
307
            wp_enqueue_script( 'sensei-chosen-ajax', Sensei()->plugin_url . 'assets/chosen/ajax-chosen.jquery.min.js', array( 'jquery', 'sensei-chosen' ), Sensei()->version, true );
308
309
        }
310
311
    }
312
313
314
	/**
315
	 * admin_install_notice function.
316
	 *
317
	 * @access public
318
	 * @return void
319
	 */
320
	function admin_install_notice() {
0 ignored issues
show
Best Practice introduced by
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...
321
	    ?>
322
	    <div id="message" class="updated sensei-message sensei-connect">
323
324
            <p>
325
                <?php _e( '<strong>Welcome to Sensei</strong> &#8211; You\'re almost ready to create some courses!', 'woothemes-sensei' ); ?>
326
            </p>
327
328
            <p class="submit">
329
330
                <a href="<?php echo esc_url( add_query_arg('install_sensei_pages', 'true', admin_url('admin.php?page=woothemes-sensei-settings') ) ); ?>"
331
                   class="button-primary">
332
333
                    <?php _e( 'Install Sensei Pages', 'woothemes-sensei' ); ?>
334
335
                </a>
336
337
                <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' ) ) ); ?>">
338
339
                    <?php _e('Skip setup', 'woothemes-sensei'); ?>
340
341
                </a>
342
343
            </p>
344
	    </div>
345
	    <?php
346
	} // End admin_install_notice()
347
348
349
	/**
350
	 * admin_installed_notice function.
351
	 *
352
	 * @access public
353
	 * @return void
354
	 */
355
	function admin_installed_notice() {
0 ignored issues
show
Best Practice introduced by
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...
356
	    ?>
357
	    <div id="message" class="updated sensei-message sensei-connect">
358
359
	    	<p>
360
                <?php _e( '<strong>Sensei has been installed</strong> &#8211; You\'re ready to start creating courses!', 'woothemes-sensei' ); ?>
361
            </p>
362
363
			<p class="submit">
364
                <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/">
365
                    <?php _e('Documentation', 'woothemes-sensei'); ?>
366
                </a>
367
            </p>
368
369
            <p>
370
371
                <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">
372
                    <?php _e('Tweet', 'woothemes-sensei'); ?>
373
                </a>
374
375
                <script>
376
                    !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");
377
                </script>
378
379
            </p>
380
381
	    </div>
382
	    <?php
383
384
	    // Set installed option
385
	    update_option('sensei_installed', 0);
386
	} // End admin_installed_notice()
387
388
389
	/**
390
	 * Language pack install notice.
391
	 *
392
	 * @since 1.9.0
393
	 */
394
	public function language_pack_install_notice() {
395
		?>
396
		<div id="message" class="updated sensei-message sensei-connect">
397
				<p><?php _e( '<strong>Sensei in your language</strong> &#8211; There is a translation available for your language.', 'woothemes-sensei' ); ?><p>
398
399
				<p class="submit">
400
					<a href="<?php echo esc_url( Sensei_Language_Pack_Manager::get_install_uri() ); ?>" class="button-primary"><?php _e( 'Install', 'woothemes-sensei' ); ?></a>
401
					<a href="<?php echo esc_url( Sensei_Language_Pack_Manager::get_dismiss_uri() ) ?>" class="docs button"><?php _e( 'Hide this notice', 'woothemes-sensei' ); ?></a>
402
				</p>
403
		</div>
404
		<?php
405
	}
406
407
408
	/**
409
	 * admin_notices_styles function.
410
	 *
411
	 * @access public
412
	 * @return void
413
	 */
414
	function admin_notices_styles() {
0 ignored issues
show
Best Practice introduced by
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...
415
416
		// Installed notices
417
	    if ( 1 == get_option( 'sensei_installed' ) ) {
418
419
	    	wp_enqueue_style( 'sensei-activation', plugins_url(  '/assets/css/activation.css', dirname( __FILE__ ) ), '', Sensei()->version );
420
421
	    	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'])) {
422
	    		add_action( 'admin_notices', array( $this, 'admin_install_notice' ) );
423
	    	} elseif ( !isset($_GET['page']) || $_GET['page']!='woothemes-sensei-settings' ) {
424
	    		add_action( 'admin_notices', array( $this, 'admin_installed_notice' ) );
425
	    	} // End If Statement
426
427
	    } // End If Statement
428
429
	    if ( Sensei_Language_Pack_Manager::has_language_pack_available() ) {
430
	    	add_action( 'admin_notices', array( $this, 'language_pack_install_notice' ) );
431
	    }
432
433
	} // End admin_notices_styles()
434
435
	/**
436
	 * Add links for duplicating lessons & courses
437
	 * @param  array  $actions Default actions
438
	 * @param  object $post    Current post
439
	 * @return array           Modified actions
440
	 */
441
	public function duplicate_action_link( $actions, $post ) {
442
		switch( $post->post_type ) {
443
			case 'lesson':
444
				$confirm = __( 'This will duplicate the lesson quiz and all of its questions. Are you sure you want to do this?', 'woothemes-sensei' );
445
				$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>";
446
			break;
447
448
			case 'course':
449
				$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' );
450
				$actions['duplicate'] = '<a href="' . $this->get_duplicate_link( $post->ID ) . '" title="' . esc_attr(__( 'Duplicate this course', 'woothemes-sensei' ) ) . '">' .  __('Duplicate', 'woothemes-sensei' ) . '</a>';
451
				$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>';
452
			break;
453
		}
454
455
		return $actions;
456
	}
457
458
	/**
459
	 * Generate duplicationlink
460
	 * @param  integer $post_id      Post ID
461
	 * @param  boolean $with_lessons Include lessons or not
462
	 * @return string                Duplication link
463
	 */
464
	private function get_duplicate_link( $post_id = 0, $with_lessons = false ) {
465
466
		$post = get_post( $post_id );
467
468
		$action = 'duplicate_' . $post->post_type;
469
470
		if( 'course' == $post->post_type && $with_lessons ) {
471
			$action .= '_with_lessons';
472
		}
473
474
		return apply_filters( $action . '_link', admin_url( 'admin.php?action=' . $action . '&post=' . $post_id ), $post_id );
475
	}
476
477
	/**
478
	 * Duplicate lesson
479
	 * @return void
480
	 */
481
	public function duplicate_lesson_action() {
482
		$this->duplicate_content( 'lesson' );
483
	}
484
485
	/**
486
	 * Duplicate course
487
	 * @return void
488
	 */
489
	public function duplicate_course_action() {
490
		$this->duplicate_content( 'course' );
491
	}
492
493
	/**
494
	 * Duplicate course with lessons
495
	 * @return void
496
	 */
497
	public function duplicate_course_with_lessons_action() {
498
		$this->duplicate_content( 'course', true );
499
	}
500
501
	/**
502
	 * Duplicate content
503
	 * @param  string  $post_type    Post type being duplicated
504
	 * @param  boolean $with_lessons Include lessons or not
505
	 * @return void
506
	 */
507
	private function duplicate_content( $post_type = 'lesson', $with_lessons = false ) {
508
		if ( ! isset( $_GET['post'] ) ) {
509
			wp_die( sprintf( __( 'Please supply a %1$s ID.', 'woothemes-sensei' ) ), $post_type );
510
		}
511
512
		$post_id = $_GET['post'];
513
		$post = get_post( $post_id );
514
515
		if( ! is_wp_error( $post ) ) {
516
517
			$new_post = $this->duplicate_post( $post );
518
519
			if( $new_post && ! is_wp_error( $new_post ) ) {
520
521
				if( 'lesson' == $new_post->post_type ) {
522
					$this->duplicate_lesson_quizzes( $post_id, $new_post->ID );
523
				}
524
525
				if( 'course' == $new_post->post_type && $with_lessons ) {
526
					$this->duplicate_course_lessons( $post_id, $new_post->ID );
527
				}
528
529
				$redirect_url = admin_url( 'post.php?post=' . $new_post->ID . '&action=edit' );
530
			} else {
531
				$redirect_url = admin_url( 'edit.php?post_type=' . $post->post_type . '&message=duplicate_failed' );
532
			}
533
534
			wp_safe_redirect( esc_url_raw( $redirect_url ) );
535
			exit;
536
		}
537
	}
538
539
	/**
540
	 * Duplicate quizzes inside lessons
541
	 * @param  integer $old_lesson_id ID of original lesson
542
	 * @param  integer $new_lesson_id ID of duplicate lesson
543
	 * @return void
544
	 */
545
	private function duplicate_lesson_quizzes( $old_lesson_id, $new_lesson_id ) {
546
547
        $old_quiz_id = Sensei()->lesson->lesson_quizzes( $old_lesson_id );
548
        $old_quiz_questions = Sensei()->lesson->lesson_quiz_questions( $old_quiz_id );
549
550
        // duplicate the generic wp post information
551
		$new_quiz = $this->duplicate_post( get_post( $old_quiz_id ), '' );
552
553
		//update the new lesson data
554
        add_post_meta( $new_lesson_id, '_lesson_quiz', $new_quiz->ID );
555
556
		//update the new quiz data
557
        add_post_meta( $new_quiz->ID, '_quiz_lesson', $new_lesson_id );
558
        wp_update_post(
559
            array(
560
                'ID' => $new_quiz->ID,
561
                'post_parent' => $new_lesson_id
562
            )
563
        );
564
565
		foreach( $old_quiz_questions as $question ) {
566
567
			// copy the question order over to the new quiz
568
			$old_question_order = get_post_meta( $question->ID, '_quiz_question_order'. $old_quiz_id, true );
569
            $new_question_order = str_ireplace( $old_quiz_id, $new_quiz->ID , $old_question_order );
570
            add_post_meta( $question->ID, '_quiz_question_order' . $new_quiz->ID, $new_question_order );
571
572
			// Add question to quiz
573
			add_post_meta( $question->ID, '_quiz_id', $new_quiz->ID, false );
574
575
		}
576
	}
577
578
	/**
579
	 * Duplicate lessons inside a course
580
	 * @param  integer $old_course_id ID of original course
581
	 * @param  integer $new_course_id ID of duplicated course
582
	 * @return void
583
	 */
584
	private function duplicate_course_lessons( $old_course_id, $new_course_id ) {
585
		$lesson_args = array(
586
			'post_type' => 'lesson',
587
			'posts_per_page' => -1,
588
			'meta_key' => '_lesson_course',
589
			'meta_value' => $old_course_id,
590
			'suppress_filters' 	=> 0
591
		);
592
		$lessons = get_posts( $lesson_args );
593
594
		foreach( $lessons as $lesson ) {
595
			$new_lesson = $this->duplicate_post( $lesson, '', true );
596
			add_post_meta( $new_lesson->ID, '_lesson_course', $new_course_id );
597
598
			$this->duplicate_lesson_quizzes( $lesson->ID, $new_lesson->ID );
599
		}
600
	}
601
602
	/**
603
	 * Duplicate post
604
	 * @param  object  $post          Post to be duplicated
605
	 * @param  string  $suffix        Suffix for duplicated post title
606
	 * @param  boolean $ignore_course Ignore lesson course when dulicating
607
	 * @return object                 Duplicate post object
608
	 */
609
	private function duplicate_post( $post, $suffix = ' (Duplicate)', $ignore_course = false ) {
610
611
		$new_post = array();
612
613
		foreach( $post as $k => $v ) {
614
			if( ! in_array( $k, array( 'ID', 'post_status', 'post_date', 'post_date_gmt', 'post_name', 'post_modified', 'post_modified_gmt', 'guid', 'comment_count' ) ) ) {
615
				$new_post[ $k ] = $v;
616
			}
617
		}
618
619
		$new_post['post_title'] .= __( $suffix, 'woothemes-sensei' );
620
621
		$new_post['post_date'] = current_time( 'mysql' );
622
		$new_post['post_date_gmt'] = get_gmt_from_date( $new_post['post_date'] );
623
		$new_post['post_modified'] = $new_post['post_date'];
624
		$new_post['post_modified_gmt'] = $new_post['post_date_gmt'];
625
626
		switch( $post->post_type ) {
627
			case 'course': $new_post['post_status'] = 'draft'; break;
628
			case 'lesson': $new_post['post_status'] = 'draft'; break;
629
			case 'quiz': $new_post['post_status'] = 'publish'; break;
630
			case 'question': $new_post['post_status'] = 'publish'; break;
631
		}
632
633
		// As per wp_update_post() we need to escape the data from the db.
634
		$new_post = wp_slash( $new_post );
635
636
		$new_post_id = wp_insert_post( $new_post );
637
638
		if( ! is_wp_error( $new_post_id ) ) {
639
640
			$post_meta = get_post_custom( $post->ID );
641
			if( $post_meta && count( $post_meta ) > 0 ) {
642
643
				$ignore_meta = array( '_quiz_lesson', '_quiz_id', '_lesson_quiz' );
644
645
				if( $ignore_course ) {
646
					$ignore_meta[] = '_lesson_course';
647
				}
648
649
				foreach( $post_meta as $key => $meta ) {
650
					foreach( $meta as $value ) {
651
						$value = maybe_unserialize( $value );
652
						if( ! in_array( $key, $ignore_meta ) ) {
653
							add_post_meta( $new_post_id, $key, $value );
654
						}
655
					}
656
				}
657
			}
658
659
			add_post_meta( $new_post_id, '_duplicate', $post->ID );
660
661
			$taxonomies = get_object_taxonomies( $post->post_type, 'objects' );
662
663
			foreach ( $taxonomies as $slug => $tax ) {
664
				$terms = get_the_terms( $post->ID, $slug );
665
				if( isset( $terms ) && is_array( $terms ) && 0 < count( $terms ) ) {
666
					foreach( $terms as $term ) {
667
						wp_set_object_terms( $new_post_id, $term->term_id, $slug, true );
668
					}
669
				}
670
			}
671
672
			$new_post = get_post( $new_post_id );
673
674
			return $new_post;
675
		}
676
677
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Sensei_Admin::duplicate_post of type object.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
678
	}
679
680
	/**
681
	 * Add options to filter lessons
682
	 * @return void
683
	 */
684
	public function lesson_filter_options() {
685
		global $typenow;
686
687
		if( is_admin() && 'lesson' == $typenow ) {
688
689
			$args = array(
690
				'post_type' => 'course',
691
				'post_status' => array('publish', 'pending', 'draft', 'future', 'private'),
692
				'posts_per_page' => -1,
693
				'suppress_filters' => 0,
694
				'orderby' => 'menu_order date',
695
				'order' => 'ASC',
696
			);
697
			$courses = get_posts( $args );
698
699
			$selected = isset( $_GET['lesson_course'] ) ? $_GET['lesson_course'] : '';
700
			$course_options = '';
701
			foreach( $courses as $course ) {
702
				$course_options .= '<option value="' . esc_attr( $course->ID ) . '" ' . selected( $selected, $course->ID, false ) . '>' . get_the_title( $course->ID ) . '</option>';
703
			}
704
705
			$output = '<select name="lesson_course" id="dropdown_lesson_course">';
706
			$output .= '<option value="">'.__( 'Show all courses', 'woothemes-sensei' ).'</option>';
707
			$output .= $course_options;
708
			$output .= '</select>';
709
710
			echo $output;
711
		}
712
	}
713
714
	/**
715
	 * Filter lessons
716
	 * @param  array $request Current request
717
	 * @return array          Modified request
718
	 */
719
	public function lesson_filter_actions( $request ) {
720
		global $typenow;
721
722
		if( is_admin() && 'lesson' == $typenow ) {
723
			$lesson_course = isset( $_GET['lesson_course'] ) ? $_GET['lesson_course'] : '';
724
725
			if( $lesson_course ) {
726
				$request['meta_key'] = '_lesson_course';
727
				$request['meta_value'] = $lesson_course;
728
				$request['meta_compare'] = '=';
729
			}
730
		}
731
732
		return $request;
733
	}
734
735
	/**
736
	 * Adding Sensei items to 'At a glance' dashboard widget
737
	 * @param  array $items Existing items
738
	 * @return array        Updated items
739
	 */
740
	public function glance_items( $items = array() ) {
741
742
		$types = array( 'course', 'lesson', 'question' );
743
744
		foreach( $types as $type ) {
745
			if( ! post_type_exists( $type ) ) continue;
746
747
			$num_posts = wp_count_posts( $type );
748
749
			if( $num_posts ) {
750
751
				$published = intval( $num_posts->publish );
752
				$post_type = get_post_type_object( $type );
753
754
				$text = _n( '%s ' . $post_type->labels->singular_name, '%s ' . $post_type->labels->name, $published, 'woothemes-sensei' );
755
				$text = sprintf( $text, number_format_i18n( $published ) );
756
757
				if ( current_user_can( $post_type->cap->edit_posts ) ) {
758
					$items[] = sprintf( '<a class="%1$s-count" href="edit.php?post_type=%1$s">%2$s</a>', $type, $text ) . "\n";
759
				} else {
760
					$items[] = sprintf( '<span class="%1$s-count">%2$s</span>', $type, $text ) . "\n";
761
				}
762
			}
763
		}
764
765
		return $items;
766
	}
767
768
	/**
769
	 * Process lesson and course deletion
770
	 * @param  integer $post_id Post ID
771
	 * @param  object  $post    Post object
772
	 * @return void
773
	 */
774
	public function delete_content( $post_id, $post ) {
775
776
		$type = $post->post_type;
777
778
		if( in_array( $type, array( 'lesson', 'course' ) ) ) {
779
780
			$meta_key = '_' . $type . '_prerequisite';
781
782
			$args = array(
783
				'post_type' => $type,
784
				'post_status' => 'any',
785
				'posts_per_page' => -1,
786
				'meta_key' => $meta_key,
787
				'meta_value' => $post_id
788
			);
789
790
			$posts = get_posts( $args );
791
792
			foreach( $posts as $post ) {
793
				delete_post_meta( $post->ID, $meta_key );
794
			}
795
		}
796
	}
797
798
	/**
799
	 * Delete all user activity when user is deleted
800
	 * @param  integer $user_id User ID
801
	 * @return void
802
	 */
803
	public function delete_user_activity( $user_id = 0 ) {
804
		if( $user_id ) {
805
			WooThemes_Sensei_Utils::delete_all_user_activity( $user_id );
806
		}
807
	}
808
809
	public function render_settings( $settings = array(), $post_id = 0, $group_id = '' ) {
810
811
		$html = '';
812
813
		if( 0 == count( $settings ) ) return $html;
814
815
		$html .= '<div class="sensei-options-panel">' . "\n";
816
817
			$html .= '<div class="options_group" id="' . esc_attr( $group_id ) . '">' . "\n";
818
819
				foreach( $settings as $field ) {
820
821
					$data = '';
822
823
					if( $post_id ) {
824
						$data = get_post_meta( $post_id, '_' . $field['id'], true );
825
						if( ! $data && isset( $field['default'] ) ) {
826
							$data = $field['default'];
827
						}
828
					} else {
829
						$option = get_option( $field['id'] );
830
						if( isset( $field['default'] ) ) {
831
							$data = $field['default'];
832
							if( $option ) {
833
								$data = $option;
834
							}
835
						}
836
					}
837
838
					$disabled = '';
839
					if( isset( $field['disabled'] ) && $field['disabled'] ) {
840
						$disabled = disabled( $field['disabled'], true, false );
841
					}
842
843
					if( 'hidden' != $field['type'] ) {
844
845
						$class_tail = '';
846
847
						if( isset( $field['class'] ) ) {
848
							$class_tail .= $field['class'];
849
						}
850
851
						if( isset( $field['disabled'] ) && $field['disabled'] ) {
852
							$class_tail .= ' disabled';
853
						}
854
855
						$html .= '<p class="form-field ' . esc_attr( $field['id'] ) . ' ' . esc_attr( $class_tail ) . '">' . "\n";
856
					}
857
858
						if( ! in_array( $field['type'], array( 'hidden', 'checkbox_multi', 'radio' ) ) ) {
859
							$html .= '<label for="' . esc_attr( $field['id'] ) . '">' . "\n";
860
						}
861
862
							if( $field['label'] ) {
863
								$html .= '<span class="label">' . esc_html( $field['label'] ) . '</span>';
864
							}
865
866
							switch( $field['type'] ) {
867
								case 'text':
868
								case 'password':
869
									$html .= '<input id="' . esc_attr( $field['id'] ) . '" type="' . $field['type'] . '" name="' . esc_attr( $field['id'] ) . '" placeholder="' . esc_attr( $field['placeholder'] ) . '" value="' . $data . '" ' . $disabled . ' />' . "\n";
870
								break;
871
872
								case 'number':
873
874
									$min = '';
875 View Code Duplication
									if( isset( $field['min'] ) ) {
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...
876
										$min = 'min="' . esc_attr( $field['min'] ) . '"';
877
									}
878
879
									$max = '';
880 View Code Duplication
									if( isset( $field['max'] ) ) {
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...
881
										$max = 'max="' . esc_attr( $field['max'] ) . '"';
882
									}
883
884
									$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";
885
								break;
886
887 View Code Duplication
								case 'textarea':
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...
888
									$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";
889
								break;
890
891
								case 'checkbox':
892
                                    //backwards compatibility
893
                                    if( empty( $data ) || 'on' == $data ){
894
                                        $checked_value = 'on';
895
                                    }elseif( 'yes' == $data  ) {
896
897
                                        $checked_value = 'yes';
898
899
                                    }elseif( 'auto' == $data  ) {
900
901
                                        $checked_value = 'auto';
902
903
                                    } else {
904
                                        $checked_value = 1;
905
                                        $data = intval( $data );
906
                                    }
907
									$checked = checked( $checked_value, $data, false );
908
									$html .= '<input id="' . esc_attr( $field['id'] ) . '" type="' . $field['type'] . '" name="' . esc_attr( $field['id'] ) . '" ' . $checked . ' ' . $disabled . '/>' . "\n";
909
								break;
910
911 View Code Duplication
								case 'checkbox_multi':
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...
912
									foreach( $field['options'] as $k => $v ) {
913
										$checked = false;
914
										if( in_array( $k, $data ) ) {
915
											$checked = true;
916
										}
917
										$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";
918
									}
919
								break;
920
921 View Code Duplication
								case 'radio':
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...
922
									foreach( $field['options'] as $k => $v ) {
923
										$checked = false;
924
										if( $k == $data ) {
925
											$checked = true;
926
										}
927
										$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";
928
									}
929
								break;
930
931 View Code Duplication
								case 'select':
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...
932
									$html .= '<select name="' . esc_attr( $field['id'] ) . '" id="' . esc_attr( $field['id'] ) . '" ' . $disabled . '>' . "\n";
933
									foreach( $field['options'] as $k => $v ) {
934
										$selected = false;
935
										if( $k == $data ) {
936
											$selected = true;
937
										}
938
										$html .= '<option ' . selected( $selected, true, false ) . ' value="' . esc_attr( $k ) . '">' . $v . '</option>' . "\n";
939
									}
940
									$html .= '</select><br/>' . "\n";
941
								break;
942
943 View Code Duplication
								case 'select_multi':
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...
944
									$html .= '<select name="' . esc_attr( $field['id'] ) . '[]" id="' . esc_attr( $field['id'] ) . '" multiple="multiple" ' . $disabled . '>' . "\n";
945
									foreach( $field['options'] as $k => $v ) {
946
										$selected = false;
947
										if( in_array( $k, $data ) ) {
948
											$selected = true;
949
										}
950
										$html .= '<option ' . selected( $selected, true, false ) . ' value="' . esc_attr( $k ) . '" />' . $v . '</option>' . "\n";
951
									}
952
									$html .= '</select> . "\n"';
953
								break;
954
955 View Code Duplication
								case 'hidden':
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...
956
									$html .= '<input id="' . esc_attr( $field['id'] ) . '" type="' . $field['type'] . '" name="' . esc_attr( $field['id'] ) . '" value="' . $data . '" ' . $disabled . '/>' . "\n";
957
								break;
958
959
							}
960
961
							if( $field['description'] ) {
962
								$html .= ' <span class="description">' . esc_html( $field['description'] ) . '</span>' . "\n";
963
							}
964
965
						if( ! in_array( $field['type'], array( 'hidden', 'checkbox_multi', 'radio' ) ) ) {
966
							$html .= '</label>' . "\n";
967
						}
968
969
					if( 'hidden' != $field['type'] ) {
970
						$html .= '</p>' . "\n";
971
					}
972
973
				}
974
975
			$html .= '</div>' . "\n";
976
977
		$html .= '</div>' . "\n";
978
979
		return $html;
980
	}
981
982
	/**
983
	 * Dsplay Course Order screen
984
	 * @return void
985
	 */
986
	public function course_order_screen() {
987
988
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
989
		wp_enqueue_script( 'woothemes-sensei-settings', esc_url( Sensei()->plugin_url . 'assets/js/settings' . $suffix . '.js' ), array( 'jquery', 'jquery-ui-sortable' ), Sensei()->version );
990
991
		?><div id="course-order" class="wrap course-order">
992
		<h2><?php _e( 'Order Courses', 'woothemes-sensei' ); ?></h2><?php
993
994
		$html = '';
995
996 View Code Duplication
		if( isset( $_POST['course-order'] ) && 0 < strlen( $_POST['course-order'] ) ) {
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...
997
			$ordered = $this->save_course_order( esc_attr( $_POST['course-order'] ) );
998
999
			if( $ordered ) {
1000
				$html .= '<div class="updated fade">' . "\n";
1001
				$html .= '<p>' . __( 'The course order has been saved.', 'woothemes-sensei' ) . '</p>' . "\n";
1002
				$html .= '</div>' . "\n";
1003
			}
1004
		}
1005
1006
		$courses = Sensei()->course->get_all_courses();
1007
1008
		if( 0 < count( $courses ) ) {
1009
1010
            // order the courses as set by the users
1011
            $all_course_ids = array();
1012
            foreach( $courses as $course ){
1013
1014
                $all_course_ids[] = (string)$course->ID;
1015
1016
            }
1017
            $order_string = $this->get_course_order();
1018
1019
            if( !empty( $order_string ) ){
1020
                $ordered_course_ids = explode(',' , $order_string );
1021
                $all_course_ids = array_unique( array_merge( $ordered_course_ids , $all_course_ids ) );
1022
            }
1023
1024
1025
			$html .= '<form id="editgrouping" method="post" action="" class="validate">' . "\n";
1026
			$html .= '<ul class="sortable-course-list">' . "\n";
1027
			$count = 0;
1028
			foreach ( $all_course_ids as $course_id ) {
1029
                $course = get_post( $course_id );
1030
				$count++;
1031
				$class = 'course';
1032
				if ( $count == 1 ) { $class .= ' first'; }
1033
				if ( $count == count( $course ) ) { $class .= ' last'; }
1034
				if ( $count % 2 != 0 ) {
1035
					$class .= ' alternate';
1036
				}
1037
				$html .= '<li class="' . esc_attr( $class ) . '"><span rel="' . esc_attr( $course->ID ) . '" style="width: 100%;"> ' . $course->post_title . '</span></li>' . "\n";
1038
			}
1039
			$html .= '</ul>' . "\n";
1040
1041
			$html .= '<input type="hidden" name="course-order" value="' . esc_attr( $order_string ) . '" />' . "\n";
1042
			$html .= '<input type="submit" class="button-primary" value="' . __( 'Save course order', 'woothemes-sensei' ) . '" />' . "\n";
1043
		}
1044
1045
		echo $html;
1046
1047
		?></div><?php
1048
	}
1049
1050
	public function get_course_order() {
1051
		return get_option( 'sensei_course_order', '' );
1052
	}
1053
1054
	public function save_course_order( $order_string = '' ) {
1055
		$order = explode( ',', $order_string );
1056
1057
		update_option( 'sensei_course_order', $order_string );
1058
1059
		$i = 1;
1060
		foreach( $order as $course_id ) {
1061
1062
			if( $course_id ) {
1063
1064
				$update_args = array(
1065
					'ID' => $course_id,
1066
					'menu_order' => $i,
1067
				);
1068
1069
				wp_update_post( $update_args );
1070
1071
				++$i;
1072
			}
1073
		}
1074
1075
		return true;
1076
	}
1077
1078
	/**
1079
	 * Dsplay Lesson Order screen
1080
	 * @return void
1081
	 */
1082
	public function lesson_order_screen() {
1083
1084
		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
1085
		wp_enqueue_script( 'woothemes-sensei-settings', esc_url( Sensei()->plugin_url . 'assets/js/settings' . $suffix . '.js' ), array( 'jquery', 'jquery-ui-sortable' ), Sensei()->version );
1086
1087
		?><div id="lesson-order" class="wrap lesson-order">
1088
		<h2><?php _e( 'Order Lessons', 'woothemes-sensei' ); ?></h2><?php
1089
1090
		$html = '';
1091
1092 View Code Duplication
		if( isset( $_POST['lesson-order'] ) ) {
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...
1093
1094
			$ordered = $this->save_lesson_order( esc_attr( $_POST['lesson-order'] ), esc_attr( $_POST['course_id'] ) );
1095
1096
			if( $ordered ) {
1097
				$html .= '<div class="updated fade">' . "\n";
1098
				$html .= '<p>' . __( 'The lesson order has been saved.', 'woothemes-sensei' ) . '</p>' . "\n";
1099
				$html .= '</div>' . "\n";
1100
			}
1101
		}
1102
1103
		$args = array(
1104
			'post_type' => 'course',
1105
			'post_status' => array('publish', 'draft', 'future', 'private'),
1106
			'posts_per_page' => -1,
1107
			'orderby' => 'name',
1108
			'order' => 'ASC',
1109
		);
1110
		$courses = get_posts( $args );
1111
1112
		$html .= '<form action="' . admin_url( 'edit.php' ) . '" method="get">' . "\n";
1113
		$html .= '<input type="hidden" name="post_type" value="lesson" />' . "\n";
1114
		$html .= '<input type="hidden" name="page" value="lesson-order" />' . "\n";
1115
		$html .= '<select id="lesson-order-course" name="course_id">' . "\n";
1116
		$html .= '<option value="">' . __( 'Select a course', 'woothemes-sensei' ) . '</option>' . "\n";
1117
1118
		foreach( $courses as $course ) {
1119
			$course_id = '';
1120
			if( isset( $_GET['course_id'] ) ) {
1121
				$course_id = intval( $_GET['course_id'] );
1122
			}
1123
			$html .= '<option value="' . esc_attr( intval( $course->ID ) ) . '" ' . selected( $course->ID, $course_id, false ) .'>' . get_the_title( $course->ID ) . '</option>' . "\n";
1124
		}
1125
1126
		$html .= '</select>' . "\n";
1127
		$html .= '<input type="submit" class="button-primary lesson-order-select-course-submit" value="' . __( 'Select', 'woothemes-sensei' ) . '" />' . "\n";
1128
		$html .= '</form>' . "\n";
1129
1130
		$html .= '<script type="text/javascript">' . "\n";
1131
		$html .= 'jQuery( \'#lesson-order-course\' ).select2({width:"resolve"});' . "\n";
1132
		$html .= '</script>' . "\n";
1133
1134
		if( isset( $_GET['course_id'] ) ) {
1135
			$course_id = intval( $_GET['course_id'] );
1136
			if( $course_id > 0 ) {
1137
1138
				$order_string = $this->get_lesson_order( $course_id );
1139
1140
				$html .= '<form id="editgrouping" method="post" action="" class="validate">' . "\n";
1141
1142
				$displayed_lessons = array();
1143
1144
                $modules = Sensei()->modules->get_course_modules( intval( $course_id ) );
1145
1146
                foreach( $modules as $module ) {
1147
1148
                    $args = array(
1149
                        'post_type' => 'lesson',
1150
                        'post_status' => 'publish',
1151
                        'posts_per_page' => -1,
1152
                        'meta_query' => array(
1153
                            array(
1154
                                'key' => '_lesson_course',
1155
                                'value' => intval( $course_id ),
1156
                                'compare' => '='
1157
                            )
1158
                        ),
1159
                        'tax_query' => array(
1160
                            array(
1161
                                'taxonomy' => Sensei()->modules->taxonomy,
1162
                                'field' => 'id',
1163
                                'terms' => intval( $module->term_id )
1164
                            )
1165
                        ),
1166
                        'meta_key' => '_order_module_' . $module->term_id,
1167
                        'orderby' => 'meta_value_num date',
1168
                        'order' => 'ASC',
1169
                        'suppress_filters' => 0
1170
                    );
1171
1172
                    $lessons = get_posts( $args );
1173
1174
                    if( count( $lessons ) > 0 ) {
1175
                        $html .= '<h3>' . $module->name . '</h3>' . "\n";
1176
                        $html .= '<ul class="sortable-lesson-list" data-module_id="' . $module->term_id . '">' . "\n";
1177
1178
                        $count = 0;
1179 View Code Duplication
                        foreach( $lessons as $lesson ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1180
                            $count++;
1181
                            $class = 'lesson';
1182
                            if ( $count == 1 ) { $class .= ' first'; }
1183
                            if ( $count == count( $lesson ) ) { $class .= ' last'; }
1184
                            if ( $count % 2 != 0 ) {
1185
                                $class .= ' alternate';
1186
                            }
1187
1188
                            $html .= '<li class="' . esc_attr( $class ) . '"><span rel="' . esc_attr( $lesson->ID ) . '" style="width: 100%;"> ' . $lesson->post_title . '</span></li>' . "\n";
1189
1190
                            $displayed_lessons[] = $lesson->ID;
1191
                        }
1192
1193
                        $html .= '</ul>' . "\n";
1194
1195
                        $html .= '<input type="hidden" name="lesson-order-module-' . $module->term_id . '" value="" />' . "\n";
1196
                    }
1197
                }
1198
1199
1200
                $lessons = Sensei()->course->course_lessons( $course_id );
1201
1202
				if( 0 < count( $lessons ) ) {
1203
1204
                    //get module term ids, will be used to exclude lessons
1205
                    $module_items_ids = array();
1206
                    if( ! empty( $modules ) ) {
1207
                        foreach ($modules as $module) {
1208
                            $module_items_ids[] = $module->term_id;
1209
                        }
1210
                    }
1211
1212 View Code Duplication
					if( 0 < count( $displayed_lessons ) ) {
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...
1213
						$html .= '<h3>' . __( 'Other Lessons', 'woothemes-sensei' ) . '</h3>' . "\n";
1214
					}
1215
1216
					$html .= '<ul class="sortable-lesson-list" data-module_id="0">' . "\n";
1217
					$count = 0;
1218
					foreach ( $lessons as $lesson ) {
1219
1220
                        // if lesson belongs to one fo the course modules then exclude it here
1221
                        // as it is listed above
1222
                        if( has_term( $module_items_ids, 'module', $lesson->ID )  ){
1223
1224
                            continue;
1225
1226
                        }
1227
1228
						$count++;
1229
						$class = 'lesson';
1230
						if ( $count == 1 ) { $class .= ' first'; }
1231
						if ( $count == count( $lesson ) ) { $class .= ' last'; }
1232
						if ( $count % 2 != 0 ) {
1233
1234
							$class .= ' alternate';
1235
1236
						}
1237
						$html .= '<li class="' . esc_attr( $class ) . '"><span rel="' . esc_attr( $lesson->ID ) . '" style="width: 100%;"> ' . $lesson->post_title . '</span></li>' . "\n";
1238
1239
						$displayed_lessons[] = $lesson->ID;
1240
					}
1241
					$html .= '</ul>' . "\n";
1242 View Code Duplication
				} else {
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...
1243
					if( 0 == count( $displayed_lessons ) ) {
1244
						$html .= '<p><em>' . __( 'There are no lessons in this course.', 'woothemes-sensei' ) . '</em></p>';
1245
					}
1246
				}
1247
1248
				if( 0 < count( $displayed_lessons ) ) {
1249
					$html .= '<input type="hidden" name="lesson-order" value="' . esc_attr( $order_string ) . '" />' . "\n";
1250
					$html .= '<input type="hidden" name="course_id" value="' . $course_id . '" />' . "\n";
1251
					$html .= '<input type="submit" class="button-primary" value="' . __( 'Save lesson order', 'woothemes-sensei' ) . '" />' . "\n";
1252
				}
1253
			}
1254
		}
1255
1256
		echo $html;
1257
1258
		?></div><?php
1259
	}
1260
1261
	public function get_lesson_order( $course_id = 0 ) {
1262
		$order_string = get_post_meta( $course_id, '_lesson_order', true );
1263
		return $order_string;
1264
	}
1265
1266
	public function save_lesson_order( $order_string = '', $course_id = 0 ) {
1267
1268
		if( $course_id ) {
1269
1270
            $modules = Sensei()->modules->get_course_modules( intval( $course_id ) );
1271
1272
            foreach( $modules as $module ) {
1273
1274
                $module_order_string = $_POST[ 'lesson-order-module-' . $module->term_id ];
1275
1276
                if( $module_order_string ) {
1277
                    $order = explode( ',', $module_order_string );
1278
                    $i = 1;
1279
                    foreach( $order as $lesson_id ) {
1280
                        if( $lesson_id ) {
1281
                            update_post_meta( $lesson_id, '_order_module_' . $module->term_id, $i );
1282
                            ++$i;
1283
                        }
1284
                    }
1285
                }
1286
            }
1287
1288
1289
			if( $order_string ) {
1290
				update_post_meta( $course_id, '_lesson_order', $order_string );
1291
1292
				$order = explode( ',', $order_string );
1293
1294
				$i = 1;
1295
				foreach( $order as $lesson_id ) {
1296
					if( $lesson_id ) {
1297
						update_post_meta( $lesson_id, '_order_' . $course_id, $i );
1298
						++$i;
1299
					}
1300
				}
1301
			}
1302
1303
			return true;
1304
		}
1305
1306
		return false;
1307
	}
1308
1309
	function sensei_add_custom_menu_items() {
0 ignored issues
show
Best Practice introduced by
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...
1310
		global $pagenow;
1311
1312
		if( 'nav-menus.php' == $pagenow ) {
1313
			add_meta_box( 'add-sensei-links', 'Sensei', array( $this, 'wp_nav_menu_item_sensei_links_meta_box' ), 'nav-menus', 'side', 'low' );
1314
		}
1315
	}
1316
1317
	function wp_nav_menu_item_sensei_links_meta_box( $object ) {
0 ignored issues
show
Unused Code introduced by
The parameter $object is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Best Practice introduced by
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...
1318
		global $nav_menu_selected_id;
1319
1320
		$menu_items = array(
1321
			'#senseicourses' => __( 'Courses', 'woothemes-sensei' ),
1322
			'#senseilessons' => __( 'Lessons', 'woothemes-sensei' ),
1323
			'#senseimycourses' => __( 'My Courses', 'woothemes-sensei' ),
1324
			'#senseilearnerprofile' => __( 'My Profile', 'woothemes-sensei' ),
1325
			'#senseimymessages' => __( 'My Messages', 'woothemes-sensei' ),
1326
			'#senseiloginlogout' => __( 'Login', 'woothemes-sensei' ) . '|' . __( 'Logout', 'woothemes-sensei' )
1327
		);
1328
1329
		$menu_items_obj = array();
1330
		foreach ( $menu_items as $value => $title ) {
1331
			$menu_items_obj[$title] = new stdClass;
1332
			$menu_items_obj[$title]->object_id			= esc_attr( $value );
1333
			$menu_items_obj[$title]->title				= esc_attr( $title );
1334
			$menu_items_obj[$title]->url				= esc_attr( $value );
1335
			$menu_items_obj[$title]->description 		= 'description';
1336
			$menu_items_obj[$title]->db_id 				= 0;
1337
			$menu_items_obj[$title]->object 			= 'sensei';
1338
			$menu_items_obj[$title]->menu_item_parent 	= 0;
1339
			$menu_items_obj[$title]->type 				= 'custom';
1340
			$menu_items_obj[$title]->target 			= '';
1341
			$menu_items_obj[$title]->attr_title 		= '';
1342
			$menu_items_obj[$title]->classes 			= array();
1343
			$menu_items_obj[$title]->xfn 				= '';
1344
		}
1345
1346
		$walker = new Walker_Nav_Menu_Checklist( array() );
1347
		?>
1348
1349
		<div id="sensei-links" class="senseidiv taxonomydiv">
1350
			<div id="tabs-panel-sensei-links-all" class="tabs-panel tabs-panel-view-all tabs-panel-active">
1351
1352
				<ul id="sensei-linkschecklist" class="list:sensei-links categorychecklist form-no-clear">
1353
					<?php echo walk_nav_menu_tree( array_map( 'wp_setup_nav_menu_item', $menu_items_obj ), 0, (object)array( 'walker' => $walker ) ); ?>
1354
				</ul>
1355
1356
			</div>
1357
			<p class="button-controls">
1358
				<span class="add-to-menu">
1359
					<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" />
1360
					<span class="spinner"></span>
1361
				</span>
1362
			</p>
1363
		</div><!-- .senseidiv -->
1364
		<?php
1365
	}
1366
1367
	/**
1368
	 * Adding admin notice if the current
1369
     * installed theme is not compatible
1370
     *
1371
	 * @return void
1372
	 */
1373
	public function theme_compatibility_notices() {
1374
1375
        if( isset( $_GET['sensei_hide_notice'] ) ) {
1376
        	switch( esc_attr( $_GET['sensei_hide_notice'] ) ) {
1377
				case 'menu_settings': add_user_meta( get_current_user_id(), 'sensei_hide_menu_settings_notice', true ); break;
1378
				case 'theme_check': add_user_meta( get_current_user_id(), 'sensei_hide_theme_check_notice', true ); break;
1379
			}
1380
        }
1381
1382
        // white list templates that are already support by default and do not show notice for them
1383
        $template = get_option( 'template' );
1384
1385
        $white_list = array(    'twentyeleven',
1386
                                'twentytwelve',
1387
                                'twentyfourteen',
1388
                                'twentyfifteen',
1389
                                'twentysixteen',
1390
                                'storefront',
1391
                                                );
1392
1393
        if ( in_array( $template, $white_list ) ) {
1394
1395
            return;
1396
1397
        }
1398
1399
        // don't show the notice if the user chose to hide it
1400
        $hide_theme_check_notice = get_user_meta( get_current_user_id(), 'sensei_hide_theme_check_notice', true );
1401
        if(  $hide_theme_check_notice ) {
1402
1403
            return;
1404
1405
        }
1406
1407
        // show the notice for themes not supporting sensei
1408
	    if ( ! current_theme_supports( 'sensei' ) ) {
1409
            ?>
1410
1411
            <div id="message" class="error sensei-message sensei-connect">
1412
                    <p>
1413
                        <strong>
1414
1415
                            <?php _e('Your theme does not declare Sensei support', 'woothemes-sensei' ); ?>
1416
1417
                        </strong> &#8211;
1418
1419
                        <?php _e( 'if you encounter layout issues please read our integration guide or choose a ', 'woothemes-sensei' ); ?>
1420
1421
                        <a href="http://www.woothemes.com/product-category/themes/sensei-themes/"> <?php  _e( 'Sensei theme', 'woothemes-sensei' ) ?> </a>
1422
1423
                        :)
1424
1425
                    </p>
1426
                    <p class="submit">
1427
                        <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">
1428
1429
                            <?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' ); ?>
1430
1431
                        </a>
1432
                    </p>
1433
            </div>
1434
            <?php
1435
		}
1436
	}
1437
1438
	/**
1439
	 * Reset theme check notice
1440
	 * @return void
1441
	 */
1442
	public function reset_theme_check_notices() {
1443
		global $current_user;
1444
		wp_get_current_user();
1445
        $user_id = $current_user->ID;
1446
1447
		delete_user_meta( $user_id, 'sensei_hide_theme_check_notice' );
1448
	}
1449
1450
	/**
1451
	 * Set Sensei users access to the admin area when WooCommerce is installed
1452
	 * Allow Teachers to access the admin area
1453
	 *
1454
	 * @param  bool $prevent_access
1455
	 * @return bool
1456
	 */
1457
	public function admin_access( $prevent_access ) {
1458
		if ( current_user_can( 'manage_sensei_grades' ) ) {
1459
			return false;
1460
		}
1461
1462
		return $prevent_access;
1463
	}
1464
1465
    /**
1466
     * Hooked onto admin_init. Listens for install_sensei_pages and skip_install_sensei_pages query args
1467
     * on the sensei settings page.
1468
     *
1469
     * The function
1470
     *
1471
     * @since 1.8.7
1472
     */
1473
    public  static function install_pages(){
1474
1475
        // only fire on the settings page
1476
        if( ! isset( $_GET['page'] )
1477
            || 'woothemes-sensei-settings' != $_GET['page']
1478
            || 1 == get_option('skip_install_sensei_pages') ){
1479
1480
            return;
1481
1482
        }
1483
1484
        // Install/page installer
1485
        $install_complete = false;
1486
1487
        // Add pages button
1488
        $settings_url = '';
1489
        if (isset($_GET['install_sensei_pages']) && $_GET['install_sensei_pages']) {
1490
1491
            Sensei()->admin->create_pages();
1492
            update_option('skip_install_sensei_pages', 1);
1493
            $install_complete = true;
1494
            $settings_url = remove_query_arg('install_sensei_pages');
1495
1496
            // Skip button
1497
        } elseif (isset($_GET['skip_install_sensei_pages']) && $_GET['skip_install_sensei_pages']) {
1498
1499
            update_option('skip_install_sensei_pages', 1);
1500
            $install_complete = true;
1501
            $settings_url = remove_query_arg('skip_install_sensei_pages');
1502
1503
        }
1504
1505
        if ($install_complete) {
1506
1507
            // Flush rules after install
1508
            flush_rewrite_rules( true );
1509
1510
            // Set installed option
1511
            update_option('sensei_installed', 0);
1512
1513
            $complete_url = add_query_arg( 'sensei_install_complete', 'true', $settings_url  );
1514
            wp_redirect( $complete_url );
1515
1516
        }
1517
1518
    }// end install_pages
1519
1520
} // End Class
1521
1522
/**
1523
 * Legacy Class WooThemes_Sensei_Admin
1524
 * for backward compatibility
1525
 * @since 1.9.0
1526
 */
1527
class WooThemes_Sensei_Admin extends Sensei_Admin{ }
1528