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 / course-bundle.php

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

305 lines 10.3 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 use StoreEngine\Utils\Formatting;
6 use StoreEngine\Utils\Helper;
7 use StoreEngine\Utils\Template;
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 class CourseBundle extends AbstractIntegration {
14 const ID = 'storeengine/course-bundle';
15
16 public function setup(): void {
17 $this->id = static::ID;
18 $this->label = __( 'Academy Course Bundle', 'storeengine' );
19 $this->items_label = __( 'Select Course (Only paid courses)', 'storeengine' );
20 $this->logo = STOREENGINE_ASSETS_URI . 'images/integrations/academy-lms.svg';
21 $this->isEnabled = defined( 'ACADEMY_VERSION' );
22 }
23
24 public function dispatch_hooks(): void {
25 add_action( 'academy_pro/templates/course_bundle/single_bundle_enroll_content', [ $this, 'single_bundle_price' ] );
26 add_action( 'academy_pro/templates/course_bundle/single_bundle_enroll_content', [ $this, 'single_bundle_enroll_content_form' ] );
27 add_action( 'academy_pro/templates/course_bundle/single_bundle_enroll_content', [ $this, 'single_enroll_content' ] );
28 }
29
30 public function single_bundle_price() {
31 $bundle_id = get_the_ID();
32 $type = get_post_meta( $bundle_id, 'academy_course_bundle_type', true );
33 // return if the monetized engine is not storeengine
34 if ( 'storeengine' !== \Academy\Helper::monetization_engine() || 'free' === $type ) {
35 return;
36 }
37
38 $bundle_id = get_the_ID();
39 $price = '';
40 $regular_price = '';
41 $sale_price = '';
42
43 if ( \Academy\Helper::is_active_storeengine() ) {
44 $integration_repository = Helper::get_integration_repository_by_id( $this->get_id(), $bundle_id );
45 if ( ! empty( $integration_repository ) ) {
46 $integration_repository = current( $integration_repository );
47 $price = $integration_repository->price;
48 $sale_price = $price->get_price();
49 $price = $price->get_price_html();
50 }
51 }
52
53 ob_start();
54
55 \AcademyPro\Helper::get_template(
56 'course-bundle/enroll/price.php',
57 apply_filters(
58 'academy_pro/single/bundle_price_args', // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
59 array(
60 'enrolled' => false,
61 'is_paid' => true,
62 'regular_price' => $regular_price,
63 'sale_price' => $sale_price,
64 'price' => $price,
65 ),
66 $bundle_id
67 )
68 );
69
70 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
71 echo apply_filters( 'academy/templates/single_course/price_content_args', ob_get_clean(), $bundle_id );
72 }
73
74 public function single_enroll_content() {
75 // return if the monetized engine is not storeengine
76 if ( 'storeengine' !== \Academy\Helper::monetization_engine() ) {
77 return;
78 }
79
80 $bundle_id = get_the_ID();
81 $duration = \AcademyProCourseBundle\Helper::get_bundle_duration( $bundle_id );
82 $total_lessons = \AcademyProCourseBundle\Helper::get_bundle_lessons( $bundle_id );
83 $total_enrolled = $this->get_bundle_enrolled( $bundle_id );
84 $max_students = \AcademyProCourseBundle\Helper::get_max_students( $bundle_id );
85 $total_enroll_count_status = \Academy\Helper::get_settings( 'is_enabled_course_single_enroll_count', true );
86 $last_update = get_the_modified_time( get_option( 'date_format' ), $bundle_id );
87
88 ob_start();
89
90 \AcademyPro\Helper::get_template(
91 'course-bundle/enroll/content.php',
92 apply_filters(
93 'academy_pro/single/bundle_content_args', // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
94 array(
95 'duration' => $duration,
96 'total_lessons' => $total_lessons,
97 'total_enroll_count_status' => $total_enroll_count_status,
98 'total_enrolled' => $total_enrolled,
99 'max_students' => $max_students,
100 'last_update' => $last_update,
101 ),
102 $bundle_id
103 )
104 );
105
106 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
107 echo apply_filters( 'academy/templates/single_course/enroll_content', ob_get_clean(), $bundle_id );
108 }
109
110 public function single_bundle_enroll_content_form() {
111 global $post;
112
113 $engine = \Academy\Helper::get_settings( 'monetization_engine' );
114
115 if ( 'alms_course_bundle' !== get_post_type( get_the_ID() ) || 'storeengine' !== $engine ) {
116 return;
117 }
118
119 $integrations = $this->get_integration_repository( $post->ID );
120 if ( empty( $integrations ) || ! count( $integrations ) ) {
121 return;
122 }
123
124 $purchased_bundles = maybe_unserialize( get_user_meta( get_current_user_id(), '_academy_pro_purchased_course_bundles', true ) );
125 $purchased_bundles = ! empty( $purchased_bundles ) ? $purchased_bundles : [];
126
127 if ( in_array( $post->ID, $purchased_bundles ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
128 Template::get_template( 'integrations/academy-lms/course-bundle-continue.php' );
129
130 return;
131 }
132
133 Template::get_template( 'integrations/academy-lms/add-to-cart.php', [
134 'integrations' => $integrations,
135 'integration_count' => count( $integrations ),
136 ] );
137 }
138
139 public function get_items( array $args = [] ): array {
140 $args = wp_parse_args( $args, [
141 'search' => '',
142 ] );
143 $search = $args['search'];
144
145 $course_query = new \WP_Query(
146 [
147 'post_type' => 'academy_courses',
148 'post_status' => 'publish',
149 's' => $search,
150 'per_page' => 10,
151 'meta_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
152 'key' => 'academy_course_type',
153 'value' => 'paid',
154 'compare' => '=',
155 ],
156 ]
157 );
158
159 $items = [];
160
161 if ( $course_query->have_posts() ) {
162 $items = array_map(
163 function ( $post ) {
164 return (object) [
165 'value' => $post->ID,
166 'label' => $post->post_title,
167 ];
168 },
169 $course_query->posts
170 );
171 }
172
173 return $items;
174 }
175
176 protected function purchase_created( $integration, $order ) {
177 $bundle_id = $integration->get_integration_id();
178 $purchased_bundles = $this->get_purchased_bundle_ids( $order->get_user_id() );
179
180 if ( in_array( $bundle_id, $purchased_bundles ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
181 return;
182 }
183
184 foreach ( $this->get_bundled_course_ids( $bundle_id ) as $course_id ) {
185 \Academy\Helper::do_enroll( $course_id, $order->get_user_id() );
186 }
187
188 $purchased_bundles[] = $bundle_id;
189
190 $this->save_purchased_bundle_ids( $order->get_user_id(), $purchased_bundles );
191 }
192
193 protected function purchase_removed( $integration, $order ) {
194 global $wpdb;
195 $bundles = $this->get_purchased_bundle_ids( $order->get_user_id() );
196 if ( ! in_array( $integration->get_integration_id(), $bundles ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
197 // Early bail, if not purchased.
198 return;
199 }
200
201 $course_ids = $this->get_bundled_course_ids( $integration->get_integration_id() );
202 $course_ids_placeholder = implode( ',', array_fill( 0, count( $course_ids ), '%d' ) );
203
204 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
205 $wpdb->query(
206 $wpdb->prepare(
207 "
208 UPDATE {$wpdb->posts}
209 SET post_status='on-hold'
210 WHERE post_author=%d
211 AND post_parent IN ($course_ids_placeholder)
212 AND post_type='academy_enrolled';
213 ",
214 $order->get_user_id(),
215 ...$course_ids
216 )
217 );
218
219 $ids = $wpdb->get_col(
220 $wpdb->prepare(
221 "
222 SELECT ID FROM {$wpdb->posts}
223 WHERE post_status='on-hold'
224 AND post_author=%d
225 AND post_parent IN ($course_ids_placeholder)
226 AND post_type='academy_enrolled';
227 ",
228 $order->get_user_id(),
229 ...$course_ids
230 )
231 );
232 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
233
234 if ( ! empty( $ids ) ) {
235 array_map( 'clean_post_cache', $ids );
236 }
237
238 $this->save_purchased_bundle_ids(
239 $order->get_user_id(),
240 array_filter( $bundles, fn ( $bid ) => $bid !== $integration->get_integration_id() )
241 );
242 }
243
244 private function get_bundle_enrolled( int $bundle_id ): int {
245 global $wpdb;
246 $enrolled = 0;
247
248 $integration_repositories = Helper::get_integration_repository_by_id( $this->get_id(), $bundle_id );
249
250 if ( empty( $integration_repositories ) ) {
251 return $enrolled;
252 }
253
254 $price_ids = [];
255
256 foreach ( $integration_repositories as $integration_repository ) {
257 $price_ids[] = $integration_repository->price->get_id();
258 }
259
260 $price_ids_formatter = implode( ',', array_fill( 0, count( $price_ids ), '%d' ) );
261
262 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
263 $enrolled_ids = $wpdb->get_var(
264 $wpdb->prepare(
265 "
266 SELECT COUNT(*)
267 FROM (
268 SELECT DISTINCT opl.order_id
269 FROM {$wpdb->prefix}storeengine_order_product_lookup AS opl
270 INNER JOIN {$wpdb->prefix}storeengine_orders AS o ON opl.order_id = o.id
271 WHERE opl.price_id IN ($price_ids_formatter)
272 AND o.status = 'completed'
273 ) AS subquery
274 ",
275 ...$price_ids
276 )
277 );
278 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
279
280 return (int) $enrolled_ids;
281 }
282
283 private function get_bundled_course_ids( int $bundle_id ): array {
284 $courses = get_post_meta( $bundle_id, 'academy_course_bundle_courses_ids', true );
285
286 if ( empty( $courses ) || ! is_array( $courses ) ) {
287 $courses = [];
288 }
289
290 return wp_list_pluck( $courses, 'value' );
291 }
292
293 private function get_purchased_bundle_ids( int $user_id ): array {
294 return Formatting::parse_ids( get_user_meta( $user_id, '_academy_pro_purchased_course_bundles', true ) );
295 }
296
297 private function save_purchased_bundle_ids( int $user_id, ?array $bundle_ids = null ): void {
298 update_user_meta(
299 $user_id,
300 '_academy_pro_purchased_course_bundles',
301 Formatting::parse_ids( $bundle_ids )
302 );
303 }
304 }
305