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-modules.php (22 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 15 and the first side effect is on line 3.

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
3
if ( ! defined( 'ABSPATH' ) ) exit;
4
5
/**
6
 * Sensei Modules Class
7
 *
8
 * Sensei Module Functionality
9
 *
10
 * @package Content
11
 * @author Automattic
12
 *
13
 * @since 1.8.0
14
 */
15
class Sensei_Core_Modules
16
{
17
    private $dir;
18
    private $file;
19
    private $assets_dir;
20
    private $assets_url;
21
    private $order_page_slug;
22
    public $taxonomy;
23
24
    public function __construct( $file )
25
    {
26
        $this->file = $file;
27
        $this->dir = dirname($this->file);
28
        $this->assets_dir = trailingslashit($this->dir) . 'assets';
29
        $this->assets_url = esc_url(trailingslashit(plugins_url('/assets/', $this->file)));
30
        $this->taxonomy = 'module';
31
        $this->order_page_slug = 'module-order';
32
33
        // setup taxonomy
34
        add_action( 'init', array( $this, 'setup_modules_taxonomy' ), 10 );
35
36
        // Manage lesson meta boxes for taxonomy
37
        add_action('add_meta_boxes', array($this, 'modules_metaboxes'), 20, 2 );
38
39
        // Save lesson meta box
40
        add_action('save_post', array($this, 'save_lesson_module'), 10, 1);
41
42
        //Reset the none modules lessons transient
43
        add_action( 'save_post', array( 'Sensei_Core_Modules', 'reset_none_modules_transient' ) );
44
45
        // Frontend styling
46
        add_action('wp_enqueue_scripts', array($this, 'enqueue_styles'));
47
48
        // Admin styling
49
        add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_styles'));
50
        add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'),  20 , 2 );
51
52
        // Handle module completion record
53
        add_action('sensei_lesson_status_updated', array($this, 'update_lesson_status_module_progress'), 10, 3);
54
        add_action('sensei_user_lesson_reset', array($this, 'save_lesson_module_progress'), 10, 2);
55
        add_action('wp', array($this, 'save_module_progress'), 10);
56
57
        // Handle module ordering
58
        add_action('admin_menu', array($this, 'register_modules_admin_menu_items'), 30 );
59
        add_filter('manage_edit-course_columns', array($this, 'course_columns'), 11, 1);
60
        add_action('manage_posts_custom_column', array($this, 'course_column_content'), 11, 2);
61
62
        // Ensure modules always show under courses
63
        add_action( 'admin_menu', array( $this, 'remove_lessons_menu_model_taxonomy' ) , 10 );
64
        add_action( 'admin_menu', array( $this, 'remove_courses_menu_model_taxonomy' ) , 10 );
65
        add_action( 'admin_menu', array( $this, 'redirect_to_lesson_module_taxonomy_to_course' ) , 20 );
66
67
        // Add course field to taxonomy
68
        add_action($this->taxonomy . '_add_form_fields', array($this, 'add_module_fields'), 50, 1);
69
        add_action($this->taxonomy . '_edit_form_fields', array($this, 'edit_module_fields'), 1, 1);
70
        add_action('edited_' . $this->taxonomy, array($this, 'save_module_course'), 10, 2);
71
        add_action('created_' . $this->taxonomy, array($this, 'save_module_course'), 10, 2);
72
        add_action('wp_ajax_sensei_json_search_courses', array($this, 'search_courses_json'));
73
74
        // Manage module taxonomy archive page
75
        add_filter('template_include', array($this, 'module_archive_template'), 10);
76
        add_action('pre_get_posts', array($this, 'module_archive_filter'), 10, 1);
77
        add_filter('sensei_lessons_archive_text', array($this, 'module_archive_title'));
78
        add_action('sensei_content_lesson_inside_before', array($this, 'module_archive_description'), 11);
79
        add_action('sensei_pagination', array($this, 'module_navigation_links'), 11);
80
        add_filter('body_class', array($this, 'module_archive_body_class'));
81
82
        // add modules to the single course template
83
        add_action( 'sensei_single_course_content_inside_after', array($this, 'load_course_module_content_template') , 8 );
84
85
        //Single Course modules actions. Add to single-course/course-modules.php
86
        add_action('sensei_single_course_modules_before',array( $this,'course_modules_title' ), 20);
87
88
        // Set up display on single lesson page
89
        add_filter('sensei_breadcrumb_output', array($this, 'module_breadcrumb_link'), 10, 2);
90
91
        // Add 'Modules' columns to Analysis tables
92
        add_filter('sensei_analysis_overview_columns', array($this, 'analysis_overview_column_title'), 10, 2);
93
        add_filter('sensei_analysis_overview_column_data', array($this, 'analysis_overview_column_data'), 10, 3);
94
        add_filter('sensei_analysis_course_columns', array($this, 'analysis_course_column_title'), 10, 2);
95
        add_filter('sensei_analysis_course_column_data', array($this, 'analysis_course_column_data'), 10, 3);
96
97
        // Manage module taxonomy columns
98
        add_filter('manage_edit-' . $this->taxonomy . '_columns', array($this, 'taxonomy_column_headings'), 1, 1);
99
        add_filter('manage_' . $this->taxonomy . '_custom_column', array($this, 'taxonomy_column_content'), 1, 3);
100
        add_filter('sensei_module_lesson_list_title', array($this, 'sensei_course_preview_titles'), 10, 2);
101
102
        //store new modules created on the course edit screen
103
        add_action( 'wp_ajax_sensei_add_new_module_term', array( 'Sensei_Core_Modules','add_new_module_term' ) );
104
105
        // for non admin users, only show taxonomies that belong to them
106
        add_filter('get_terms', array( $this, 'filter_module_terms' ), 20, 3 );
107
        add_filter('get_object_terms', array( $this, 'filter_course_selected_terms' ), 20, 3 );
108
109
        // add the teacher name next to the module term in for admin users
110
        add_filter('get_terms', array( $this, 'append_teacher_name_to_module' ), 70, 3 );
111
112
        // remove the default modules  metabox
113
        add_action('admin_init',array( 'Sensei_Core_Modules' , 'remove_default_modules_box' ));
114
115
    } // end constructor
116
117
    /**
118
     * Alter a module term slug when a new taxonomy term is created
119
     * This will add the creators user name to the slug for uniqueness.
120
     *
121
     * @since 1.8.0
122
     *
123
     * @param $term_id
124
     * @param $tt_id
125
     * @param $taxonomy
126
     *
127
     * @return void
128
     * @deprecated since 1.9.0
129
     */
130
    public function change_module_term_slug( $term_id, $tt_id, $taxonomy ){
0 ignored issues
show
The parameter $term_id 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...
The parameter $tt_id 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...
The parameter $taxonomy 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...
131
132
        _deprecated_function('change_module_term_slug', '1.9.0' );
133
134
    }// end add_module_term_group
135
136
    /**
137
     * Hook in all meta boxes related tot he modules taxonomy
138
     *
139
     * @since 1.8.0
140
     *
141
     * @param string $post_type
142
     * @param WP_Post $post
143
     *
144
     * @return void
145
     */
146
    public function modules_metaboxes( $post_type, $post )
0 ignored issues
show
The parameter $post 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...
147
    {
148
        if ('lesson' == $post_type ) {
149
150
            // Remove default taxonomy meta box from Lesson edit screen
151
            remove_meta_box($this->taxonomy . 'div', 'lesson', 'side');
152
153
            // Add custom meta box to limit module selection to one per lesson
154
            add_meta_box($this->taxonomy . '_select', __('Lesson Module', 'woothemes-sensei'), array($this, 'lesson_module_metabox'), 'lesson', 'side', 'default');
155
        }
156
157
        if( 'course' == $post_type ){
158
            // Course modules selection metabox
159
            add_meta_box( $this->taxonomy . '_course_mb', __('Course Modules', 'woothemes-sensei'), array( $this, 'course_module_metabox'), 'course', 'side', 'core');
160
        }
161
    }
162
163
    /**
164
     * Build content for custom module meta box
165
     *
166
     * @since 1.8.0
167
     * @param  object $post Current post object
168
     * @return void
169
     */
170
    public function lesson_module_metabox($post)
171
    {
172
173
        // Get lesson course
174
        $lesson_course = get_post_meta($post->ID, '_lesson_course', true);
175
176
        $html = '';
177
178
        // Only show module selection if this lesson is part of a course
179
        if ($lesson_course && $lesson_course > 0) {
180
181
            // Get existing lesson module
182
            $lesson_module = 0;
183
            $lesson_module_list = wp_get_post_terms($post->ID, $this->taxonomy);
184
            if (is_array($lesson_module_list) && count($lesson_module_list) > 0) {
185
                foreach ($lesson_module_list as $single_module) {
186
                    $lesson_module = $single_module->term_id;
187
                    break;
188
                }
189
            }
190
191
            // Get the available modules for this lesson's course
192
            $modules = $this->get_course_modules($lesson_course);
193
194
            // Build the HTML to output
195
            $html .= '<input type="hidden" name="' . esc_attr('woo_lesson_' . $this->taxonomy . '_nonce') . '" id="' . esc_attr('woo_lesson_' . $this->taxonomy . '_nonce') . '" value="' . esc_attr(wp_create_nonce(plugin_basename($this->file))) . '" />';
196
            if (is_array($modules) && count($modules) > 0) {
197
                $html .= '<select id="lesson-module-options" name="lesson_module" class="widefat">' . "\n";
198
                $html .= '<option value="">' . __('None', 'woothemes-sensei') . '</option>';
199
                foreach ($modules as $module) {
200
                    $html .= '<option value="' . esc_attr(absint($module->term_id)) . '"' . selected($module->term_id, $lesson_module, false) . '>' . esc_html($module->name) . '</option>' . "\n";
201
                }
202
                $html .= '</select>' . "\n";
203
            } else {
204
                $course_url = admin_url('post.php?post=' . urlencode($lesson_course) . '&action=edit');
205
                $html .= '<p>' . sprintf(__('No modules are available for this lesson yet. %1$sPlease add some to %3$sthe course%4$s.%2$s', 'woothemes-sensei'), '<em>', '</em>', '<a href="' . esc_url($course_url) . '">', '</a>') . '</p>';
206
            } // End If Statement
207
208
        } else {
209
            $html .= '<p>' . sprintf(__('No modules are available for this lesson yet. %1$sPlease select a course first.%2$s', 'woothemes-sensei'), '<em>', '</em>') . '</p>';
210
        } // End If Statement
211
212
        // Output the HTML
213
        echo $html;
214
    }
215
216
    /**
217
     * Save module to lesson
218
     *
219
     * @since 1.8.0
220
     * @param  integer $post_id ID of post
221
     * @return mixed            Post ID on permissions failure, boolean true on success
222
     */
223
    public function save_lesson_module($post_id)
224
    {
225
        global $post;
226
227
        // Verify post type and nonce
228 View Code Duplication
        if ((get_post_type() != 'lesson') || !isset($_POST['woo_lesson_' . $this->taxonomy . '_nonce'] )
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
229
            ||!wp_verify_nonce($_POST['woo_lesson_' . $this->taxonomy . '_nonce'], plugin_basename($this->file))) {
230
            return $post_id;
231
        }
232
233
        // Check if user has permissions to edit lessons
234
        $post_type = get_post_type_object($post->post_type);
235
        if (!current_user_can($post_type->cap->edit_post, $post_id)) {
236
            return $post_id;
237
        }
238
239
        // Check if user has permissions to edit this specific post
240
        if (!current_user_can('edit_post', $post_id)) {
241
            return $post_id;
242
        }
243
244
        // Cast module ID as an integer if selected, otherwise leave as empty string
245
        if ( isset( $_POST['lesson_module'] ) ) {
246
247
            if( empty ( $_POST['lesson_module'] ) ){
248
                wp_delete_object_term_relationships($post_id, $this->taxonomy  );
249
                return true;
250
            }
251
252
            $module_id = intval( $_POST['lesson_module'] );
253
254
            // Assign lesson to selected module
255
            wp_set_object_terms($post_id, $module_id, $this->taxonomy, false);
256
257
            // Set default order for lesson inside module
258
            if (!get_post_meta($post_id, '_order_module_' . $module_id, true)) {
259
                update_post_meta($post_id, '_order_module_' . $module_id, 0);
260
            }
261
        }
262
263
        return true;
264
    }
265
266
    /**
267
     * Display course field on new module screen
268
     *
269
     * @since 1.8.0
270
     * @param object $taxonomy Taxonomy object
271
     * @return void
272
     */
273
    public function add_module_fields($taxonomy)
0 ignored issues
show
The parameter $taxonomy 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...
274
    {
275
        ?>
276
        <div class="form-field">
277
            <label for="module_courses"><?php _e('Course(s)', 'woothemes-sensei'); ?></label>
278
            <input type="hidden" id="module_courses" name="module_courses" class="ajax_chosen_select_courses"
279
                    placeholder="<?php esc_attr_e('Search for courses', 'woothemes-sensei'); ?>" />
280
            <span
281
                class="description"><?php _e('Search for and select the courses that this module will belong to.', 'woothemes-sensei'); ?></span>
282
        </div>
283
    <?php
284
    }
285
286
    /**
287
     * Display course field on module edit screen
288
     *
289
     * @since 1.8.0
290
     * @param  object $module Module term object
291
     * @return void
292
     */
293
    public function edit_module_fields($module)
294
    {
295
296
        $module_id = $module->term_id;
297
298
        // Get module's existing courses
299
        $args = array(
300
            'post_type' => 'course',
301
            'post_status' => array('publish', 'draft', 'future', 'private'),
302
            'posts_per_page' => -1,
303
            'tax_query' => array(
304
                array(
305
                    'taxonomy' => $this->taxonomy,
306
                    'field' => 'id',
307
                    'terms' => $module_id
308
                )
309
            )
310
        );
311
        $courses = get_posts($args);
312
313
        //build the defaults array
314
        $module_courses = array();
315
        if (isset($courses) && is_array($courses)) {
316
            foreach ($courses as $course) {
317
                $module_courses[] =   array( 'id' =>$course->ID, 'details'=>$course->post_title );
318
            }
319
        }
320
321
        ?>
322
        <tr class="form-field">
323
            <th scope="row" valign="top"><label
324
                    for="module_courses"><?php _e('Course(s)', 'woothemes-sensei'); ?></label></th>
325
            <td>
326
                <input type="hidden"
327
                       data-defaults="<?php echo esc_attr( json_encode($module_courses)); ?>"
328
                       value="<?php echo esc_attr( json_encode($module_courses) ); ?>"
329
                       id="module_courses" name="module_courses"
330
                       class="ajax_chosen_select_courses"
331
                       placeholder="<?php esc_attr_e('Search for courses...', 'woothemes-sensei'); ?>"
332
                    />
333
                <span
334
                    class="description"><?php _e('Search for and select the courses that this module will belong to.', 'woothemes-sensei'); ?></span>
335
            </td>
336
        </tr>
337
    <?php
338
    }
339
340
    /**
341
     * Save module course on add/edit
342
     *
343
     * @since 1.8.0
344
     * @param  integer $module_id ID of module
345
     * @return void
346
     */
347
    public function save_module_course($module_id)
348
    {
349
350
        // Get module's existing courses
351
        $args = array(
352
            'post_type' => 'course',
353
            'post_status' => array('publish', 'draft', 'future', 'private'),
354
            'posts_per_page' => -1,
355
            'tax_query' => array(
356
                array(
357
                    'taxonomy' => $this->taxonomy,
358
                    'field' => 'id',
359
                    'terms' => $module_id
360
                )
361
            )
362
        );
363
        $courses = get_posts($args);
364
365
        // Remove module from existing courses
366
        if (isset($courses) && is_array($courses)) {
367
            foreach ($courses as $course) {
368
                wp_remove_object_terms($course->ID, $module_id, $this->taxonomy);
369
            }
370
        }
371
372
        // Add module to selected courses
373
        if ( isset( $_POST['module_courses'] ) && ! empty( $_POST['module_courses'] ) ) {
374
375
            $course_ids = explode( ",", $_POST['module_courses'] );
376
377
            foreach ( $course_ids as $course_id ) {
378
379
                wp_set_object_terms($course_id, $module_id, $this->taxonomy, true);
380
381
            }
382
        }
383
    }
384
385
    /**
386
     * Ajax function to search for courses matching term
387
     *
388
     * @since 1.8.0
389
     * @return void
390
     */
391
    public function search_courses_json()
392
    {
393
394
        // Security check
395
        check_ajax_referer('search-courses', 'security');
396
397
        // Set content type
398
        header('Content-Type: application/json; charset=utf-8');
399
400
        // Get user input
401
        $term = urldecode(stripslashes($_GET['term']));
402
403
        // Return nothing if term is empty
404
        if (empty($term))
405
            die();
406
407
        // Set a default if none is given
408
        $default = isset($_GET['default']) ? $_GET['default'] : __('No course', 'woothemes-sensei');
409
410
        // Set up array of results
411
        $found_courses = array('' => $default);
412
413
        // Fetch results
414
        $args = array(
415
            'post_type' => 'course',
416
            'post_status' => array('publish', 'draft', 'future', 'private'),
417
            'posts_per_page' => -1,
418
            'orderby' => 'title',
419
            's' => $term
420
        );
421
        $courses = get_posts($args);
422
423
        // Add results to array
424
        if ($courses) {
425
            foreach ($courses as $course) {
426
                $found_courses[$course->ID] = $course->post_title;
427
            }
428
        }
429
430
        // Encode and return results for processing & selection
431
        echo json_encode($found_courses);
432
        die();
433
    }
434
435
    /**
436
     * display modules on single course pages
437
     *
438
     * @since 1.8.0
439
     * @return void
440
     */
441
    public function single_course_modules(){
442
443
        _deprecated_function('Sensei_Modules->single_course_modules','Sensei 1.9.0', 'Sensei()->modules->load_course_module_content_template');
444
        // only show modules on the course that has modules
445
        if( is_singular( 'course' ) && has_term( '', 'module' )  )  {
446
447
            $this->load_course_module_content_template();
448
449
        }
450
451
    } // end single_course_modules
452
453
    public function sensei_course_preview_titles($title, $lesson_id)
454
    {
455
        global $post, $current_user;
456
457
        $course_id = $post->ID;
458
        $title_text = '';
459
460
        if (method_exists('Sensei_Utils', 'is_preview_lesson') && Sensei_Utils::is_preview_lesson($lesson_id)) {
461
            $is_user_taking_course = Sensei_Utils::sensei_check_for_activity(array('post_id' => $course_id, 'user_id' => $current_user->ID, 'type' => 'sensei_course_status'));
462
            if (!$is_user_taking_course) {
463
                if (method_exists('WooThemes_Sensei_Frontend', 'sensei_lesson_preview_title_text')) {
464
                    $title_text = Sensei()->frontend->sensei_lesson_preview_title_text($course_id);
465
                    // Remove brackets for display here
466
                    $title_text = str_replace('(', '', $title_text);
467
                    $title_text = str_replace(')', '', $title_text);
468
                    $title_text = '<span class="preview-label">' . $title_text . '</span>';
469
                }
470
                $title .= ' ' . $title_text;
471
            }
472
        }
473
474
        return $title;
475
    }
476
477
    public function module_breadcrumb_link($html, $separator)
478
    {
479
        global $post;
480
        // Lesson
481
        if (is_singular('lesson')) {
482
            if (has_term('', $this->taxonomy, $post->ID)) {
483
                $module = $this->get_lesson_module($post->ID);
484
                if( $module ) {
485
                    $html .= ' ' . $separator . ' <a href="' . esc_url($module->url) . '" title="' .  __('Back to the module', 'woothemes-sensei') . '">' . $module->name . '</a>';
486
                }
487
            }
488
        }
489
        // Module
490
        if (is_tax($this->taxonomy)) {
491
            if (isset($_GET['course_id']) && 0 < intval($_GET['course_id'])) {
492
                $course_id = intval($_GET['course_id']);
493
                $html .= '<a href="' . esc_url(get_permalink($course_id)) . '" title="' .  __('Back to the course', 'woothemes-sensei') . '">' . get_the_title($course_id) . '</a>';
494
            }
495
        }
496
        return $html;
497
    }
498
499
    /**
500
     * Set lesson archive template to display on module taxonomy archive page
501
     *
502
     * @since 1.8.0
503
     * @param  string $template Default template
504
     * @return string           Modified template
505
     */
506
    public function module_archive_template($template) {
507
508
        if ( ! is_tax($this->taxonomy) ) {
509
            return $template;
510
        }
511
512
        $file = 'archive-lesson.php';
513
        $find = array( $file, Sensei()->template_url . $file );
514
515
        // locate the template file
516
        $template = locate_template($find);
517
        if (!$template) {
518
519
            $template = Sensei()->plugin_path() . 'templates/' . $file;
520
521
        }
522
523
524
        return $template;
525
    }
526
527
    /**
528
     * Modify module taxonomy archive query
529
     *
530
     * @since 1.8.0
531
     * @param  object $query The query object passed by reference
532
     * @return void
533
     */
534
    public function module_archive_filter($query)
535
    {
536
        if (is_tax($this->taxonomy) && $query->is_main_query()) {
537
538
539
            // Limit to lessons only
540
            $query->set('post_type', 'lesson');
541
542
            // Set order of lessons
543
            if (version_compare(Sensei()->version, '1.6.0', '>=')) {
544
                $module_id = $query->queried_object_id;
545
                $query->set('meta_key', '_order_module_' . $module_id);
546
                $query->set('orderby', 'meta_value_num date');
547
            } else {
548
                $query->set('orderby', 'menu_order');
549
            }
550
            $query->set('order', 'ASC');
551
552
            // Limit to specific course if specified
553
            if (isset($_GET['course_id']) && 0 < intval($_GET['course_id'])) {
554
                $course_id = intval($_GET['course_id']);
555
                $meta_query[] = array(
556
                    'key' => '_lesson_course',
557
                    'value' => intval($course_id)
558
                );
559
                $query->set('meta_query', $meta_query);
560
            }
561
562
        }
563
    }
564
565
    /**
566
     * Modify archive page title
567
     *
568
     * @since 1.8.0
569
     * @param  string $title Default title
570
     * @return string        Modified title
571
     */
572
    public function module_archive_title($title)
573
    {
574
        if (is_tax($this->taxonomy)) {
575
            $title = apply_filters('sensei_module_archive_title', get_queried_object()->name);
576
        }
577
        return $title;
578
    }
579
580
    /**
581
     * Display module description on taxonomy archive page
582
     *
583
     * @since 1.8.0
584
     * @return void
585
     */
586
    public function module_archive_description()
587
    {
588
        if (is_tax($this->taxonomy)) {
589
590
            $module = get_queried_object();
591
592
            $module_progress = false;
593
            if (is_user_logged_in() && isset($_GET['course_id']) && intval($_GET['course_id']) > 0) {
594
                global $current_user;
595
                wp_get_current_user();
596
                $module_progress = $this->get_user_module_progress($module->term_id, $_GET['course_id'], $current_user->ID);
597
            }
598
599
            if ($module_progress && $module_progress > 0) {
600
                $status = __('Completed', 'woothemes-sensei');
601
                $class = 'completed';
602
                if ($module_progress < 100) {
603
                    $status = __('In progress', 'woothemes-sensei');
604
                    $class = 'in-progress';
605
                }
606
                echo '<p class="status ' . esc_attr($class) . '">' . $status . '</p>';
607
            }
608
609
            echo '<p class="archive-description module-description">' . apply_filters('sensei_module_archive_description', nl2br($module->description), $module->term_id) . '</p>';
610
        }
611
    }
612
613
    public function module_archive_body_class($classes)
614
    {
615
        if (is_tax($this->taxonomy)) {
616
            $classes[] = 'module-archive';
617
        }
618
        return $classes;
619
    }
620
621
    /**
622
     * Display module navigation links on module taxonomy archive page
623
     *
624
     * @since 1.8.0
625
     * @return void
626
     */
627
    public function module_navigation_links()
628
    {
629
        if (is_tax($this->taxonomy) && isset($_GET['course_id'])) {
630
631
            $queried_module = get_queried_object();
632
            $course_modules = $this->get_course_modules($_GET['course_id']);
633
634
            $prev_module = false;
635
            $next_module = false;
636
            $on_current = false;
637
            foreach ($course_modules as $module) {
638
                $this_module = $module;
639
                if ($on_current) {
640
                    $next_module = $this_module;
641
                    break;
642
                }
643
                if ($this_module == $queried_module) {
644
                    $on_current = true;
645
                } else {
646
                    $prev_module = $module;
647
                }
648
            }
649
650
            ?>
651
            <div id="post-entries" class="post-entries module-navigation fix">
652 View Code Duplication
                <?php if ($next_module) {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
653
                    $module_link = add_query_arg('course_id', intval($_GET['course_id']), get_term_link($next_module, $this->taxonomy));
654
                    ?>
655
                    <div class="nav-next fr"><a href="<?php echo esc_url($module_link); ?>"
656
                                                title="<?php esc_attr_e('Next module', 'woothemes-sensei'); ?>"><?php echo $next_module->name; ?>
657
                            <span class="meta-nav"></span></a></div>
658
                <?php } ?>
659 View Code Duplication
                <?php if ($prev_module) {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
660
                    $module_link = add_query_arg('course_id', intval($_GET['course_id']), get_term_link($prev_module, $this->taxonomy));
661
                    ?>
662
                    <div class="nav-prev fl"><a href="<?php echo esc_url($module_link); ?>"
663
                                                title="<?php _e('Previous module', 'woothemes-sensei'); ?>"><span
664
                                class="meta-nav"></span> <?php echo $prev_module->name; ?></a></div>
665
                <?php } ?>
666
            </div>
667
        <?php
668
        }
669
    }
670
671
    /**
672
     * Trigger save_lesson_module_progress() when a lesson status is updated for a specific user
673
     *
674
     * @since 1.8.0
675
     * @param  string $status Status of the lesson for the user
676
     * @param  integer $user_id ID of user
677
     * @param  integer $lesson_id ID of lesson
678
     * @return void
679
     */
680
    public function update_lesson_status_module_progress($status = '', $user_id = 0, $lesson_id = 0)
681
    {
682
        $this->save_lesson_module_progress($user_id, $lesson_id);
683
    }
684
685
    /**
686
     * Save lesson's module progress for a specific user
687
     *
688
     * @since 1.8.0
689
     * @param  integer $user_id ID of user
690
     * @param  integer $lesson_id ID of lesson
691
     * @return void
692
     */
693
    public function save_lesson_module_progress($user_id = 0, $lesson_id = 0)
694
    {
695
        $module = $this->get_lesson_module($lesson_id);
696
        $course_id = get_post_meta($lesson_id, '_lesson_course', true);
697
        if ($module && $course_id) {
698
            $this->save_user_module_progress(intval($module->term_id), intval($course_id), intval($user_id));
699
        }
700
    }
701
702
    /**
703
     * Save progress of module for user
704
     *
705
     * @since 1.8.0
706
     * @return void
707
     */
708
    public function save_module_progress()
709
    {
710
        if (is_tax($this->taxonomy) && is_user_logged_in() && isset($_GET['course_id']) && 0 < intval($_GET['course_id'])) {
711
            global $current_user;
712
            wp_get_current_user();
713
            $user_id = $current_user->ID;
714
715
            $module = get_queried_object();
716
717
            $this->save_user_module_progress(intval($module->term_id), intval($_GET['course_id']), intval($user_id));
718
        }
719
    }
720
721
    /**
722
     * Save module progess for user
723
     *
724
     * @since 1.8.0
725
     *
726
     * @param  integer $module_id ID of module
727
     * @param  integer $course_id ID of course
728
     * @param  integer $user_id ID of user
729
     * @return void
730
     */
731
    public function save_user_module_progress($module_id = 0, $course_id = 0, $user_id = 0)
732
    {
733
        $module_progress = $this->calculate_user_module_progress($user_id, $module_id, $course_id);
734
        update_user_meta(intval($user_id), '_module_progress_' . intval($course_id) . '_' . intval($module_id), intval($module_progress));
735
736
        do_action('sensei_module_save_user_progress', $course_id, $module_id, $user_id, $module_progress);
737
    }
738
739
    /**
740
     * Get module progress for a user
741
     *
742
     * @since 1.8.0
743
     *
744
     * @param  integer $module_id ID of module
745
     * @param  integer $course_id ID of course
746
     * @param  integer $user_id ID of user
747
     * @return mixed              Module progress percentage on success, false on failure
748
     */
749
    public function get_user_module_progress($module_id = 0, $course_id = 0, $user_id = 0)
750
    {
751
        $module_progress = get_user_meta(intval($user_id), '_module_progress_' . intval($course_id) . '_' . intval($module_id), true);
752
        if ($module_progress) {
753
            return (float)$module_progress;
754
        }
755
        return false;
756
    }
757
758
    /**
759
     * Calculate module progess for user
760
     *
761
     * @since 1.8.0
762
     *
763
     * @param  integer $user_id ID of user
764
     * @param  integer $module_id ID of module
765
     * @param  integer $course_id ID of course
766
     * @return integer            Module progress percentage
767
     */
768
    public function calculate_user_module_progress($user_id = 0, $module_id = 0, $course_id = 0)
769
    {
770
771
        $args = array(
772
            'post_type' => 'lesson',
773
            'post_status' => 'publish',
774
            'posts_per_page' => -1,
775
            'tax_query' => array(
776
                array(
777
                    'taxonomy' => $this->taxonomy,
778
                    'field' => 'id',
779
                    'terms' => $module_id
780
                )
781
            ),
782
            'meta_query' => array(
783
                array(
784
                    'key' => '_lesson_course',
785
                    'value' => $course_id
786
                )
787
            ),
788
            'fields' => 'ids'
789
        );
790
        $lessons = get_posts($args);
791
792
        if (is_wp_error($lessons) || 0 >= count($lessons)) return 0;
793
794
        $completed = false;
0 ignored issues
show
$completed is not used, you could remove the assignment.

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

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

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

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

Loading history...
795
        $lesson_count = 0;
796
        $completed_count = 0;
797
        foreach ($lessons as $lesson_id) {
798
            $completed = Sensei_Utils::user_completed_lesson($lesson_id, $user_id);
799
            ++$lesson_count;
800
            if ($completed) {
801
                ++$completed_count;
802
            }
803
        }
804
        $module_progress = ($completed_count / $lesson_count) * 100;
805
806
        return (float)$module_progress;
807
    }
808
809
    /**
810
     * Register admin screen for ordering modules
811
     *
812
     * @since 1.8.0
813
     *
814
     * @return void
815
     */
816
    public function register_modules_admin_menu_items()
817
    {
818
        //add the modules link under the Course main menu
819
        add_submenu_page('edit.php?post_type=course', __('Modules', 'woothemes-sensei'), __('Modules', 'woothemes-sensei'), 'manage_categories', 'edit-tags.php?taxonomy=module','' );
820
821
        // Regsiter new admin page for module ordering
822
        $hook = add_submenu_page('edit.php?post_type=course', __('Order Modules', 'woothemes-sensei'), __('Order Modules', 'woothemes-sensei'), 'edit_lessons', $this->order_page_slug, array($this, 'module_order_screen'));
0 ignored issues
show
$hook is not used, you could remove the assignment.

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

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

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

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

Loading history...
823
824
    }
825
826
    /**
827
     * Display Module Order screen
828
     *
829
     * @since 1.8.0
830
     *
831
     * @return void
832
     */
833
    public function module_order_screen()
834
    {
835
        ?>
836
        <div id="<?php echo esc_attr($this->order_page_slug); ?>"
837
             class="wrap <?php echo esc_attr($this->order_page_slug); ?>">
838
        <h2><?php _e('Order Modules', 'woothemes-sensei'); ?></h2><?php
839
840
        $html = '';
841
842 View Code Duplication
        if (isset($_POST['module-order']) && 0 < strlen($_POST['module-order'])) {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
843
            $ordered = $this->save_course_module_order(esc_attr($_POST['module-order']), esc_attr($_POST['course_id']));
844
845
            if ($ordered) {
846
                $html .= '<div class="updated fade">' . "\n";
847
                $html .= '<p>' . __('The module order has been saved for this course.', 'woothemes-sensei') . '</p>' . "\n";
848
                $html .= '</div>' . "\n";
849
            }
850
        }
851
852
        $courses = Sensei()->course->get_all_courses();
853
854
        $html .= '<form action="' . admin_url('edit.php') . '" method="get">' . "\n";
855
        $html .= '<input type="hidden" name="post_type" value="course" />' . "\n";
856
        $html .= '<input type="hidden" name="page" value="' . esc_attr($this->order_page_slug) . '" />' . "\n";
857
        $html .= '<select id="module-order-course" name="course_id">' . "\n";
858
        $html .= '<option value="">' . __('Select a course', 'woothemes-sensei') . '</option>' . "\n";
859
860
        foreach ($courses as $course) {
861
            if (has_term('', $this->taxonomy, $course->ID)) {
862
                $course_id = '';
863
                if (isset($_GET['course_id'])) {
864
                    $course_id = intval($_GET['course_id']);
865
                }
866
                $html .= '<option value="' . esc_attr(intval($course->ID)) . '" ' . selected($course->ID, $course_id, false) . '>' . get_the_title($course->ID) . '</option>' . "\n";
867
            }
868
        }
869
870
        $html .= '</select>' . "\n";
871
        $html .= '<input type="submit" class="button-primary module-order-select-course-submit" value="' . __('Select', 'woothemes-sensei') . '" />' . "\n";
872
        $html .= '</form>' . "\n";
873
874
        if (isset($_GET['course_id'])) {
875
            $course_id = intval($_GET['course_id']);
876
            if ($course_id > 0) {
877
                $modules = $this->get_course_modules($course_id);
878
                $modules = $this->append_teacher_name_to_module( $modules, array( 'module' ), array() );
879
                if ($modules) {
880
881
                    $order = $this->get_course_module_order($course_id);
882
883
                    $order_string='';
884
                    if ($order) {
885
                        $order_string = implode(',', $order);
886
                    }
887
888
                    $html .= '<form id="editgrouping" method="post" action="" class="validate">' . "\n";
889
                    $html .= '<ul class="sortable-module-list">' . "\n";
890
                    $count = 0;
891 View Code Duplication
                    foreach ($modules as $module) {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
892
                        $count++;
893
                        $class = $this->taxonomy;
894
                        if ($count == 1) {
895
                            $class .= ' first';
896
                        }
897
                        if ($count == count($module)) {
898
                            $class .= ' last';
899
                        }
900
                        if ($count % 2 != 0) {
901
                            $class .= ' alternate';
902
                        }
903
                        $html .= '<li class="' . esc_attr($class) . '"><span rel="' . esc_attr($module->term_id) . '" style="width: 100%;"> ' . $module->name . '</span></li>' . "\n";
904
                    }
905
                    $html .= '</ul>' . "\n";
906
907
                    $html .= '<input type="hidden" name="module-order" value="' . $order_string . '" />' . "\n";
908
                    $html .= '<input type="hidden" name="course_id" value="' . $course_id . '" />' . "\n";
909
                    $html .= '<input type="submit" class="button-primary" value="' . __('Save module order', 'woothemes-sensei') . '" />' . "\n";
910
                    $html .= '<a href="' . admin_url('post.php?post=' . $course_id . '&action=edit') . '" class="button-secondary">' . __('Edit course', 'woothemes-sensei') . '</a>' . "\n";
911
                }
912
            }
913
        }
914
915
        echo $html;
916
917
        ?></div><?php
918
    }
919
920
    /**
921
     * Add 'Module order' column to courses list table
922
     *
923
     * @since 1.8.0
924
     *
925
     * @param  array $columns Existing columns
926
     * @return array           Modifed columns
927
     */
928
    public function course_columns($columns = array())
929
    {
930
        $columns['module_order'] = __('Module order', 'woothemes-sensei');
931
        return $columns;
932
    }
933
934
    /**
935
     * Load content in 'Module order' column
936
     *
937
     * @since 1.8.0
938
     *
939
     * @param  string $column Current column name
940
     * @param  integer $course_id ID of course
941
     * @return void
942
     */
943
    public function course_column_content($column = '', $course_id = 0)
944
    {
945
        if ($column == 'module_order') {
946
            if (has_term('', $this->taxonomy, $course_id)) {
947
                echo '<a class="button-secondary" href="' . admin_url('edit.php?post_type=course&page=module-order&course_id=' . urlencode(intval($course_id))) . '">' . __('Order modules', 'woothemes-sensei') . '</a>';
948
            }
949
        }
950
    }
951
952
    /**
953
     * Save module order for course
954
     *
955
     * @since 1.8.0
956
     *
957
     * @param  string $order_string Comma-separated string of module IDs
958
     * @param  integer $course_id ID of course
959
     * @return boolean                 True on success, false on failure
960
     */
961
    private function save_course_module_order($order_string = '', $course_id = 0)
962
    {
963
        if ($order_string && $course_id) {
964
            $order = explode(',', $order_string);
965
            update_post_meta(intval($course_id), '_module_order', $order);
966
            return true;
967
        }
968
        return false;
969
    }
970
971
    /**
972
     * Get module order for course
973
     *
974
     * @since 1.8.0
975
     *
976
     * @param  integer $course_id ID of course
977
     * @return mixed              Module order on success, false if no module order has been saved
978
     */
979
    public function get_course_module_order($course_id = 0)
980
    {
981
        if ($course_id) {
982
            $order = get_post_meta(intval($course_id), '_module_order', true);
983
            return $order;
984
        }
985
        return false;
986
    }
987
988
    /**
989
     * Modify module taxonomy columns
990
     *
991
     * @since 1.8.0
992
     *
993
     * @param  array $columns Default columns
994
     * @return array          Modified columns
995
     */
996
    public function taxonomy_column_headings($columns)
997
    {
998
999
        unset($columns['posts']);
1000
1001
        $columns['lessons'] = __('Lessons', 'woothemes-sensei');
1002
1003
        return $columns;
1004
    }
1005
1006
    /**
1007
     * Manage content in custom module taxonomy columns
1008
     *
1009
     * @since 1.8.0
1010
     *
1011
     * @param  string $column_data Default data for column
1012
     * @param  string $column_name Name of current column
1013
     * @param  integer $term_id ID of current term
1014
     * @return string               Modified column data
1015
     */
1016
    public function taxonomy_column_content($column_data, $column_name, $term_id)
1017
    {
1018
1019
        $args = array(
1020
            'post_status' => 'publish',
1021
            'posts_per_page' => -1,
1022
            'tax_query' => array(
1023
                array(
1024
                    'taxonomy' => $this->taxonomy,
1025
                    'field' => 'id',
1026
                    'terms' => intval($term_id)
1027
                )
1028
            )
1029
        );
1030
1031
        $module = get_term($term_id, $this->taxonomy);
1032
1033
        switch ($column_name) {
1034
1035
            case 'lessons':
1036
                $args['post_type'] = 'lesson';
1037
                $lessons = get_posts($args);
1038
                $total_lessons = count($lessons);
1039
                $column_data = '<a href="' . admin_url('edit.php?module=' . urlencode($module->slug) . '&post_type=lesson') . '">' . intval($total_lessons) . '</a>';
1040
                break;
1041
        }
1042
1043
        return $column_data;
1044
    }
1045
1046
    /**
1047
     * Add 'Module' columns to Analysis Lesson Overview table
1048
     *
1049
     * @since 1.8.0
1050
     *
1051
     * @param  array $columns Default columns
1052
     * @return array          Modified columns
1053
     */
1054
    public function analysis_overview_column_title($columns)
1055
    {
1056
1057
        if ( isset( $_GET['view'] ) && 'lessons' == $_GET['view'] ) {
1058
            $new_columns = array();
1059
            if (is_array($columns) && 0 < count($columns)) {
1060
                foreach ($columns as $column => $title) {
1061
                    $new_columns[$column] = $title;
1062
                    if ($column == 'title') {
1063
                        $new_columns['lesson_module'] = __('Module', 'woothemes-sensei');
1064
                    }
1065
                }
1066
            }
1067
1068
            if (0 < count($new_columns)) {
1069
                return $new_columns;
1070
            }
1071
        }
1072
1073
        return $columns;
1074
    }
1075
1076
    /**
1077
     * Data for 'Module' column Analysis Lesson Overview table
1078
     *
1079
     * @since 1.8.0
1080
     *
1081
     * @param  array $columns Table column data
1082
     * @param  WP_Post $lesson
1083
     * @return array              Updated column data
1084
     */
1085 View Code Duplication
    public function analysis_overview_column_data($columns, $lesson )
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
1086
    {
1087
1088
        if ( isset( $_GET['view'] ) && 'lessons' == $_GET['view'] ) {
1089
            $lesson_module = '';
1090
            $lesson_module_list = wp_get_post_terms($lesson->ID, $this->taxonomy);
1091
            if (is_array($lesson_module_list) && count($lesson_module_list) > 0) {
1092
                foreach ($lesson_module_list as $single_module) {
1093
                    $lesson_module = '<a href="' . esc_url(admin_url('edit-tags.php?action=edit&taxonomy=' . urlencode($this->taxonomy) . '&tag_ID=' . urlencode($single_module->term_id))) . '">' . $single_module->name . '</a>';
1094
                    break;
1095
                }
1096
            }
1097
1098
            $columns['lesson_module'] = $lesson_module;
1099
        }
1100
1101
        return $columns;
1102
    }
1103
1104
    /**
1105
     * Add 'Module' columns to Analysis Course table
1106
     *
1107
     * @since 1.8.0
1108
     *
1109
     * @param  array $columns Default columns
1110
     * @return array          Modified columns
1111
     */
1112
    public function analysis_course_column_title($columns)
1113
    {
1114
        if ( isset( $_GET['view'] ) && 'lessons' == $_GET['view'] ) {
1115
            $columns['lesson_module'] = __('Module', 'woothemes-sensei');
1116
        }
1117
        return $columns;
1118
    }
1119
1120
    /**
1121
     * Data for 'Module' column in Analysis Course table
1122
     *
1123
     * @since 1.8.0
1124
     *
1125
     * @param  array $columns Table column data
1126
     * @param  WP_Post $lesson
1127
     * @return array              Updated columns data
1128
     */
1129 View Code Duplication
    public function analysis_course_column_data($columns, $lesson )
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
1130
    {
1131
1132
        if ( isset( $_GET['course_id'] ) ) {
1133
            $lesson_module = '';
1134
            $lesson_module_list = wp_get_post_terms($lesson->ID, $this->taxonomy);
1135
            if (is_array($lesson_module_list) && count($lesson_module_list) > 0) {
1136
                foreach ($lesson_module_list as $single_module) {
1137
                    $lesson_module = '<a href="' . esc_url(admin_url('edit-tags.php?action=edit&taxonomy=' . urlencode($this->taxonomy) . '&tag_ID=' . urlencode($single_module->term_id))) . '">' . $single_module->name . '</a>';
1138
                    break;
1139
                }
1140
            }
1141
1142
            $columns['lesson_module'] = $lesson_module;
1143
        }
1144
1145
        return $columns;
1146
    }
1147
1148
    /**
1149
     * Get module for lesson
1150
     *
1151
     * This function also checks if the module still
1152
     * exists on the course before returning it. Although
1153
     * the lesson has a module the same module must exist on the
1154
     * course for it to be valid.
1155
     *
1156
     * @since 1.8.0
1157
     *
1158
     * @param  integer $lesson_id ID of lesson
1159
     * @return object             Module taxonomy term object
1160
     */
1161
    public function get_lesson_module($lesson_id = 0)
1162
    {
1163
        $lesson_id = intval($lesson_id);
1164
        if ( ! ( intval( $lesson_id > 0) ) ) {
1165
            return false;
1166
        }
1167
1168
        // get taxonomy terms on this lesson
1169
        $modules = wp_get_post_terms($lesson_id, $this->taxonomy);
1170
1171
        //check if error returned
1172
        if(    empty( $modules )
1173
            || is_wp_error( $modules )
1174
            || isset( $modules['errors'] ) ){
1175
1176
            return false;
1177
1178
        }
1179
1180
       // get the last item in the array there should be only be 1 really.
1181
       // this method works for all php versions.
1182
       foreach( $modules as $module ){
1183
           break;
1184
       }
1185
1186
        if ( ! isset($module) || ! is_object($module) || is_wp_error($module)) {
1187
            return false;
1188
        }
1189
1190
        $module->url = get_term_link($module, $this->taxonomy);
1191
        $course_id = intval(get_post_meta(intval($lesson_id), '_lesson_course', true));
1192
        if (isset($course_id) && 0 < $course_id) {
1193
1194
            // the course should contain the same module taxonomy term for this to be valid
1195
            if( ! has_term( $module, $this->taxonomy, $course_id)){
1196
                return false;
1197
            }
1198
1199
            $module->url = esc_url(add_query_arg('course_id', intval($course_id), $module->url));
1200
        }
1201
        return $module;
1202
1203
    }
1204
1205
	/**
1206
	 * Get ordered array of all modules in course
1207
	 *
1208
	 * @since 1.8.0
1209
	 *
1210
	 * @param  integer $course_id ID of course
1211
	 * @return array              Ordered array of module taxonomy term objects
1212
	 */
1213
	public function get_course_modules($course_id = 0) {
1214
1215
		$course_id = intval($course_id);
1216
		if ( empty(  $course_id ) ) {
1217
			return array();
1218
		}
1219
1220
		// Get modules for course
1221
		$modules = wp_get_post_terms( $course_id, $this->taxonomy );
1222
1223
		// Get custom module order for course
1224
		$order = $this->get_course_module_order($course_id);
1225
1226
		if ( ! $order) {
1227
			return $modules;
1228
		}
1229
1230
		// Sort by custom order
1231
		$ordered_modules = array();
1232
		$unordered_modules = array();
1233
		foreach ( $modules as $module ) {
1234
			$order_key = array_search($module->term_id, $order);
1235
			if ($order_key !== false) {
1236
				$ordered_modules[$order_key] = $module;
1237
			} else {
1238
				$unordered_modules[] = $module;
1239
			}
1240
		}
1241
1242
		// Order modules correctly
1243
		ksort( $ordered_modules );
1244
1245
		// Append modules that have not yet been ordered
1246
		if ( count($unordered_modules) > 0 ) {
1247
			$ordered_modules = array_merge($ordered_modules, $unordered_modules);
1248
		}
1249
1250
		// remove order key but maintain order
1251
		$ordered_modules_with_keys_in_sequence = array();
1252
		foreach ( $ordered_modules as $key => $module ) {
1253
1254
			$ordered_modules_with_keys_in_sequence[] = $module;
1255
1256
		}
1257
1258
		return $ordered_modules_with_keys_in_sequence;
1259
1260
	}
1261
1262
    /**
1263
     * Load frontend CSS
1264
     *
1265
     * @since 1.8.0
1266
     *
1267
     * @return void
1268
     */
1269
    public function enqueue_styles() {
1270
1271
        wp_register_style($this->taxonomy . '-frontend', esc_url($this->assets_url) . 'css/modules-frontend.css', Sensei()->version );
1272
        wp_enqueue_style($this->taxonomy . '-frontend');
1273
1274
    }
1275
1276
    /**
1277
     * Load admin Javascript
1278
     *
1279
     * @since 1.8.0
1280
     *
1281
     * @return void
1282
     */
1283
    public function admin_enqueue_scripts( $hook ) {
1284
1285
        /**
1286
         * Filter the page hooks where modules admin script can be loaded on.
1287
         *
1288
         * @param array $white_listed_pages
1289
         */
1290
        $script_on_pages_white_list = apply_filters( 'sensei_module_admin_script_page_white_lists', array(
1291
            'edit-tags.php',
1292
            'course_page_module-order',
1293
            'post-new.php',
1294
            'post.php'
1295
        ) );
1296
1297
        if ( ! in_array( $hook, $script_on_pages_white_list ) ) {
1298
            return;
1299
        }
1300
1301
        $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
1302
1303
        wp_enqueue_script( 'sensei-chosen', Sensei()->plugin_url . 'assets/chosen/chosen.jquery' . $suffix . '.js', array( 'jquery' ), Sensei()->version , true);
1304
        wp_enqueue_script( 'sensei-chosen-ajax', Sensei()->plugin_url . 'assets/chosen/ajax-chosen.jquery' . $suffix . '.js', array( 'jquery', 'sensei-chosen' ), Sensei()->version , true );
1305
        wp_enqueue_script( $this->taxonomy . '-admin', esc_url( $this->assets_url ) . 'js/modules-admin' . $suffix . '.js', array( 'jquery', 'sensei-chosen', 'sensei-chosen-ajax', 'jquery-ui-sortable', 'sensei-core-select2' ), Sensei()->version, true );
1306
1307
        // localized module data
1308
        $localize_modulesAdmin = array(
1309
            'search_courses_nonce' => wp_create_nonce( 'search-courses' ),
1310
            'selectPlaceholder'    => __( 'Search for courses', 'woothemes-sensei' )
1311
        );
1312
1313
        wp_localize_script( $this->taxonomy . '-admin' ,'modulesAdmin', $localize_modulesAdmin );
1314
    }
1315
1316
    /**
1317
     * Load admin CSS
1318
     *
1319
     * @since 1.8.0
1320
     *
1321
     * @return void
1322
     */
1323
    public function admin_enqueue_styles() {
1324
1325
        wp_register_style($this->taxonomy . '-sortable', esc_url($this->assets_url) . 'css/modules-admin.css','',Sensei()->version );
1326
        wp_enqueue_style($this->taxonomy . '-sortable');
1327
1328
    }
1329
1330
    /**
1331
     * Show the title modules on the single course template.
1332
     *
1333
     * Function is hooked into sensei_single_course_modules_before.
1334
     *
1335
     * @since 1.8.0
1336
     * @return void
1337
     */
1338
    public function course_modules_title( ) {
1339
1340
       if( sensei_module_has_lessons() ){
1341
1342
            echo '<header><h2>' . __('Modules', 'woothemes-sensei') . '</h2></header>';
1343
1344
        }
1345
1346
    }
1347
1348
    /**
1349
     * Display the single course modules content this will only show
1350
     * if the course has modules.
1351
     *
1352
     * @since 1.8.0
1353
     * @return void
1354
     */
1355 View Code Duplication
    public function load_course_module_content_template(){
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
1356
1357
	    if ( ! is_singular( 'course' )  ) {
1358
		    return;
1359
	    }
1360
1361
        // load backwards compatible template name if it exists in the users theme
1362
        $located_template= locate_template( Sensei()->template_url . 'single-course/course-modules.php' );
1363
        if( $located_template ){
1364
1365
            Sensei_Templates::get_template( 'single-course/course-modules.php' );
1366
            return;
1367
1368
        }
1369
1370
        Sensei_Templates::get_template( 'single-course/modules.php' );
1371
1372
    } // end course_module_content
1373
1374
    /**
1375
     * Returns all lessons for the given module ID
1376
     *
1377
     * @since 1.8.0
1378
     *
1379
     * @param $course_id
1380
     * @param $term_id
1381
     * @return array $lessons
1382
     */
1383
    public function get_lessons( $course_id , $term_id ){
1384
1385
        $lesson_query = $this->get_lessons_query( $course_id, $term_id );
1386
1387
        if( isset( $lesson_query->posts ) ){
1388
1389
            return $lesson_query->posts;
1390
1391
        }else{
1392
1393
            return array();
1394
1395
        }
1396
1397
    } // end get lessons
1398
1399
    /**
1400
     * Returns all lessons for the given module ID
1401
     *
1402
     * @since 1.8.0
1403
     *
1404
     * @param $course_id
1405
     * @param $term_id
1406
     * @return WP_Query $lessons_query
1407
     */
1408
    public function get_lessons_query( $course_id , $term_id ){
1409
1410
        if( empty( $term_id ) || empty( $course_id ) ){
1411
1412
            return array();
1413
1414
        }
1415
1416
        $args = array(
1417
            'post_type' => 'lesson',
1418
            'post_status' => 'publish',
1419
            'posts_per_page' => -1,
1420
            'meta_query' => array(
1421
                array(
1422
                    'key' => '_lesson_course',
1423
                    'value' => intval($course_id),
1424
                    'compare' => '='
1425
                )
1426
            ),
1427
            'tax_query' => array(
1428
                array(
1429
                    'taxonomy' => 'module',
1430
                    'field' => 'id',
1431
                    'terms' => intval( $term_id )
1432
                )
1433
            ),
1434
            'orderby' => 'menu_order',
1435
            'order' => 'ASC',
1436
            'suppress_filters' => 0
1437
        );
1438
1439
        if (version_compare( Sensei()->version, '1.6.0', '>=')) {
1440
            $args['meta_key'] = '_order_module_' . intval( $term_id );
1441
            $args['orderby'] = 'meta_value_num date';
1442
        }
1443
1444
        $lessons_query = new WP_Query( $args );
1445
1446
        return $lessons_query;
1447
1448
    } // end get lessons
1449
1450
    /**
1451
     * Find the lesson in the given course that doesn't belong
1452
     * to any of the courses modules
1453
     *
1454
     *
1455
     * @param $course_id
1456
     *
1457
     * @return array $non_module_lessons
1458
     */
1459
    public function get_none_module_lessons( $course_id ){
1460
1461
        $non_module_lessons = array();
1462
1463
        //exit if there is no course id passed in
1464
        if( empty( $course_id ) || 'course' != get_post_type( $course_id ) ) {
1465
1466
            return $non_module_lessons;
1467
        }
1468
1469
        //save some time and check if we already have the saved
1470
        if( get_transient( 'sensei_'. $course_id .'_none_module_lessons') ){
1471
1472
            return get_transient( 'sensei_'. $course_id .'_none_module_lessons');
1473
1474
        }
1475
1476
        // create terms array which must be excluded from other arrays
1477
        $course_modules = $this->get_course_modules( $course_id );
1478
1479
        //exit if there are no module on this course
1480
        if( empty( $course_modules ) || ! is_array( $course_modules ) ){
1481
1482
            return  Sensei()->course->course_lessons( $course_id );
1483
1484
        }
1485
1486
        $terms = array();
1487
        foreach( $course_modules as $module ){
1488
1489
            array_push( $terms ,  $module->term_id );
1490
1491
        }
1492
1493
        $args = array(
1494
            'post_type' => 'lesson',
1495
            'post_status' => 'publish',
1496
            'posts_per_page' => -1,
1497
            'meta_query' => array(
1498
                array(
1499
                    'key' => '_lesson_course',
1500
                    'value' => intval( $course_id ),
1501
                    'compare' => '='
1502
                )
1503
            ),
1504
            'tax_query' => array(
1505
                array(
1506
                    'taxonomy' => 'module',
1507
                    'field' => 'id',
1508
                    'terms' =>  $terms,
1509
                    'operator' => 'NOT IN'
1510
                )
1511
            ),
1512
            'orderby' => 'menu_order',
1513
            'order' => 'ASC',
1514
            'suppress_filters' => 0
1515
        );
1516
1517
        $wp_lessons_query = new WP_Query( $args );
1518
1519
        if( isset( $wp_lessons_query->posts) && count( $wp_lessons_query->posts ) > 0  ){
1520
            $non_module_lessons = $wp_lessons_query->get_posts();
1521
            set_transient( 'sensei_'. $course_id .'_none_module_lessons', $non_module_lessons, 10 * DAY_IN_SECONDS );
1522
        }
1523
1524
        return $non_module_lessons;
1525
    } // end get_none_module_lessons
1526
1527
    /**
1528
     * Register the modules taxonomy
1529
     *
1530
     * @since 1.8.0
1531
     */
1532
    public function setup_modules_taxonomy(){
1533
1534
        $labels = array(
1535
            'name' => __('Modules', 'woothemes-sensei'),
1536
            'singular_name' => __('Module', 'woothemes-sensei'),
1537
            'search_items' => __('Search Modules', 'woothemes-sensei'),
1538
            'all_items' => __('All Modules', 'woothemes-sensei'),
1539
            'parent_item' => __('Parent Module', 'woothemes-sensei'),
1540
            'parent_item_colon' => __('Parent Module:', 'woothemes-sensei'),
1541
            'edit_item' => __('Edit Module', 'woothemes-sensei'),
1542
            'update_item' => __('Update Module', 'woothemes-sensei'),
1543
            'add_new_item' => __('Add New Module', 'woothemes-sensei'),
1544
            'new_item_name' => __('New Module Name', 'woothemes-sensei'),
1545
            'menu_name' => __('Modules', 'woothemes-sensei'),
1546
        );
1547
1548
        /**
1549
         * Filter to alter the Sensei Modules rewrite slug
1550
         *
1551
         * @since 1.8.0
1552
         * @param string default 'modules'
1553
         */
1554
        $modules_rewrite_slug = apply_filters('sensei_module_slug', 'modules');
1555
1556
        $args = array(
1557
            'public' => true,
1558
            'hierarchical' => true,
1559
            'show_admin_column' => true,
1560
            'capabilities' => array(
1561
                'manage_terms' => 'manage_categories',
1562
                'edit_terms'   => 'edit_courses',
1563
                'delete_terms' => 'manage_categories',
1564
                'assign_terms' => 'edit_courses'
1565
            ),
1566
            'show_in_nav_menus' => false,
1567
            'show_in_quick_edit' => false,
1568
            'show_ui' => true,
1569
            'rewrite' => array('slug' => $modules_rewrite_slug ),
1570
            'labels' => $labels
1571
        );
1572
1573
        register_taxonomy( 'module' , array('course', 'lesson'), $args);
1574
1575
    }// end setup_modules_taxonomy
1576
1577
    /**
1578
     * When the wants to edit the lesson modules redirect them to the course modules.
1579
     *
1580
     * This function is hooked into the admin_menu
1581
     *
1582
     * @since 1.8.0
1583
     * @return void
1584
     */
1585
    function redirect_to_lesson_module_taxonomy_to_course( ){
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...
1586
1587
        global $typenow , $taxnow;
1588
1589
        if( 'lesson'== $typenow && 'module'==$taxnow ){
1590
            wp_safe_redirect( esc_url_raw( 'edit-tags.php?taxonomy=module&post_type=course'  ) );
1591
        }
1592
1593
    }// end redirect to course taxonomy
1594
1595
    /**
1596
     * Completely remove the module menu item under lessons.
1597
     *
1598
     * This function is hooked into the admin_menu
1599
     *
1600
     * @since 1.8.0
1601
     * @return void
1602
     */
1603 View Code Duplication
    public function remove_lessons_menu_model_taxonomy(){
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
1604
        global $submenu;
1605
1606
        if( ! isset( $submenu['edit.php?post_type=lesson'] ) || !is_array( $submenu['edit.php?post_type=lesson'] ) ){
1607
            return; // exit
1608
        }
1609
1610
        $lesson_main_menu = $submenu['edit.php?post_type=lesson'];
1611
        foreach( $lesson_main_menu as $index => $sub_item ){
1612
1613
            if( 'edit-tags.php?taxonomy=module&amp;post_type=lesson' == $sub_item[2] ){
1614
                unset( $submenu['edit.php?post_type=lesson'][ $index ]);
1615
            }
1616
        }
1617
1618
    }// end remove lesson module tax
1619
1620
    /**
1621
     * Completely remove the second modules under courses
1622
     *
1623
     * This function is hooked into the admin_menu
1624
     *
1625
     * @since 1.8.0
1626
     * @return void
1627
     */
1628 View Code Duplication
    public function remove_courses_menu_model_taxonomy(){
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
1629
        global $submenu;
1630
1631
        if( ! isset( $submenu['edit.php?post_type=course'] ) || !is_array( $submenu['edit.php?post_type=course'] ) ){
1632
            return; // exit
1633
        }
1634
1635
        $course_main_menu = $submenu['edit.php?post_type=course'];
1636
        foreach( $course_main_menu as $index => $sub_item ){
1637
1638
            if( 'edit-tags.php?taxonomy=module&amp;post_type=course' == $sub_item[2] ){
1639
                unset( $submenu['edit.php?post_type=course'][ $index ]);
1640
            }
1641
        }
1642
1643
    }// end remove courses module tax
1644
1645
    /**
1646
     * Determine the author of a module term term by looking at
1647
     * the prefixed author id. This function will query the full term object.
1648
     * Will return the admin user author could not be determined.
1649
     *
1650
     * @since 1.8.0
1651
     *
1652
     * @param string $term_name
1653
     * @return array $owners { type WP_User }. Empty array if none if found.
1654
     */
1655
    public static function get_term_authors( $term_name ){
1656
1657
        $terms = get_terms( array( 'module') , array( 'name__like'=>$term_name, 'hide_empty' => false )  );
1658
1659
        $owners = array();
1660
        if( empty( $terms ) ){
1661
1662
            return $owners;
1663
1664
        }
1665
1666
        // setup the admin user
1667
1668
1669
        //if there are more handle them appropriately and get the ones we really need that matches the desired name exactly
1670
        foreach( $terms as $term){
1671
            if( $term->name == $term_name ){
1672
1673
                // look for the author in the slug
1674
                $owners[] = Sensei_Core_Modules::get_term_author( $term->slug  );
1675
1676
            }// end if term name
1677
1678
        } // end for each
1679
1680
        return $owners;
1681
1682
    }// end get_term_author
1683
1684
    /**
1685
     * Looks at a term slug and figures out
1686
     * which author created the slug. The author was
1687
     * appended when the user saved the module term in the course edit
1688
     * screen.
1689
     *
1690
     * @since 1.8.0
1691
     *
1692
     * @param $slug
1693
     * @return WP_User $author if no author is found or invalid term is passed the admin user will be returned.
1694
     */
1695
    public static function get_term_author( $slug='' ){
1696
1697
        $term_owner = get_user_by( 'email', get_bloginfo( 'admin_email' ) );
1698
1699
        if( empty( $slug ) ){
1700
1701
            return $term_owner;
1702
1703
        }
1704
1705
        // look for the author in the slug
1706
        $slug_parts = explode( '-', $slug );
1707
1708
        if( count( $slug_parts ) > 1 ){
1709
1710
            // get the user data
1711
            $possible_user_id = $slug_parts[0];
1712
            $author = get_userdata( $possible_user_id );
1713
1714
            // if the user doesnt exist for the first part of the slug
1715
            // then this slug was also created by admin
1716
            if( is_a( $author, 'WP_User' ) ){
1717
1718
                $term_owner =  $author;
1719
1720
            }
1721
        }
1722
1723
        return $term_owner;
1724
    }
1725
1726
    /**
1727
     * Display the Sensei modules taxonomy terms metabox
1728
     *
1729
     * @since 1.8.0
1730
     *
1731
     * @hooked into add_meta_box
1732
     *
1733
     * @param WP_Post $post Post object.
1734
     */
1735
    public function course_module_metabox( $post ) {
1736
1737
        $tax_name = 'module';
1738
        $taxonomy = get_taxonomy( 'module' );
1739
1740
        ?>
1741
        <div id="taxonomy-<?php echo $tax_name; ?>" class="categorydiv">
1742
            <ul id="<?php echo $tax_name; ?>-tabs" class="category-tabs">
1743
                <li class="tabs"><a href="#<?php echo $tax_name; ?>-all"><?php echo $taxonomy->labels->all_items; ?></a></li>
1744
                <li class="hide-if-no-js"><a href="#<?php echo $tax_name; ?>-pop"><?php _e( 'Most Used' ); ?></a></li>
1745
            </ul>
1746
1747
            <div id="<?php echo $tax_name; ?>-pop" class="tabs-panel" style="display: none;">
1748
                <ul id="<?php echo $tax_name; ?>checklist-pop" class="categorychecklist form-no-clear" >
1749
                    <?php $popular_ids = wp_popular_terms_checklist( $tax_name ); ?>
1750
                </ul>
1751
            </div>
1752
1753
            <div id="<?php echo $tax_name; ?>-all" class="tabs-panel">
1754
                <?php
1755
                $name = ( $tax_name == 'category' ) ? 'post_category' : 'tax_input[' . $tax_name . ']';
1756
                echo "<input type='hidden' name='{$name}[]' value='0' />"; // Allows for an empty term set to be sent. 0 is an invalid Term ID and will be ignored by empty() checks.
1757
                ?>
1758
                <ul id="<?php echo $tax_name; ?>checklist" data-wp-lists="list:<?php echo $tax_name; ?>" class="categorychecklist form-no-clear">
1759
                    <?php wp_terms_checklist( $post->ID, array( 'taxonomy'=>$tax_name , 'popular_cats' => $popular_ids ) ); ?>
1760
                </ul>
1761
            </div>
1762
            <?php if ( current_user_can( $taxonomy->cap->edit_terms ) ) : ?>
1763
                <div id="<?php echo $tax_name; ?>-adder" class="wp-hidden-children">
1764
                    <h4>
1765
                        <a id="sensei-<?php echo $tax_name; ?>-add-toggle" href="#<?php echo $tax_name; ?>-add" class="hide-if-no-js">
1766
                            <?php
1767
                            /* translators: %s: add new taxonomy label */
1768
                            printf( __( '+ %s' ), $taxonomy->labels->add_new_item );
1769
                            ?>
1770
                        </a>
1771
                    </h4>
1772
                    <p id="sensei-<?php echo $tax_name; ?>-add" class="category-add wp-hidden-child">
1773
                        <label class="screen-reader-text" for="new<?php echo $tax_name; ?>"><?php echo $taxonomy->labels->add_new_item; ?></label>
1774
                        <input type="text" name="new<?php echo $tax_name; ?>" id="new<?php echo $tax_name; ?>" class="form-required form-input-tip" value="<?php echo esc_attr( $taxonomy->labels->new_item_name ); ?>" aria-required="true"/>
1775
                        <a class="button" id="sensei-<?php echo $tax_name; ?>-add-submit" class="button category-add-submit"><?php echo esc_attr( $taxonomy->labels->add_new_item ); ?></a>
1776
                        <?php wp_nonce_field( '_ajax_nonce-add-' . $tax_name, 'add_module_nonce' ); ?>
1777
                        <span id="<?php echo $tax_name; ?>-ajax-response"></span>
1778
                    </p>
1779
                </div>
1780
            <?php endif; ?>
1781
        </div>
1782
    <?php
1783
1784
    } // end course_module_metabox
1785
1786
1787
    /**
1788
     * Submits a new module term prefixed with the
1789
     * the current author id.
1790
     *
1791
     * @since 1.8.0
1792
     */
1793
    public static function add_new_module_term( ) {
1794
1795
1796
        if( ! isset( $_POST[ 'security' ] ) || ! wp_verify_nonce( $_POST[ 'security' ], '_ajax_nonce-add-module'  ) ){
1797
            wp_send_json_error( array('error'=> 'wrong security nonce') );
1798
        }
1799
1800
        // get the term an create the new term storing infomration
1801
        $term_name = sanitize_text_field( $_POST['newTerm'] );
1802
1803
        if( current_user_can('manage_options' ) ) {
1804
1805
            $term_slug = str_ireplace(' ', '-', trim( $term_name ) );
1806
1807
        } else {
1808
1809
            $term_slug =  get_current_user_id() . '-' . str_ireplace(' ', '-', trim( $term_name ) );
1810
1811
        }
1812
1813
        $course_id = sanitize_text_field( $_POST['course_id'] );
1814
1815
        // save the term
1816
        $slug = wp_insert_term( $term_name,'module', array('slug'=> $term_slug)  );
1817
1818
        // send error for all errors except term exits
1819
        if( is_wp_error( $slug ) ){
1820
1821
            // prepare for possible term name and id to be passed down if term exists
1822
            $term_data = array();
1823
1824
            // if term exists also send back the term name and id
1825
            if( isset( $slug->errors['term_exists'] ) ){
1826
1827
                $term = get_term_by( 'slug', $term_slug, 'module');
1828
                $term_data['name'] = $term_name;
1829
                $term_data['id'] = $term->term_id;
1830
1831
                // set the object terms
1832
                wp_set_object_terms( $course_id, $term->term_id, 'module', true );
1833
            }
1834
1835
            wp_send_json_error(array( 'errors'=>$slug->errors , 'term'=> $term_data ) );
1836
1837
        }
1838
1839
        //make sure the new term is checked for this course
1840
1841
        wp_set_object_terms( $course_id, $slug['term_id'], 'module', true );
1842
1843
        // Handle request then generate response using WP_Ajax_Response
1844
        wp_send_json_success( array( 'termId' => $slug['term_id'], 'termName' => $term_name ) );
1845
1846
    }
1847
1848
    /**
1849
     * Limit the course module metabox
1850
     * term list to only those on courses belonging to current teacher.
1851
     *
1852
     * Hooked into 'get_terms'
1853
     *
1854
     * @since 1.8.0
1855
     */
1856
    public function filter_module_terms( $terms, $taxonomies, $args ){
1857
1858
        //dont limit for admins and other taxonomies. This should also only apply to admin
1859
        if( current_user_can( 'manage_options' ) || !in_array( 'module', $taxonomies ) || ! is_admin()  ){
1860
            return $terms;
1861
        }
1862
1863
        // avoid infinite call loop
1864
        remove_filter('get_terms', array( $this, 'filter_module_terms' ), 20, 3 );
1865
1866
        // in certain cases the array is passed in as reference to the parent term_id => parent_id
1867
        if( isset( $args['fields'] ) && 'id=>parent' == $args['fields'] ){
1868
            // change only scrub the terms ids form the array keys
1869
            $terms = array_keys( $terms );
1870
        }
1871
1872
        $teachers_terms =  $this->filter_terms_by_owner( $terms, get_current_user_id() );
1873
1874
        // add filter again as removed above
1875
        add_filter('get_terms', array( $this, 'filter_module_terms' ), 20, 3 );
1876
1877
        return $teachers_terms;
1878
    }// end filter_module_terms
1879
1880
    /**
1881
     * For the selected items on a course module only return those
1882
     * for the current user. This does not apply to admin and super admin users.
1883
     *
1884
     * hooked into get_object_terms
1885
     *
1886
     * @since 1.8.0
1887
     */
1888
    public function filter_course_selected_terms( $terms, $course_ids_array, $taxonomies ){
1889
1890
        //dont limit for admins and other taxonomies. This should also only apply to admin
1891 View Code Duplication
        if( current_user_can( 'manage_options' ) || ! is_admin() || empty( $terms )
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1892
            // only apply this to module only taxonomy queries so 1 taxonomy only:
1893
            ||  count( $taxonomies ) > 1 || !in_array( 'module', $taxonomies )  ){
1894
            return $terms;
1895
        }
1896
1897
        $term_objects = $this->filter_terms_by_owner( $terms, get_current_user_id() );
1898
1899
        // if term objects were passed in send back objects
1900
        // if term id were passed in send that back
1901
        if( is_object( $terms[0] ) ){
1902
            return $term_objects;
1903
        }
1904
1905
        $terms = array();
1906
        foreach( $term_objects as $term_object ){
1907
            $terms[] = $term_object->term_id;
1908
        }
1909
1910
        return $terms;
1911
1912
1913
    }// end filter_course_selected_terms
1914
1915
    /**
1916
     * Filter the given terms and only return the
1917
     * terms that belong to the given user id.
1918
     *
1919
     * @since 1.8.0
1920
     * @param $terms
1921
     * @param $user_id
1922
     * @return array
1923
     */
1924
    public function filter_terms_by_owner( $terms, $user_id ){
1925
1926
        $users_terms = array();
1927
1928
        foreach( $terms as $index => $term ){
1929
1930
            if( is_numeric( $term ) ){
1931
                // the term id was given, get the term object
1932
                $term = get_term( $term, 'module' );
1933
            }
1934
1935
            $author = Sensei_Core_Modules::get_term_author( $term->slug );
1936
1937
            if ( $user_id == $author->ID ) {
1938
                // add the term to the teachers terms
1939
                $users_terms[] = $term;
1940
            }
1941
1942
        }
1943
1944
        return $users_terms;
1945
1946
    } // end filter terms by owner
1947
1948
    /**
1949
     * Add the teacher name next to modules. Only works in Admin for Admin users.
1950
     * This will not add name to terms belonging to admin user.
1951
     *
1952
     * Hooked into 'get_terms'
1953
     *
1954
     * @since 1.8.0
1955
     */
1956
    public function append_teacher_name_to_module( $terms, $taxonomies, $args )
1957
    {
1958
1959
        // only for admin users ont he module taxonomy
1960 View Code Duplication
        if ( empty( $terms ) || !current_user_can('manage_options') || !in_array('module', $taxonomies) || !is_admin()) {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1961
            return $terms;
1962
        }
1963
1964
        // in certain cases the array is passed in as reference to the parent term_id => parent_id
1965
        // simply return this as wp doesn't need an array of stdObject Term
1966
        if (isset( $args['fields'] ) && 'id=>parent' == $args['fields']) {
1967
1968
            return $terms;
1969
1970
        }
1971
1972
        // loop through and update all terms adding the author name
1973
        foreach( $terms as $index => $term ){
1974
1975
            if( is_numeric( $term ) ){
1976
                // the term id was given, get the term object
1977
                $term = get_term( $term, 'module' );
1978
            }
1979
1980
            $author = Sensei_Core_Modules::get_term_author( $term->slug );
1981
1982
            if( ! user_can( $author, 'manage_options' ) && isset( $term->name ) ) {
1983
                $term->name = $term->name . ' (' . $author->display_name . ') ';
1984
            }
1985
1986
            // add the term to the teachers terms
1987
            $users_terms[] = $term;
1988
1989
        }
1990
1991
        return $users_terms;
1992
    }
1993
1994
    /**
1995
     * Remove modules metabox that come by default
1996
     * with the modules taxonomy. We are removing this as
1997
     * we have created our own custom meta box.
1998
     */
1999
    public static function remove_default_modules_box() {
2000
2001
        remove_meta_box('modulediv', 'course', 'side');
2002
2003
    }
2004
2005
    /**
2006
     * When a course is save make sure to reset the transient set
2007
     * for it when determining the none module lessons.
2008
     *
2009
     * @sine 1.9.0
2010
     * @param $post_id
2011
     */
2012
    public static function reset_none_modules_transient ( $post_id ){
2013
2014
        // this should only apply to course and lesson post types
2015
        if( in_array( get_post_type( $post_id ), array( 'course', 'lesson' ) ) ){
2016
2017
            $course_id = '';
2018
2019
            if( 'lesson' == get_post_type( $post_id ) ){
2020
2021
                $course_id = Sensei()->lesson->get_course_id( $post_id );
2022
2023
            }
2024
2025
2026
            if( !empty( $course_id ) ){
2027
2028
                delete_transient( 'sensei_'. $course_id .'_none_module_lessons' );
2029
2030
            }
2031
2032
        } // end if is a course or a lesson
2033
2034
    } // end reset_none_modules_transient
2035
2036
    /**
2037
     * This function calls the deprecated hook 'sensei_single_course_modules_content' to fire
2038
     *
2039
     * @since 1.9.0
2040
     * @deprecated since 1.9.0
2041
     *
2042
     */
2043
    public static function deprecate_sensei_single_course_modules_content(){
2044
2045
        sensei_do_deprecated_action( 'sensei_single_course_modules_content','1.9.0','sensei_single_course_modules_before or sensei_single_course_modules_after' );
2046
2047
    }
2048
2049
    /**
2050
     * Setup the single course module loop.
2051
     *
2052
     * Setup the global $sensei_modules_loop
2053
     *
2054
     * @since 1.9.0
2055
     */
2056
    public static function setup_single_course_module_loop(){
2057
2058
        global $sensei_modules_loop, $post;
2059
        $course_id = $post->ID;
2060
2061
        $modules = Sensei()->modules->get_course_modules( $course_id );
2062
2063
        //initial setup
2064
        $sensei_modules_loop['total'] = 0;
2065
        $sensei_modules_loop['modules'] = array();
2066
        $sensei_modules_loop['current'] = -1;
2067
2068
        // exit if this course doesn't have modules
2069
        if( !$modules || empty( $modules )  ){
0 ignored issues
show
Bug Best Practice introduced by
The expression $modules of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2070
            return;
2071
        }
2072
2073
2074
        $lessons_in_all_modules = array();
2075
        foreach( $modules as $term ){
2076
2077
            $lessons_in_this_module = Sensei()->modules->get_lessons( $course_id , $term->term_id);
2078
            $lessons_in_all_modules = array_merge(  $lessons_in_all_modules, $lessons_in_this_module  );
2079
2080
        }
2081
2082
2083
        //setup all of the modules loop variables
2084
        $sensei_modules_loop['total'] = count( $modules );
2085
        $sensei_modules_loop['modules'] = $modules;
2086
        $sensei_modules_loop['current'] = -1;
2087
        $sensei_modules_loop['course_id'] = $course_id;
2088
2089
    }// end setup_single_course_module_loop
2090
2091
    /**
2092
     * Tear down the course module loop.
2093
     *
2094
     * @since 1.9.0
2095
     *
2096
     */
2097
    public static function teardown_single_course_module_loop(){
2098
2099
        global $sensei_modules_loop, $wp_query, $post;
2100
2101
        //reset all of the modules loop variables
2102
        $sensei_modules_loop['total'] = 0;
2103
        $sensei_modules_loop['modules'] = array();
2104
        $sensei_modules_loop['current'] = -1;
2105
2106
        // set the current course to be the global post again
2107
        wp_reset_query();
2108
        $post = $wp_query->post;
2109
    }// end teardown_single_course_module_loop
2110
2111
} // end modules class
2112