Issues (896)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/class-sensei-wc.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
    /**
17
     * Load the files needed for the woocommerce integration.
18
     *
19
     * @since 1.9.0
20
     */
21
    public static function load_woocommerce_integration_hooks(){
22
23
	    $woocommerce_hooks_file_path = Sensei()->plugin_path() . 'includes/hooks/woocommerce.php';
24
        require_once( $woocommerce_hooks_file_path );
25
26
    }
27
    /**
28
     * check if WooCommerce plugin is loaded and allowed by Sensei
29
     *
30
     * @since 1.9.0
31
     * @return bool
32
     */
33
    public static function is_woocommerce_active(){
34
35
        $is_woocommerce_enabled_in_settings = isset( Sensei()->settings->settings['woocommerce_enabled'] ) && Sensei()->settings->settings['woocommerce_enabled'];
36
        return self::is_woocommerce_present() && $is_woocommerce_enabled_in_settings;
37
38
    } // end is_woocommerce_active
39
40
    /**
41
     * Checks if the WooCommerce plugin is installed and activation.
42
     *
43
     * If you need to check if WooCommerce is activated use Sensei_Utils::is_woocommerce_active().
44
     * This function does nott check to see if the Sensei setting for WooCommerce is enabled.
45
     *
46
     * @since 1.9.0
47
     *
48
     * @return bool
49
     */
50
    public static function is_woocommerce_present(){
51
52
        $active_plugins = (array) get_option( 'active_plugins', array() );
53
54
        if ( is_multisite() ){
55
56
            $active_plugins = array_merge( $active_plugins, get_site_option( 'active_sitewide_plugins', array() ) );
57
58
        }
59
60
        $is_woocommerce_plugin_present_and_activated = in_array( 'woocommerce/woocommerce.php', $active_plugins ) || array_key_exists( 'woocommerce/woocommerce.php', $active_plugins );
61
62
        return class_exists( 'Woocommerce' ) || $is_woocommerce_plugin_present_and_activated;
63
64
    }// end is_woocommerce_present
65
66
    /**
67
     * Find the order active number (completed or processing ) for a given user on a course. It will return the latest order.
68
     *
69
     * If multiple exist we will return the latest order.
70
     *
71
     * @param $user_id
72
     * @param $course_id
73
     * @return array $user_course_orders
74
     */
75
    public static function get_learner_course_active_order_id( $user_id, $course_id ){
76
77
        $course_product_id = get_post_meta( $course_id, '_course_woocommerce_product', true );
78
79
        $orders_query = new WP_Query( array(
80
            'post_type'   => 'shop_order',
81
            'posts_per_page' => -1,
82
            'post_status' => array( 'wc-processing', 'wc-completed' ),
83
            'meta_key'=> '_customer_user',
84
            'meta_value'=> $user_id,
85
        ) );
86
87
        if( $orders_query->post_count == 0 ){
88
89
            return false;
90
91
        }
92
93
        foreach( $orders_query->get_posts() as $order ){
94
95
            $order = new WC_Order( $order->ID );
96
            $items = $order->get_items();
97
98
            $user_orders =  array();
99
100
            foreach( $items as $item ){
101
102
                // if the product id on the order and the one given to this function
103
                // this order has been placed by the given user on the given course.
104
                $product = wc_get_product( $item['product_id'] );
105
106
                if ( is_object( $product ) && $product->is_type( 'variable' )) {
107
108
                    $item_product_id = $item['variation_id'];
109
110
                } else {
111
112
                    $item_product_id =  $item['product_id'];
113
114
                }
115
116
                if( $course_product_id == $item_product_id ){
117
118
                    return $order->id;
119
120
                }
121
122
123
            }//end for each order item
124
125
        } // end for each order
126
127
        // if we reach this place we found no order
128
        return false;
129
130
    } // end get_learner_course_active_order_ids
131
132
    /**
133
     * Output WooCommerce specific course filters
134
     * Removing the paged argument
135
     *
136
     * @since 1.9.0
137
     * @param $filter_links
138
     * @return mixed
139
     */
140
    public static function add_course_archive_wc_filter_links( $filter_links ){
141
142
        $free_courses = self::get_free_courses();
143
        $paid_courses = self::get_paid_courses();
144
145
        if ( empty( $free_courses ) || empty( $paid_courses )  ){
146
            // do not show any WooCommerce filters if all courses are
147
            // free or if all courses are paid
148
            return $filter_links;
149
150
        }
151
152
        $filter_links[] = array(
153
            'id'=>'paid' ,
154
            'url'=> add_query_arg( array( 'course_filter'=>'paid'), Sensei_Course::get_courses_page_url() ),
155
            'title'=>__( 'Paid', 'woothemes-sensei' )
156
        );
157
158
        $filter_links[] = array(
159
            'id'=>'free',
160
            'url'=> add_query_arg( array( 'course_filter'=>'free'), Sensei_Course::get_courses_page_url() ),
161
            'title'=>__( 'Free', 'woothemes-sensei' )
162
        );
163
164
        return $filter_links;
165
166
    }// end add_course_archive_wc_filter_links
167
168
    /**
169
     * Apply the free filter the the course query
170
     * getting all course with no products or products with zero price
171
     *
172
     * hooked into pre_get_posts
173
     *
174
     * @since 1.9.0
175
     * @param WP_Query $query
176
     * @return WP_Query $query
177
     */
178
    public static function course_archive_wc_filter_free( $query ){
179
180
        if( isset( $_GET['course_filter'] ) && 'free' == $_GET['course_filter']
181
            && 'course' == $query->get( 'post_type') && $query->is_main_query()  ){
182
183
            // setup the course meta query
184
            $meta_query = self::get_free_courses_meta_query_args();
185
186
            // manipulate the query to return free courses
187
            $query->set('meta_query', $meta_query );
188
189
            // don't show any paid courses
190
            $courses = self::get_paid_courses();
191
            $ids = array();
192
            foreach( $courses as $course ){
193
                $ids[] = $course->ID;
194
            }
195
            $query->set( 'post__not_in', $ids );
196
197
        }// end if course_filter
198
199
        return $query;
200
201
    }// course_archive_wc_filter_free
202
203
    /**
204
     * Apply the paid filter to the course query on the courses page
205
     * will include all course with a product attached with a price
206
     * more than 0
207
     *
208
     * hooked into pre_get_posts
209
     *
210
     * @since 1.9.0
211
     * @param WP_Query $query
212
     * @return WP_Query $query
213
     */
214
    public static function course_archive_wc_filter_paid( $query ){
215
216
        if( isset( $_GET['course_filter'] ) && 'paid' == $_GET['course_filter']
217
            && 'course' == $query->get( 'post_type') && $query->is_main_query() ){
218
219
            // setup the course meta query
220
            $meta_query = self::get_paid_courses_meta_query_args();
221
222
            // manipulate the query to return free courses
223
            $query->set('meta_query', $meta_query );
224
225
        }
226
227
        return $query;
228
229
    }
230
231
    /**
232
     * Load the WooCommerce single product actions above
233
     * single courses if woocommerce is active allowing purchase
234
     * information and actions to be hooked from WooCommerce.
235
     */
236
    public static function do_single_course_wc_single_product_action(){
237
238
        /**
239
         * this hooks is documented within the WooCommerce plugin.
240
         */
241
        if ( Sensei_WC::is_woocommerce_active() ) {
242
243
            do_action( 'woocommerce_before_single_product' );
244
245
        } // End If Statement
246
247
    }// end do_single_course_wc_single_product_action
248
249
    /**
250
     * Hooking into the single lesson page to alter the
251
     * user access permissions based on if they have purchased the
252
     * course the lesson belongs to.
253
     *
254
     * This function will only return false or the passed in user_access value.
255
     * It doesn't return true in order to avoid altering other options.
256
     *
257
     * @since 1.9.0
258
     *
259
     * @param $can_user_view_lesson
260
     * @param $lesson_id
261
     * @param $user_id
262
     * @return bool
263
     */
264
    public static function alter_can_user_view_lesson ( $can_user_view_lesson, $lesson_id, $user_id  ){
265
266
	    // do not override access to admins
267
	    $course_id = Sensei()->lesson->get_course_id( $lesson_id );
268
	    if ( sensei_all_access() || Sensei_Utils::is_preview_lesson( $lesson_id )
269
	         || Sensei_Utils::user_started_course( $course_id, $user_id )  ){
270
271
			return true;
272
273
	    }
274
275
        // check if the course has a valid product attached to it
276
        // which the user should have purchased if they want to access
277
        // the current lesson
278
        $course_id = get_post_meta( $lesson_id , '_lesson_course', true);
279
        $wc_post_id = get_post_meta( $course_id, '_course_woocommerce_product', true );
280
        $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...
281
        if( isset ($product) && is_object($product) ){
282
283
            // valid product found
284
            $order_id = self::get_learner_course_active_order_id( $user_id, $course_id );
285
286
            // product has a successful order so this user may access the content
287
            // this function may only return false or the default
288
            // returning true may override other negatives which we don't want
289
            if( ! $order_id ){
290
291
                return false;
292
293
            }
294
295
        }
296
297
        // return the passed in value
298
        return $can_user_view_lesson;
299
300
    }
301
302
    /**
303
     * Add course link to order thank you and details pages.
304
     *
305
     * @since  1.4.5
306
     * @access public
307
     *
308
     * @return void
309
     */
310
    public static function course_link_from_order( ) {
311
312
        if( ! is_order_received_page() ){
313
            return;
314
        }
315
316
        $order_id = get_query_var( 'order-received' );
317
		$order = new WC_Order( $order_id );
318
319
		// exit early if not wc-completed or wc-processing
320
		if( 'wc-completed' != $order->post_status
321
            && 'wc-processing' != $order->post_status  ) {
322
            return;
323
        }
324
325
        $course_links = array(); // store the for links for courses purchased
326
		foreach ( $order->get_items() as $item ) {
327
328
            if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
329
330
                // If item has variation_id then its a variation of the product
331
                $item_id = $item['variation_id'];
332
333
            } else {
334
335
                //If not its real product set its id to item_id
336
                $item_id = $item['product_id'];
337
338
            } // End If Statement
339
340
            $user_id = get_post_meta( $order->id, '_customer_user', true );
341
342
            if( $user_id ) {
343
344
                // Get all courses for product
345
                $args = Sensei_Course::get_default_query_args();
346
                $args['meta_query'] = array( array(
347
                            'key' => '_course_woocommerce_product',
348
                            'value' => $item_id
349
                        ) );
350
                $args['orderby'] = 'menu_order date';
351
                $args['order'] = 'ASC';
352
353
                // loop through courses
354
                $courses = get_posts( $args );
355
                if( $courses && count( $courses ) > 0 ) {
356
357
                    foreach( $courses as $course ) {
358
359
                        $title = $course->post_title;
360
                        $permalink = get_permalink( $course->ID );
361
                        $course_links[] .= '<a href="' . esc_url( $permalink ) . '" >' . $title . '</a> ';
362
363
                    } // end for each
364
365
                    // close the message div
366
367
                }// end if $courses check
368
            }
369
        }// end loop through orders
370
371
        // add the courses to the WooCommerce notice
372
        if( ! empty( $course_links) ){
373
374
            $courses_html = _nx(
375
                'You have purchased the following course:',
376
                'You have purchased the following courses:',
377
                count( $course_links ),
378
                'Purchase thank you note on Checkout page. The course link(s) will be show', 'woothemes-sensei'
379
            );
380
381
            foreach( $course_links as $link ){
382
383
                $courses_html .= '<li>' . $link . '</li>';
384
385
            }
386
387
            $courses_html .= ' </ul>';
388
389
            wc_add_notice( $courses_html, 'success' );
390
        }
391
392
	} // end course_link_order_form
393
394
    /**
395
     * Show the message that a user should complete
396
     * their purchase if the course is in the cart
397
     *
398
     * This should be used within the course loop or single course page
399
     *
400
     * @since 1.9.0
401
     */
402
    public static function course_in_cart_message(){
403
404
        global $post;
405
406
        if( self::is_course_in_cart( $post->ID ) ){ ?>
407
408
            <div class="sensei-message info">
409
                <?php
410
411
                $cart_link =  '<a class="cart-complete" href="' . WC()->cart->get_checkout_url()
412
                              . '" title="' . __('complete purchase', 'woothemes-sensei') . '">'
413
                              . __('complete the purchase', 'woothemes-sensei') . '</a>';
414
415
                echo sprintf(  __('You have already added this Course to your cart. Please %1$s to access the course.', 'woothemes-sensei'), $cart_link );
416
417
                ?>
418
            </div>
419
        <?php }
420
421
    } // End sensei_woocommerce_in_cart_message()
422
423
    /**
424
     * Checks the cart to see if a course is in the cart.
425
     *
426
     * @param $course_id
427
     * @return bool
428
     */
429
    public static function is_course_in_cart( $course_id ){
430
431
        $wc_post_id = absint( get_post_meta( $course_id, '_course_woocommerce_product', true ) );
432
        $user_course_status_id = Sensei_Utils::user_started_course( $course_id , get_current_user_id() );
433
434
        if ( 0 < intval( $wc_post_id ) && ! $user_course_status_id ) {
435
436
            if ( self::is_product_in_cart( $wc_post_id ) ) {
437
438
                return true;
439
440
            }
441
442
        }
443
444
        return false;
445
446
    }// is_course_in_cart
447
448
    /**
449
     * Check the cart to see if the product is in the cart
450
     *
451
     * @param $product_id
452
     * @return bool
453
     */
454
    public static function is_product_in_cart( $product_id ){
455
456
        if ( 0 < $product_id ) {
457
458
            $product = wc_get_product( $product_id );
459
460
            $parent_id = '';
461
            if( isset( $product->variation_id ) && 0 < intval( $product->variation_id ) ) {
462
                $wc_product_id = $product->parent->id;
463
            }
464
            foreach( WC()->cart->get_cart() as $cart_item_key => $values ) {
465
466
                $cart_product = $values['data'];
467
                if( $product_id == $cart_product->id ) {
468
469
                    return true;
470
471
                }
472
473
            }
474
        } // End If Statement
475
476
        return false;
477
478
    } // end is_product_in_car
479
480
    /**
481
     * Get all free WooCommerce products
482
     *
483
     * @since 1.9.0
484
     *
485
     * @return array $free_products{
486
     *  @type int $wp_post_id
487
     * }
488
     */
489
    public static function get_free_product_ids(){
490
491
        return  get_posts( array(
492
            'post_type' => 'product',
493
            'posts_per_page' => '1000',
494
            'fields' => 'ids',
495
            'meta_query'=> array(
496
                'relation' => 'OR',
497
                array(
498
                    'key'=> '_regular_price',
499
                    'value' => 0,
500
                ),
501
                array(
502
                    'key'=> '_sale_price',
503
                    'value' => 0,
504
                ),
505
            ),
506
        ));
507
508
    }// end get free product query
509
510
    /**
511
     * The metat query for courses that are free
512
     *
513
     * @since 1.9.0
514
     * @return array $wp_meta_query_param
515
     */
516
    public static function get_free_courses_meta_query_args(){
517
518
        return array(
519
            'relation' => 'OR',
520
            array(
521
                'key'     => '_course_woocommerce_product',
522
                'value' => '-',
523
                'compare' => '=',
524
            ),
525
            array(
526
                'key'     => '_course_woocommerce_product',
527
                'value' => self::get_free_product_ids(),
528
                'compare' => 'IN',
529
            ),
530
        );
531
532
    }// get_free_courses_meta_query
533
534
    /**
535
     * The metat query for courses that are free
536
     *
537
     * @since 1.9.0
538
     * @return array $wp_query_meta_query_args_param
539
     */
540
    public static function get_paid_courses_meta_query_args(){
541
542
        $paid_product_ids = self::get_paid_product_ids();
543
544
        return array(
545
            array(
546
                'key'     => '_course_woocommerce_product',
547
                // when empty we give a false post_id to ensure the caller doesn't get any courses for their
548
                // query
549
                'value' => empty( $paid_product_ids  )? '-1000' : $paid_product_ids,
550
                'compare' => 'IN',
551
            ),
552
        );
553
554
    }// get_free_courses_meta_query
555
556
    /**
557
     * The WordPress Query args
558
     * for paid products on sale
559
     *
560
     * @since 1.9.0
561
     * @return array $product_query_args
562
     */
563 View Code Duplication
    public static function get_paid_products_on_sale_query_args(){
564
565
        $args = array(
566
                   'post_type' 		=> 'product',
567
                   'posts_per_page' 		=> 1000,
568
                   'orderby'         	=> 'date',
569
                   'order'           	=> 'DESC',
570
                   'suppress_filters' 	=> 0
571
        );
572
573
        $args[ 'fields' ]     = 'ids';
574
575
        $args[ 'meta_query' ] = array(
576
            'relation' => 'AND',
577
            array(
578
                'key'=> '_regular_price',
579
                'compare' => '>',
580
                'value' => 0,
581
            ),
582
            array(
583
                'key'=> '_sale_price',
584
                'compare' => '>',
585
                'value' => 0,
586
            ),
587
        );
588
589
        return $args;
590
591
    } // get_paid_products_on_sale_query_args
592
593
594
    /**
595
     * Return the WordPress query args for
596
     * products not on sale but that is not a free
597
     *
598
     * @since 1.9.0
599
     *
600
     * @return array
601
     */
602 View Code Duplication
    public static function get_paid_products_not_on_sale_query_args(){
603
604
        $args = array(
605
            'post_type' 		=> 'product',
606
            'posts_per_page' 		=> 1000,
607
            'orderby'         	=> 'date',
608
            'order'           	=> 'DESC',
609
            'suppress_filters' 	=> 0
610
        );
611
612
        $args[ 'fields' ]     = 'ids';
613
        $args[ 'meta_query' ] = array(
614
            'relation' => 'AND',
615
            array(
616
                'key'=> '_regular_price',
617
                'compare' => '>',
618
                'value' => 0,
619
            ),
620
            array(
621
                'key'=> '_sale_price',
622
                'compare' => '=',
623
                'value' => '',
624
            ),
625
        );
626
627
        return $args;
628
629
630
    } // get_paid_courses_meta_query
631
632
    /**
633
     * Get all WooCommerce non-free product id's
634
     *
635
     * @since 1.9.0
636
     *
637
     * @return array $woocommerce_paid_product_ids
638
     */
639
    public static function get_paid_product_ids(){
640
641
        // get all the paid WooCommerce products that has regular
642
        // and sale price greater than 0
643
        // will be used later to check for course with the id as meta
644
        $paid_product_ids_with_sale =  get_posts( self::get_paid_products_on_sale_query_args() );
645
646
        // get all the paid WooCommerce products that has regular price
647
        // greater than 0 without a sale price
648
        // will be used later to check for course with the id as meta
649
        $paid_product_ids_without_sale = get_posts( self::get_paid_products_not_on_sale_query_args() );
650
651
        // combine products ID's with regular and sale price grater than zero and those without
652
        // sale but regular price greater than zero
653
        $woocommerce_paid_product_ids = array_merge( $paid_product_ids_with_sale, $paid_product_ids_without_sale );
654
655
        // if
656
        if( empty($woocommerce_paid_product_ids) ){
657
            return array( );
658
        }
659
        return $woocommerce_paid_product_ids;
660
661
    }
662
663
    /**
664
     * Get all free courses.
665
     *
666
     * This course that have a WC product attached
667
     * that has a price or sale price of zero and
668
     * other courses with no WooCommerce products
669
     * attached.
670
     *
671
     * @since 1.9.0
672
     *
673
     * @param array $args
674
     * @return array
675
     */
676
    public static function get_free_courses( $args = array() ){
677
678
        $free_course_query_args = Sensei_Course::get_default_query_args();
679
        $free_course_query_args[ 'meta_query' ] = self::get_free_courses_meta_query_args();
680
681
	    if( !empty( $args ) ){
682
		    wp_parse_args( $args, $free_course_query_args  );
683
	    }
684
685
        // don't show any paid courses
686
        $courses = self::get_paid_courses();
687
        $ids = array();
688
        foreach( $courses as $course ){
689
            $ids[] = $course->ID;
690
        }
691
        $free_course_query_args[ 'post__not_in' ] =  $ids;
692
693
        return get_posts( $free_course_query_args );
694
695
    }
696
697
    /**
698
     * Return all products that are not free
699
     *
700
     * @since 1.9.0
701
     * @param array $args override default arg values
702
     *
703
     * @return array
704
     */
705
    public static function get_paid_courses( $args = array() ){
706
707
        $paid_course_query_args = Sensei_Course::get_default_query_args();
708
709
        $paid_course_query_args[ 'meta_query' ] = self::get_paid_courses_meta_query_args();
710
711
	    if( !empty( $args ) ){
712
		    wp_parse_args( $args, $paid_course_query_args  );
713
	    }
714
715
        return get_posts(  $paid_course_query_args );
716
    }
717
718
    /**
719
     * Show the WooCommerce add to cart button for the  current course
720
     *
721
     * The function will only show the button if
722
     * 1- the user can buy the course
723
     * 2- if they have completed their pre-requisite
724
     * 3- if the course has a valid product attached
725
     *
726
     * @since 1.9.0
727
     * @param int $course_id
728
     * @return string $html markup for the button or nothing if user not allowed to buy
729
     */
730
    public static function the_add_to_cart_button_html( $course_id ){
731
732
        if ( ! Sensei_Course::is_prerequisite_complete( $course_id ) || self::is_course_in_cart( $course_id ) ) {
733
            return '';
734
        }
735
736
        $wc_post_id = self::get_course_product_id( $course_id );
737
738
        // Check if customer purchased the product
739
        if ( self::has_customer_bought_product(  get_current_user_id(), $wc_post_id )
740
            || empty( $wc_post_id ) ) {
741
742
            return '';
743
744
        }
745
746
        // based on simple.php in WC templates/single-product/add-to-cart/
747
        // Get the product
748
        $product = self::get_product_object( $wc_post_id );
749
750
        // do not show the button for invalid products, non purchasable products, out
751
        // of stock product or if course is already in cart
752
        if ( ! isset ( $product )
753
            || ! is_object( $product )
754
            || ! $product->is_purchasable()
755
            || ! $product->is_in_stock()
756
            || self::is_course_in_cart( $wc_post_id ) ) {
757
758
            return '';
759
760
        }
761
762
        //
763
        // button  output:
764
        //
765
        ?>
766
767
        <form action="<?php echo esc_url( $product->add_to_cart_url() ); ?>"
768
              class="cart"
769
              method="post"
770
              enctype="multipart/form-data">
771
772
            <input type="hidden" name="product_id" value="<?php echo esc_attr( $product->id ); ?>" />
773
774
            <input type="hidden" name="quantity" value="1" />
775
776
            <?php if ( isset( $product->variation_id ) && 0 < intval( $product->variation_id ) ) { ?>
777
778
                <input type="hidden" name="variation_id" value="<?php echo $product->variation_id; ?>" />
779
                <?php if( isset( $product->variation_data ) && is_array( $product->variation_data ) && count( $product->variation_data ) > 0 ) { ?>
780
781
                    <?php foreach( $product->variation_data as $att => $val ) { ?>
782
783
                        <input type="hidden" name="<?php echo esc_attr( $att ); ?>" id="<?php echo esc_attr( str_replace( 'attribute_', '', $att ) ); ?>" value="<?php echo esc_attr( $val ); ?>" />
784
785
                    <?php } ?>
786
787
                <?php } ?>
788
789
            <?php } ?>
790
791
            <button type="submit" class="single_add_to_cart_button button alt">
792
                <?php $button_text = $product->get_price_html() . ' - ' . __( 'Purchase this Course', 'woothemes-sensei' ); ?>
793
                <?php
794
                /**
795
                 * Filter Add to Cart button text
796
                 *
797
                 * @since 1.9.1
798
                 *
799
                 * @param string $button_text
800
                 */
801
                echo apply_filters( 'sensei_wc_single_add_to_cart_button_text', $button_text );
802
                ?>
803
            </button>
804
805
        </form>
806
807
        <?php
808
    } // end the_add_to_cart_button_html
809
810
    /**
811
     * Alter the no permissions message on the single course page
812
     * Changes the message to a WooCommerce specific message.
813
     *
814
     * @since 1.9.0
815
     *
816
     * @param $message
817
     * @param $post_id
818
     *
819
     * @return string $message
820
     */
821
    public static function alter_no_permissions_message( $message, $post_id ){
822
823
        if( empty( $post_id ) || 'course'!=get_post_type( $post_id ) ){
824
            return  $message;
825
        }
826
827
        $product_id = self::get_course_product_id( $post_id );
828
829
        if( ! $product_id
830
            || ! self::has_customer_bought_product( get_current_user_id(),$product_id ) ){
831
832
            return $message;
833
834
        }
835
836
        ob_start();
837
        self::the_course_no_permissions_message( $post_id );
838
        $woocommerce_course_no_permissions_message = ob_get_clean();
839
840
        return $woocommerce_course_no_permissions_message ;
841
842
    }
843
    /**
844
     * Show the no permissions message when a user is logged in
845
     * and have not yet purchased the current course
846
     *
847
     * @since 1.9.0
848
     */
849
    public static function the_course_no_permissions_message( $course_id ){
850
851
        // login link
852
        $my_courses_page_id = intval( Sensei()->settings->settings[ 'my_course_page' ] );
853
	    $login_link =  '<a href="' . esc_url( get_permalink( $my_courses_page_id ) ) . '">' . __( 'log in', 'woothemes-sensei' ) . '</a>';
854
	    $wc_product_id =  self::get_course_product_id( $course_id );
855
856
	    if ( self::is_product_in_cart( $wc_product_id ) ) {
857
858
		    $cart_link = '<a href="' . wc_get_checkout_url() . '" title="' . __( 'Checkout','woocommerce' ) . '">' . __( 'checkout', 'woocommerce' ) . '</a>';
859
860
		    $message = sprintf( __( 'This course is already in your cart, please proceed to %1$s, to gain access.', 'woothemes-sensei' ), $cart_link );
861
		    ?>
862
		    <span class="add-to-cart-login">
863
		            <?php echo $message; ?>
864
		        </span>
865
866
		    <?php
867
868
	    } elseif ( is_user_logged_in() ) {
869
870
		    ?>
871
		    <style>
872
			    .sensei-message.alert {
873
				    display: none;
874
			    }
875
		    </style>
876
877
		    <?php
878
879
	    } else {
880
		    $message = sprintf( __( 'Or %1$s to access your purchased courses', 'woothemes-sensei' ), $login_link );
881
	        ?>
882
		        <span class="add-to-cart-login">
883
		            <?php echo $message; ?>
884
		        </span>
885
886
	        <?php
887
	    }
888
    }
889
890
    /**
891
     * Checks if a user has bought a product item.
892
     *
893
     * @since  1.9.0
894
     *
895
     * @param  int $user_id
896
     * @param  int $product_id
897
     *
898
     * @return bool
899
     */
900
    public static function has_customer_bought_product ( $user_id, $product_id ){
901
902
        $orders = self::get_user_product_orders( $user_id, $product_id );
903
904
        foreach ( $orders as $order_id ) {
905
906
            $order = new WC_Order( $order_id->ID );
907
908
            // wc-active is the subscriptions complete status
909
            if ( ! in_array( $order->post_status, array( 'wc-processing', 'wc-completed' ) )
910
                || ! ( 0 < sizeof( $order->get_items() ) )  ){
911
912
                continue;
913
914
            }
915
916
            foreach( $order->get_items() as $item ) {
917
918
                // Check if user has bought product
919
                if ( $item['product_id'] == $product_id || $item['variation_id'] == $product_id ) {
920
921
                    // Check if user has an active subscription for product
922
                    if( class_exists( 'WC_Subscriptions_Manager' ) ) {
923
                        $sub_key = wcs_get_subscription( $order );
924
                        if( $sub_key ) {
925
                            $sub = wcs_get_subscription( $sub_key );
926
                            if( $sub && isset( $sub['status'] ) ) {
927
                                if( 'active' == $sub['status'] ) {
928
                                    return true;
929
                                } else {
930
                                    return false;
931
                                }
932
                            }
933
                        }
934
                    }
935
936
                    // Customer has bought product
937
                    return true;
938
                } // End If Statement
939
940
            } // End For each item
941
942
        } // End For each order
943
944
	    // default is no order
945
	    return false;
946
947
    } // end has customer bought product
948
949
    /**
950
     * Return the product id for the given course
951
     *
952
     * @since 1.9.0
953
     *
954
     * @param int $course_id
955
     *
956
     * @return string $woocommerce_product_id or false if none exist
957
     *
958
     */
959
    public static function get_course_product_id( $course_id ){
960
961
        $product_id =  get_post_meta( $course_id, '_course_woocommerce_product', true );
962
963
        if( empty( $product_id ) || 'product' != get_post_type( $product_id ) ){
964
            return false;
965
        }
966
967
        return $product_id;
968
969
    }
970
971
    /**
972
     * Alter the body classes adding WooCommerce to the body
973
     *
974
     * Speciall cases where this is needed is template no-permissions.php
975
     *
976
     * @param array $classes
977
     * @return array
978
     */
979
    public static function add_woocommerce_body_class( $classes ){
980
981
        if( ! in_array( 'woocommerce', $classes ) && defined( 'SENSEI_NO_PERMISSION' ) && SENSEI_NO_PERMISSION ){
982
983
            $classes[] ='woocommerce';
984
985
        }
986
987
        return $classes;
988
989
    }
990
991
    /**
992
     * Responds to when a subscription product is purchased
993
     *
994
     * @since   1.2.0
995
     * @since  1.9.0 move to class Sensei_WC
996
     *
997
     * @param   WC_Order $order
998
     *
999
     * @return  void
1000
     */
1001
    public static function activate_subscription(  $order ) {
1002
1003
        $order_user = get_user_by('id', $order->user_id);
1004
        $user['ID'] = $order_user->ID;
1005
        $user['user_login'] = $order_user->user_login;
1006
        $user['user_email'] = $order_user->user_email;
1007
        $user['user_url'] = $order_user->user_url;
1008
1009
        // Run through each product ordered
1010
        if ( ! sizeof($order->get_items() )>0 ) {
1011
1012
            return;
1013
1014
        }
1015
1016
        foreach($order->get_items() as $item) {
1017
1018
            $product_type = '';
1019
1020
            if (isset($item['variation_id']) && $item['variation_id'] > 0) {
1021
1022
                $item_id = $item['variation_id'];
1023
                $product_type = 'subscription_variation';
1024
1025
            } else {
1026
1027
                $item_id = $item['product_id'];
1028
1029
            } // End If Statement
1030
1031
            $_product = self::get_product_object( $item_id, $product_type );
1032
1033
            // Get courses that use the WC product
1034
            $courses = array();
1035
1036
            if ( ! in_array( $product_type, self::get_subscription_types() ) ) {
1037
1038
                $courses = Sensei()->course->get_product_courses( $item_id );
1039
1040
            } // End If Statement
1041
1042
            // Loop and add the user to the course.
1043
            foreach ( $courses as $course_item ){
1044
1045
                Sensei_Utils::user_start_course( intval( $user['ID'] ), $course_item->ID  );
1046
1047
            } // End For Loop
1048
1049
        } // End For Loop
1050
1051
    } // End activate_subscription()
1052
1053
    /**
1054
     * Adds detail to to the WooCommerce order
1055
     *
1056
     * @since   1.4.5
1057
     * @since 1.9.0 function moved to class Sensei_WC and renamed from sensei_woocommerce_email_course_details to email_course_details
1058
     *
1059
     * @param   WC_Order $order
1060
     *
1061
     * @return  void
1062
     */
1063
    public static function email_course_details(  $order ){
1064
1065
        global $woocommerce;
1066
1067
        // exit early if not wc-completed or wc-processing
1068
        if( 'wc-completed' != $order->post_status
1069
            && 'wc-processing' != $order->post_status  ) {
1070
            return;
1071
        }
1072
1073
        $order_items = $order->get_items();
1074
        $order_id = $order->id;
1075
1076
        //If object have items go through them all to find course
1077
        if ( 0 < sizeof( $order_items ) ) {
1078
1079
            $course_details_html =  '<h2>' . __( 'Course details', 'woothemes-sensei' ) . '</h2>';
1080
            $order_contains_courses = false;
1081
1082
1083
            foreach ( $order_items as $item ) {
1084
1085
                $product_type = '';
1086
                if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
1087
                    // If item has variation_id then its from variation
1088
                    $item_id = $item['variation_id'];
1089
                    $product_type = 'variation';
1090
                } else {
1091
                    // If not its real product set its id to item_id
1092
                    $item_id = $item['product_id'];
1093
                } // End If Statement
1094
1095
                $user_id = get_post_meta( $order_id, '_customer_user', true );
1096
1097
                if( $user_id ) {
1098
1099
                    // Get all courses for product
1100
                    $args = array(
1101
                        'posts_per_page' => -1,
1102
                        'post_type' => 'course',
1103
                        'meta_query' => array(
1104
                            array(
1105
                                'key' => '_course_woocommerce_product',
1106
                                'value' => $item_id
1107
                            )
1108
                        ),
1109
                        'orderby' => 'menu_order date',
1110
                        'order' => 'ASC',
1111
                    );
1112
                    $courses = get_posts( $args );
1113
1114
                    if( $courses && count( $courses ) > 0 ) {
1115
1116
                        foreach( $courses as $course ) {
1117
1118
                            $title = $course->post_title;
1119
                            $permalink = get_permalink( $course->ID );
1120
                            $order_contains_courses = true;
1121
                            $course_details_html .=  '<p><strong>' . sprintf( __( 'View course: %1$s', 'woothemes-sensei' ), '</strong><a href="' . esc_url( $permalink ) . '">' . $title . '</a>' ) . '</p>';
1122
                        }
1123
1124
1125
                    } // end if has courses
1126
1127
                } // end if $userPid
1128
1129
            } // end for each order item
1130
1131
            // Output Course details
1132
            if( $order_contains_courses ){
1133
1134
                echo $course_details_html;
1135
1136
            }
1137
1138
1139
        } // end if  order items not empty
1140
1141
    }// end email_course_details
1142
1143
    /**
1144
     * sensei_woocommerce_complete_order description
1145
     * @since   1.0.3
1146
     * @access  public
1147
     * @param   int $order_id WC order ID
1148
     * @return  void
1149
     */
1150
    public static function complete_order ( $order_id = 0 ) {
1151
1152
        $order_user = array();
1153
1154
        // Check for WooCommerce
1155
        if ( Sensei_WC::is_woocommerce_active() && ( 0 < $order_id ) ) {
1156
            // Get order object
1157
            $order = new WC_Order( $order_id );
1158
1159
	        if ( ! in_array( $order->get_status(), array( 'complete', 'processing' ) ) ) {
1160
1161
		        return;
1162
1163
	        }
1164
1165
            $user = get_user_by( 'id', $order->get_user_id() );
1166
            $order_user['ID'] = $user->ID;
1167
            $order_user['user_login'] = $user->user_login;
1168
            $order_user['user_email'] = $user->user_email;
1169
            $order_user['user_url'] = $user->user_url;
1170
            // Run through each product ordered
1171
            if ( 0 < sizeof( $order->get_items() ) ) {
1172
1173
                foreach( $order->get_items() as $item ) {
1174
1175
                    $product_type = '';
1176
                    if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
1177
1178
                        $item_id = $item['variation_id'];
1179
                        $product_type = 'variation';
1180
1181
                    } else {
1182
1183
                        $item_id = $item['product_id'];
1184
1185
                    } // End If Statement
1186
1187
                    $_product = Sensei_WC::get_product_object( $item_id, $product_type );
1188
1189
                    // Get courses that use the WC product
1190
                    $courses = Sensei()->course->get_product_courses( $_product->id );
1191
1192
                    // Loop and update those courses
1193
                    foreach ( $courses as $course_item ) {
1194
1195
                        $update_course = self::course_update( $course_item->ID, $order_user );
1196
1197
                    } // End For Loop
1198
1199
                } // End For Loop
1200
1201
            } // End If Statement
1202
            // Add meta to indicate that payment has been completed successfully
1203
            update_post_meta( $order_id, 'sensei_payment_complete', '1' );
1204
1205
        } // End If Statement
1206
1207
    } // End sensei_woocommerce_complete_order()
1208
1209
    /**
1210
     * Responds to when an order is cancelled.
1211
     *
1212
     * @since   1.2.0
1213
     * @since   1.9.0 Move function to the Sensei_WC class
1214
     * @param   integer| WC_Order $order_id order ID
1215
     * @return  void
1216
     */
1217
    public static function cancel_order ( $order_id ) {
1218
1219
		// Get order object
1220
		if( is_object( $order_id ) ){
1221
1222
			$order = $order_id;
1223
1224
		}else{
1225
1226
			$order = new WC_Order( $order_id );
1227
		}
1228
1229
		if ( ! in_array( $order->get_status(), array( 'cancelled', 'refunded' ) ) ) {
1230
1231
			return;
1232
1233
		}
1234
1235
        // Run through each product ordered
1236
        if ( 0 < sizeof( $order->get_items() ) ) {
1237
1238
            // Get order user
1239
            $user_id = $order->__get( 'user_id' );
1240
1241
            foreach( $order->get_items() as $item ) {
1242
1243
                $product_type = '';
1244
                if ( isset( $item['variation_id'] ) && ( 0 < $item['variation_id'] ) ) {
1245
1246
                    $item_id = $item['variation_id'];
1247
                    $product_type = 'variation';
1248
1249
                } else {
1250
1251
                    $item_id = $item['product_id'];
1252
1253
                } // End If Statement
1254
1255
                $_product = Sensei_WC::get_product_object( $item_id, $product_type );
1256
1257
                // Get courses that use the WC product
1258
                $courses = array();
1259
                $courses = Sensei()->course->get_product_courses( $item_id );
1260
1261
                // Loop and update those courses
1262
                foreach ($courses as $course_item){
1263
1264
	                if( self::has_customer_bought_product( $user_id, $course_item->ID ) ){
1265
		                continue;
1266
	                }
1267
                    // Check and Remove course from courses user meta
1268
                    $dataset_changes = Sensei_Utils::sensei_remove_user_from_course( $course_item->ID, $user_id );
1269
1270
                } // End For Loop
1271
1272
            } // End For Loop
1273
1274
        } // End If Statement
1275
1276
    } // End sensei_woocommerce_cancel_order()
1277
1278
    /**
1279
     * Returns the WooCommerce Product Object
1280
     *
1281
     * The code caters for pre and post WooCommerce 2.2 installations.
1282
     *
1283
     * @since   1.1.1
1284
     * @access  public
1285
     * @param   integer $wc_product_id Product ID or Variation ID
1286
     * @param   string  $product_type  '' or 'variation'
1287
     * @return   WC_Product $wc_product_object
1288
     */
1289
    public static function get_product_object ( $wc_product_id = 0, $product_type = '' ) {
1290
1291
        $wc_product_object = false;
1292
        if ( 0 < intval( $wc_product_id ) ) {
1293
1294
            // Get the product
1295
            if ( function_exists( 'wc_get_product' ) ) {
1296
1297
                $wc_product_object = wc_get_product( $wc_product_id ); // Post WC 2.3
1298
1299
            } elseif ( function_exists( 'get_product' ) ) {
1300
1301
                $wc_product_object = get_product( $wc_product_id ); // Post WC 2.0
1302
1303
            } else {
1304
1305
                // Pre WC 2.0
1306
                if ( 'variation' == $product_type || 'subscription_variation' == $product_type ) {
1307
1308
                    $wc_product_object = new WC_Product_Variation( $wc_product_id );
1309
1310
                } else {
1311
1312
                    $wc_product_object = new WC_Product( $wc_product_id );
1313
1314
                } // End If Statement
1315
1316
            } // End If Statement
1317
1318
        } // End If Statement
1319
1320
        return $wc_product_object;
1321
1322
    } // End sensei_get_woocommerce_product_object()
1323
1324
    /**
1325
     * If customer has purchased the course, update Sensei to indicate that they are taking the course.
1326
     *
1327
     * @since  1.0.0
1328
     * @since 1.9.0 move to class Sensei_WC
1329
     *
1330
     * @param  int 			$course_id  (default: 0)
1331
     * @param  array/Object $order_user (default: array()) Specific user's data.
1332
     *
1333
     * @return bool|int
1334
     */
1335
    public static function course_update ( $course_id = 0, $order_user = array()  ) {
1336
1337
        global $current_user;
1338
1339
        if ( ! isset( $current_user ) || !$current_user->ID > 0 ) return false;
1340
1341
        $data_update = false;
1342
1343
        // Get the product ID
1344
        $wc_post_id = get_post_meta( intval( $course_id ), '_course_woocommerce_product', true );
1345
1346
        // Check if in the admin
1347
        if ( is_admin() ) {
1348
1349
            $user_login = $order_user['user_login'];
1350
            $user_email = $order_user['user_email'];
1351
            $user_url = $order_user['user_url'];
1352
            $user_id = $order_user['ID'];
1353
1354
        } else {
1355
1356
            $user_login = $current_user->user_login;
1357
            $user_email = $current_user->user_email;
1358
            $user_url = $current_user->user_url;
1359
            $user_id = $current_user->ID;
1360
1361
        } // End If Statement
1362
1363
        // This doesn't appear to be purely WooCommerce related. Should it be in a separate function?
1364
        $course_prerequisite_id = (int) get_post_meta( $course_id, '_course_prerequisite', true );
1365
        if( 0 < absint( $course_prerequisite_id ) ) {
1366
1367
            $prereq_course_complete = Sensei_Utils::user_completed_course( $course_prerequisite_id, intval( $user_id ) );
1368
            if ( ! $prereq_course_complete ) {
1369
1370
                // Remove all course user meta
1371
                return Sensei_Utils::sensei_remove_user_from_course( $course_id, $user_id );
1372
1373
            }
1374
        }
1375
1376
        $is_user_taking_course = Sensei_Utils::user_started_course( intval( $course_id ), intval( $user_id ) );
1377
1378
        if ( ! $is_user_taking_course
1379
            && Sensei_WC::is_woocommerce_active()
1380
            && 0 < $wc_post_id
1381
            && Sensei_WC::has_customer_bought_product( $user_id, $wc_post_id ) ) {
1382
1383
	            $activity_logged = Sensei_Utils::user_start_course( intval( $user_id ), intval( $course_id ) );
1384
1385
	            if ( true == $activity_logged ) {
1386
1387
		            $is_user_taking_course = true;
1388
1389
	            } // End If Statement
1390
1391
        }// end if is user taking course
1392
1393
        return $is_user_taking_course;
1394
1395
    } // End course_update()
1396
1397
    /**
1398
     * Disable guest checkout if a course product is in the cart
1399
     *
1400
     * @since 1.1.0
1401
     * @since 1.9.0 move to class Sensei_WC
1402
     *
1403
     * @param  boolean $guest_checkout Current guest checkout setting
1404
     *
1405
     * @return boolean                 Modified guest checkout setting
1406
     */
1407
    public static function disable_guest_checkout( $guest_checkout ) {
1408
1409
        if( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
1410
1411
            if( isset( WC()->cart->cart_contents ) && count( WC()->cart->cart_contents ) > 0 ) {
1412
1413
                foreach( WC()->cart->cart_contents as $cart_key => $product ) {
1414
                    if( isset( $product['product_id'] ) ) {
1415
1416
                        $args = array(
1417
                            'posts_per_page' => -1,
1418
                            'post_type' => 'course',
1419
                            'meta_query' => array(
1420
                                array(
1421
                                    'key' => '_course_woocommerce_product',
1422
                                    'value' => $product['product_id']
1423
                                )
1424
                            )
1425
                        );
1426
1427
                        $posts = get_posts( $args );
1428
1429
                        if( $posts && count( $posts ) > 0 ) {
1430
1431
                            foreach( $posts as $course ) {
1432
                                $guest_checkout = '';
1433
                                break;
1434
1435
                            }
1436
                        }
1437
1438
                    }
1439
1440
                }
1441
1442
            }
1443
        }
1444
1445
        return $guest_checkout;
1446
1447
    }// end disable_guest_checkout
1448
1449
    /**
1450
     * Change order status with virtual products to completed
1451
     *
1452
     * @since  1.1.0
1453
     * @since 1.9.0 move to class Sensei_WC
1454
     *
1455
     * @param string $order_status
1456
     * @param int $order_id
1457
     *
1458
     * @return string
1459
     **/
1460
    public static function virtual_order_payment_complete( $order_status, $order_id ) {
1461
1462
        $order = new WC_Order( $order_id );
1463
1464
        if ( ! isset ( $order ) ) return '';
1465
1466
        if ( $order_status == 'wc-processing' && ( $order->post_status == 'wc-on-hold' || $order->post_status == 'wc-pending' || $order->post_status == 'wc-failed' ) ) {
1467
1468
            $virtual_order = true;
1469
1470
            if ( count( $order->get_items() ) > 0 ) {
1471
1472
                foreach( $order->get_items() as $item ) {
1473
1474
                    if ( $item['product_id'] > 0 ) {
1475
                        $_product = $order->get_product_from_item( $item );
1476
                        if ( ! $_product->is_virtual() ) {
1477
1478
                            $virtual_order = false;
1479
                            break;
1480
1481
                        } // End If Statement
1482
1483
                    } // End If Statement
1484
1485
                } // End For Loop
1486
1487
            } // End If Statement
1488
1489
            // virtual order, mark as completed
1490
            if ( $virtual_order ) {
1491
1492
                return 'completed';
1493
1494
            } // End If Statement
1495
1496
        } // End If Statement
1497
1498
        return $order_status;
1499
1500
    }// end virtual_order_payment_complete
1501
1502
1503
    /**
1504
     * Determine if the user has and active subscription to give them access
1505
     * to the requested resource.
1506
     *
1507
     * @since 1.9.0
1508
     *
1509
     * @param  boolean$user_access_permission
1510
     * @param  integer $user_id
1511
     * @return boolean $user_access_permission
1512
     */
1513
    public static function get_subscription_permission( $user_access_permission , $user_id ){
1514
1515
        global $post;
1516
1517
        // ignore the current case if the following conditions are met
1518
        if ( ! class_exists( 'WC_Subscriptions' ) || empty( $user_id )
1519
            || ! in_array( $post->post_type, array( 'course','lesson','quiz' ) )
1520
            || ! wcs_user_has_subscription( $user_id) ){
1521
1522
            return $user_access_permission;
1523
1524
        }
1525
1526
        // at this user has a subscription
1527
        // is the subscription on the the current course?
1528
1529
        $course_id = 0;
1530
        if ( 'course' == $post->post_type ){
1531
1532
            $course_id = $post->ID;
1533
1534
        } elseif ( 'lesson' == $post->post_type ) {
1535
1536
            $course_id = Sensei()->lesson->get_course_id( $post->ID );
1537
1538
        } else {
1539
1540
            $lesson_id =  Sensei()->quiz->get_lesson_id( $post->ID );
1541
            $course_id = Sensei()->lesson->get_course_id( $lesson_id );
1542
1543
        }
1544
1545
        // if the course has no subscription WooCommerce product attached to return the permissions as is
1546
        $product_id = Sensei_WC::get_course_product_id( $course_id );
1547
        $product = wc_get_product( $product_id );
1548
        if( ! in_array( $product->get_type(), self::get_subscription_types() ) ){
1549
1550
            return $user_access_permission;
1551
1552
        }
1553
1554
        // give access if user has active subscription on the product otherwise restrict it.
1555
        // also check if the user was added to the course directly after the subscription started.
1556
        if( wcs_user_has_subscription( $user_id, $product_id, 'active'  )
1557
            || wcs_user_has_subscription( $user_id, $product_id, 'pending-cancel'  )
1558
            || self::was_user_added_without_subscription( $user_id, $product_id, $course_id  ) ){
1559
1560
            $user_access_permission = true;
1561
1562
        }else{
1563
1564
            $user_access_permission = false;
1565
            // do not show the WC permissions message
1566
            remove_filter( 'sensei_the_no_permissions_message', array( 'Sensei_WC', 'alter_no_permissions_message' ), 20, 2 );
1567
            Sensei()->permissions_message['title'] = __( 'No active subscription', 'woothemes-sensei' );
1568
            Sensei()->permissions_message['message'] = __( 'Sorry, you do not have an access to this content without an active subscription.', 'woothemes-sensei' );
1569
        }
1570
1571
        return $user_access_permission;
1572
1573
    } // end get_subscription_permission
1574
1575
	/**
1576
	 * @since 1.9.0
1577
	 *
1578
	 * @param $has_user_started_course
1579
	 * @param $course_id
1580
	 * @param $user_id
1581
	 *
1582
	 * @return bool $has_user_started_course
1583
	 */
1584
	public static function get_subscription_user_started_course( $has_user_started_course, $course_id, $user_id ){
1585
1586
		if ( ! is_user_logged_in( ) ) {
1587
1588
			return $has_user_started_course;
1589
1590
		}
1591
1592
		// cached user course access for this process instance
1593
		global $sensei_wc_subscription_access_store;
1594
		if ( ! is_array( $sensei_wc_subscription_access_store ) ) {
1595
			$sensei_wc_subscription_access_store = array();
1596
		}
1597
1598
		// user temp cached data so we don't output the mesage again
1599
		$user_data_index_key = $course_id .'_' . $user_id;
1600
		if ( isset( $sensei_wc_subscription_access_store[ $user_data_index_key  ] ) ) {
1601
1602
			return $sensei_wc_subscription_access_store[ $user_data_index_key ];
1603
1604
		} else {
1605
1606
			if( empty( $course_id ) || empty( $user_id ) ){
1607
				return $has_user_started_course;
1608
			}
1609
1610
			// if the course has no subscription WooCommerce product attached to return the permissions as is
1611
			$product_id = Sensei_WC::get_course_product_id( $course_id );
1612
			$product = wc_get_product( $product_id );
1613
			if( ! in_array( $product->get_type(), self::get_subscription_types() ) ){
1614
1615
				return $has_user_started_course;
1616
1617
			}
1618
1619
			// give access if user has active subscription on the product otherwise restrict it.
1620
			// also check if the user was added to the course directly after the subscription started.
1621
			if( wcs_user_has_subscription( $user_id, $product_id, 'active'  )
1622
			    || wcs_user_has_subscription( $user_id, $product_id, 'pending-cancel'  )
1623
			    || self::was_user_added_without_subscription( $user_id, $product_id, $course_id  )  ){
1624
1625
				$has_user_started_course = true;
1626
1627
			} else {
1628
1629
				$has_user_started_course = false;
1630
1631
			}
1632
			$sensei_wc_subscription_access_store[ $user_data_index_key ] = $has_user_started_course;
1633
			return $has_user_started_course;
1634
		}
1635
1636
	}
1637
1638
    /**
1639
     * Get all the valid subscription types.
1640
     *
1641
     * @since 1.9.0
1642
     * @return array
1643
     */
1644
    public static function get_subscription_types(){
1645
1646
        return array( 'subscription','subscription_variation','variable-subscription' );
1647
1648
    }
1649
1650
    /**
1651
     * Compare the user's subscriptions end date with the date
1652
     * the user was added to the course. If the user was added after
1653
     * the subscription ended they were manually added and this will return
1654
     * true.
1655
     *
1656
     * Important to note that all subscriptions for the user is compared.
1657
     *
1658
     * @since 1.9.0
1659
     *
1660
     * @param $user_id
1661
     * @param $product_id
1662
     * @param $course_id
1663
     *
1664
     * @return bool
1665
     */
1666
    public static function was_user_added_without_subscription($user_id, $product_id, $course_id ){
1667
1668
        $course_start_date = '';
1669
        $subscription_start_date = '';
1670
        $is_a_subscription ='';
1671
        $was_user_added_without_subscription = false;
1672
1673
        // if user is not on the course they were not added
1674
	    remove_filter( 'sensei_user_started_course',     array( 'Sensei_WC', 'get_subscription_user_started_course' ), 10, 3 );
1675
        if( ! Sensei_Utils::user_started_course( $course_id, $user_id ) ){
1676
1677
            return false;
1678
1679
        }
1680
1681
        // if user doesn't have a subscription and is taking the course
1682
        // they were added manually
1683
        if ( ! wcs_user_has_subscription($user_id, $product_id)
1684
            && Sensei_Utils::user_started_course( $course_id, get_current_user_id() )  ){
1685
1686
            return true;
1687
1688
        }
1689
1690
	    add_filter( 'sensei_user_started_course',     array( 'Sensei_WC', 'get_subscription_user_started_course' ), 10, 3 );
1691
1692
        $course_status =  Sensei_Utils::user_course_status( $course_id, $user_id );
1693
1694
        // comparing dates setup data
1695
        $course_start_date = date_create( $course_status->comment_date );
1696
        $subscriptions = wcs_get_users_subscriptions( $user_id );
1697
1698
        // comparing every subscription
1699
        foreach( $subscriptions as $subscription ){
1700
1701
            // for the following statuses we know the user was not added
1702
            // manually
1703
            $status = $subscription->get_status();
1704
            if ( in_array( $status, array( 'pending-canceled', 'active', 'on-hold', 'pending' ) ) ) {
1705
1706
                continue;
1707
1708
            }
1709
1710
            $current_subscription_start_date = date_create( $subscription->modified_date );
1711
1712
            // is the last updated subscription date newer than course start date
1713
            if (  $current_subscription_start_date > $course_start_date   ) {
1714
1715
                return false;
1716
1717
            }
1718
1719
        }
1720
1721
        return $was_user_added_without_subscription;
1722
    }
1723
1724
	/**
1725
	 * Get all the orders for a specific user and product combination
1726
	 *
1727
	 * @param int $user_id
1728
	 * @param $product_id
1729
	 *
1730
	 * @return array $orders
1731
	 */
1732
	public static function get_user_product_orders( $user_id =  0, $product_id ) {
1733
1734
		$args = array(
1735
			'numberposts' => -1,
1736
			'post_type' => 'shop_order',
1737
			'meta_key'    => '_customer_user',
1738
			'meta_value'  => intval( $user_id ),
1739
		);
1740
1741
		if( class_exists( 'WC_Subscriptions_Manager' ) ) {
1742
			$args['post_type'] = array( 'shop_order', 'shop_subscription' );
1743
		}
1744
1745
		return get_posts( $args );
1746
1747
	}
1748
1749
	/**
1750
	 * Determine if a course can be purchased. Purchasable
1751
	 * courses have valid products attached. These can also be products
1752
	 * with price of Zero.
1753
	 *
1754
	 *
1755
	 * @since 1.9.0
1756
	 *
1757
	 * @param int $course_id
1758
	 *
1759
	 * @return bool
1760
	 */
1761
	public static function is_course_purchasable( $course_id = 0 ){
1762
1763
		if( ! self::is_woocommerce_active() ){
1764
			return false;
1765
		}
1766
		$course_product = wc_get_product( self::get_course_product_id( $course_id ) );
1767
1768
		return $course_product->is_purchasable();
1769
1770
	}
1771
1772
}// end Sensei_WC
1773