Completed
Push — master ( c5ae6e...245c84 )
by Dwain
06:04
created

Sensei_WC::course_update()   C

Complexity

Conditions 11
Paths 19

Size

Total Lines 62
Code Lines 28

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 62
rs 6.1722
cc 11
eloc 28
nc 19
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
if ( ! defined( 'ABSPATH' ) ) exit; // security check, don't load file outside WP
3
4
/**
5
 * Sensei WooCommerce class
6
 *
7
 * All functions needed to integrate Sensei and WooCommerce
8
 *
9
 * @package Access-Management
10
 * @author Automattic
11
 * @since 1.9.0
12
 */
13
14
Class Sensei_WC{
15
    /**
16
     * Load the files needed for the woocommerce integration.
17
     *
18
     * @since 1.9.0
19
     */
20
    public static function load_woocommerce_integration_hooks(){
21
22
        require_once( __DIR__ . '/hooks/woocommerce.php' );
23
24
    }
25
    /**
26
     * check if WooCommerce plugin is loaded and allowed by Sensei
27
     *
28
     * @since 1.9.0
29
     * @return bool
30
     */
31
    public static function is_woocommerce_active(){
32
33
        $is_woocommerce_enabled_in_settings = isset( Sensei()->settings->settings['woocommerce_enabled'] ) && Sensei()->settings->settings['woocommerce_enabled'];
34
        return self::is_woocommerce_present() && $is_woocommerce_enabled_in_settings;
35
36
    } // end is_woocommerce_active
37
38
    /**
39
     * Checks if the WooCommerce plugin is installed and activation.
40
     *
41
     * If you need to check if WooCommerce is activated use Sensei_Utils::is_woocommerce_active().
42
     * This function does nott check to see if the Sensei setting for WooCommerce is enabled.
43
     *
44
     * @since 1.9.0
45
     *
46
     * @return bool
47
     */
48
    public static function is_woocommerce_present(){
49
50
        $active_plugins = (array) get_option( 'active_plugins', array() );
51
52
        if ( is_multisite() ){
53
54
            $active_plugins = array_merge( $active_plugins, get_site_option( 'active_sitewide_plugins', array() ) );
55
56
        }
57
58
        $is_woocommerce_plugin_present_and_activated = in_array( 'woocommerce/woocommerce.php', $active_plugins ) || array_key_exists( 'woocommerce/woocommerce.php', $active_plugins );
59
60
        return class_exists( 'Woocommerce' ) || $is_woocommerce_plugin_present_and_activated;
61
62
    }// end is_woocommerce_present
63
64
    /**
65
     * Find the order active number (completed or processing ) for a given user on a course. It will return the latest order.
66
     *
67
     * If multiple exist we will return the latest order.
68
     *
69
     * @param $user_id
70
     * @param $course_id
71
     * @return array $user_course_orders
72
     */
73
    public static function get_learner_course_active_order_id( $user_id, $course_id ){
74
75
        $course_product_id = get_post_meta( $course_id, '_course_woocommerce_product', true );
76
77
        $orders_query = new WP_Query( array(
78
            'post_type'   => 'shop_order',
79
            'posts_per_page' => -1,
80
            'post_status' => array( 'wc-processing', 'wc-completed' ),
81
            'meta_key'=> '_customer_user',
82
            'meta_value'=> $user_id,
83
        ) );
84
85
        if( $orders_query->post_count == 0 ){
86
87
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Sensei_WC::get_learner_course_active_order_id of type array.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
88
89
        }
90
91
        foreach( $orders_query->get_posts() as $order ){
92
93
            $order = new WC_Order( $order->ID );
94
            $items = $order->get_items();
95
96
            $user_orders =  array();
0 ignored issues
show
Unused Code introduced by
$user_orders 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...
97
98
            foreach( $items as $item ){
99
100
                // if the product id on the order and the one given to this function
101
                // this order has been placed by the given user on the given course.
102
                $product = wc_get_product( $item['product_id'] );
103
104
                if ( $product->is_type( 'variable' )) {
105
106
                    $item_product_id = $item['variation_id'];
107
108
                } else {
109
110
                    $item_product_id =  $item['product_id'];
111
112
                }
113
114
                if( $course_product_id == $item_product_id ){
115
116
                    return $order->id;
117
118
                }
119
120
121
            }//end for each order item
122
123
        } // end for each order
124
125
        // if we reach this place we found no order
126
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Sensei_WC::get_learner_course_active_order_id of type array.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
127
128
    } // end get_learner_course_active_order_ids
129
130
    /**
131
     * Output WooCommerce specific course filters
132
     * Removing the paged argument
133
     *
134
     * @since 1.9.0
135
     * @param $filter_links
136
     * @return mixed
137
     */
138
    public static function add_course_archive_wc_filter_links( $filter_links ){
139
140
        $free_courses = self::get_free_courses();
141
        $paid_courses = self::get_paid_courses();
142
143
        if ( empty( $free_courses ) || empty( $paid_courses )  ){
144
            // do not show any WooCommerce filters if all courses are
145
            // free or if all courses are paid
146
            return $filter_links;
147
148
        }
149
150
        $filter_links[] = array(
151
            'id'=>'paid' ,
152
            'url'=> add_query_arg( array( 'course_filter'=>'paid'), Sensei_Course::get_courses_page_url() ),
153
            'title'=>__( 'Paid', 'woothemes-sensei' )
154
        );
155
156
        $filter_links[] = array(
157
            'id'=>'free',
158
            'url'=> add_query_arg( array( 'course_filter'=>'free'), Sensei_Course::get_courses_page_url() ),
159
            'title'=>__( 'Free', 'woothemes-sensei' )
160
        );
161
162
        return $filter_links;
163
164
    }// end add_course_archive_wc_filter_links
165
166
    /**
167
     * Apply the free filter the the course query
168
     * getting all course with no products or products with zero price
169
     *
170
     * hooked into pre_get_posts
171
     *
172
     * @since 1.9.0
173
     * @param WP_Query $query
174
     * @return WP_Query $query
175
     */
176
    public static function course_archive_wc_filter_free( $query ){
177
178
        if( isset( $_GET['course_filter'] ) && 'free' == $_GET['course_filter']
179
            && 'course' == $query->get( 'post_type') && $query->is_main_query()  ){
180
181
            // setup the course meta query
182
            $meta_query = self::get_free_courses_meta_query_args();
183
184
            // manipulate the query to return free courses
185
            $query->set('meta_query', $meta_query );
186
187
            // don't show any paid courses
188
            $courses = self::get_paid_courses();
189
            $ids = array();
190
            foreach( $courses as $course ){
191
                $ids[] = $course->ID;
192
            }
193
            $query->set( 'post__not_in', $ids );
194
195
        }// end if course_filter
196
197
        return $query;
198
199
    }// course_archive_wc_filter_free
200
201
    /**
202
     * Apply the paid filter to the course query on the courses page
203
     * will include all course with a product attached with a price
204
     * more than 0
205
     *
206
     * hooked into pre_get_posts
207
     *
208
     * @since 1.9.0
209
     * @param WP_Query $query
210
     * @return WP_Query $query
211
     */
212
    public static function course_archive_wc_filter_paid( $query ){
213
214
        if( isset( $_GET['course_filter'] ) && 'paid' == $_GET['course_filter']
215
            && 'course' == $query->get( 'post_type') && $query->is_main_query() ){
216
217
            // setup the course meta query
218
            $meta_query = self::get_paid_courses_meta_query_args();
219
220
            // manipulate the query to return free courses
221
            $query->set('meta_query', $meta_query );
222
223
        }
224
225
        return $query;
226
227
    }
228
229
    /**
230
     * Load the WooCommerce single product actions above
231
     * single courses if woocommerce is active allowing purchase
232
     * information and actions to be hooked from WooCommerce.
233
     */
234
    public static function do_single_course_wc_single_product_action(){
235
236
        /**
237
         * this hooks is documented within the WooCommerce plugin.
238
         */
239
        if ( Sensei_WC::is_woocommerce_active() ) {
240
241
            do_action( 'woocommerce_before_single_product' );
242
243
        } // End If Statement
244
245
    }// end do_single_course_wc_single_product_action
246
247
    /**
248
     * Hooking into the single lesson page to alter the
249
     * user access permissions based on if they have purchased the
250
     * course the lesson belongs to.
251
     *
252
     * This function will only return false or the passed in user_access value.
253
     * It doesn't return true in order to avoid altering other options.
254
     *
255
     * @since 1.9.0
256
     *
257
     * @param $can_user_view_lesson
258
     * @param $lesson_id
259
     * @param $user_id
260
     * @return bool
261
     */
262
    public static function alter_can_user_view_lesson ( $can_user_view_lesson, $lesson_id, $user_id  ){
263
264
        // check if the course has a valid product attached to it
265
        // which the user should have purchased if they want to access
266
        // the current lesson
267
        $course_id = get_post_meta( $lesson_id , '_lesson_course', true);
268
        $wc_post_id = get_post_meta( $course_id, '_course_woocommerce_product', true );
269
        $product = Sensei()->sensei_get_woocommerce_product_object($wc_post_id);
0 ignored issues
show
Deprecated Code introduced by
The method Sensei_Main::sensei_get_...mmerce_product_object() has been deprecated with message: since 1.9.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
270
        if( isset ($product) && is_object($product) ){
271
272
            // valid product found
273
            $order_id = self::get_learner_course_active_order_id( $user_id, $course_id );
274
275
            // product has a successful order so this user may access the content
276
            // this function may only return false or the default
277
            // returning true may override other negatives which we don't want
278
            if( ! $order_id ){
0 ignored issues
show
Bug Best Practice introduced by
The expression $order_id 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...
279
280
                return false;
281
282
            }
283
284
        }
285
286
        // return the passed in value
287
        return $can_user_view_lesson;
288
289
    }
290
291
    /**
292
     * Add course link to order thank you and details pages.
293
     *
294
     * @since  1.4.5
295
     * @access public
296
     *
297
     * @return void
298
     */
299
    public static function course_link_from_order( ) {
300
301
        if( ! is_order_received_page() ){
302
            return;
303
        }
304
305
        $order_id = get_query_var( 'order-received' );
306
		$order = new WC_Order( $order_id );
307
308
		// exit early if not wc-completed or wc-processing
309
		if( 'wc-completed' != $order->post_status
310
            && 'wc-processing' != $order->post_status  ) {
311
            return;
312
        }
313
314
        $course_links = array(); // store the for links for courses purchased
315
		foreach ( $order->get_items() as $item ) {
316
317
            if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
318
319
                // If item has variation_id then its a variation of the product
320
                $item_id = $item['variation_id'];
321
322
            } else {
323
324
                //If not its real product set its id to item_id
325
                $item_id = $item['product_id'];
326
327
            } // End If Statement
328
329
            $user_id = get_post_meta( $order->id, '_customer_user', true );
330
331
            if( $user_id ) {
332
333
                // Get all courses for product
334
                $args = Sensei_Course::get_default_query_args();
335
                $args['meta_query'] = array( array(
336
                            'key' => '_course_woocommerce_product',
337
                            'value' => $item_id
338
                        ) );
339
                $args['orderby'] = 'menu_order date';
340
                $args['order'] = 'ASC';
341
342
                // loop through courses
343
                $courses = get_posts( $args );
344
                if( $courses && count( $courses ) > 0 ) {
345
346
                    foreach( $courses as $course ) {
347
348
                        $title = $course->post_title;
349
                        $permalink = get_permalink( $course->ID );
350
                        $course_links[] .= '<a href="' . esc_url( $permalink ) . '" >' . $title . '</a> ';
351
352
                    } // end for each
353
354
                    // close the message div
355
356
                }// end if $courses check
357
            }
358
        }// end loop through orders
359
360
        // add the courses to the WooCommerce notice
361
        if( ! empty( $course_links) ){
362
363
            $courses_html = _nx(
364
                'You have purchased the following course:',
365
                'You have purchased the following courses:',
366
                count( $course_links ),
367
                'Purchase thank you note on Checkout page. The course link(s) will be show', 'woothemes-sensei'
368
            );
369
370
            foreach( $course_links as $link ){
371
372
                $courses_html .= '<li>' . $link . '</li>';
373
374
            }
375
376
            $courses_html .= ' </ul>';
377
378
            wc_add_notice( $courses_html, 'success' );
379
        }
380
381
	} // end course_link_order_form
382
383
    /**
384
     * Show the message that a user should complete
385
     * their purchase if the course is in the cart
386
     *
387
     * This should be used within the course loop or single course page
388
     *
389
     * @since 1.9.0
390
     */
391
    public static function course_in_cart_message(){
392
393
        global $post;
394
395 View Code Duplication
        if( self::is_course_in_cart( $post->ID ) ){ ?>
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
396
397
            <div class="sensei-message info">
398
                <?php
399
400
                $cart_link =  '<a class="cart-complete" href="' . WC()->cart->get_checkout_url()
401
                              . '" title="' . __('complete purchase', 'woothemes-sensei') . '">'
402
                              . __('complete the purchase', 'woothemes-sensei') . '</a>';
403
404
                echo sprintf(  __('You have already added this Course to your cart. Please %1$s to access the course.', 'woothemes-sensei'), $cart_link );
405
406
                ?>
407
            </div>
408
        <?php }
409
410
    } // End sensei_woocommerce_in_cart_message()
411
412
    /**
413
     * Checks the cart to see if a course is in the cart.
414
     *
415
     * @param $course_id
416
     * @return bool
417
     */
418
    public static function is_course_in_cart( $course_id ){
419
420
        $wc_post_id = absint( get_post_meta( $course_id, '_course_woocommerce_product', true ) );
421
        $user_course_status_id = Sensei_Utils::user_started_course( $course_id , get_current_user_id() );
422
423
        if ( 0 < intval( $wc_post_id ) && ! $user_course_status_id ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $user_course_status_id of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
424
425
            if ( self::is_product_in_cart( $wc_post_id ) ) {
426
427
                return true;
428
429
            }
430
431
        }
432
433
        return false;
434
435
    }// is_course_in_cart
436
437
    /**
438
     * Check the cart to see if the product is in the cart
439
     *
440
     * @param $product_id
441
     * @return bool
442
     */
443
    public static function is_product_in_cart( $product_id ){
444
445
        if ( 0 < $product_id ) {
446
447
            $product = wc_get_product( $product_id );
448
449
            $parent_id = '';
0 ignored issues
show
Unused Code introduced by
$parent_id 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...
450
            if( isset( $product->variation_id ) && 0 < intval( $product->variation_id ) ) {
451
                $wc_product_id = $product->parent->id;
0 ignored issues
show
Unused Code introduced by
$wc_product_id 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...
452
            }
453
            foreach( WC()->cart->get_cart() as $cart_item_key => $values ) {
454
455
                $cart_product = $values['data'];
456
                if( $product_id == $cart_product->id ) {
457
458
                    return true;
459
460
                }
461
462
            }
463
        } // End If Statement
464
465
        return false;
466
467
    } // end is_product_in_car
468
469
    /**
470
     * Get all free WooCommerce products
471
     *
472
     * @since 1.9.0
473
     *
474
     * @return array $free_products{
475
     *  @type int $wp_post_id
476
     * }
477
     */
478
    public static function get_free_product_ids(){
479
480
        return  get_posts( array(
481
            'post_type' => 'product',
482
            'posts_per_page' => '1000',
483
            'fields' => 'ids',
484
            'meta_query'=> array(
485
                'relation' => 'OR',
486
                array(
487
                    'key'=> '_regular_price',
488
                    'value' => 0,
489
                ),
490
                array(
491
                    'key'=> '_sale_price',
492
                    'value' => 0,
493
                ),
494
            ),
495
        ));
496
497
    }// end get free product query
498
499
    /**
500
     * The metat query for courses that are free
501
     *
502
     * @since 1.9.0
503
     * @return array $wp_meta_query_param
504
     */
505
    public static function get_free_courses_meta_query_args(){
506
507
        return array(
508
            'relation' => 'OR',
509
            array(
510
                'key'     => '_course_woocommerce_product',
511
                'value' => '-',
512
                'compare' => '=',
513
            ),
514
            array(
515
                'key'     => '_course_woocommerce_product',
516
                'value' => self::get_free_product_ids(),
517
                'compare' => 'IN',
518
            ),
519
        );
520
521
    }// get_free_courses_meta_query
522
523
    /**
524
     * The metat query for courses that are free
525
     *
526
     * @since 1.9.0
527
     * @return array $wp_query_meta_query_args_param
528
     */
529
    public static function get_paid_courses_meta_query_args(){
530
531
        $paid_product_ids = self::get_paid_product_ids();
532
533
        return array(
534
            array(
535
                'key'     => '_course_woocommerce_product',
536
                // when empty we give a false post_id to ensure the caller doesn't get any courses for their
537
                // query
538
                'value' => empty( $paid_product_ids  )? '-1000' : $paid_product_ids,
539
                'compare' => 'IN',
540
            ),
541
        );
542
543
    }// get_free_courses_meta_query
544
545
    /**
546
     * The WordPress Query args
547
     * for paid products on sale
548
     *
549
     * @since 1.9.0
550
     * @return array $product_query_args
551
     */
552 View Code Duplication
    public static function get_paid_products_on_sale_query_args(){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
553
554
        $args = array(
555
                   'post_type' 		=> 'product',
556
                   'posts_per_page' 		=> 1000,
557
                   'orderby'         	=> 'date',
558
                   'order'           	=> 'DESC',
559
                   'suppress_filters' 	=> 0
560
        );
561
562
        $args[ 'fields' ]     = 'ids';
563
564
        $args[ 'meta_query' ] = array(
565
            'relation' => 'AND',
566
            array(
567
                'key'=> '_regular_price',
568
                'compare' => '>',
569
                'value' => 0,
570
            ),
571
            array(
572
                'key'=> '_sale_price',
573
                'compare' => '>',
574
                'value' => 0,
575
            ),
576
        );
577
578
        return $args;
579
580
    } // get_paid_products_on_sale_query_args
581
582
583
    /**
584
     * Return the WordPress query args for
585
     * products not on sale but that is not a free
586
     *
587
     * @since 1.9.0
588
     *
589
     * @return array
590
     */
591 View Code Duplication
    public static function get_paid_products_not_on_sale_query_args(){
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
592
593
        $args = array(
594
            'post_type' 		=> 'product',
595
            'posts_per_page' 		=> 1000,
596
            'orderby'         	=> 'date',
597
            'order'           	=> 'DESC',
598
            'suppress_filters' 	=> 0
599
        );
600
601
        $args[ 'fields' ]     = 'ids';
602
        $args[ 'meta_query' ] = array(
603
            'relation' => 'AND',
604
            array(
605
                'key'=> '_regular_price',
606
                'compare' => '>',
607
                'value' => 0,
608
            ),
609
            array(
610
                'key'=> '_sale_price',
611
                'compare' => '=',
612
                'value' => '',
613
            ),
614
        );
615
616
        return $args;
617
618
619
    } // get_paid_courses_meta_query
620
621
    /**
622
     * Get all WooCommerce non-free product id's
623
     *
624
     * @since 1.9.0
625
     *
626
     * @return array $woocommerce_paid_product_ids
627
     */
628
    public static function get_paid_product_ids(){
629
630
        // get all the paid WooCommerce products that has regular
631
        // and sale price greater than 0
632
        // will be used later to check for course with the id as meta
633
        $paid_product_ids_with_sale =  get_posts( self::get_paid_products_on_sale_query_args() );
634
635
        // get all the paid WooCommerce products that has regular price
636
        // greater than 0 without a sale price
637
        // will be used later to check for course with the id as meta
638
        $paid_product_ids_without_sale = get_posts( self::get_paid_products_not_on_sale_query_args() );
639
640
        // combine products ID's with regular and sale price grater than zero and those without
641
        // sale but regular price greater than zero
642
        $woocommerce_paid_product_ids = array_merge( $paid_product_ids_with_sale, $paid_product_ids_without_sale );
643
644
        // if
645
        if( empty($woocommerce_paid_product_ids) ){
646
            return array( );
647
        }
648
        return $woocommerce_paid_product_ids;
649
650
    }
651
652
    /**
653
     * Get all free courses.
654
     *
655
     * This course that have a WC product attached
656
     * that has a price or sale price of zero and
657
     * other courses with no WooCommerce products
658
     * attached.
659
     *
660
     * @since 1.9.0
661
     *
662
     * @return array
663
     */
664
    public static function get_free_courses(){
665
666
        $free_course_query_args = Sensei_Course::get_default_query_args();
667
        $free_course_query_args[ 'meta_query' ] = self::get_free_courses_meta_query_args();
668
669
        // don't show any paid courses
670
        $courses = self::get_paid_courses();
671
        $ids = array();
672
        foreach( $courses as $course ){
673
            $ids[] = $course->ID;
674
        }
675
        $free_course_query_args[ 'post__not_in' ] =  $ids;
676
677
        return get_posts( $free_course_query_args );
678
679
    }
680
681
    /**
682
     * Return all products that are not free
683
     *
684
     * @since 1.9.0
685
     * @return array
686
     */
687
    public static function get_paid_courses(){
688
689
        $paid_course_query_args = Sensei_Course::get_default_query_args();
690
691
        $paid_course_query_args[ 'meta_query' ] = self::get_paid_courses_meta_query_args();
692
693
        return get_posts(  $paid_course_query_args );
694
    }
695
696
    /**
697
     * Show the WooCommerce add to cart button for the  current course
698
     *
699
     * The function will only show the button if
700
     * 1- the user can buy the course
701
     * 2- if they have completed their pre-requisite
702
     * 3- if the course has a valid product attached
703
     *
704
     * @since 1.9.0
705
     * @param int $course_id
706
     * @return string $html markup for the button or nothing if user not allowed to buy
707
     */
708
    public static function the_add_to_cart_button_html( $course_id ){
709
710
        if ( ! Sensei_Course::is_prerequisite_complete( $course_id )) {
711
            return '';
712
        }
713
714
        $wc_post_id = self::get_course_product_id( $course_id );
715
716
        // Check if customer purchased the product
717
        if ( self::has_customer_bought_product(  get_current_user_id(), $wc_post_id )
718
            || empty( $wc_post_id ) ) {
719
720
            return '';
721
722
        }
723
724
        // based on simple.php in WC templates/single-product/add-to-cart/
725
        // Get the product
726
        $product = Sensei()->sensei_get_woocommerce_product_object( $wc_post_id );
0 ignored issues
show
Deprecated Code introduced by
The method Sensei_Main::sensei_get_...mmerce_product_object() has been deprecated with message: since 1.9.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
727
728
        // do not show the button for invalid products, non purchasable products, out
729
        // of stock product or if course is already in cart
730
        if ( ! isset ( $product )
731
            || ! is_object( $product )
732
            || ! $product->is_purchasable()
733
            || ! $product->is_in_stock()
734
            || self::is_course_in_cart( $wc_post_id ) ) {
735
736
            return '';
737
738
        }
739
740
        //
741
        // button  output:
742
        //
743
        ?>
744
745
        <form action="<?php echo esc_url( $product->add_to_cart_url() ); ?>"
746
              class="cart"
747
              method="post"
748
              enctype="multipart/form-data">
749
750
            <input type="hidden" name="product_id" value="<?php echo esc_attr( $product->id ); ?>" />
751
752
            <input type="hidden" name="quantity" value="1" />
753
754
            <?php if ( isset( $product->variation_id ) && 0 < intval( $product->variation_id ) ) { ?>
755
756
                <input type="hidden" name="variation_id" value="<?php echo $product->variation_id; ?>" />
757
                <?php if( isset( $product->variation_data ) && is_array( $product->variation_data ) && count( $product->variation_data ) > 0 ) { ?>
758
759
                    <?php foreach( $product->variation_data as $att => $val ) { ?>
760
761
                        <input type="hidden" name="<?php echo esc_attr( $att ); ?>" id="<?php echo esc_attr( str_replace( 'attribute_', '', $att ) ); ?>" value="<?php echo esc_attr( $val ); ?>" />
762
763
                    <?php } ?>
764
765
                <?php } ?>
766
767
            <?php } ?>
768
769
            <button type="submit" class="single_add_to_cart_button button alt">
770
                <?php echo $product->get_price_html(); ?> - <?php  _e('Purchase this Course', 'woothemes-sensei'); ?>
771
            </button>
772
773
        </form>
774
775
        <?php
776
    } // end the_add_to_cart_button_html
777
778
    /**
779
     * Alter the no permissions message on the single course page
780
     * Changes the message to a WooCommerce specific message.
781
     *
782
     * @since 1.9.0
783
     *
784
     * @param $message
785
     * @param $post_id
786
     *
787
     * @return string $message
788
     */
789
    public static function alter_no_permissions_message( $message, $post_id ){
790
791
        if( empty( $post_id ) || 'course'!=get_post_type( $post_id ) ){
792
            return  $message;
793
        }
794
795
        $product_id = self::get_course_product_id( $post_id );
796
797
        if( ! $product_id
798
            || self::has_customer_bought_product( get_current_user_id(),$product_id ) ){
799
800
            return $message;
801
802
        }
803
804
        ob_start();
805
        self::the_course_no_permissions_message( $post_id );
806
        $woocommerce_course_no_permissions_message = ob_get_clean();
807
808
        return $woocommerce_course_no_permissions_message ;
809
810
    }
811
    /**
812
     * Show the no permissions message when a user is logged in
813
     * and have not yet purchased the current course
814
     *
815
     * @since 1.9.0
816
     */
817
    public static function the_course_no_permissions_message( $course_id ){
0 ignored issues
show
Unused Code introduced by
The parameter $course_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...
818
819
        // login link
820
        $my_courses_page_id = intval( Sensei()->settings->settings[ 'my_course_page' ] );
821
        $login_link =  '<a href="' . esc_url( get_permalink( $my_courses_page_id ) ) . '">' . __( 'log in', 'woothemes-sensei' ) . '</a>';
822
823
        ?>
824
825
        <span class="add-to-cart-login">
826
            <?php echo sprintf( __( 'Or %1$s to access your purchased courses', 'woothemes-sensei' ), $login_link ); ?>
827
        </span>
828
829
    <?php }
830
831
    /**
832
     * Checks if a user has bought a product item.
833
     *
834
     * @since  1.9.0
835
     *
836
     * @param  int $user_id
837
     * @param  int $product_id
838
     *
839
     * @return bool
840
     */
841
    public static function has_customer_bought_product ( $user_id, $product_id ){
842
843
        $orders = get_posts( array(
844
            'numberposts' => -1,
845
            'post_type' => 'shope_order',
846
            'meta_key'    => '_customer_user',
847
            'meta_value'  => intval( $user_id ),
848
            'post_status' => array( 'wc-complete','wc-processing' ),
849
        ) );
850
851
        foreach ( $orders as $order_id ) {
852
853
            $order = new WC_Order( $order_id->ID );
854
855
            // wc-active is the subscriptions complete status
856
            if ( ! in_array( $order->post_status, array( 'wc-complete','wc-processing' ) ) ){
857
858
                continue;
859
860
            }
861
862
            if ( ! ( 0 < sizeof( $order->get_items() ) ) ) {
863
864
                continue;
865
866
            }
867
868
            foreach( $order->get_items() as $item ) {
869
870
                // Check if user has bought product
871
                if ( $item['product_id'] == $product_id || $item['variation_id'] == $product_id ) {
872
873
                    // Check if user has an active subscription for product
874
                    if( class_exists( 'WC_Subscriptions_Manager' ) ) {
875
                        $sub_key = wcs_get_subscription( $order );
876
                        if( $sub_key ) {
877
                            $sub = wcs_get_subscription( $sub_key );
878
                            if( $sub && isset( $sub['status'] ) ) {
879
                                if( 'active' == $sub['status'] ) {
880
                                    return true;
881
                                } else {
882
                                    return false;
883
                                }
884
                            }
885
                        }
886
                    }
887
888
                    // Customer has bought product
889
                    return true;
890
                } // End If Statement
891
892
            } // End For each item
893
894
        } // End For each order
895
896
    } // end has customer bought product
897
898
    /**
899
     * Return the product id for the given course
900
     *
901
     * @since 1.9.0
902
     *
903
     * @param int $course_id
904
     *
905
     * @return string $woocommerce_product_id or false if none exist
906
     *
907
     */
908
    public static function get_course_product_id( $course_id ){
909
910
        $product_id =  get_post_meta( $course_id, '_course_woocommerce_product', true );
911
912
        if( empty( $product_id ) || 'product' != get_post_type( $product_id ) ){
913
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Sensei_WC::get_course_product_id of type string.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
914
        }
915
916
        return $product_id;
917
918
    }
919
920
    /**
921
     * Alter the body classes adding WooCommerce to the body
922
     *
923
     * @param array $classes
924
     * @return array
925
     */
926
    public static function add_woocommerce_body_class( $classes ){
927
928
        if( ! in_array( 'woocommerce', $classes ) ){
929
930
            $classes[] ='woocommerce';
931
932
        }
933
934
        return $classes;
935
936
    }
937
938
    /**
939
     * Responds to when a subscription product is purchased
940
     *
941
     * @since   1.2.0
942
     * @since  1.9.0 move to class Sensei_WC
943
     *
944
     * @param   WC_Order $order
945
     *
946
     * @return  void
947
     */
948
    public static function activate_subscription(  $order ) {
949
950
        $order_user = get_user_by('id', $order->user_id);
951
        $user['ID'] = $order_user->ID;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$user was never initialized. Although not strictly required by PHP, it is generally a good practice to add $user = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
952
        $user['user_login'] = $order_user->user_login;
953
        $user['user_email'] = $order_user->user_email;
954
        $user['user_url'] = $order_user->user_url;
955
956
        // Run through each product ordered
957
        if ( ! sizeof($order->get_items() )>0 ) {
958
959
            return;
960
961
        }
962
963
        foreach($order->get_items() as $item) {
964
965
            $product_type = '';
966
967
            if (isset($item['variation_id']) && $item['variation_id'] > 0) {
968
969
                $item_id = $item['variation_id'];
970
                $product_type = 'subscription_variation';
971
972
            } else {
973
974
                $item_id = $item['product_id'];
975
976
            } // End If Statement
977
978
            $_product = self::get_product_object( $item_id, $product_type );
0 ignored issues
show
Unused Code introduced by
$_product 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...
979
980
            // Get courses that use the WC product
981
            $courses = array();
982
983
            if ( ! in_array( $product_type, self::get_subscription_types() ) ) {
984
985
                $courses = Sensei()->course->get_product_courses( $item_id );
986
987
            } // End If Statement
988
989
            // Loop and add the user to the course.
990
            foreach ( $courses as $course_item ){
991
992
                Sensei_Utils::user_start_course( intval( $user['ID'] ), $course_item->ID  );
993
994
            } // End For Loop
995
996
        } // End For Loop
997
998
    } // End activate_subscription()
999
1000
    /**
1001
     * Adds detail to to the WooCommerce order
1002
     *
1003
     * @since   1.4.5
1004
     * @since 1.9.0 function moved to class Sensei_WC and renamed from sensei_woocommerce_email_course_details to email_course_details
1005
     *
1006
     * @param   WC_Order $order
1007
     *
1008
     * @return  void
1009
     */
1010
    public static function email_course_details(  $order ){
1011
1012
        global $woocommerce;
1013
1014
        // exit early if not wc-completed or wc-processing
1015
        if( 'wc-completed' != $order->post_status
1016
            && 'wc-processing' != $order->post_status  ) {
1017
            return;
1018
        }
1019
1020
        $order_items = $order->get_items();
1021
        $order_id = $order->id;
1022
1023
        //If object have items go through them all to find course
1024
        if ( 0 < sizeof( $order_items ) ) {
1025
1026
            $course_details_html =  '<h2>' . __( 'Course details', 'woothemes-sensei' ) . '</h2>';
1027
            $order_contains_courses = false;
1028
1029
1030
            foreach ( $order_items as $item ) {
1031
1032
                $product_type = '';
0 ignored issues
show
Unused Code introduced by
$product_type 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...
1033
                if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
1034
                    // If item has variation_id then its from variation
1035
                    $item_id = $item['variation_id'];
1036
                    $product_type = 'variation';
0 ignored issues
show
Unused Code introduced by
$product_type 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...
1037
                } else {
1038
                    // If not its real product set its id to item_id
1039
                    $item_id = $item['product_id'];
1040
                } // End If Statement
1041
1042
                $user_id = get_post_meta( $order_id, '_customer_user', true );
1043
1044
                if( $user_id ) {
1045
1046
                    // Get all courses for product
1047
                    $args = array(
1048
                        'posts_per_page' => -1,
1049
                        'post_type' => 'course',
1050
                        'meta_query' => array(
1051
                            array(
1052
                                'key' => '_course_woocommerce_product',
1053
                                'value' => $item_id
1054
                            )
1055
                        ),
1056
                        'orderby' => 'menu_order date',
1057
                        'order' => 'ASC',
1058
                    );
1059
                    $courses = get_posts( $args );
1060
1061
                    if( $courses && count( $courses ) > 0 ) {
1062
1063
                        foreach( $courses as $course ) {
1064
1065
                            $title = $course->post_title;
1066
                            $permalink = get_permalink( $course->ID );
1067
                            $order_contains_courses = true;
1068
                            $course_details_html .=  '<p><strong>' . sprintf( __( 'View course: %1$s', 'woothemes-sensei' ), '</strong><a href="' . esc_url( $permalink ) . '">' . $title . '</a>' ) . '</p>';
1069
                        }
1070
1071
1072
                    } // end if has courses
1073
1074
                } // end if $userPid
1075
1076
            } // end for each order item
1077
1078
            // Output Course details
1079
            if( $order_contains_courses ){
1080
1081
                echo $course_details_html;
1082
1083
            }
1084
1085
1086
        } // end if  order items not empty
1087
1088
    }// end email_course_details
1089
1090
    /**
1091
     * sensei_woocommerce_complete_order description
1092
     * @since   1.0.3
1093
     * @access  public
1094
     * @param   int $order_id WC order ID
1095
     * @return  void
1096
     */
1097
    public static function complete_order ( $order_id = 0 ) {
1098
1099
        $order_user = array();
1100
1101
        // Check for WooCommerce
1102
        if ( Sensei_WC::is_woocommerce_active() && ( 0 < $order_id ) ) {
1103
            // Get order object
1104
            $order = new WC_Order( $order_id );
1105
            $user = get_user_by( 'id', $order->get_user_id() );
1106
            $order_user['ID'] = $user->ID;
1107
            $order_user['user_login'] = $user->user_login;
1108
            $order_user['user_email'] = $user->user_email;
1109
            $order_user['user_url'] = $user->user_url;
1110
            // Run through each product ordered
1111
            if ( 0 < sizeof( $order->get_items() ) ) {
1112
1113 View Code Duplication
                foreach( $order->get_items() as $item ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1114
1115
                    $product_type = '';
1116
                    if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
1117
1118
                        $item_id = $item['variation_id'];
1119
                        $product_type = 'variation';
1120
1121
                    } else {
1122
1123
                        $item_id = $item['product_id'];
1124
1125
                    } // End If Statement
1126
1127
                    $_product = Sensei_WC::get_product_object( $item_id, $product_type );
1128
1129
                    // Get courses that use the WC product
1130
                    $courses = Sensei()->course->get_product_courses( $_product->id );
1131
1132
                    // Loop and update those courses
1133
                    foreach ( $courses as $course_item ) {
1134
1135
                        $update_course = Sensei()->woocommerce_course_update( $course_item->ID, $order_user );
0 ignored issues
show
Unused Code introduced by
$update_course 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...
Deprecated Code introduced by
The method Sensei_Main::woocommerce_course_update() has been deprecated with message: since 1.9.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1136
1137
                    } // End For Loop
1138
1139
                } // End For Loop
1140
1141
            } // End If Statement
1142
            // Add meta to indicate that payment has been completed successfully
1143
            update_post_meta( $order_id, 'sensei_payment_complete', '1' );
1144
1145
        } // End If Statement
1146
1147
    } // End sensei_woocommerce_complete_order()
1148
1149
    /**
1150
     * Responds to when an order is cancelled.
1151
     *
1152
     * @since   1.2.0
1153
     * @since   1.9.0 Move function to the Sensei_WC class
1154
     * @param   integer| WC_Order $order_id order ID
1155
     * @return  void
1156
     */
1157
    public static function cancel_order ( $order_id ) {
1158
1159
        // Get order object
1160
        if( is_object( $order_id ) ){
1161
1162
            $order = $order_id;
1163
1164
        }else{
1165
1166
            $order = new WC_Order( $order_id );
1167
        }
1168
1169
        // Run through each product ordered
1170
        if ( 0 < sizeof( $order->get_items() ) ) {
1171
1172
            // Get order user
1173
            $user_id = $order->__get( 'user_id' );
1174
1175 View Code Duplication
            foreach( $order->get_items() as $item ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1176
1177
                $product_type = '';
1178
                if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
1179
1180
                    $item_id = $item['variation_id'];
1181
                    $product_type = 'variation';
1182
1183
                } else {
1184
1185
                    $item_id = $item['product_id'];
1186
1187
                } // End If Statement
1188
1189
                $_product = Sensei_WC::get_product_object( $item_id, $product_type );
0 ignored issues
show
Unused Code introduced by
$_product 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...
1190
1191
                // Get courses that use the WC product
1192
                $courses = array();
0 ignored issues
show
Unused Code introduced by
$courses 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...
1193
                $courses = Sensei()->course->get_product_courses( $item_id );
1194
1195
                // Loop and update those courses
1196
                foreach ($courses as $course_item){
1197
1198
                    // Check and Remove course from courses user meta
1199
                    $dataset_changes = Sensei_Utils::sensei_remove_user_from_course( $course_item->ID, $user_id );
0 ignored issues
show
Unused Code introduced by
$dataset_changes 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...
1200
1201
                } // End For Loop
1202
1203
            } // End For Loop
1204
1205
        } // End If Statement
1206
1207
    } // End sensei_woocommerce_cancel_order()
1208
1209
    /**
1210
     * Returns the WooCommerce Product Object
1211
     *
1212
     * The code caters for pre and post WooCommerce 2.2 installations.
1213
     *
1214
     * @since   1.1.1
1215
     * @access  public
1216
     * @param   integer $wc_product_id Product ID or Variation ID
1217
     * @param   string  $product_type  '' or 'variation'
1218
     * @return   WC_Product $wc_product_object
1219
     */
1220
    public static function get_product_object ( $wc_product_id = 0, $product_type = '' ) {
1221
1222
        $wc_product_object = false;
1223
        if ( 0 < intval( $wc_product_id ) ) {
1224
1225
            // Get the product
1226
            if ( function_exists( 'wc_get_product' ) ) {
1227
1228
                $wc_product_object = wc_get_product( $wc_product_id ); // Post WC 2.3
1229
1230
            } elseif ( function_exists( 'get_product' ) ) {
1231
1232
                $wc_product_object = get_product( $wc_product_id ); // Post WC 2.0
1233
1234
            } else {
1235
1236
                // Pre WC 2.0
1237
                if ( 'variation' == $product_type || 'subscription_variation' == $product_type ) {
1238
1239
                    $wc_product_object = new WC_Product_Variation( $wc_product_id );
1240
1241
                } else {
1242
1243
                    $wc_product_object = new WC_Product( $wc_product_id );
1244
1245
                } // End If Statement
1246
1247
            } // End If Statement
1248
1249
        } // End If Statement
1250
1251
        return $wc_product_object;
1252
1253
    } // End sensei_get_woocommerce_product_object()
1254
1255
    /**
1256
     * If customer has purchased the course, update Sensei to indicate that they are taking the course.
1257
     *
1258
     * @since  1.0.0
1259
     * @since 1.9.0 move to class Sensei_WC
1260
     *
1261
     * @param  int 			$course_id  (default: 0)
1262
     * @param  array/Object $order_user (default: array()) Specific user's data.
0 ignored issues
show
Documentation introduced by
The doc-type array/Object could not be parsed: Unknown type name "array/Object" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1263
     *
1264
     * @return bool|int
1265
     */
1266
    public static function course_update ( $course_id = 0, $order_user = array()  ) {
1267
1268
        global $current_user;
1269
1270
        if ( ! isset( $current_user ) || !$current_user->ID > 0 ) return false;
1271
1272
        $data_update = false;
0 ignored issues
show
Unused Code introduced by
$data_update 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...
1273
1274
        // Get the product ID
1275
        $wc_post_id = get_post_meta( intval( $course_id ), '_course_woocommerce_product', true );
1276
1277
        // Check if in the admin
1278
        if ( is_admin() ) {
1279
1280
            $user_login = $order_user['user_login'];
0 ignored issues
show
Unused Code introduced by
$user_login 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...
1281
            $user_email = $order_user['user_email'];
0 ignored issues
show
Unused Code introduced by
$user_email 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...
1282
            $user_url = $order_user['user_url'];
0 ignored issues
show
Unused Code introduced by
$user_url 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...
1283
            $user_id = $order_user['ID'];
1284
1285
        } else {
1286
1287
            $user_login = $current_user->user_login;
0 ignored issues
show
Unused Code introduced by
$user_login 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...
1288
            $user_email = $current_user->user_email;
0 ignored issues
show
Unused Code introduced by
$user_email 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...
1289
            $user_url = $current_user->user_url;
0 ignored issues
show
Unused Code introduced by
$user_url 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...
1290
            $user_id = $current_user->ID;
1291
1292
        } // End If Statement
1293
1294
        // This doesn't appear to be purely WooCommerce related. Should it be in a separate function?
1295
        $course_prerequisite_id = (int) get_post_meta( $course_id, '_course_prerequisite', true );
1296
        if( 0 < absint( $course_prerequisite_id ) ) {
1297
1298
            $prereq_course_complete = Sensei_Utils::user_completed_course( $course_prerequisite_id, intval( $user_id ) );
1299
            if ( ! $prereq_course_complete ) {
1300
1301
                // Remove all course user meta
1302
                return Sensei_Utils::sensei_remove_user_from_course( $course_id, $user_id );
1303
1304
            }
1305
        }
1306
1307
        $is_user_taking_course = Sensei_Utils::user_started_course( intval( $course_id ), intval( $user_id ) );
1308
1309
        if( ! $is_user_taking_course ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $is_user_taking_course of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1310
1311
            if ( Sensei_WC::is_woocommerce_active() && Sensei_WC::has_customer_bought_product( $user_id, $wc_post_id ) && ( 0 < $wc_post_id ) ) {
1312
1313
                $activity_logged = Sensei_Utils::user_start_course( intval( $user_id), intval( $course_id ) );
1314
1315
                $is_user_taking_course = false;
1316
                if ( true == $activity_logged ) {
1317
1318
                    $is_user_taking_course = true;
1319
1320
                } // End If Statement
1321
1322
            } // End If Statement
1323
        }// end if is user taking course
1324
1325
        return $is_user_taking_course;
1326
1327
    } // End course_update()
1328
1329
    /**
1330
     * Disable guest checkout if a course product is in the cart
1331
     *
1332
     * @since 1.1.0
1333
     * @since 1.9.0 move to class Sensei_WC
1334
     *
1335
     * @param  boolean $guest_checkout Current guest checkout setting
1336
     *
1337
     * @return boolean                 Modified guest checkout setting
1338
     */
1339
    public static function disable_guest_checkout( $guest_checkout ) {
1340
1341
        if( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
1342
1343
            if( isset( WC()->cart->cart_contents ) && count( WC()->cart->cart_contents ) > 0 ) {
1344
1345
                foreach( WC()->cart->cart_contents as $cart_key => $product ) {
1346
                    if( isset( $product['product_id'] ) ) {
1347
1348
                        $args = array(
1349
                            'posts_per_page' => -1,
1350
                            'post_type' => 'course',
1351
                            'meta_query' => array(
1352
                                array(
1353
                                    'key' => '_course_woocommerce_product',
1354
                                    'value' => $product['product_id']
1355
                                )
1356
                            )
1357
                        );
1358
1359
                        $posts = get_posts( $args );
1360
1361
                        if( $posts && count( $posts ) > 0 ) {
1362
1363
                            foreach( $posts as $course ) {
1364
                                $guest_checkout = '';
1365
                                break;
1366
1367
                            }
1368
                        }
1369
1370
                    }
1371
1372
                }
1373
1374
            }
1375
        }
1376
1377
        return $guest_checkout;
1378
1379
    }// end disable_guest_checkout
1380
1381
    /**
1382
     * Change order status with virtual products to completed
1383
     *
1384
     * @since  1.1.0
1385
     * @since 1.9.0 move to class Sensei_WC
1386
     *
1387
     * @param string $order_status
1388
     * @param int $order_id
1389
     *
1390
     * @return string
1391
     **/
1392
    public static function virtual_order_payment_complete( $order_status, $order_id ) {
1393
1394
        $order = new WC_Order( $order_id );
1395
1396
        if ( ! isset ( $order ) ) return '';
1397
1398
        if ( $order_status == 'wc-processing' && ( $order->post_status == 'wc-on-hold' || $order->post_status == 'wc-pending' || $order->post_status == 'wc-failed' ) ) {
1399
1400
            $virtual_order = true;
1401
1402
            if ( count( $order->get_items() ) > 0 ) {
1403
1404
                foreach( $order->get_items() as $item ) {
1405
1406
                    if ( $item['product_id'] > 0 ) {
1407
                        $_product = $order->get_product_from_item( $item );
1408
                        if ( ! $_product->is_virtual() ) {
1409
1410
                            $virtual_order = false;
1411
                            break;
1412
1413
                        } // End If Statement
1414
1415
                    } // End If Statement
1416
1417
                } // End For Loop
1418
1419
            } // End If Statement
1420
1421
            // virtual order, mark as completed
1422
            if ( $virtual_order ) {
1423
1424
                return 'completed';
1425
1426
            } // End If Statement
1427
1428
        } // End If Statement
1429
1430
        return $order_status;
1431
1432
    }// end virtual_order_payment_complete
1433
1434
1435
    /**
1436
     * Determine if the user has and active subscription to give them access
1437
     * to the requested resource.
1438
     *
1439
     * @since 1.9.0
1440
     *
1441
     * @param  boolean$user_access_permission
1442
     * @param  integer $user_id
1443
     * @return boolean $user_access_permission
1444
     */
1445
    public static function get_subscription_permission( $user_access_permission, $user_id ){
1446
1447
        global $post;
1448
1449
        // ignore the current case if the following conditions are met
1450
        if ( ! class_exists( 'WC_Subscriptions' ) || empty( $user_id )
1451
            || ! in_array( $post->post_type, array( 'course','lesson','quiz' ) )
1452
            || ! wcs_user_has_subscription( $user_id) ){
1453
1454
            return $user_access_permission;
1455
1456
        }
1457
1458
        // at this user has a subscription
1459
        // is the subscription on the the current course?
1460
1461
        $course_id = 0;
0 ignored issues
show
Unused Code introduced by
$course_id 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...
1462
        if ( 'course' == $post->post_type ){
1463
1464
            $course_id = $post->ID;
1465
1466
        } elseif ( 'lesson' == $post->post_type ) {
1467
1468
            $course_id = Sensei()->lesson->get_course_id( $post->ID );
1469
1470
        } else {
1471
1472
            $lesson_id =  Sensei()->quiz->get_lesson_id( $post->ID );
1473
            $course_id = Sensei()->lesson->get_course_id( $lesson_id );
1474
1475
        }
1476
1477
        // if the course has no subscription WooCommerce product attached to return the permissions as is
1478
        $product_id = Sensei_WC::get_course_product_id( $course_id );
1479
        $product = wc_get_product( $product_id );
1480
        if( ! in_array( $product->get_type(), self::get_subscription_types() ) ){
1481
1482
            return $user_access_permission;
1483
1484
        }
1485
1486
        // give access if user has active subscription on the product otherwise retrict it
1487
        if( wcs_user_has_subscription( $user_id, $product_id, 'active'  ) ){
1488
1489
            $user_access_permission = true;
1490
1491
        }else{
1492
1493
            $user_access_permission = false;
1494
            // do not show the WC permissions message
1495
            remove_filter( 'sensei_the_no_permissions_message', array( 'Sensei_WC', 'alter_no_permissions_message' ), 20, 2 );
1496
            Sensei()->permissions_message['title'] = __( 'No active subscription', 'woothemes-sensei' );
1497
            Sensei()->permissions_message['message'] = __( 'Sorry, you do not have an access to this content without an active subscription.', 'woothemes-sensei' );
1498
        }
1499
1500
        return $user_access_permission;
1501
1502
    } // end get_subscription_permission
1503
1504
    /**
1505
     * Get all the valid subscription types.
1506
     *
1507
     * @since 1.9.0
1508
     * @return array
1509
     */
1510
    public static function get_subscription_types(){
1511
1512
        return array( 'subscription','subscription_variation','variable-subscription' );
1513
1514
    }
1515
1516
}// end Sensei_WC
1517