Completed
Push — master ( 7d92ba...201c1b )
by Dwain
05:05
created

Sensei_WC::get_course_product_id()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 11
rs 9.4286
cc 3
eloc 5
nc 2
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 Sensei
10
 * @category WooCommerce
11
 * @since 1.9.0
12
 */
13
14
Class Sensei_WC{
15
    /**
16
     * Load the files needed for the woocommerce integration.
17
     *
18
     * @since 1.9.0
19
     */
20
    public static function load_woocommerce_integration_hooks(){
21
22
        require_once( __DIR__ . '/hooks/woocommerce.php' );
23
24
    }
25
    /**
26
     * check if WooCommerce plugin is loaded
27
     *
28
     * @since 1.9.0
29
     * @return bool
30
     */
31
    public static function is_woocommerce_active(){
32
33
        $active_plugins = (array) get_option( 'active_plugins', array() );
34
35
        if ( is_multisite() ){
36
37
            $active_plugins = array_merge( $active_plugins, get_site_option( 'active_sitewide_plugins', array() ) );
38
39
        }
40
41
        return in_array( 'woocommerce/woocommerce.php', $active_plugins ) || array_key_exists( 'woocommerce/woocommerce.php', $active_plugins );
42
43
    } // end is_woocommerce_active
44
45
    /**
46
     * Find the order active number (completed or processing ) for a given user on a course. It will return the latest order.
47
     *
48
     * If multiple exist we will return the latest order.
49
     *
50
     * @param $user_id
51
     * @param $course_id
52
     * @return array $user_course_orders
53
     */
54
    public static function get_learner_course_active_order_id( $user_id, $course_id ){
55
56
        $course_product_id = get_post_meta( $course_id, '_course_woocommerce_product', true );
57
58
        $orders_query = new WP_Query( array(
59
            'post_type'   => 'shop_order',
60
            'posts_per_page' => -1,
61
            'post_status' => array( 'wc-processing', 'wc-completed' ),
62
            'meta_key'=> '_customer_user',
63
            'meta_value'=> $user_id,
64
        ) );
65
66
        if( $orders_query->post_count == 0 ){
67
68
            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...
69
70
        }
71
72
        foreach( $orders_query->get_posts() as $order ){
73
74
            $order = new WC_Order( $order->ID );
75
            $items = $order->get_items();
76
77
            $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...
78
79
            foreach( $items as $item ){
80
81
                // if the product id on the order and the one given to this function
82
                // this order has been placed by the given user on the given course.
83
                $product = wc_get_product( $item['product_id'] );
84
85
                if ( $product->is_type( 'variable' )) {
86
87
                    $item_product_id = $item['variation_id'];
88
89
                } else {
90
91
                    $item_product_id =  $item['product_id'];
92
93
                }
94
95
                if( $course_product_id == $item_product_id ){
96
97
                    return $order->id;
98
99
                }
100
101
102
            }//end for each order item
103
104
        } // end for each order
105
106
        // if we reach this place we found no order
107
        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...
108
109
    } // end get_learner_course_active_order_ids
110
111
    /**
112
     * Output WooCommerce specific course filters
113
     * Removing the paged argument
114
     *
115
     * @since 1.9.0
116
     * @param $filter_links
117
     * @return mixed
118
     */
119
    public static function add_course_archive_wc_filter_links( $filter_links ){
120
121
        $course_url = remove_query_arg('paged', WooThemes_Sensei_Utils::get_current_url() );
122
123
        $free_courses = self::get_free_courses();
124
        $paid_courses = self::get_paid_courses();
125
126
        if ( empty( $free_courses ) || empty( $paid_courses )  ){
127
            // do not show any WooCommerce filters if all courses are
128
            // free or if all courses are paid
129
            return $filter_links;
130
131
        }
132
133
        $filter_links[] = array(    'id'=>'paid' ,
134
                                    'url'=> add_query_arg('course_filter', 'paid', $course_url),
135
                                    'title'=>__( 'Paid', 'woothemes-sensei' )
136
        );
137
138
        $filter_links[] = array(    'id'=>'free',
139
                                    'url'=>add_query_arg('course_filter', 'free', $course_url),
140
                                    'title'=>__( 'Free', 'woothemes-sensei' )
141
        );
142
143
        return $filter_links;
144
145
    }// end add_course_archive_wc_filter_links
146
147
    /**
148
     * Apply the free filter the the course query
149
     * getting all course with no products or products with zero price
150
     *
151
     * hooked into pre_get_posts
152
     *
153
     * @since 1.9.0
154
     * @param WP_Query $query
155
     * @return WP_Query $query
156
     */
157
    public static function course_archive_wc_filter_free( $query ){
158
159
        if( isset( $_GET['course_filter'] ) && 'free' == $_GET['course_filter']
160
            && 'course' == $query->get( 'post_type') && $query->is_main_query()  ){
161
162
            // setup the course meta query
163
            $meta_query = self::get_free_courses_meta_query_args();
164
165
            // manipulate the query to return free courses
166
            $query->set('meta_query', $meta_query );
167
168
            // don't show any paid courses
169
            $courses = self::get_paid_courses();
170
            $ids = array();
171
            foreach( $courses as $course ){
172
                $ids[] = $course->ID;
173
            }
174
            $query->set( 'post__not_in', $ids );
175
176
        }// end if course_filter
177
178
        return $query;
179
180
    }// course_archive_wc_filter_free
181
182
    /**
183
     * Apply the paid filter to the course query on the courses page
184
     * will include all course with a product attached with a price
185
     * more than 0
186
     *
187
     * hooked into pre_get_posts
188
     *
189
     * @since 1.9.0
190
     * @param WP_Query $query
191
     * @return WP_Query $query
192
     */
193
    public static function course_archive_wc_filter_paid( $query ){
194
195
        if( isset( $_GET['course_filter'] ) && 'paid' == $_GET['course_filter']
196
            && 'course' == $query->get( 'post_type') && $query->is_main_query() ){
197
198
            // setup the course meta query
199
            $meta_query = self::get_paid_courses_meta_query_args();
200
201
            // manipulate the query to return free courses
202
            $query->set('meta_query', $meta_query );
203
204
        }
205
206
        return $query;
207
208
    }
209
210
    /**
211
     * Load the WooCommerce single product actions above
212
     * single courses if woocommerce is active allowing purchase
213
     * information and actions to be hooked from WooCommerce.
214
     */
215
    public static function do_single_course_wc_single_product_action(){
216
217
        /**
218
         * this hooks is documented within the WooCommerce plugin.
219
         */
220
        if ( WooThemes_Sensei_Utils::sensei_is_woocommerce_activated() ) {
221
222
            do_action( 'woocommerce_before_single_product' );
223
224
        } // End If Statement
225
226
    }// end do_single_course_wc_single_product_action
227
228
    /**
229
     * Hooking into the single lesson page to alter the
230
     * user access permissions based on if they have purchased the
231
     * course the lesson belongs to.
232
     *
233
     * This function will only return false or the passed in user_access value.
234
     * It doesn't return true in order to avoid altering other options.
235
     *
236
     * @since 1.9.0
237
     *
238
     * @param $can_user_view_lesson
239
     * @param $lesson_id
240
     * @param $user_id
241
     * @return bool
242
     */
243
    public static function alter_can_user_view_lesson ( $can_user_view_lesson, $lesson_id, $user_id  ){
244
245
        // check if the course has a valid product attached to it
246
        // which the user should have purchased if they want to access
247
        // the current lesson
248
        $course_id = get_post_meta( $lesson_id , '_lesson_course', true);
249
        $wc_post_id = get_post_meta( $course_id, '_course_woocommerce_product', true );
250
        $product = Sensei()->sensei_get_woocommerce_product_object($wc_post_id);
251
        if( isset ($product) && is_object($product) ){
252
253
            // valid product found
254
            $order_id = self::get_learner_course_active_order_id( $user_id, $course_id );
255
256
            // product has a successful order so this user may access the content
257
            // this function may only return false or the default
258
            // returning true may override other negatives which we don't want
259
            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...
260
261
                return false;
262
263
            }
264
265
        }
266
267
        // return the passed in value
268
        return $can_user_view_lesson;
269
270
    }
271
272
    /**
273
     * Add course link to order thank you and details pages.
274
     *
275
     * @since  1.4.5
276
     * @access public
277
     *
278
     * @return void
279
     */
280
    public static function course_link_from_order( ) {
281
282
        if( ! is_order_received_page() ){
283
            return;
284
        }
285
286
        $order_id = get_query_var( 'order-received' );
287
		$order = new WC_Order( $order_id );
288
289
		// exit early if not wc-completed or wc-processing
290
		if( 'wc-completed' != $order->post_status
291
            && 'wc-processing' != $order->post_status  ) {
292
            return;
293
        }
294
295
        $course_links = array(); // store the for links for courses purchased
296
		foreach ( $order->get_items() as $item ) {
297
298
            if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
299
300
                // If item has variation_id then its a variation of the product
301
                $item_id = $item['variation_id'];
302
303
            } else {
304
305
                //If not its real product set its id to item_id
306
                $item_id = $item['product_id'];
307
308
            } // End If Statement
309
310
            $user_id = get_post_meta( $order->id, '_customer_user', true );
311
312
            if( $user_id ) {
313
314
                // Get all courses for product
315
                $args = Sensei_Course::get_default_query_args();
316
                $args['meta_query'] = array( array(
317
                            'key' => '_course_woocommerce_product',
318
                            'value' => $item_id
319
                        ) );
320
                $args['orderby'] = 'menu_order date';
321
                $args['order'] = 'ASC';
322
323
                // loop through courses
324
                $courses = get_posts( $args );
325
                if( $courses && count( $courses ) > 0 ) {
326
327
                    foreach( $courses as $course ) {
328
329
                        $title = $course->post_title;
330
                        $permalink = get_permalink( $course->ID );
331
                        $course_links[] .= '<a href="' . esc_url( $permalink ) . '" >' . $title . '</a> ';
332
333
                    } // end for each
334
335
                    // close the message div
336
337
                }// end if $courses check
338
            }
339
        }// end loop through orders
340
341
        // add the courses to the WooCommerce notice
342
        if( ! empty( $course_links) ){
343
344
            $courses_html = _nx(
345
                'You have purchased the following course:',
346
                'You have purchased the following courses:',
347
                count( $course_links ),
348
                'Purchase thank you note on Checkout page. The course link(s) will be show', 'woothemes-sensei'
349
            );
350
351
            foreach( $course_links as $link ){
352
353
                $courses_html .= '<li>' . $link . '</li>';
354
355
            }
356
357
            $courses_html .= ' </ul>';
358
359
            wc_add_notice( $courses_html, 'success' );
360
        }
361
362
	} // end course_link_order_form
363
364
    /**
365
     * Show the message that a user should complete
366
     * their purchase if the course is in the cart
367
     *
368
     * This should be used within the course loop or single course page
369
     *
370
     * @since 1.9.0
371
     */
372
    public static function course_in_cart_message(){
373
374
        global $post;
375
376
        if( self::is_course_in_cart( $post->ID ) ){ ?>
377
378
            <div class="sensei-message info">'
379
                <?php
380
381
                $cart_link =  '<a class="cart-complete" href="' . WC()->cart->get_checkout_url()
382
                              . '" title="' . __('complete purchase', 'woothemes-sensei') . '">'
383
                              . __('complete the purchase', 'woothemes-sensei') . '</a>';
384
385
                sprintf(  __('You have already added this Course to your cart. Please %1$s to access the course.', 'woothemes-sensei'), $cart_link );
386
387
                ?>
388
            </div>
389
        <?php }
390
391
    } // End sensei_woocommerce_in_cart_message()
392
393
    /**
394
     * Checks the cart to see if a course is in the cart.
395
     *
396
     * @param $course_id
397
     * @return bool
398
     */
399
    public static function is_course_in_cart( $course_id ){
400
401
        $wc_post_id = absint( get_post_meta( $course_id, '_course_woocommerce_product', true ) );
402
        $user_course_status_id = WooThemes_Sensei_Utils::user_started_course( $course_id , get_current_user_id() );
403
404
        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...
405
406
            if ( self::is_product_in_cart( $wc_post_id ) ) {
407
408
                return true;
409
410
            }
411
412
        }
413
414
        return false;
415
416
    }// is_course_in_cart
417
418
    /**
419
     * Check the cart to see if the product is in the cart
420
     *
421
     * @param $product_id
422
     * @return bool
423
     */
424
    public static function is_product_in_cart( $product_id ){
425
426
        if ( 0 < $product_id ) {
427
428
            $product = wc_get_product( $product_id );
429
430
            $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...
431
            if( isset( $product->variation_id ) && 0 < intval( $product->variation_id ) ) {
432
                $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...
433
            }
434
            foreach( WC()->cart->get_cart() as $cart_item_key => $values ) {
435
436
                $cart_product = $values['data'];
437
                if( $product_id == $cart_product->id ) {
438
439
                    return true;
440
441
                }
442
443
            }
444
        } // End If Statement
445
446
        return false;
447
448
    } // end is_product_in_car
449
450
    /**
451
     * Get all free WooCommerce products
452
     *
453
     * @since 1.9.0
454
     *
455
     * @return array $free_products{
456
     *  @type int $wp_post_id
457
     * }
458
     */
459
    public static function get_free_product_ids(){
460
461
        return  get_posts( array(
462
            'post_type' => 'product',
463
            'posts_per_page' => '1000',
464
            'fields' => 'ids',
465
            'meta_query'=> array(
466
                'relation' => 'OR',
467
                array(
468
                    'key'=> '_regular_price',
469
                    'value' => 0,
470
                ),
471
                array(
472
                    'key'=> '_sale_price',
473
                    'value' => 0,
474
                ),
475
            ),
476
        ));
477
478
    }// end get free product query
479
480
    /**
481
     * The metat query for courses that are free
482
     *
483
     * @since 1.9.0
484
     * @return array $wp_meta_query_param
485
     */
486
    public static function get_free_courses_meta_query_args(){
487
488
        return array(
489
            'relation' => 'OR',
490
            array(
491
                'key'     => '_course_woocommerce_product',
492
                'value' => '-',
493
                'compare' => '=',
494
            ),
495
            array(
496
                'key'     => '_course_woocommerce_product',
497
                'value' => self::get_free_product_ids(),
498
                'compare' => 'IN',
499
            ),
500
        );
501
502
    }// get_free_courses_meta_query
503
504
    /**
505
     * The metat query for courses that are free
506
     *
507
     * @since 1.9.0
508
     * @return array $wp_query_meta_query_args_param
509
     */
510
    public static function get_paid_courses_meta_query_args(){
511
512
        $paid_product_ids = self::get_paid_product_ids();
513
514
        return array(
515
            array(
516
                'key'     => '_course_woocommerce_product',
517
                // when empty we give a false post_id to ensure the caller doesn't get any courses for their
518
                // query
519
                'value' => empty( $paid_product_ids  )? '-1000' : $paid_product_ids,
520
                'compare' => 'IN',
521
            ),
522
        );
523
524
    }// get_free_courses_meta_query
525
526
    /**
527
     * The WordPress Query args
528
     * for paid products on sale
529
     *
530
     * @since 1.9.0
531
     * @return array $product_query_args
532
     */
533 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...
534
535
        $args = array(
536
                   'post_type' 		=> 'product',
537
                   'posts_per_page' 		=> 1000,
538
                   'orderby'         	=> 'date',
539
                   'order'           	=> 'DESC',
540
                   'suppress_filters' 	=> 0
541
        );
542
543
        $args[ 'fields' ]     = 'ids';
544
545
        $args[ 'meta_query' ] = array(
546
            'relation' => 'AND',
547
            array(
548
                'key'=> '_regular_price',
549
                'compare' => '>',
550
                'value' => 0,
551
            ),
552
            array(
553
                'key'=> '_sale_price',
554
                'compare' => '>',
555
                'value' => 0,
556
            ),
557
        );
558
559
        return $args;
560
561
    } // get_paid_products_on_sale_query_args
562
563
564
    /**
565
     * Return the WordPress query args for
566
     * products not on sale but that is not a free
567
     *
568
     * @since 1.9.0
569
     *
570
     * @return array
571
     */
572 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...
573
574
        $args = array(
575
            'post_type' 		=> 'product',
576
            'posts_per_page' 		=> 1000,
577
            'orderby'         	=> 'date',
578
            'order'           	=> 'DESC',
579
            'suppress_filters' 	=> 0
580
        );
581
582
        $args[ 'fields' ]     = 'ids';
583
        $args[ 'meta_query' ] = array(
584
            'relation' => 'AND',
585
            array(
586
                'key'=> '_regular_price',
587
                'compare' => '>',
588
                'value' => 0,
589
            ),
590
            array(
591
                'key'=> '_sale_price',
592
                'compare' => '=',
593
                'value' => '',
594
            ),
595
        );
596
597
        return $args;
598
599
600
    } // get_paid_courses_meta_query
601
602
    /**
603
     * Get all WooCommerce non-free product id's
604
     *
605
     * @since 1.9.0
606
     *
607
     * @return array $woocommerce_paid_product_ids
608
     */
609
    public static function get_paid_product_ids(){
610
611
        // get all the paid WooCommerce products that has regular
612
        // and sale price greater than 0
613
        // will be used later to check for course with the id as meta
614
        $paid_product_ids_with_sale =  get_posts( self::get_paid_products_on_sale_query_args() );
615
616
        // get all the paid WooCommerce products that has regular price
617
        // greater than 0 without a sale price
618
        // will be used later to check for course with the id as meta
619
        $paid_product_ids_without_sale = get_posts( self::get_paid_products_not_on_sale_query_args() );
620
621
        // combine products ID's with regular and sale price grater than zero and those without
622
        // sale but regular price greater than zero
623
        $woocommerce_paid_product_ids = array_merge( $paid_product_ids_with_sale, $paid_product_ids_without_sale );
624
625
        // if
626
        if( empty($woocommerce_paid_product_ids) ){
627
            return array( );
628
        }
629
        return $woocommerce_paid_product_ids;
630
631
    }
632
633
    /**
634
     * Get all free courses.
635
     *
636
     * This course that have a WC product attached
637
     * that has a price or sale price of zero and
638
     * other courses with no WooCommerce products
639
     * attached.
640
     *
641
     * @since 1.9.0
642
     *
643
     * @return array
644
     */
645
    public static function get_free_courses(){
646
647
        $free_course_query_args = Sensei_Course::get_default_query_args();
648
        $free_course_query_args[ 'meta_query' ] = self::get_free_courses_meta_query_args();
649
650
        // don't show any paid courses
651
        $courses = self::get_paid_courses();
652
        $ids = array();
653
        foreach( $courses as $course ){
654
            $ids[] = $course->ID;
655
        }
656
        $free_course_query_args[ 'post__not_in' ] =  $ids;
657
658
        return get_posts( $free_course_query_args );
659
660
    }
661
662
    /**
663
     * Return all products that are not free
664
     *
665
     * @since 1.9.0
666
     * @return array
667
     */
668
    public static function get_paid_courses(){
669
670
        $paid_course_query_args = Sensei_Course::get_default_query_args();
671
672
        $paid_course_query_args[ 'meta_query' ] = self::get_paid_courses_meta_query_args();
673
674
        return get_posts(  $paid_course_query_args );
675
    }
676
677
    /**
678
     * Show the WooCommerce add to cart button for the  current course
679
     *
680
     * The function will only show the button if
681
     * 1- the user can buy the course
682
     * 2- if they have completed their pre-requisite
683
     * 3- if the course has a valid product attached
684
     *
685
     * @since 1.9.0
686
     * @param int $course_id
687
     * @return string $html markup for the button or nothing if user not allowed to buy
688
     */
689
    public static function the_add_to_cart_button_html( $course_id ){
690
691
        if ( ! Sensei_Course::is_prerequisite_complete( $course_id )) {
692
            return '';
693
        }
694
695
        $wc_post_id = self::get_course_product_id( $course_id );
696
697
        // Check if customer purchased the product
698
        if ( self::has_customer_bought_product(  get_current_user_id(), $wc_post_id )
699
            || empty( $wc_post_id ) ) {
700
701
            return '';
702
703
        }
704
705
        // based on simple.php in WC templates/single-product/add-to-cart/
706
        // Get the product
707
        $product = Sensei()->sensei_get_woocommerce_product_object( $wc_post_id );
708
709
        // do not show the button for invalid products, non purchasable products, out
710
        // of stock product or if course is already in cart
711
        if ( ! isset ( $product )
712
            || ! is_object( $product )
713
            || ! $product->is_purchasable()
714
            || ! $product->is_in_stock()
715
            || self::is_course_in_cart( $wc_post_id ) ) {
716
717
            return '';
718
719
        }
720
721
        //
722
        // button  output:
723
        //
724
        ?>
725
726
        <form action="<?php echo esc_url( $product->add_to_cart_url() ); ?>"
727
              class="cart"
728
              method="post"
729
              enctype="multipart/form-data">
730
731
            <input type="hidden" name="product_id" value="<?php echo esc_attr( $product->id ); ?>" />
732
733
            <input type="hidden" name="quantity" value="1" />
734
735
            <?php if ( isset( $product->variation_id ) && 0 < intval( $product->variation_id ) ) { ?>
736
737
                <input type="hidden" name="variation_id" value="<?php echo $product->variation_id; ?>" />
738
                <?php if( isset( $product->variation_data ) && is_array( $product->variation_data ) && count( $product->variation_data ) > 0 ) { ?>
739
740
                    <?php foreach( $product->variation_data as $att => $val ) { ?>
741
742
                        <input type="hidden" name="<?php echo esc_attr( $att ); ?>" id="<?php echo esc_attr( str_replace( 'attribute_', '', $att ) ); ?>" value="<?php echo esc_attr( $val ); ?>" />
743
744
                    <?php } ?>
745
746
                <?php } ?>
747
748
            <?php } ?>
749
750
            <button type="submit" class="single_add_to_cart_button button alt">
751
                <?php echo $product->get_price_html(); ?> - <?php  _e('Purchase this Course', 'woothemes-sensei'); ?>
752
            </button>
753
754
        </form>
755
756
        <?php
757
    } // end the_add_to_cart_button_html
758
759
    /**
760
     * Alter the no permissions message on the single course page
761
     * Changes the message to a WooCommerce specific message.
762
     *
763
     * @since 1.9.0
764
     *
765
     * @param $message
766
     * @param $post_id
767
     *
768
     * @return string $message
769
     */
770
    public static function alter_no_permissions_message( $message, $post_id ){
771
772
        if( empty( $post_id ) || 'course'!=get_post_type( $post_id ) ){
773
            return  $message;
774
        }
775
776
        $product_id = self::get_course_product_id( $post_id );
777
778
        if( ! $product_id
779
            || self::has_customer_bought_product( get_current_user_id(),$product_id ) ){
780
781
            return $message;
782
783
        }
784
785
        ob_start();
786
        self::the_course_no_permissions_message( $post_id );
787
        $woocommerce_course_no_permissions_message = ob_get_clean();
788
789
        return $woocommerce_course_no_permissions_message ;
790
791
    }
792
    /**
793
     * Show the no permissions message when a user is logged in
794
     * and have not yet purchased the current course
795
     *
796
     * @since 1.9.0
797
     */
798
    public static function the_course_no_permissions_message( $course_id ){
0 ignored issues
show
Unused Code introduced by
The parameter $course_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
799
800
        // login link
801
        $my_courses_page_id = intval( Sensei()->settings->settings[ 'my_course_page' ] );
802
        $login_link =  '<a href="' . esc_url( get_permalink( $my_courses_page_id ) ) . '">' . __( 'log in', 'woothemes-sensei' ) . '</a>';
803
804
        ?>
805
806
        <span class="add-to-cart-login">
807
            <?php echo sprintf( __( 'Or %1$s to access your purchased courses', 'woothemes-sensei' ), $login_link ); ?>
808
        </span>
809
810
    <?php }
811
812
    /**
813
     * Checks if a user has bought a product item.
814
     *
815
     * @since  1.9.0
816
     *
817
     * @param  int $user_id
818
     * @param  int $product_id
819
     *
820
     * @return bool
821
     */
822
    public static function has_customer_bought_product ( $user_id, $product_id ){
823
824
        $orders = get_posts( array(
825
            'posts_per_page' => -1,
826
            'meta_key'    => '_customer_user',
827
            'meta_value'  => intval( $user_id ),
828
            'post_type'   => 'shop_order',
829
            'post_status' =>  array( 'wc-processing', 'wc-completed' ),
830
        ) );
831
832
        foreach ( $orders as $order_id ) {
833
834
            $order = new WC_Order( $order_id->ID );
835
836
            if ( $order->post_status != 'wc-completed' && $order->post_status != 'wc-processing' ) {
837
838
                continue;
839
            }
840
841
            if ( ! ( 0 < sizeof( $order->get_items() ) ) ) {
842
843
                continue;
844
845
            }
846
847
            foreach( $order->get_items() as $item ) {
848
849
                // Check if user has bought product
850
                if ( $item['product_id'] == $product_id || $item['variation_id'] == $product_id ) {
851
852
                    // Check if user has an active subscription for product
853
                    if( class_exists( 'WC_Subscriptions_Manager' ) ) {
854
                        $sub_key = WC_Subscriptions_Manager::get_subscription_key( $order_id->ID, $product_id );
855
                        if( $sub_key ) {
856
                            $sub = WC_Subscriptions_Manager::get_subscription( $sub_key );
857
                            if( $sub && isset( $sub['status'] ) ) {
858
                                if( 'active' == $sub['status'] ) {
859
                                    return true;
860
                                } else {
861
                                    return false;
862
                                }
863
                            }
864
                        }
865
                    }
866
867
                    // Customer has bought product
868
                    return true;
869
                } // End If Statement
870
871
            } // End For each item
872
873
        } // End For each order
874
875
    } // end has customer bought product
876
877
    /**
878
     * Return the product id for the given course
879
     *
880
     * @since 1.9.0
881
     *
882
     * @param int $course_id
883
     *
884
     * @return string $woocommerce_product_id or false if none exist
885
     *
886
     */
887
    public static function get_course_product_id( $course_id ){
888
889
        $product_id =  get_post_meta( $course_id, '_course_woocommerce_product', true );
890
891
        if( empty( $product_id ) || 'product' != get_post_type( $product_id ) ){
892
            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...
893
        }
894
895
        return $product_id;
896
897
    }
898
899
    /**
900
     * Alter the body classes adding WooCommerce to the body
901
     *
902
     * @param array $classes
903
     * @return array
904
     */
905
    public static function add_woocommerce_body_class( $classes ){
906
907
        if( ! in_array( 'woocommerce', $classes ) ){
908
909
            $classes[] ='woocommerce';
910
911
        }
912
913
914
        return $classes;
915
916
    }
917
918
}// end Sensei_WC
919