Completed
Push — master ( eccc4a...c847f3 )
by Dwain
04:57
created

Sensei_WC::alter_can_user_view_lesson()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 37
Code Lines 13

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 37
rs 6.7272
cc 7
eloc 13
nc 4
nop 3
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
	    $woocommerce_hooks_file_path = Sensei()->plugin_path() . 'includes/hooks/woocommerce.php';
23
        require_once( $woocommerce_hooks_file_path );
24
25
    }
26
    /**
27
     * check if WooCommerce plugin is loaded and allowed by Sensei
28
     *
29
     * @since 1.9.0
30
     * @return bool
31
     */
32
    public static function is_woocommerce_active(){
33
34
        $is_woocommerce_enabled_in_settings = isset( Sensei()->settings->settings['woocommerce_enabled'] ) && Sensei()->settings->settings['woocommerce_enabled'];
35
        return self::is_woocommerce_present() && $is_woocommerce_enabled_in_settings;
36
37
    } // end is_woocommerce_active
38
39
    /**
40
     * Checks if the WooCommerce plugin is installed and activation.
41
     *
42
     * If you need to check if WooCommerce is activated use Sensei_Utils::is_woocommerce_active().
43
     * This function does nott check to see if the Sensei setting for WooCommerce is enabled.
44
     *
45
     * @since 1.9.0
46
     *
47
     * @return bool
48
     */
49
    public static function is_woocommerce_present(){
50
51
        $active_plugins = (array) get_option( 'active_plugins', array() );
52
53
        if ( is_multisite() ){
54
55
            $active_plugins = array_merge( $active_plugins, get_site_option( 'active_sitewide_plugins', array() ) );
56
57
        }
58
59
        $is_woocommerce_plugin_present_and_activated = in_array( 'woocommerce/woocommerce.php', $active_plugins ) || array_key_exists( 'woocommerce/woocommerce.php', $active_plugins );
60
61
        return class_exists( 'Woocommerce' ) || $is_woocommerce_plugin_present_and_activated;
62
63
    }// end is_woocommerce_present
64
65
    /**
66
     * Find the order active number (completed or processing ) for a given user on a course. It will return the latest order.
67
     *
68
     * If multiple exist we will return the latest order.
69
     *
70
     * @param $user_id
71
     * @param $course_id
72
     * @return array $user_course_orders
73
     */
74
    public static function get_learner_course_active_order_id( $user_id, $course_id ){
75
76
        $course_product_id = get_post_meta( $course_id, '_course_woocommerce_product', true );
77
78
        $orders_query = new WP_Query( array(
79
            'post_type'   => 'shop_order',
80
            'posts_per_page' => -1,
81
            'post_status' => array( 'wc-processing', 'wc-completed' ),
82
            'meta_key'=> '_customer_user',
83
            'meta_value'=> $user_id,
84
        ) );
85
86
        if( $orders_query->post_count == 0 ){
87
88
            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...
89
90
        }
91
92
        foreach( $orders_query->get_posts() as $order ){
93
94
            $order = new WC_Order( $order->ID );
95
            $items = $order->get_items();
96
97
            $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...
98
99
            foreach( $items as $item ){
100
101
                // if the product id on the order and the one given to this function
102
                // this order has been placed by the given user on the given course.
103
                $product = wc_get_product( $item['product_id'] );
104
105
                if ( is_object( $product ) && $product->is_type( 'variable' )) {
106
107
                    $item_product_id = $item['variation_id'];
108
109
                } else {
110
111
                    $item_product_id =  $item['product_id'];
112
113
                }
114
115
                if( $course_product_id == $item_product_id ){
116
117
                    return $order->id;
118
119
                }
120
121
122
            }//end for each order item
123
124
        } // end for each order
125
126
        // if we reach this place we found no order
127
        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...
128
129
    } // end get_learner_course_active_order_ids
130
131
    /**
132
     * Output WooCommerce specific course filters
133
     * Removing the paged argument
134
     *
135
     * @since 1.9.0
136
     * @param $filter_links
137
     * @return mixed
138
     */
139
    public static function add_course_archive_wc_filter_links( $filter_links ){
140
141
        $free_courses = self::get_free_courses();
142
        $paid_courses = self::get_paid_courses();
143
144
        if ( empty( $free_courses ) || empty( $paid_courses )  ){
145
            // do not show any WooCommerce filters if all courses are
146
            // free or if all courses are paid
147
            return $filter_links;
148
149
        }
150
151
        $filter_links[] = array(
152
            'id'=>'paid' ,
153
            'url'=> add_query_arg( array( 'course_filter'=>'paid'), Sensei_Course::get_courses_page_url() ),
154
            'title'=>__( 'Paid', 'woothemes-sensei' )
155
        );
156
157
        $filter_links[] = array(
158
            'id'=>'free',
159
            'url'=> add_query_arg( array( 'course_filter'=>'free'), Sensei_Course::get_courses_page_url() ),
160
            'title'=>__( 'Free', 'woothemes-sensei' )
161
        );
162
163
        return $filter_links;
164
165
    }// end add_course_archive_wc_filter_links
166
167
    /**
168
     * Apply the free filter the the course query
169
     * getting all course with no products or products with zero price
170
     *
171
     * hooked into pre_get_posts
172
     *
173
     * @since 1.9.0
174
     * @param WP_Query $query
175
     * @return WP_Query $query
176
     */
177
    public static function course_archive_wc_filter_free( $query ){
178
179
        if( isset( $_GET['course_filter'] ) && 'free' == $_GET['course_filter']
180
            && 'course' == $query->get( 'post_type') && $query->is_main_query()  ){
181
182
            // setup the course meta query
183
            $meta_query = self::get_free_courses_meta_query_args();
184
185
            // manipulate the query to return free courses
186
            $query->set('meta_query', $meta_query );
187
188
            // don't show any paid courses
189
            $courses = self::get_paid_courses();
190
            $ids = array();
191
            foreach( $courses as $course ){
192
                $ids[] = $course->ID;
193
            }
194
            $query->set( 'post__not_in', $ids );
195
196
        }// end if course_filter
197
198
        return $query;
199
200
    }// course_archive_wc_filter_free
201
202
    /**
203
     * Apply the paid filter to the course query on the courses page
204
     * will include all course with a product attached with a price
205
     * more than 0
206
     *
207
     * hooked into pre_get_posts
208
     *
209
     * @since 1.9.0
210
     * @param WP_Query $query
211
     * @return WP_Query $query
212
     */
213
    public static function course_archive_wc_filter_paid( $query ){
214
215
        if( isset( $_GET['course_filter'] ) && 'paid' == $_GET['course_filter']
216
            && 'course' == $query->get( 'post_type') && $query->is_main_query() ){
217
218
            // setup the course meta query
219
            $meta_query = self::get_paid_courses_meta_query_args();
220
221
            // manipulate the query to return free courses
222
            $query->set('meta_query', $meta_query );
223
224
        }
225
226
        return $query;
227
228
    }
229
230
    /**
231
     * Load the WooCommerce single product actions above
232
     * single courses if woocommerce is active allowing purchase
233
     * information and actions to be hooked from WooCommerce.
234
     */
235
    public static function do_single_course_wc_single_product_action(){
236
237
        /**
238
         * this hooks is documented within the WooCommerce plugin.
239
         */
240
        if ( Sensei_WC::is_woocommerce_active() ) {
241
242
            do_action( 'woocommerce_before_single_product' );
243
244
        } // End If Statement
245
246
    }// end do_single_course_wc_single_product_action
247
248
    /**
249
     * Hooking into the single lesson page to alter the
250
     * user access permissions based on if they have purchased the
251
     * course the lesson belongs to.
252
     *
253
     * This function will only return false or the passed in user_access value.
254
     * It doesn't return true in order to avoid altering other options.
255
     *
256
     * @since 1.9.0
257
     *
258
     * @param $can_user_view_lesson
259
     * @param $lesson_id
260
     * @param $user_id
261
     * @return bool
262
     */
263
    public static function alter_can_user_view_lesson ( $can_user_view_lesson, $lesson_id, $user_id  ){
264
265
	    // do not override access to admins
266
	    $course_id = Sensei()->lesson->get_course_id( $lesson_id );
267
	    if ( sensei_all_access() || Sensei_Utils::is_preview_lesson( $lesson_id )
268
	         || Sensei_Utils::user_started_course( $course_id, $user_id )  ){
0 ignored issues
show
Bug introduced by
It seems like $course_id defined by Sensei()->lesson->get_course_id($lesson_id) on line 266 can also be of type boolean; however, Sensei_Utils::user_started_course() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug Best Practice introduced by
The expression \Sensei_Utils::user_star...e($course_id, $user_id) of type false|string is loosely compared to true; 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...
269
270
			return true;
271
272
	    }
273
274
        // check if the course has a valid product attached to it
275
        // which the user should have purchased if they want to access
276
        // the current lesson
277
        $course_id = get_post_meta( $lesson_id , '_lesson_course', true);
278
        $wc_post_id = get_post_meta( $course_id, '_course_woocommerce_product', true );
279
        $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...
280
        if( isset ($product) && is_object($product) ){
281
282
            // valid product found
283
            $order_id = self::get_learner_course_active_order_id( $user_id, $course_id );
284
285
            // product has a successful order so this user may access the content
286
            // this function may only return false or the default
287
            // returning true may override other negatives which we don't want
288
            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...
289
290
                return false;
291
292
            }
293
294
        }
295
296
        // return the passed in value
297
        return $can_user_view_lesson;
298
299
    }
300
301
    /**
302
     * Add course link to order thank you and details pages.
303
     *
304
     * @since  1.4.5
305
     * @access public
306
     *
307
     * @return void
308
     */
309
    public static function course_link_from_order( ) {
310
311
        if( ! is_order_received_page() ){
312
            return;
313
        }
314
315
        $order_id = get_query_var( 'order-received' );
316
		$order = new WC_Order( $order_id );
317
318
		// exit early if not wc-completed or wc-processing
319
		if( 'wc-completed' != $order->post_status
320
            && 'wc-processing' != $order->post_status  ) {
321
            return;
322
        }
323
324
        $course_links = array(); // store the for links for courses purchased
325
		foreach ( $order->get_items() as $item ) {
326
327
            if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
328
329
                // If item has variation_id then its a variation of the product
330
                $item_id = $item['variation_id'];
331
332
            } else {
333
334
                //If not its real product set its id to item_id
335
                $item_id = $item['product_id'];
336
337
            } // End If Statement
338
339
            $user_id = get_post_meta( $order->id, '_customer_user', true );
340
341
            if( $user_id ) {
342
343
                // Get all courses for product
344
                $args = Sensei_Course::get_default_query_args();
345
                $args['meta_query'] = array( array(
346
                            'key' => '_course_woocommerce_product',
347
                            'value' => $item_id
348
                        ) );
349
                $args['orderby'] = 'menu_order date';
350
                $args['order'] = 'ASC';
351
352
                // loop through courses
353
                $courses = get_posts( $args );
354
                if( $courses && count( $courses ) > 0 ) {
355
356
                    foreach( $courses as $course ) {
357
358
                        $title = $course->post_title;
359
                        $permalink = get_permalink( $course->ID );
360
                        $course_links[] .= '<a href="' . esc_url( $permalink ) . '" >' . $title . '</a> ';
361
362
                    } // end for each
363
364
                    // close the message div
365
366
                }// end if $courses check
367
            }
368
        }// end loop through orders
369
370
        // add the courses to the WooCommerce notice
371
        if( ! empty( $course_links) ){
372
373
            $courses_html = _nx(
374
                'You have purchased the following course:',
375
                'You have purchased the following courses:',
376
                count( $course_links ),
377
                'Purchase thank you note on Checkout page. The course link(s) will be show', 'woothemes-sensei'
378
            );
379
380
            foreach( $course_links as $link ){
381
382
                $courses_html .= '<li>' . $link . '</li>';
383
384
            }
385
386
            $courses_html .= ' </ul>';
387
388
            wc_add_notice( $courses_html, 'success' );
389
        }
390
391
	} // end course_link_order_form
392
393
    /**
394
     * Show the message that a user should complete
395
     * their purchase if the course is in the cart
396
     *
397
     * This should be used within the course loop or single course page
398
     *
399
     * @since 1.9.0
400
     */
401
    public static function course_in_cart_message(){
402
403
        global $post;
404
405
        if( self::is_course_in_cart( $post->ID ) ){ ?>
406
407
            <div class="sensei-message info">
408
                <?php
409
410
                $cart_link =  '<a class="cart-complete" href="' . WC()->cart->get_checkout_url()
411
                              . '" title="' . __('complete purchase', 'woothemes-sensei') . '">'
412
                              . __('complete the purchase', 'woothemes-sensei') . '</a>';
413
414
                echo sprintf(  __('You have already added this Course to your cart. Please %1$s to access the course.', 'woothemes-sensei'), $cart_link );
415
416
                ?>
417
            </div>
418
        <?php }
419
420
    } // End sensei_woocommerce_in_cart_message()
421
422
    /**
423
     * Checks the cart to see if a course is in the cart.
424
     *
425
     * @param $course_id
426
     * @return bool
427
     */
428
    public static function is_course_in_cart( $course_id ){
429
430
        $wc_post_id = absint( get_post_meta( $course_id, '_course_woocommerce_product', true ) );
431
        $user_course_status_id = Sensei_Utils::user_started_course( $course_id , get_current_user_id() );
432
433
        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...
434
435
            if ( self::is_product_in_cart( $wc_post_id ) ) {
436
437
                return true;
438
439
            }
440
441
        }
442
443
        return false;
444
445
    }// is_course_in_cart
446
447
    /**
448
     * Check the cart to see if the product is in the cart
449
     *
450
     * @param $product_id
451
     * @return bool
452
     */
453
    public static function is_product_in_cart( $product_id ){
454
455
        if ( 0 < $product_id ) {
456
457
            $product = wc_get_product( $product_id );
458
459
            $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...
460
            if( isset( $product->variation_id ) && 0 < intval( $product->variation_id ) ) {
461
                $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...
462
            }
463
            foreach( WC()->cart->get_cart() as $cart_item_key => $values ) {
464
465
                $cart_product = $values['data'];
466
                if( $product_id == $cart_product->id ) {
467
468
                    return true;
469
470
                }
471
472
            }
473
        } // End If Statement
474
475
        return false;
476
477
    } // end is_product_in_car
478
479
    /**
480
     * Get all free WooCommerce products
481
     *
482
     * @since 1.9.0
483
     *
484
     * @return array $free_products{
485
     *  @type int $wp_post_id
486
     * }
487
     */
488
    public static function get_free_product_ids(){
489
490
        return  get_posts( array(
491
            'post_type' => 'product',
492
            'posts_per_page' => '1000',
493
            'fields' => 'ids',
494
            'meta_query'=> array(
495
                'relation' => 'OR',
496
                array(
497
                    'key'=> '_regular_price',
498
                    'value' => 0,
499
                ),
500
                array(
501
                    'key'=> '_sale_price',
502
                    'value' => 0,
503
                ),
504
            ),
505
        ));
506
507
    }// end get free product query
508
509
    /**
510
     * The metat query for courses that are free
511
     *
512
     * @since 1.9.0
513
     * @return array $wp_meta_query_param
514
     */
515
    public static function get_free_courses_meta_query_args(){
516
517
        return array(
518
            'relation' => 'OR',
519
            array(
520
                'key'     => '_course_woocommerce_product',
521
                'value' => '-',
522
                'compare' => '=',
523
            ),
524
            array(
525
                'key'     => '_course_woocommerce_product',
526
                'value' => self::get_free_product_ids(),
527
                'compare' => 'IN',
528
            ),
529
        );
530
531
    }// get_free_courses_meta_query
532
533
    /**
534
     * The metat query for courses that are free
535
     *
536
     * @since 1.9.0
537
     * @return array $wp_query_meta_query_args_param
538
     */
539
    public static function get_paid_courses_meta_query_args(){
540
541
        $paid_product_ids = self::get_paid_product_ids();
542
543
        return array(
544
            array(
545
                'key'     => '_course_woocommerce_product',
546
                // when empty we give a false post_id to ensure the caller doesn't get any courses for their
547
                // query
548
                'value' => empty( $paid_product_ids  )? '-1000' : $paid_product_ids,
549
                'compare' => 'IN',
550
            ),
551
        );
552
553
    }// get_free_courses_meta_query
554
555
    /**
556
     * The WordPress Query args
557
     * for paid products on sale
558
     *
559
     * @since 1.9.0
560
     * @return array $product_query_args
561
     */
562 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...
563
564
        $args = array(
565
                   'post_type' 		=> 'product',
566
                   'posts_per_page' 		=> 1000,
567
                   'orderby'         	=> 'date',
568
                   'order'           	=> 'DESC',
569
                   'suppress_filters' 	=> 0
570
        );
571
572
        $args[ 'fields' ]     = 'ids';
573
574
        $args[ 'meta_query' ] = array(
575
            'relation' => 'AND',
576
            array(
577
                'key'=> '_regular_price',
578
                'compare' => '>',
579
                'value' => 0,
580
            ),
581
            array(
582
                'key'=> '_sale_price',
583
                'compare' => '>',
584
                'value' => 0,
585
            ),
586
        );
587
588
        return $args;
589
590
    } // get_paid_products_on_sale_query_args
591
592
593
    /**
594
     * Return the WordPress query args for
595
     * products not on sale but that is not a free
596
     *
597
     * @since 1.9.0
598
     *
599
     * @return array
600
     */
601 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...
602
603
        $args = array(
604
            'post_type' 		=> 'product',
605
            'posts_per_page' 		=> 1000,
606
            'orderby'         	=> 'date',
607
            'order'           	=> 'DESC',
608
            'suppress_filters' 	=> 0
609
        );
610
611
        $args[ 'fields' ]     = 'ids';
612
        $args[ 'meta_query' ] = array(
613
            'relation' => 'AND',
614
            array(
615
                'key'=> '_regular_price',
616
                'compare' => '>',
617
                'value' => 0,
618
            ),
619
            array(
620
                'key'=> '_sale_price',
621
                'compare' => '=',
622
                'value' => '',
623
            ),
624
        );
625
626
        return $args;
627
628
629
    } // get_paid_courses_meta_query
630
631
    /**
632
     * Get all WooCommerce non-free product id's
633
     *
634
     * @since 1.9.0
635
     *
636
     * @return array $woocommerce_paid_product_ids
637
     */
638
    public static function get_paid_product_ids(){
639
640
        // get all the paid WooCommerce products that has regular
641
        // and sale price greater than 0
642
        // will be used later to check for course with the id as meta
643
        $paid_product_ids_with_sale =  get_posts( self::get_paid_products_on_sale_query_args() );
644
645
        // get all the paid WooCommerce products that has regular price
646
        // greater than 0 without a sale price
647
        // will be used later to check for course with the id as meta
648
        $paid_product_ids_without_sale = get_posts( self::get_paid_products_not_on_sale_query_args() );
649
650
        // combine products ID's with regular and sale price grater than zero and those without
651
        // sale but regular price greater than zero
652
        $woocommerce_paid_product_ids = array_merge( $paid_product_ids_with_sale, $paid_product_ids_without_sale );
653
654
        // if
655
        if( empty($woocommerce_paid_product_ids) ){
656
            return array( );
657
        }
658
        return $woocommerce_paid_product_ids;
659
660
    }
661
662
    /**
663
     * Get all free courses.
664
     *
665
     * This course that have a WC product attached
666
     * that has a price or sale price of zero and
667
     * other courses with no WooCommerce products
668
     * attached.
669
     *
670
     * @since 1.9.0
671
     *
672
     * @return array
673
     */
674
    public static function get_free_courses(){
675
676
        $free_course_query_args = Sensei_Course::get_default_query_args();
677
        $free_course_query_args[ 'meta_query' ] = self::get_free_courses_meta_query_args();
678
679
        // don't show any paid courses
680
        $courses = self::get_paid_courses();
681
        $ids = array();
682
        foreach( $courses as $course ){
683
            $ids[] = $course->ID;
684
        }
685
        $free_course_query_args[ 'post__not_in' ] =  $ids;
686
687
        return get_posts( $free_course_query_args );
688
689
    }
690
691
    /**
692
     * Return all products that are not free
693
     *
694
     * @since 1.9.0
695
     * @return array
696
     */
697
    public static function get_paid_courses(){
698
699
        $paid_course_query_args = Sensei_Course::get_default_query_args();
700
701
        $paid_course_query_args[ 'meta_query' ] = self::get_paid_courses_meta_query_args();
702
703
        return get_posts(  $paid_course_query_args );
704
    }
705
706
    /**
707
     * Show the WooCommerce add to cart button for the  current course
708
     *
709
     * The function will only show the button if
710
     * 1- the user can buy the course
711
     * 2- if they have completed their pre-requisite
712
     * 3- if the course has a valid product attached
713
     *
714
     * @since 1.9.0
715
     * @param int $course_id
716
     * @return string $html markup for the button or nothing if user not allowed to buy
717
     */
718
    public static function the_add_to_cart_button_html( $course_id ){
719
720
        if ( ! Sensei_Course::is_prerequisite_complete( $course_id )) {
721
            return '';
722
        }
723
724
        $wc_post_id = self::get_course_product_id( $course_id );
725
726
        // Check if customer purchased the product
727
        if ( self::has_customer_bought_product(  get_current_user_id(), $wc_post_id )
728
            || empty( $wc_post_id ) ) {
729
730
            return '';
731
732
        }
733
734
        // based on simple.php in WC templates/single-product/add-to-cart/
735
        // Get the product
736
        $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...
737
738
        // do not show the button for invalid products, non purchasable products, out
739
        // of stock product or if course is already in cart
740
        if ( ! isset ( $product )
741
            || ! is_object( $product )
742
            || ! $product->is_purchasable()
743
            || ! $product->is_in_stock()
744
            || self::is_course_in_cart( $wc_post_id ) ) {
745
746
            return '';
747
748
        }
749
750
        //
751
        // button  output:
752
        //
753
        ?>
754
755
        <form action="<?php echo esc_url( $product->add_to_cart_url() ); ?>"
756
              class="cart"
757
              method="post"
758
              enctype="multipart/form-data">
759
760
            <input type="hidden" name="product_id" value="<?php echo esc_attr( $product->id ); ?>" />
761
762
            <input type="hidden" name="quantity" value="1" />
763
764
            <?php if ( isset( $product->variation_id ) && 0 < intval( $product->variation_id ) ) { ?>
765
766
                <input type="hidden" name="variation_id" value="<?php echo $product->variation_id; ?>" />
767
                <?php if( isset( $product->variation_data ) && is_array( $product->variation_data ) && count( $product->variation_data ) > 0 ) { ?>
768
769
                    <?php foreach( $product->variation_data as $att => $val ) { ?>
770
771
                        <input type="hidden" name="<?php echo esc_attr( $att ); ?>" id="<?php echo esc_attr( str_replace( 'attribute_', '', $att ) ); ?>" value="<?php echo esc_attr( $val ); ?>" />
772
773
                    <?php } ?>
774
775
                <?php } ?>
776
777
            <?php } ?>
778
779
            <button type="submit" class="single_add_to_cart_button button alt">
780
                <?php echo $product->get_price_html(); ?> - <?php  _e('Purchase this Course', 'woothemes-sensei'); ?>
781
            </button>
782
783
        </form>
784
785
        <?php
786
    } // end the_add_to_cart_button_html
787
788
    /**
789
     * Alter the no permissions message on the single course page
790
     * Changes the message to a WooCommerce specific message.
791
     *
792
     * @since 1.9.0
793
     *
794
     * @param $message
795
     * @param $post_id
796
     *
797
     * @return string $message
798
     */
799
    public static function alter_no_permissions_message( $message, $post_id ){
800
801
        if( empty( $post_id ) || 'course'!=get_post_type( $post_id ) ){
802
            return  $message;
803
        }
804
805
        $product_id = self::get_course_product_id( $post_id );
806
807
        if( ! $product_id
808
            || ! self::has_customer_bought_product( get_current_user_id(),$product_id ) ){
809
810
            return $message;
811
812
        }
813
814
        ob_start();
815
        self::the_course_no_permissions_message( $post_id );
816
        $woocommerce_course_no_permissions_message = ob_get_clean();
817
818
        return $woocommerce_course_no_permissions_message ;
819
820
    }
821
    /**
822
     * Show the no permissions message when a user is logged in
823
     * and have not yet purchased the current course
824
     *
825
     * @since 1.9.0
826
     */
827
    public static function the_course_no_permissions_message( $course_id ){
828
829
        // login link
830
        $my_courses_page_id = intval( Sensei()->settings->settings[ 'my_course_page' ] );
831
	    $login_link =  '<a href="' . esc_url( get_permalink( $my_courses_page_id ) ) . '">' . __( 'log in', 'woothemes-sensei' ) . '</a>';
832
	    $wc_product_id =  self::get_course_product_id( $course_id );
833
834
	    if ( self::is_product_in_cart( $wc_product_id ) ) {
835
836
		    $cart_link = '<a href="' . wc_get_checkout_url() . '" title="' . __( 'Checkout','woocommerce' ) . '">' . __( 'checkout', 'woocommerce' ) . '</a>';
837
838
		    $message = sprintf( __( 'This course is already in your cart, please proceed to %1$s, to gain access.', 'woothemes-sensei' ), $cart_link );
839
		    ?>
840
		    <span class="add-to-cart-login">
841
		            <?php echo $message; ?>
842
		        </span>
843
844
		    <?php
845
846
	    } elseif ( is_user_logged_in() ) {
847
848
		    ?>
849
		    <style>
850
			    .sensei-message.alert {
851
				    display: none;
852
			    }
853
		    </style>
854
855
		    <?php
856
857
	    } else {
858
859
		    $message = sprintf( __( 'Or %1$s to access your purchased courses', 'woothemes-sensei' ), $login_link );
860
	        ?>
861
		        <span class="add-to-cart-login">
862
		            <?php echo $message; ?>
863
		        </span>
864
865
	        <?php
866
	    }
867
    }
868
869
    /**
870
     * Checks if a user has bought a product item.
871
     *
872
     * @since  1.9.0
873
     *
874
     * @param  int $user_id
875
     * @param  int $product_id
876
     *
877
     * @return bool
878
     */
879
    public static function has_customer_bought_product ( $user_id, $product_id ){
880
881
        $orders = self::get_user_product_orders( $user_id, $product_id );
882
883
        foreach ( $orders as $order_id ) {
884
885
            $order = new WC_Order( $order_id->ID );
886
887
            // wc-active is the subscriptions complete status
888
            if ( ! in_array( $order->post_status, array( 'wc-complete','wc-processing' ) )
889
                || ! ( 0 < sizeof( $order->get_items() ) )  ){
890
891
                continue;
892
893
            }
894
895
            foreach( $order->get_items() as $item ) {
896
897
                // Check if user has bought product
898
                if ( $item['product_id'] == $product_id || $item['variation_id'] == $product_id ) {
899
900
                    // Check if user has an active subscription for product
901
                    if( class_exists( 'WC_Subscriptions_Manager' ) ) {
902
                        $sub_key = wcs_get_subscription( $order );
903
                        if( $sub_key ) {
904
                            $sub = wcs_get_subscription( $sub_key );
905
                            if( $sub && isset( $sub['status'] ) ) {
906
                                if( 'active' == $sub['status'] ) {
907
                                    return true;
908
                                } else {
909
                                    return false;
910
                                }
911
                            }
912
                        }
913
                    }
914
915
                    // Customer has bought product
916
                    return true;
917
                } // End If Statement
918
919
            } // End For each item
920
921
        } // End For each order
922
923
	    // default is no order
924
	    return false;
925
926
    } // end has customer bought product
927
928
    /**
929
     * Return the product id for the given course
930
     *
931
     * @since 1.9.0
932
     *
933
     * @param int $course_id
934
     *
935
     * @return string $woocommerce_product_id or false if none exist
936
     *
937
     */
938
    public static function get_course_product_id( $course_id ){
939
940
        $product_id =  get_post_meta( $course_id, '_course_woocommerce_product', true );
941
942
        if( empty( $product_id ) || 'product' != get_post_type( $product_id ) ){
943
            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...
944
        }
945
946
        return $product_id;
947
948
    }
949
950
    /**
951
     * Alter the body classes adding WooCommerce to the body
952
     *
953
     * Speciall cases where this is needed is template no-permissions.php
954
     *
955
     * @param array $classes
956
     * @return array
957
     */
958
    public static function add_woocommerce_body_class( $classes ){
959
960
        if( ! in_array( 'woocommerce', $classes ) && defined( 'SENSEI_NO_PERMISSION' ) && SENSEI_NO_PERMISSION ){
961
962
            $classes[] ='woocommerce';
963
964
        }
965
966
        return $classes;
967
968
    }
969
970
    /**
971
     * Responds to when a subscription product is purchased
972
     *
973
     * @since   1.2.0
974
     * @since  1.9.0 move to class Sensei_WC
975
     *
976
     * @param   WC_Order $order
977
     *
978
     * @return  void
979
     */
980
    public static function activate_subscription(  $order ) {
981
982
        $order_user = get_user_by('id', $order->user_id);
983
        $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...
984
        $user['user_login'] = $order_user->user_login;
985
        $user['user_email'] = $order_user->user_email;
986
        $user['user_url'] = $order_user->user_url;
987
988
        // Run through each product ordered
989
        if ( ! sizeof($order->get_items() )>0 ) {
990
991
            return;
992
993
        }
994
995
        foreach($order->get_items() as $item) {
996
997
            $product_type = '';
998
999
            if (isset($item['variation_id']) && $item['variation_id'] > 0) {
1000
1001
                $item_id = $item['variation_id'];
1002
                $product_type = 'subscription_variation';
1003
1004
            } else {
1005
1006
                $item_id = $item['product_id'];
1007
1008
            } // End If Statement
1009
1010
            $_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...
1011
1012
            // Get courses that use the WC product
1013
            $courses = array();
1014
1015
            if ( ! in_array( $product_type, self::get_subscription_types() ) ) {
1016
1017
                $courses = Sensei()->course->get_product_courses( $item_id );
1018
1019
            } // End If Statement
1020
1021
            // Loop and add the user to the course.
1022
            foreach ( $courses as $course_item ){
1023
1024
                Sensei_Utils::user_start_course( intval( $user['ID'] ), $course_item->ID  );
1025
1026
            } // End For Loop
1027
1028
        } // End For Loop
1029
1030
    } // End activate_subscription()
1031
1032
    /**
1033
     * Adds detail to to the WooCommerce order
1034
     *
1035
     * @since   1.4.5
1036
     * @since 1.9.0 function moved to class Sensei_WC and renamed from sensei_woocommerce_email_course_details to email_course_details
1037
     *
1038
     * @param   WC_Order $order
1039
     *
1040
     * @return  void
1041
     */
1042
    public static function email_course_details(  $order ){
1043
1044
        global $woocommerce;
1045
1046
        // exit early if not wc-completed or wc-processing
1047
        if( 'wc-completed' != $order->post_status
1048
            && 'wc-processing' != $order->post_status  ) {
1049
            return;
1050
        }
1051
1052
        $order_items = $order->get_items();
1053
        $order_id = $order->id;
1054
1055
        //If object have items go through them all to find course
1056
        if ( 0 < sizeof( $order_items ) ) {
1057
1058
            $course_details_html =  '<h2>' . __( 'Course details', 'woothemes-sensei' ) . '</h2>';
1059
            $order_contains_courses = false;
1060
1061
1062
            foreach ( $order_items as $item ) {
1063
1064
                $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...
1065
                if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
1066
                    // If item has variation_id then its from variation
1067
                    $item_id = $item['variation_id'];
1068
                    $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...
1069
                } else {
1070
                    // If not its real product set its id to item_id
1071
                    $item_id = $item['product_id'];
1072
                } // End If Statement
1073
1074
                $user_id = get_post_meta( $order_id, '_customer_user', true );
1075
1076
                if( $user_id ) {
1077
1078
                    // Get all courses for product
1079
                    $args = array(
1080
                        'posts_per_page' => -1,
1081
                        'post_type' => 'course',
1082
                        'meta_query' => array(
1083
                            array(
1084
                                'key' => '_course_woocommerce_product',
1085
                                'value' => $item_id
1086
                            )
1087
                        ),
1088
                        'orderby' => 'menu_order date',
1089
                        'order' => 'ASC',
1090
                    );
1091
                    $courses = get_posts( $args );
1092
1093
                    if( $courses && count( $courses ) > 0 ) {
1094
1095
                        foreach( $courses as $course ) {
1096
1097
                            $title = $course->post_title;
1098
                            $permalink = get_permalink( $course->ID );
1099
                            $order_contains_courses = true;
1100
                            $course_details_html .=  '<p><strong>' . sprintf( __( 'View course: %1$s', 'woothemes-sensei' ), '</strong><a href="' . esc_url( $permalink ) . '">' . $title . '</a>' ) . '</p>';
1101
                        }
1102
1103
1104
                    } // end if has courses
1105
1106
                } // end if $userPid
1107
1108
            } // end for each order item
1109
1110
            // Output Course details
1111
            if( $order_contains_courses ){
1112
1113
                echo $course_details_html;
1114
1115
            }
1116
1117
1118
        } // end if  order items not empty
1119
1120
    }// end email_course_details
1121
1122
    /**
1123
     * sensei_woocommerce_complete_order description
1124
     * @since   1.0.3
1125
     * @access  public
1126
     * @param   int $order_id WC order ID
1127
     * @return  void
1128
     */
1129
    public static function complete_order ( $order_id = 0 ) {
1130
1131
        $order_user = array();
1132
1133
        // Check for WooCommerce
1134
        if ( Sensei_WC::is_woocommerce_active() && ( 0 < $order_id ) ) {
1135
            // Get order object
1136
            $order = new WC_Order( $order_id );
1137
1138
	        if ( ! in_array( $order->get_status(), array( 'complete', 'processing' ) ) ) {
1139
1140
		        return;
1141
1142
	        }
1143
1144
            $user = get_user_by( 'id', $order->get_user_id() );
1145
            $order_user['ID'] = $user->ID;
1146
            $order_user['user_login'] = $user->user_login;
1147
            $order_user['user_email'] = $user->user_email;
1148
            $order_user['user_url'] = $user->user_url;
1149
            // Run through each product ordered
1150
            if ( 0 < sizeof( $order->get_items() ) ) {
1151
1152
                foreach( $order->get_items() as $item ) {
1153
1154
                    $product_type = '';
1155
                    if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
1156
1157
                        $item_id = $item['variation_id'];
1158
                        $product_type = 'variation';
1159
1160
                    } else {
1161
1162
                        $item_id = $item['product_id'];
1163
1164
                    } // End If Statement
1165
1166
                    $_product = Sensei_WC::get_product_object( $item_id, $product_type );
1167
1168
                    // Get courses that use the WC product
1169
                    $courses = Sensei()->course->get_product_courses( $_product->id );
1170
1171
                    // Loop and update those courses
1172
                    foreach ( $courses as $course_item ) {
1173
1174
                        $update_course = self::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...
1175
1176
                    } // End For Loop
1177
1178
                } // End For Loop
1179
1180
            } // End If Statement
1181
            // Add meta to indicate that payment has been completed successfully
1182
            update_post_meta( $order_id, 'sensei_payment_complete', '1' );
1183
1184
        } // End If Statement
1185
1186
    } // End sensei_woocommerce_complete_order()
1187
1188
    /**
1189
     * Responds to when an order is cancelled.
1190
     *
1191
     * @since   1.2.0
1192
     * @since   1.9.0 Move function to the Sensei_WC class
1193
     * @param   integer| WC_Order $order_id order ID
1194
     * @return  void
1195
     */
1196
    public static function cancel_order ( $order_id ) {
1197
1198
		// Get order object
1199
		if( is_object( $order_id ) ){
1200
1201
			$order = $order_id;
1202
1203
		}else{
1204
1205
			$order = new WC_Order( $order_id );
1206
		}
1207
1208
		if ( ! in_array( $order->get_status(), array( 'cancelled', 'refunded' ) ) ) {
1209
1210
			return;
1211
1212
		}
1213
1214
        // Run through each product ordered
1215
        if ( 0 < sizeof( $order->get_items() ) ) {
1216
1217
            // Get order user
1218
            $user_id = $order->__get( 'user_id' );
1219
1220
            foreach( $order->get_items() as $item ) {
1221
1222
                $product_type = '';
1223
                if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
1224
1225
                    $item_id = $item['variation_id'];
1226
                    $product_type = 'variation';
1227
1228
                } else {
1229
1230
                    $item_id = $item['product_id'];
1231
1232
                } // End If Statement
1233
1234
                $_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...
1235
1236
                // Get courses that use the WC product
1237
                $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...
1238
                $courses = Sensei()->course->get_product_courses( $item_id );
1239
1240
                // Loop and update those courses
1241
                foreach ($courses as $course_item){
1242
1243
	                if( self::has_customer_bought_product( $user_id, $course_item->ID ) ){
1244
		                continue;
1245
	                }
1246
                    // Check and Remove course from courses user meta
1247
                    $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...
1248
1249
                } // End For Loop
1250
1251
            } // End For Loop
1252
1253
        } // End If Statement
1254
1255
    } // End sensei_woocommerce_cancel_order()
1256
1257
    /**
1258
     * Returns the WooCommerce Product Object
1259
     *
1260
     * The code caters for pre and post WooCommerce 2.2 installations.
1261
     *
1262
     * @since   1.1.1
1263
     * @access  public
1264
     * @param   integer $wc_product_id Product ID or Variation ID
1265
     * @param   string  $product_type  '' or 'variation'
1266
     * @return   WC_Product $wc_product_object
1267
     */
1268
    public static function get_product_object ( $wc_product_id = 0, $product_type = '' ) {
1269
1270
        $wc_product_object = false;
1271
        if ( 0 < intval( $wc_product_id ) ) {
1272
1273
            // Get the product
1274
            if ( function_exists( 'wc_get_product' ) ) {
1275
1276
                $wc_product_object = wc_get_product( $wc_product_id ); // Post WC 2.3
1277
1278
            } elseif ( function_exists( 'get_product' ) ) {
1279
1280
                $wc_product_object = get_product( $wc_product_id ); // Post WC 2.0
1281
1282
            } else {
1283
1284
                // Pre WC 2.0
1285
                if ( 'variation' == $product_type || 'subscription_variation' == $product_type ) {
1286
1287
                    $wc_product_object = new WC_Product_Variation( $wc_product_id );
1288
1289
                } else {
1290
1291
                    $wc_product_object = new WC_Product( $wc_product_id );
1292
1293
                } // End If Statement
1294
1295
            } // End If Statement
1296
1297
        } // End If Statement
1298
1299
        return $wc_product_object;
1300
1301
    } // End sensei_get_woocommerce_product_object()
1302
1303
    /**
1304
     * If customer has purchased the course, update Sensei to indicate that they are taking the course.
1305
     *
1306
     * @since  1.0.0
1307
     * @since 1.9.0 move to class Sensei_WC
1308
     *
1309
     * @param  int 			$course_id  (default: 0)
1310
     * @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...
1311
     *
1312
     * @return bool|int
1313
     */
1314
    public static function course_update ( $course_id = 0, $order_user = array()  ) {
1315
1316
        global $current_user;
1317
1318
        if ( ! isset( $current_user ) || !$current_user->ID > 0 ) return false;
1319
1320
        $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...
1321
1322
        // Get the product ID
1323
        $wc_post_id = get_post_meta( intval( $course_id ), '_course_woocommerce_product', true );
1324
1325
        // Check if in the admin
1326
        if ( is_admin() ) {
1327
1328
            $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...
1329
            $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...
1330
            $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...
1331
            $user_id = $order_user['ID'];
1332
1333
        } else {
1334
1335
            $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...
1336
            $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...
1337
            $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...
1338
            $user_id = $current_user->ID;
1339
1340
        } // End If Statement
1341
1342
        // This doesn't appear to be purely WooCommerce related. Should it be in a separate function?
1343
        $course_prerequisite_id = (int) get_post_meta( $course_id, '_course_prerequisite', true );
1344
        if( 0 < absint( $course_prerequisite_id ) ) {
1345
1346
            $prereq_course_complete = Sensei_Utils::user_completed_course( $course_prerequisite_id, intval( $user_id ) );
1347
            if ( ! $prereq_course_complete ) {
1348
1349
                // Remove all course user meta
1350
                return Sensei_Utils::sensei_remove_user_from_course( $course_id, $user_id );
1351
1352
            }
1353
        }
1354
1355
        $is_user_taking_course = Sensei_Utils::user_started_course( intval( $course_id ), intval( $user_id ) );
1356
1357
        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...
1358
            && Sensei_WC::is_woocommerce_active()
1359
            && 0 < $wc_post_id
1360
            && Sensei_WC::has_customer_bought_product( $user_id, $wc_post_id ) ) {
1361
1362
	            $activity_logged = Sensei_Utils::user_start_course( intval( $user_id ), intval( $course_id ) );
1363
1364
	            if ( true == $activity_logged ) {
1365
1366
		            $is_user_taking_course = true;
1367
1368
	            } // End If Statement
1369
1370
        }// end if is user taking course
1371
1372
        return $is_user_taking_course;
1373
1374
    } // End course_update()
1375
1376
    /**
1377
     * Disable guest checkout if a course product is in the cart
1378
     *
1379
     * @since 1.1.0
1380
     * @since 1.9.0 move to class Sensei_WC
1381
     *
1382
     * @param  boolean $guest_checkout Current guest checkout setting
1383
     *
1384
     * @return boolean                 Modified guest checkout setting
1385
     */
1386
    public static function disable_guest_checkout( $guest_checkout ) {
1387
1388
        if( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
1389
1390
            if( isset( WC()->cart->cart_contents ) && count( WC()->cart->cart_contents ) > 0 ) {
1391
1392
                foreach( WC()->cart->cart_contents as $cart_key => $product ) {
1393
                    if( isset( $product['product_id'] ) ) {
1394
1395
                        $args = array(
1396
                            'posts_per_page' => -1,
1397
                            'post_type' => 'course',
1398
                            'meta_query' => array(
1399
                                array(
1400
                                    'key' => '_course_woocommerce_product',
1401
                                    'value' => $product['product_id']
1402
                                )
1403
                            )
1404
                        );
1405
1406
                        $posts = get_posts( $args );
1407
1408
                        if( $posts && count( $posts ) > 0 ) {
1409
1410
                            foreach( $posts as $course ) {
1411
                                $guest_checkout = '';
1412
                                break;
1413
1414
                            }
1415
                        }
1416
1417
                    }
1418
1419
                }
1420
1421
            }
1422
        }
1423
1424
        return $guest_checkout;
1425
1426
    }// end disable_guest_checkout
1427
1428
    /**
1429
     * Change order status with virtual products to completed
1430
     *
1431
     * @since  1.1.0
1432
     * @since 1.9.0 move to class Sensei_WC
1433
     *
1434
     * @param string $order_status
1435
     * @param int $order_id
1436
     *
1437
     * @return string
1438
     **/
1439
    public static function virtual_order_payment_complete( $order_status, $order_id ) {
1440
1441
        $order = new WC_Order( $order_id );
1442
1443
        if ( ! isset ( $order ) ) return '';
1444
1445
        if ( $order_status == 'wc-processing' && ( $order->post_status == 'wc-on-hold' || $order->post_status == 'wc-pending' || $order->post_status == 'wc-failed' ) ) {
1446
1447
            $virtual_order = true;
1448
1449
            if ( count( $order->get_items() ) > 0 ) {
1450
1451
                foreach( $order->get_items() as $item ) {
1452
1453
                    if ( $item['product_id'] > 0 ) {
1454
                        $_product = $order->get_product_from_item( $item );
1455
                        if ( ! $_product->is_virtual() ) {
1456
1457
                            $virtual_order = false;
1458
                            break;
1459
1460
                        } // End If Statement
1461
1462
                    } // End If Statement
1463
1464
                } // End For Loop
1465
1466
            } // End If Statement
1467
1468
            // virtual order, mark as completed
1469
            if ( $virtual_order ) {
1470
1471
                return 'completed';
1472
1473
            } // End If Statement
1474
1475
        } // End If Statement
1476
1477
        return $order_status;
1478
1479
    }// end virtual_order_payment_complete
1480
1481
1482
    /**
1483
     * Determine if the user has and active subscription to give them access
1484
     * to the requested resource.
1485
     *
1486
     * @since 1.9.0
1487
     *
1488
     * @param  boolean$user_access_permission
1489
     * @param  integer $user_id
1490
     * @return boolean $user_access_permission
1491
     */
1492
    public static function get_subscription_permission( $user_access_permission, $user_id ){
1493
1494
        global $post;
1495
1496
        // ignore the current case if the following conditions are met
1497
        if ( ! class_exists( 'WC_Subscriptions' ) || empty( $user_id )
1498
            || ! in_array( $post->post_type, array( 'course','lesson','quiz' ) )
1499
            || ! wcs_user_has_subscription( $user_id) ){
1500
1501
            return $user_access_permission;
1502
1503
        }
1504
1505
        // at this user has a subscription
1506
        // is the subscription on the the current course?
1507
1508
        $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...
1509
        if ( 'course' == $post->post_type ){
1510
1511
            $course_id = $post->ID;
1512
1513
        } elseif ( 'lesson' == $post->post_type ) {
1514
1515
            $course_id = Sensei()->lesson->get_course_id( $post->ID );
1516
1517
        } else {
1518
1519
            $lesson_id =  Sensei()->quiz->get_lesson_id( $post->ID );
1520
            $course_id = Sensei()->lesson->get_course_id( $lesson_id );
1521
1522
        }
1523
1524
        // if the course has no subscription WooCommerce product attached to return the permissions as is
1525
        $product_id = Sensei_WC::get_course_product_id( $course_id );
1526
        $product = wc_get_product( $product_id );
1527
        if( ! in_array( $product->get_type(), self::get_subscription_types() ) ){
1528
1529
            return $user_access_permission;
1530
1531
        }
1532
1533
        // give access if user has active subscription on the product otherwise restrict it.
1534
        // also check if the user was added to the course directly after the subscription started.
1535
        if( wcs_user_has_subscription( $user_id, $product_id, 'active'  )
1536
            || wcs_user_has_subscription( $user_id, $product_id, 'pending-cancel'  )
1537
            || self::was_user_added_without_subscription( $user_id, $product_id, $course_id  ) ){
1538
1539
            $user_access_permission = true;
1540
1541
        }else{
1542
1543
            $user_access_permission = false;
1544
            // do not show the WC permissions message
1545
            remove_filter( 'sensei_the_no_permissions_message', array( 'Sensei_WC', 'alter_no_permissions_message' ), 20, 2 );
1546
            Sensei()->permissions_message['title'] = __( 'No active subscription', 'woothemes-sensei' );
1547
            Sensei()->permissions_message['message'] = __( 'Sorry, you do not have an access to this content without an active subscription.', 'woothemes-sensei' );
1548
        }
1549
1550
        return $user_access_permission;
1551
1552
    } // end get_subscription_permission
1553
1554
    /**
1555
     * Get all the valid subscription types.
1556
     *
1557
     * @since 1.9.0
1558
     * @return array
1559
     */
1560
    public static function get_subscription_types(){
1561
1562
        return array( 'subscription','subscription_variation','variable-subscription' );
1563
1564
    }
1565
1566
    /**
1567
     * Compare the user's subscriptions end date with the date
1568
     * the user was added to the course. If the user was added after
1569
     * the subscription ended they were manually added and this will return
1570
     * true.
1571
     *
1572
     * Important to note that all subscriptions for the user is compared.
1573
     *
1574
     * @since 1.9.0
1575
     *
1576
     * @param $user_id
1577
     * @param $product_id
1578
     * @param $course_id
1579
     *
1580
     * @return bool
1581
     */
1582
    public static function was_user_added_without_subscription($user_id, $product_id, $course_id ){
1583
1584
        $course_start_date = '';
0 ignored issues
show
Unused Code introduced by
$course_start_date 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...
1585
        $subscription_start_date = '';
0 ignored issues
show
Unused Code introduced by
$subscription_start_date 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...
1586
        $is_a_subscription ='';
0 ignored issues
show
Unused Code introduced by
$is_a_subscription 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...
1587
        $was_user_added_without_subscription = true;
1588
1589
        // if user is not on the course they were not added
1590
        if( ! Sensei_Utils::user_started_course( $course_id, $user_id ) ){
0 ignored issues
show
Bug Best Practice introduced by
The expression \Sensei_Utils::user_star...e($course_id, $user_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...
1591
1592
            return false;
1593
1594
        }
1595
1596
        // if user doesn't have a subscription and is taking the course
1597
        // they were added manually
1598
        if ( ! wcs_user_has_subscription($user_id, $product_id)
1599
            && Sensei_Utils::user_started_course( $course_id, get_current_user_id() )  ){
0 ignored issues
show
Bug Best Practice introduced by
The expression \Sensei_Utils::user_star... get_current_user_id()) of type false|string is loosely compared to true; 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...
1600
1601
            return true;
1602
1603
        }
1604
1605
        $course_status =  Sensei_Utils::user_course_status( $course_id, $user_id );
1606
1607
        // comparing dates setup data
1608
        $course_start_date = date_create( $course_status->comment_date );
1609
        $subscriptions = wcs_get_users_subscriptions( $user_id );
1610
1611
        // comparing every subscription
1612
        foreach( $subscriptions as $subscription ){
1613
1614
            // for the following statuses we know the user was not added
1615
            // manually
1616
            $status = $subscription->get_status();
1617
            if ( in_array( $status, array( 'pending-canceled', 'active', 'on-hold', 'pending' ) ) ) {
1618
1619
                continue;
1620
1621
            }
1622
1623
            $current_subscription_start_date = date_create( $subscription->modified_date );
1624
1625
            // is the last updated subscription date newer than course start date
1626
            if (  $current_subscription_start_date > $course_start_date   ) {
1627
1628
                return false;
1629
1630
            }
1631
1632
        }
1633
1634
        return $was_user_added_without_subscription;
1635
    }
1636
1637
	/**
1638
	 * Get all the orders for a specific user and product combination
1639
	 *
1640
	 * @param int $user_id
1641
	 * @param $product_id
1642
	 *
1643
	 * @return array $orders
1644
	 */
1645
	public static function get_user_product_orders( $user_id =  0, $product_id ) {
0 ignored issues
show
Unused Code introduced by
The parameter $product_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...
1646
1647
		return get_posts( array(
1648
			'numberposts' => -1,
1649
			'post_type' => 'shop_order',
1650
			'meta_key'    => '_customer_user',
1651
			'meta_value'  => intval( $user_id ),
1652
			'post_status' => array( 'wc-complete','wc-processing' ),
1653
		) );
1654
1655
	}
1656
1657
}// end Sensei_WC
1658