Completed
Push — master ( d07982...cb7231 )
by Dan
05:09
created

Sensei_WC::get_paid_courses()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

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