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