PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.2.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.2.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 59 releases
storeengine / includes / integrations / membership-addon.php

membership-addon.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.2.0, at includes/integrations/membership-addon.php

512 lines 15.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace StoreEngine\Integrations;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 die();
7 }
8
9 use StoreEngine\Addons\Membership\HelperAddon;
10 use StoreEngine\Addons\Subscription\Classes\Subscription;
11 use StoreEngine\Classes\Data\IntegrationRepositoryData;
12 use StoreEngine\Classes\Integration;
13 use StoreEngine\Classes\Order;
14 use StoreEngine\Classes\Product\SimpleProduct;
15 use StoreEngine\Utils\Formatting;
16 use StoreEngine\Utils\Helper;
17 use StoreEngine\Utils\Template;
18 use WP_Post;
19
20 class MembershipAddon extends AbstractIntegration {
21 const ID = 'storeengine/membership-addon';
22
23 public function setup(): void {
24 $this->id = static::ID;
25 $this->label = __( 'StoreEngine Membership', 'storeengine' );
26 $this->items_label = __( 'Select an Access Group', 'storeengine' );
27 $this->logo = STOREENGINE_ASSETS_URI . 'images/logo.svg';
28 $this->isEnabled = Helper::get_addon_active_status( 'membership' );
29 }
30
31 public function dispatch_hooks(): void {
32 add_filter( 'storeengine/membership/before_content_protect', [ $this, 'stop_from_redirecting' ], 10, 2 );
33
34 // AcademyLMS supports.
35 add_filter( 'academy/templates/single_course/enroll_form', [ $this, 'change_enrollment_form' ], 20, 2 );
36 add_filter( 'academy/course/get_course_type', [ $this, 'modify_course_type' ], 10, 2 );
37 add_filter( 'academy/before_enroll_course_type', [ $this, 'modify_course_type' ], 10, 2 );
38 }
39
40 public function get_items( array $args = [] ): array {
41 $args = wp_parse_args( $args, [
42 'search' => '',
43 ] );
44 $search = $args['search'];
45
46 $access_group_query = new \WP_Query(
47 [
48 'post_type' => STOREENGINE_MEMBERSHIP_POST_TYPE,
49 'post_status' => 'publish',
50 's' => $search,
51 'per_page' => 10,
52 ]
53 );
54
55 $items = [];
56
57 if ( $access_group_query->have_posts() ) {
58 $items = array_map(
59 function ( $post ) {
60 return (object) [
61 'value' => $post->ID,
62 'label' => $post->post_title,
63 ];
64 },
65 $access_group_query->posts
66 );
67 }
68
69 return $items;
70 }
71
72 protected function purchase_created( $integration, $order ) {
73 $this->update_user_meta( $order->get_customer_id(), $integration, 'completed' );
74 $this->update_course_enroll_status( $this->generate_course_ids( $integration ), $order->get_customer_id() );
75 }
76
77 protected function purchase_removed( $integration, $order ) {
78 $integration_id = $integration->get_integration_id();
79 $purchased = $this->get_purchased_membership_ids( $order->get_customer_id() );
80
81 if ( empty( $purchased ) || ! in_array( $integration_id, $purchased, true ) ) {
82 return;
83 }
84
85 $purchased_index = array_search( $integration_id, $purchased );
86
87 if ( $purchased_index !== false ) {
88 unset( $purchased[ $purchased_index ] );
89 $this->save_purchased_membership_ids( $order->get_customer_id(), $purchased );
90 }
91
92 $user_meta = get_user_meta( $order->get_customer_id(), '_storeengine_memberships', true ) ?? [];
93
94 if ( empty( $user_meta ) ) {
95 return;
96 }
97
98 $new_user_meta = [];
99
100 foreach ( $user_meta as $user_meta_item ) {
101 if ( $user_meta_item['customer_id'] === $order->get_customer_id() && $user_meta_item['price_id'] === $order_item->get_price_id() ) {
102 continue;
103 }
104 $new_user_meta[] = $user_meta_item;
105 }
106
107 if ( count( $new_user_meta ) !== count( $user_meta ) ) {
108 update_user_meta( $order->get_customer_id(), '_storeengine_memberships', $new_user_meta );
109 }
110
111 $access_group_meta = get_post_meta( $integration_id, '_storeengine_membership_content_protect_types', true );
112
113 if ( ! is_array( $access_group_meta ) ) {
114 return;
115 }
116
117 $course_ids = [];
118
119 foreach ( $access_group_meta['specifics'] ?? [] as $specific ) {
120 $post_id = $this->get_post_id( $specific['value'] );
121 if ( $post_id && 'academy_courses' === get_post_type( $post_id ) ) {
122 $course_ids[] = $post_id;
123 continue;
124 }
125
126 $arr = explode( '--single-', $specific['value'] );
127 if ( 2 !== count( $arr ) ) {
128 continue;
129 }
130 $term_id = str_replace( 'tax-', '', $arr[0] );
131 $taxonomy = $arr[1];
132 $posts = get_posts( [
133 'post_type' => 'academy_courses',
134 'fields' => 'ids',
135 'posts_per_page' => - 1,
136 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
137 [
138 'taxonomy' => $taxonomy,
139 'field' => 'term_id',
140 'terms' => $term_id,
141 ],
142 ],
143 ] );
144 foreach ( $posts as $post ) {
145 $course_ids[] = $post;
146 }
147 }
148
149 $this->update_course_enroll_status( $course_ids, $order->get_customer_id(), 'on-hold' );
150 }
151
152 private function get_purchased_membership_ids( int $user_id ): array {
153 return Formatting::parse_ids( get_user_meta( $user_id, '_storeengine_purchased_membership_ids', true ) );
154 }
155
156 private function save_purchased_membership_ids( int $user_id, ?array $membership_ids = null ): void {
157 update_user_meta(
158 $user_id,
159 '_storeengine_purchased_membership_ids',
160 Formatting::parse_ids( $membership_ids )
161 );
162 }
163
164 protected function update_user_meta( int $user_id, Integration $integration, string $order_status ) {
165 $integration_id = $integration->get_integration_id();
166 $purchased = $this->get_purchased_membership_ids( $user_id );
167
168 if ( ! in_array( $integration_id, $purchased, true ) ) {
169 $purchased[] = $integration_id;
170 $this->save_purchased_membership_ids( $user_id, $purchased );
171 }
172
173 $user_meta = get_user_meta( $user_id, '_storeengine_memberships', true );
174 if ( ! is_array( $user_meta ) ) {
175 $user_meta = [];
176 }
177
178 $updated = false;
179 if ( ! empty( $user_meta ) ) {
180 foreach ( $user_meta as $index => $user_meta_item ) {
181 if ( $user_meta_item['customer_id'] === $user_id && $user_meta_item['price_id'] === $integration->get_price_id() ) {
182 $user_meta[ $index ]['order_status'] = $order_status;
183 $updated = true;
184 }
185 }
186 }
187
188 if ( ! $updated ) {
189 $user_meta[] = [
190 'customer_id' => $user_id,
191 'price_id' => $integration->get_price_id(),
192 'order_status' => $order_status,
193 ];
194 }
195 update_user_meta( $user_id, '_storeengine_memberships', $user_meta );
196 }
197
198 protected function generate_course_ids( Integration $integration ) {
199 $integration_id = $integration->get_integration_id();
200 $access_meta = get_post_meta( $integration_id, '_storeengine_membership_content_protect_types', true ) ?? [];
201 if ( ! is_array( $access_meta ) ) {
202 return [];
203 }
204 $items = $access_meta['specifics'] ?? [];
205 $course_ids = [];
206 foreach ( $items as $item ) {
207 $post_id = $this->get_post_id( $item['value'] );
208 if ( $post_id && 'academy_courses' === get_post_type( $post_id ) ) {
209 $course_ids[] = $post_id;
210 }
211
212 if ( ! $post_id ) {
213 $item_arr = explode( '--single-', $item['value'] );
214 if ( count( $item_arr ) !== 2 || 0 !== strpos( $item_arr[0], 'tax-' ) ) {
215 continue;
216 }
217 $term_id = (int) str_replace( 'tax-', '', $item_arr[0] );
218 $taxonomy = $item_arr[1];
219
220 $posts = get_posts( [
221 'post_type' => 'academy_courses',
222 'fields' => 'ids',
223 'posts_per_page' => - 1,
224 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
225 [
226 'taxonomy' => $taxonomy,
227 'field' => 'term_id',
228 'terms' => $term_id,
229 ],
230 ],
231 ] );
232 foreach ( $posts as $post ) {
233 $course_ids[] = $post;
234 }
235 }
236 }
237
238 return $course_ids;
239 }
240
241 protected function get_post_id( $str ) {
242 if ( preg_match( '/^post-(\d+)-/', $str, $matches ) ) {
243 return (int) $matches[1];
244 }
245
246 return false;
247 }
248
249 protected function update_course_enroll_status( array $course_ids, int $user_id, string $status = 'completed' ) {
250 global $wpdb;
251
252 $course_ids = Formatting::parse_ids( $course_ids );
253
254 if ( empty( $course_ids ) ) {
255 return;
256 }
257
258 $course_ids_formatter = implode( ',', array_fill( 0, count( $course_ids ), '%d' ) );
259
260 if ( empty( $course_ids_formatter ) ) {
261 return;
262 }
263
264 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
265 $wpdb->query(
266 $wpdb->prepare(
267 "
268 UPDATE {$wpdb->posts}
269 SET post_status=%s
270 WHERE post_author=%d
271 AND post_parent IN ($course_ids_formatter)
272 AND post_type='academy_enrolled';
273 ",
274 $status,
275 $user_id,
276 ...$course_ids
277 )
278 );
279
280 $ids = $wpdb->get_col(
281 $wpdb->prepare(
282 "
283 SELECT ID FROM {$wpdb->posts}
284 WHERE post_status=%s
285 AND post_author=%d
286 AND post_parent IN ($course_ids_formatter)
287 AND post_type='academy_enrolled';
288 ",
289 $status,
290 $user_id,
291 ...$course_ids
292 )
293 );
294 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
295
296 if ( ! empty( $ids ) ) {
297 array_map( 'clean_post_cache', $ids );
298 }
299 }
300
301 public function stop_from_redirecting( $membership_id, ?WP_Post $post = null ): bool {
302 if ( ! $post ) {
303 return $membership_id;
304 }
305
306 if ( 'academy_courses' !== $post->post_type ) {
307 return $membership_id;
308 }
309
310 return false;
311 }
312
313 public function change_enrollment_form( $form, $course_id ) {
314 [ $integrations, $is_available_membership ] = $this->get_course_integrations( $course_id );
315
316 if ( empty( $integrations ) || ! $is_available_membership ) {
317 return $form;
318 }
319
320 $integrations = $this->get_only_membership_integrations( $integrations );
321
322 $is_paid_course = 'paid' === $this->course_type( (int) $course_id );
323 $is_enrolled = $this->is_enrolled( $course_id );
324
325 $is_purchased = self::is_purchased_membership( $integrations );
326 if ( $is_enrolled || $is_purchased ) {
327 return $form;
328 }
329
330 return $this->generate_enrollment_form( $form, $this->is_only_membership( $course_id ), $integrations, $is_paid_course, $is_enrolled );
331 }
332
333 public function generate_enrollment_form( $form, $is_only_membership, $integrations, $is_paid_course, $is_enrolled ): string {
334 if ( $is_paid_course && ! $is_enrolled && ! $is_only_membership ) {
335 return $form . '<div class="storeengine-or-divider"><span>' . __( 'OR', 'storeengine' ) . '</span></div>' . $this->get_enrollment_template( $integrations );
336 }
337
338 return $form . $this->get_enrollment_template( $integrations );
339 }
340
341 public function get_enrollment_template( $integrations ) {
342 ob_start();
343 Template::get_template( 'integrations/academy-lms/membership-enrollment-form.php', [
344 'product_id' => current( $integrations )->price->get_product_id(),
345 'integrations' => $integrations,
346 'count' => count( $integrations ),
347 ] );
348
349 return ob_get_clean();
350 }
351
352 public function load_enrolled_template( $course_id ) {
353 ob_start();
354 Template::get_template( 'integrations/academy-lms/membership-reload-form.php', [
355 'course_id' => $course_id,
356 ] );
357
358 return ob_get_clean();
359 }
360
361 public function is_enrolled( $course_id, $customer_id = 0 ) {
362 if ( ! $customer_id ) {
363 $customer_id = get_current_user_id();
364 }
365
366 return \Academy\Helper::is_enrolled( $course_id, $customer_id, 'on-hold' );
367 }
368
369 public function modify_course_type( $course_type, $course_id ): string {
370 if ( ! is_user_logged_in() ) {
371 return $course_type;
372 }
373
374 [ $integrations ] = $this->get_course_integrations( $course_id );
375
376 if ( count( $integrations ) > 0 ) {
377 $course_type = 'paid';
378 $is_purchased = self::is_purchased_membership( $integrations );
379 if ( $is_purchased ) {
380 return 'free';
381 }
382 }
383
384 return $course_type;
385 }
386
387 protected function get_course_integrations( $course_id ): array {
388 $all_plans = HelperAddon::get_all_plans( $course_id );
389
390 $integrations = [];
391 foreach ( $all_plans as $plan_id => $plan ) {
392 $integrations = array_merge( $integrations, $this->get_integration_repository( $plan_id ) );
393 }
394
395 $is_available_membership = self::is_available_membership( $integrations );
396
397 return [ $integrations, $is_available_membership ];
398 }
399
400 public function course_type( $course_id ) {
401 return \Academy\Helper::get_course_type( $course_id );
402 }
403
404 public static function is_purchased_membership( $integrations ): bool {
405 $subscription_price_ids = [];
406
407 foreach ( $integrations as $integration ) {
408 if ( static::ID !== $integration->integration->get_provider() ) {
409 continue;
410 }
411
412 if ( Helper::is_purchase_the_membership( $integration->integration->get_product_id(), $integration->integration->get_price_id() ) ) {
413 if ( 'subscription' === $integration->price->get_type() ) {
414 $subscription_price_ids[] = $integration->price->get_id();
415 continue;
416 }
417
418 return true;
419 }
420 }
421
422 if ( ! empty( $subscription_price_ids ) ) {
423 $subscription_price_ids_formatter = implode( ',', array_fill( 0, count( $subscription_price_ids ), '%d' ) );
424
425 global $wpdb;
426
427 // phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
428 $result = $wpdb->get_row(
429 $wpdb->prepare( "
430 SELECT o.id
431 FROM
432 {$wpdb->prefix}storeengine_order_item_meta oim
433 INNER JOIN {$wpdb->prefix}storeengine_order_items oi ON oi.order_item_id = oim.order_item_id
434 INNER JOIN {$wpdb->prefix}storeengine_orders o ON o.id = oi.order_id
435 WHERE
436 oim.meta_key = '_price_id'
437 AND oim.meta_value IN ($subscription_price_ids_formatter)
438 AND o.type = 'subscription'
439 AND o.status = 'active'
440 LIMIT 1
441 ", ...$subscription_price_ids )
442 );
443 // phpcs:enable PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
444
445 if ( $result ) {
446 return true;
447 }
448 }
449
450 return false;
451 }
452
453 public static function is_available_membership( $integrations, $is_only = false ): bool {
454 $flag = 0;
455 foreach ( $integrations as $integration_item ) {
456 if ( static::ID === $integration_item->integration->get_provider() ) {
457 $flag ++;
458 }
459 }
460
461 if ( $is_only ) {
462 if ( (int) count( $integrations ) === $flag ) {
463 return true;
464 } else {
465 return false;
466 }
467 }
468
469 if ( $flag ) {
470 return true;
471 }
472
473 return false;
474 }
475
476 /**
477 * @param $integrations
478 *
479 * @return IntegrationRepositoryData[]
480 */
481 public function get_only_membership_integrations( $integrations ): array {
482 $only_membership = [];
483 foreach ( $integrations as $integration_item ) {
484 if ( $this->get_id() === $integration_item->integration->get_provider() ) {
485 $only_membership[] = $integration_item;
486 }
487 }
488
489 return $only_membership;
490 }
491
492 protected function is_only_membership( int $course_id ): bool {
493 global $wpdb;
494
495 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
496 return ! $wpdb->get_var(
497 $wpdb->prepare(
498 "
499 SELECT id
500 FROM {$wpdb->prefix}storeengine_integrations
501 WHERE
502 provider = %s
503 AND integration_id = %d;
504 ",
505 AcademyLms::ID,
506 $course_id
507 )
508 );
509 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
510 }
511 }
512