Completed
Push — master ( 054e7e...95caf9 )
by Dwain
04:46
created

Sensei_WC::is_course_purchasable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 7
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
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 = self::get_product_object( $wc_post_id );
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 $button_text = $product->get_price_html() . ' - ' . __( 'Purchase this Course', 'woothemes-sensei' ); ?>
781
                <?php
782
                /**
783
                 * Filter Add to Cart button text
784
                 *
785
                 * @since 1.9.1
786
                 *
787
                 * @param string $button_text
788
                 */
789
                echo apply_filters( 'sensei_wc_single_add_to_cart_button_text', $button_text );
790
                ?>
791
            </button>
792
793
        </form>
794
795
        <?php
796
    } // end the_add_to_cart_button_html
797
798
    /**
799
     * Alter the no permissions message on the single course page
800
     * Changes the message to a WooCommerce specific message.
801
     *
802
     * @since 1.9.0
803
     *
804
     * @param $message
805
     * @param $post_id
806
     *
807
     * @return string $message
808
     */
809
    public static function alter_no_permissions_message( $message, $post_id ){
810
811
        if( empty( $post_id ) || 'course'!=get_post_type( $post_id ) ){
812
            return  $message;
813
        }
814
815
        $product_id = self::get_course_product_id( $post_id );
816
817
        if( ! $product_id
818
            || ! self::has_customer_bought_product( get_current_user_id(),$product_id ) ){
819
820
            return $message;
821
822
        }
823
824
        ob_start();
825
        self::the_course_no_permissions_message( $post_id );
826
        $woocommerce_course_no_permissions_message = ob_get_clean();
827
828
        return $woocommerce_course_no_permissions_message ;
829
830
    }
831
    /**
832
     * Show the no permissions message when a user is logged in
833
     * and have not yet purchased the current course
834
     *
835
     * @since 1.9.0
836
     */
837
    public static function the_course_no_permissions_message( $course_id ){
838
839
        // login link
840
        $my_courses_page_id = intval( Sensei()->settings->settings[ 'my_course_page' ] );
841
	    $login_link =  '<a href="' . esc_url( get_permalink( $my_courses_page_id ) ) . '">' . __( 'log in', 'woothemes-sensei' ) . '</a>';
842
	    $wc_product_id =  self::get_course_product_id( $course_id );
843
844
	    if ( self::is_product_in_cart( $wc_product_id ) ) {
845
846
		    $cart_link = '<a href="' . wc_get_checkout_url() . '" title="' . __( 'Checkout','woocommerce' ) . '">' . __( 'checkout', 'woocommerce' ) . '</a>';
847
848
		    $message = sprintf( __( 'This course is already in your cart, please proceed to %1$s, to gain access.', 'woothemes-sensei' ), $cart_link );
849
		    ?>
850
		    <span class="add-to-cart-login">
851
		            <?php echo $message; ?>
852
		        </span>
853
854
		    <?php
855
856
	    } elseif ( is_user_logged_in() ) {
857
858
		    ?>
859
		    <style>
860
			    .sensei-message.alert {
861
				    display: none;
862
			    }
863
		    </style>
864
865
		    <?php
866
867
	    } else {
868
869
		    $message = sprintf( __( 'Or %1$s to access your purchased courses', 'woothemes-sensei' ), $login_link );
870
	        ?>
871
		        <span class="add-to-cart-login">
872
		            <?php echo $message; ?>
873
		        </span>
874
875
	        <?php
876
	    }
877
    }
878
879
    /**
880
     * Checks if a user has bought a product item.
881
     *
882
     * @since  1.9.0
883
     *
884
     * @param  int $user_id
885
     * @param  int $product_id
886
     *
887
     * @return bool
888
     */
889
    public static function has_customer_bought_product ( $user_id, $product_id ){
890
891
        $orders = self::get_user_product_orders( $user_id, $product_id );
892
893
        foreach ( $orders as $order_id ) {
894
895
            $order = new WC_Order( $order_id->ID );
896
897
            // wc-active is the subscriptions complete status
898
            if ( ! in_array( $order->post_status, array( 'wc-processing', 'wc-completed' ) )
899
                || ! ( 0 < sizeof( $order->get_items() ) )  ){
900
901
                continue;
902
903
            }
904
905
            foreach( $order->get_items() as $item ) {
906
907
                // Check if user has bought product
908
                if ( $item['product_id'] == $product_id || $item['variation_id'] == $product_id ) {
909
910
                    // Check if user has an active subscription for product
911
                    if( class_exists( 'WC_Subscriptions_Manager' ) ) {
912
                        $sub_key = wcs_get_subscription( $order );
913
                        if( $sub_key ) {
914
                            $sub = wcs_get_subscription( $sub_key );
915
                            if( $sub && isset( $sub['status'] ) ) {
916
                                if( 'active' == $sub['status'] ) {
917
                                    return true;
918
                                } else {
919
                                    return false;
920
                                }
921
                            }
922
                        }
923
                    }
924
925
                    // Customer has bought product
926
                    return true;
927
                } // End If Statement
928
929
            } // End For each item
930
931
        } // End For each order
932
933
	    // default is no order
934
	    return false;
935
936
    } // end has customer bought product
937
938
    /**
939
     * Return the product id for the given course
940
     *
941
     * @since 1.9.0
942
     *
943
     * @param int $course_id
944
     *
945
     * @return string $woocommerce_product_id or false if none exist
946
     *
947
     */
948
    public static function get_course_product_id( $course_id ){
949
950
        $product_id =  get_post_meta( $course_id, '_course_woocommerce_product', true );
951
952
        if( empty( $product_id ) || 'product' != get_post_type( $product_id ) ){
953
            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...
954
        }
955
956
        return $product_id;
957
958
    }
959
960
    /**
961
     * Alter the body classes adding WooCommerce to the body
962
     *
963
     * Speciall cases where this is needed is template no-permissions.php
964
     *
965
     * @param array $classes
966
     * @return array
967
     */
968
    public static function add_woocommerce_body_class( $classes ){
969
970
        if( ! in_array( 'woocommerce', $classes ) && defined( 'SENSEI_NO_PERMISSION' ) && SENSEI_NO_PERMISSION ){
971
972
            $classes[] ='woocommerce';
973
974
        }
975
976
        return $classes;
977
978
    }
979
980
    /**
981
     * Responds to when a subscription product is purchased
982
     *
983
     * @since   1.2.0
984
     * @since  1.9.0 move to class Sensei_WC
985
     *
986
     * @param   WC_Order $order
987
     *
988
     * @return  void
989
     */
990
    public static function activate_subscription(  $order ) {
991
992
        $order_user = get_user_by('id', $order->user_id);
993
        $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...
994
        $user['user_login'] = $order_user->user_login;
995
        $user['user_email'] = $order_user->user_email;
996
        $user['user_url'] = $order_user->user_url;
997
998
        // Run through each product ordered
999
        if ( ! sizeof($order->get_items() )>0 ) {
1000
1001
            return;
1002
1003
        }
1004
1005
        foreach($order->get_items() as $item) {
1006
1007
            $product_type = '';
1008
1009
            if (isset($item['variation_id']) && $item['variation_id'] > 0) {
1010
1011
                $item_id = $item['variation_id'];
1012
                $product_type = 'subscription_variation';
1013
1014
            } else {
1015
1016
                $item_id = $item['product_id'];
1017
1018
            } // End If Statement
1019
1020
            $_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...
1021
1022
            // Get courses that use the WC product
1023
            $courses = array();
1024
1025
            if ( ! in_array( $product_type, self::get_subscription_types() ) ) {
1026
1027
                $courses = Sensei()->course->get_product_courses( $item_id );
1028
1029
            } // End If Statement
1030
1031
            // Loop and add the user to the course.
1032
            foreach ( $courses as $course_item ){
1033
1034
                Sensei_Utils::user_start_course( intval( $user['ID'] ), $course_item->ID  );
1035
1036
            } // End For Loop
1037
1038
        } // End For Loop
1039
1040
    } // End activate_subscription()
1041
1042
    /**
1043
     * Adds detail to to the WooCommerce order
1044
     *
1045
     * @since   1.4.5
1046
     * @since 1.9.0 function moved to class Sensei_WC and renamed from sensei_woocommerce_email_course_details to email_course_details
1047
     *
1048
     * @param   WC_Order $order
1049
     *
1050
     * @return  void
1051
     */
1052
    public static function email_course_details(  $order ){
1053
1054
        global $woocommerce;
1055
1056
        // exit early if not wc-completed or wc-processing
1057
        if( 'wc-completed' != $order->post_status
1058
            && 'wc-processing' != $order->post_status  ) {
1059
            return;
1060
        }
1061
1062
        $order_items = $order->get_items();
1063
        $order_id = $order->id;
1064
1065
        //If object have items go through them all to find course
1066
        if ( 0 < sizeof( $order_items ) ) {
1067
1068
            $course_details_html =  '<h2>' . __( 'Course details', 'woothemes-sensei' ) . '</h2>';
1069
            $order_contains_courses = false;
1070
1071
1072
            foreach ( $order_items as $item ) {
1073
1074
                $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...
1075
                if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
1076
                    // If item has variation_id then its from variation
1077
                    $item_id = $item['variation_id'];
1078
                    $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...
1079
                } else {
1080
                    // If not its real product set its id to item_id
1081
                    $item_id = $item['product_id'];
1082
                } // End If Statement
1083
1084
                $user_id = get_post_meta( $order_id, '_customer_user', true );
1085
1086
                if( $user_id ) {
1087
1088
                    // Get all courses for product
1089
                    $args = array(
1090
                        'posts_per_page' => -1,
1091
                        'post_type' => 'course',
1092
                        'meta_query' => array(
1093
                            array(
1094
                                'key' => '_course_woocommerce_product',
1095
                                'value' => $item_id
1096
                            )
1097
                        ),
1098
                        'orderby' => 'menu_order date',
1099
                        'order' => 'ASC',
1100
                    );
1101
                    $courses = get_posts( $args );
1102
1103
                    if( $courses && count( $courses ) > 0 ) {
1104
1105
                        foreach( $courses as $course ) {
1106
1107
                            $title = $course->post_title;
1108
                            $permalink = get_permalink( $course->ID );
1109
                            $order_contains_courses = true;
1110
                            $course_details_html .=  '<p><strong>' . sprintf( __( 'View course: %1$s', 'woothemes-sensei' ), '</strong><a href="' . esc_url( $permalink ) . '">' . $title . '</a>' ) . '</p>';
1111
                        }
1112
1113
1114
                    } // end if has courses
1115
1116
                } // end if $userPid
1117
1118
            } // end for each order item
1119
1120
            // Output Course details
1121
            if( $order_contains_courses ){
1122
1123
                echo $course_details_html;
1124
1125
            }
1126
1127
1128
        } // end if  order items not empty
1129
1130
    }// end email_course_details
1131
1132
    /**
1133
     * sensei_woocommerce_complete_order description
1134
     * @since   1.0.3
1135
     * @access  public
1136
     * @param   int $order_id WC order ID
1137
     * @return  void
1138
     */
1139
    public static function complete_order ( $order_id = 0 ) {
1140
1141
        $order_user = array();
1142
1143
        // Check for WooCommerce
1144
        if ( Sensei_WC::is_woocommerce_active() && ( 0 < $order_id ) ) {
1145
            // Get order object
1146
            $order = new WC_Order( $order_id );
1147
1148
	        if ( ! in_array( $order->get_status(), array( 'complete', 'processing' ) ) ) {
1149
1150
		        return;
1151
1152
	        }
1153
1154
            $user = get_user_by( 'id', $order->get_user_id() );
1155
            $order_user['ID'] = $user->ID;
1156
            $order_user['user_login'] = $user->user_login;
1157
            $order_user['user_email'] = $user->user_email;
1158
            $order_user['user_url'] = $user->user_url;
1159
            // Run through each product ordered
1160
            if ( 0 < sizeof( $order->get_items() ) ) {
1161
1162
                foreach( $order->get_items() as $item ) {
1163
1164
                    $product_type = '';
1165
                    if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
1166
1167
                        $item_id = $item['variation_id'];
1168
                        $product_type = 'variation';
1169
1170
                    } else {
1171
1172
                        $item_id = $item['product_id'];
1173
1174
                    } // End If Statement
1175
1176
                    $_product = Sensei_WC::get_product_object( $item_id, $product_type );
1177
1178
                    // Get courses that use the WC product
1179
                    $courses = Sensei()->course->get_product_courses( $_product->id );
1180
1181
                    // Loop and update those courses
1182
                    foreach ( $courses as $course_item ) {
1183
1184
                        $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...
1185
1186
                    } // End For Loop
1187
1188
                } // End For Loop
1189
1190
            } // End If Statement
1191
            // Add meta to indicate that payment has been completed successfully
1192
            update_post_meta( $order_id, 'sensei_payment_complete', '1' );
1193
1194
        } // End If Statement
1195
1196
    } // End sensei_woocommerce_complete_order()
1197
1198
    /**
1199
     * Responds to when an order is cancelled.
1200
     *
1201
     * @since   1.2.0
1202
     * @since   1.9.0 Move function to the Sensei_WC class
1203
     * @param   integer| WC_Order $order_id order ID
1204
     * @return  void
1205
     */
1206
    public static function cancel_order ( $order_id ) {
1207
1208
		// Get order object
1209
		if( is_object( $order_id ) ){
1210
1211
			$order = $order_id;
1212
1213
		}else{
1214
1215
			$order = new WC_Order( $order_id );
1216
		}
1217
1218
		if ( ! in_array( $order->get_status(), array( 'cancelled', 'refunded' ) ) ) {
1219
1220
			return;
1221
1222
		}
1223
1224
        // Run through each product ordered
1225
        if ( 0 < sizeof( $order->get_items() ) ) {
1226
1227
            // Get order user
1228
            $user_id = $order->__get( 'user_id' );
1229
1230
            foreach( $order->get_items() as $item ) {
1231
1232
                $product_type = '';
1233
                if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
1234
1235
                    $item_id = $item['variation_id'];
1236
                    $product_type = 'variation';
1237
1238
                } else {
1239
1240
                    $item_id = $item['product_id'];
1241
1242
                } // End If Statement
1243
1244
                $_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...
1245
1246
                // Get courses that use the WC product
1247
                $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...
1248
                $courses = Sensei()->course->get_product_courses( $item_id );
1249
1250
                // Loop and update those courses
1251
                foreach ($courses as $course_item){
1252
1253
	                if( self::has_customer_bought_product( $user_id, $course_item->ID ) ){
1254
		                continue;
1255
	                }
1256
                    // Check and Remove course from courses user meta
1257
                    $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...
1258
1259
                } // End For Loop
1260
1261
            } // End For Loop
1262
1263
        } // End If Statement
1264
1265
    } // End sensei_woocommerce_cancel_order()
1266
1267
    /**
1268
     * Returns the WooCommerce Product Object
1269
     *
1270
     * The code caters for pre and post WooCommerce 2.2 installations.
1271
     *
1272
     * @since   1.1.1
1273
     * @access  public
1274
     * @param   integer $wc_product_id Product ID or Variation ID
1275
     * @param   string  $product_type  '' or 'variation'
1276
     * @return   WC_Product $wc_product_object
1277
     */
1278
    public static function get_product_object ( $wc_product_id = 0, $product_type = '' ) {
1279
1280
        $wc_product_object = false;
1281
        if ( 0 < intval( $wc_product_id ) ) {
1282
1283
            // Get the product
1284
            if ( function_exists( 'wc_get_product' ) ) {
1285
1286
                $wc_product_object = wc_get_product( $wc_product_id ); // Post WC 2.3
1287
1288
            } elseif ( function_exists( 'get_product' ) ) {
1289
1290
                $wc_product_object = get_product( $wc_product_id ); // Post WC 2.0
1291
1292
            } else {
1293
1294
                // Pre WC 2.0
1295
                if ( 'variation' == $product_type || 'subscription_variation' == $product_type ) {
1296
1297
                    $wc_product_object = new WC_Product_Variation( $wc_product_id );
1298
1299
                } else {
1300
1301
                    $wc_product_object = new WC_Product( $wc_product_id );
1302
1303
                } // End If Statement
1304
1305
            } // End If Statement
1306
1307
        } // End If Statement
1308
1309
        return $wc_product_object;
1310
1311
    } // End sensei_get_woocommerce_product_object()
1312
1313
    /**
1314
     * If customer has purchased the course, update Sensei to indicate that they are taking the course.
1315
     *
1316
     * @since  1.0.0
1317
     * @since 1.9.0 move to class Sensei_WC
1318
     *
1319
     * @param  int 			$course_id  (default: 0)
1320
     * @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...
1321
     *
1322
     * @return bool|int
1323
     */
1324
    public static function course_update ( $course_id = 0, $order_user = array()  ) {
1325
1326
        global $current_user;
1327
1328
        if ( ! isset( $current_user ) || !$current_user->ID > 0 ) return false;
1329
1330
        $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...
1331
1332
        // Get the product ID
1333
        $wc_post_id = get_post_meta( intval( $course_id ), '_course_woocommerce_product', true );
1334
1335
        // Check if in the admin
1336
        if ( is_admin() ) {
1337
1338
            $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...
1339
            $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...
1340
            $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...
1341
            $user_id = $order_user['ID'];
1342
1343
        } else {
1344
1345
            $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...
1346
            $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...
1347
            $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...
1348
            $user_id = $current_user->ID;
1349
1350
        } // End If Statement
1351
1352
        // This doesn't appear to be purely WooCommerce related. Should it be in a separate function?
1353
        $course_prerequisite_id = (int) get_post_meta( $course_id, '_course_prerequisite', true );
1354
        if( 0 < absint( $course_prerequisite_id ) ) {
1355
1356
            $prereq_course_complete = Sensei_Utils::user_completed_course( $course_prerequisite_id, intval( $user_id ) );
1357
            if ( ! $prereq_course_complete ) {
1358
1359
                // Remove all course user meta
1360
                return Sensei_Utils::sensei_remove_user_from_course( $course_id, $user_id );
1361
1362
            }
1363
        }
1364
1365
        $is_user_taking_course = Sensei_Utils::user_started_course( intval( $course_id ), intval( $user_id ) );
1366
1367
        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...
1368
            && Sensei_WC::is_woocommerce_active()
1369
            && 0 < $wc_post_id
1370
            && Sensei_WC::has_customer_bought_product( $user_id, $wc_post_id ) ) {
1371
1372
	            $activity_logged = Sensei_Utils::user_start_course( intval( $user_id ), intval( $course_id ) );
1373
1374
	            if ( true == $activity_logged ) {
1375
1376
		            $is_user_taking_course = true;
1377
1378
	            } // End If Statement
1379
1380
        }// end if is user taking course
1381
1382
        return $is_user_taking_course;
1383
1384
    } // End course_update()
1385
1386
    /**
1387
     * Disable guest checkout if a course product is in the cart
1388
     *
1389
     * @since 1.1.0
1390
     * @since 1.9.0 move to class Sensei_WC
1391
     *
1392
     * @param  boolean $guest_checkout Current guest checkout setting
1393
     *
1394
     * @return boolean                 Modified guest checkout setting
1395
     */
1396
    public static function disable_guest_checkout( $guest_checkout ) {
1397
1398
        if( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
1399
1400
            if( isset( WC()->cart->cart_contents ) && count( WC()->cart->cart_contents ) > 0 ) {
1401
1402
                foreach( WC()->cart->cart_contents as $cart_key => $product ) {
1403
                    if( isset( $product['product_id'] ) ) {
1404
1405
                        $args = array(
1406
                            'posts_per_page' => -1,
1407
                            'post_type' => 'course',
1408
                            'meta_query' => array(
1409
                                array(
1410
                                    'key' => '_course_woocommerce_product',
1411
                                    'value' => $product['product_id']
1412
                                )
1413
                            )
1414
                        );
1415
1416
                        $posts = get_posts( $args );
1417
1418
                        if( $posts && count( $posts ) > 0 ) {
1419
1420
                            foreach( $posts as $course ) {
1421
                                $guest_checkout = '';
1422
                                break;
1423
1424
                            }
1425
                        }
1426
1427
                    }
1428
1429
                }
1430
1431
            }
1432
        }
1433
1434
        return $guest_checkout;
1435
1436
    }// end disable_guest_checkout
1437
1438
    /**
1439
     * Change order status with virtual products to completed
1440
     *
1441
     * @since  1.1.0
1442
     * @since 1.9.0 move to class Sensei_WC
1443
     *
1444
     * @param string $order_status
1445
     * @param int $order_id
1446
     *
1447
     * @return string
1448
     **/
1449
    public static function virtual_order_payment_complete( $order_status, $order_id ) {
1450
1451
        $order = new WC_Order( $order_id );
1452
1453
        if ( ! isset ( $order ) ) return '';
1454
1455
        if ( $order_status == 'wc-processing' && ( $order->post_status == 'wc-on-hold' || $order->post_status == 'wc-pending' || $order->post_status == 'wc-failed' ) ) {
1456
1457
            $virtual_order = true;
1458
1459
            if ( count( $order->get_items() ) > 0 ) {
1460
1461
                foreach( $order->get_items() as $item ) {
1462
1463
                    if ( $item['product_id'] > 0 ) {
1464
                        $_product = $order->get_product_from_item( $item );
1465
                        if ( ! $_product->is_virtual() ) {
1466
1467
                            $virtual_order = false;
1468
                            break;
1469
1470
                        } // End If Statement
1471
1472
                    } // End If Statement
1473
1474
                } // End For Loop
1475
1476
            } // End If Statement
1477
1478
            // virtual order, mark as completed
1479
            if ( $virtual_order ) {
1480
1481
                return 'completed';
1482
1483
            } // End If Statement
1484
1485
        } // End If Statement
1486
1487
        return $order_status;
1488
1489
    }// end virtual_order_payment_complete
1490
1491
1492
    /**
1493
     * Determine if the user has and active subscription to give them access
1494
     * to the requested resource.
1495
     *
1496
     * @since 1.9.0
1497
     *
1498
     * @param  boolean$user_access_permission
1499
     * @param  integer $user_id
1500
     * @return boolean $user_access_permission
1501
     */
1502
    public static function get_subscription_permission( $user_access_permission, $user_id ){
1503
1504
        global $post;
1505
1506
        // ignore the current case if the following conditions are met
1507
        if ( ! class_exists( 'WC_Subscriptions' ) || empty( $user_id )
1508
            || ! in_array( $post->post_type, array( 'course','lesson','quiz' ) )
1509
            || ! wcs_user_has_subscription( $user_id) ){
1510
1511
            return $user_access_permission;
1512
1513
        }
1514
1515
        // at this user has a subscription
1516
        // is the subscription on the the current course?
1517
1518
        $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...
1519
        if ( 'course' == $post->post_type ){
1520
1521
            $course_id = $post->ID;
1522
1523
        } elseif ( 'lesson' == $post->post_type ) {
1524
1525
            $course_id = Sensei()->lesson->get_course_id( $post->ID );
1526
1527
        } else {
1528
1529
            $lesson_id =  Sensei()->quiz->get_lesson_id( $post->ID );
1530
            $course_id = Sensei()->lesson->get_course_id( $lesson_id );
1531
1532
        }
1533
1534
        // if the course has no subscription WooCommerce product attached to return the permissions as is
1535
        $product_id = Sensei_WC::get_course_product_id( $course_id );
1536
        $product = wc_get_product( $product_id );
1537
        if( ! in_array( $product->get_type(), self::get_subscription_types() ) ){
1538
1539
            return $user_access_permission;
1540
1541
        }
1542
1543
        // give access if user has active subscription on the product otherwise restrict it.
1544
        // also check if the user was added to the course directly after the subscription started.
1545
        if( wcs_user_has_subscription( $user_id, $product_id, 'active'  )
1546
            || wcs_user_has_subscription( $user_id, $product_id, 'pending-cancel'  )
1547
            || self::was_user_added_without_subscription( $user_id, $product_id, $course_id  ) ){
1548
1549
            $user_access_permission = true;
1550
1551
        }else{
1552
1553
            $user_access_permission = false;
1554
            // do not show the WC permissions message
1555
            remove_filter( 'sensei_the_no_permissions_message', array( 'Sensei_WC', 'alter_no_permissions_message' ), 20, 2 );
1556
            Sensei()->permissions_message['title'] = __( 'No active subscription', 'woothemes-sensei' );
1557
            Sensei()->permissions_message['message'] = __( 'Sorry, you do not have an access to this content without an active subscription.', 'woothemes-sensei' );
1558
        }
1559
1560
        return $user_access_permission;
1561
1562
    } // end get_subscription_permission
1563
1564
    /**
1565
     * Get all the valid subscription types.
1566
     *
1567
     * @since 1.9.0
1568
     * @return array
1569
     */
1570
    public static function get_subscription_types(){
1571
1572
        return array( 'subscription','subscription_variation','variable-subscription' );
1573
1574
    }
1575
1576
    /**
1577
     * Compare the user's subscriptions end date with the date
1578
     * the user was added to the course. If the user was added after
1579
     * the subscription ended they were manually added and this will return
1580
     * true.
1581
     *
1582
     * Important to note that all subscriptions for the user is compared.
1583
     *
1584
     * @since 1.9.0
1585
     *
1586
     * @param $user_id
1587
     * @param $product_id
1588
     * @param $course_id
1589
     *
1590
     * @return bool
1591
     */
1592
    public static function was_user_added_without_subscription($user_id, $product_id, $course_id ){
1593
1594
        $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...
1595
        $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...
1596
        $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...
1597
        $was_user_added_without_subscription = true;
1598
1599
        // if user is not on the course they were not added
1600
        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...
1601
1602
            return false;
1603
1604
        }
1605
1606
        // if user doesn't have a subscription and is taking the course
1607
        // they were added manually
1608
        if ( ! wcs_user_has_subscription($user_id, $product_id)
1609
            && 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...
1610
1611
            return true;
1612
1613
        }
1614
1615
        $course_status =  Sensei_Utils::user_course_status( $course_id, $user_id );
1616
1617
        // comparing dates setup data
1618
        $course_start_date = date_create( $course_status->comment_date );
1619
        $subscriptions = wcs_get_users_subscriptions( $user_id );
1620
1621
        // comparing every subscription
1622
        foreach( $subscriptions as $subscription ){
1623
1624
            // for the following statuses we know the user was not added
1625
            // manually
1626
            $status = $subscription->get_status();
1627
            if ( in_array( $status, array( 'pending-canceled', 'active', 'on-hold', 'pending' ) ) ) {
1628
1629
                continue;
1630
1631
            }
1632
1633
            $current_subscription_start_date = date_create( $subscription->modified_date );
1634
1635
            // is the last updated subscription date newer than course start date
1636
            if (  $current_subscription_start_date > $course_start_date   ) {
1637
1638
                return false;
1639
1640
            }
1641
1642
        }
1643
1644
        return $was_user_added_without_subscription;
1645
    }
1646
1647
	/**
1648
	 * Get all the orders for a specific user and product combination
1649
	 *
1650
	 * @param int $user_id
1651
	 * @param $product_id
1652
	 *
1653
	 * @return array $orders
1654
	 */
1655
	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...
1656
1657
		return get_posts( array(
1658
			'numberposts' => -1,
1659
			'post_type' => 'shop_order',
1660
			'meta_key'    => '_customer_user',
1661
			'meta_value'  => intval( $user_id ),
1662
			'post_status' => array( 'wc-processing', 'wc-completed' ),
1663
		) );
1664
1665
	}
1666
1667
}// end Sensei_WC
1668