PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / trunk
Tutor LMS – eLearning and online course solution vtrunk
4.0.0 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 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.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / classes / TutorEDD.php
tutor / classes Last commit date
Addons.php 5 hours ago Admin.php 5 hours ago Ajax.php 5 hours ago Announcements.php 5 hours ago Assets.php 5 hours ago Backend_Page_Trait.php 1 year ago BaseController.php 1 year ago Config.php 5 hours ago Container.php 11 months ago Course.php 5 hours ago Course_Embed.php 3 years ago Course_Filter.php 5 hours ago Course_List.php 5 hours ago Course_Settings_Tabs.php 5 hours ago Course_Widget.php 1 year ago Custom_Validation.php 5 hours ago Dashboard.php 5 hours ago Earnings.php 9 months ago FormHandler.php 5 hours ago Frontend.php 5 hours ago Gutenberg.php 1 year ago Icon.php 5 hours ago Input.php 5 hours ago Instructor.php 5 hours ago Instructors_List.php 5 hours ago Lesson.php 5 hours ago Options_V2.php 5 hours ago Permalink.php 5 hours ago Post_types.php 1 day ago Private_Course_Access.php 5 hours ago Q_And_A.php 5 hours ago Question_Answers_List.php 11 months ago Quiz.php 5 hours ago QuizBuilder.php 5 hours ago Quiz_Attempts_List.php 5 hours ago RestAPI.php 2 years ago Reviews.php 5 hours ago Rewrite_Rules.php 2 years ago SampleCourse.php 5 hours ago Shortcode.php 5 hours ago Singleton.php 1 year ago Student.php 5 hours ago Students_List.php 1 year ago Taxonomies.php 1 year ago Template.php 5 hours ago Theme_Compatibility.php 3 years ago Tools.php 1 year ago Tools_V2.php 4 weeks ago Tutor.php 5 hours ago TutorEDD.php 5 hours ago Tutor_Base.php 2 years ago Tutor_Setup.php 5 hours ago Upgrader.php 5 hours ago User.php 5 hours ago UserPreference.php 5 hours ago Utils.php 5 hours ago Video_Stream.php 3 years ago WhatsNew.php 10 months ago Withdraw.php 5 hours ago Withdraw_Requests_List.php 11 months ago WooCommerce.php 5 hours ago
TutorEDD.php
401 lines
1 <?php
2 /**
3 * Integrate EDD
4 *
5 * @package Tutor\PaymentIntegration
6 * @author Themeum <support@themeum.com>
7 * @link https://themeum.com
8 * @since 1.0.0
9 */
10
11 namespace TUTOR;
12
13 defined( 'ABSPATH' ) || exit;
14
15 use Tutor\Helpers\QueryHelper;
16 use Tutor\Helpers\UrlHelper;
17 use Tutor\Models\EnrollmentModel;
18
19 /**
20 * Manage EDD integration
21 *
22 * @since 1.0.0
23 */
24 class TutorEDD extends Tutor_Base {
25
26 /**
27 * Register hooks
28 *
29 * @since 1.0.0
30 */
31 public function __construct() {
32 parent::__construct();
33
34 add_filter( 'tutor_monetization_options', array( $this, 'tutor_monetization_options' ) );
35
36 $monetize_by = tutils()->get_option( 'monetize_by' );
37 if ( 'edd' !== $monetize_by ) {
38 return;
39 }
40
41 $edd_path = WP_PLUGIN_DIR . '/easy-digital-downloads/easy-digital-downloads.php';
42 register_deactivation_hook( $edd_path, array( $this, 'edd_deactivation_handler' ) );
43
44 add_filter( 'tutor_course_sell_by', fn() => 'edd' );
45 add_action( 'save_post_' . $this->course_post_type, array( $this, 'save_course_meta' ) );
46
47 /**
48 * Is Course Purchasable
49 */
50 add_filter( 'is_course_purchasable', array( $this, 'is_course_purchasable' ), 10, 2 );
51 add_filter( 'get_tutor_course_price', array( $this, 'get_tutor_course_price' ), 10, 2 );
52 add_action( 'edd_insert_payment', array( $this, 'edd_order_created' ), 10, 2 );
53 add_action( 'edd_update_payment_status', array( $this, 'edd_update_payment_status' ), 10, 3 );
54
55 // @since 4.0.0
56 add_filter( 'tutor_order_history_card_template', fn( $template ) => tutor_get_template( 'dashboard.account.billing.edd-order-history-card' ) );
57 add_filter( 'tutor_order_history_status_options', array( $this, 'filter_order_history_status_options' ), 10, 2 );
58 add_filter( 'tutor_get_orders_by_user_id', array( $this, 'filter_tutor_get_orders_by_user_id' ), 10, 3 );
59 }
60
61 /**
62 * Handle EDD deactivation.
63 *
64 * @since 4.0.0
65 *
66 * @return void
67 */
68 public function edd_deactivation_handler() {
69 if ( 'edd' === tutor_utils()->get_option( 'monetize_by' ) ) {
70 tutor_utils()->update_option( 'monetize_by', 'free' );
71 }
72 }
73
74 /**
75 * Add Option for tutor
76 *
77 * @since 1.0.0
78 *
79 * @param array $attr option attrs.
80 *
81 * @return mixed
82 */
83 public function add_options( $attr ) {
84 $attr['tutor_edd'] = array(
85 'label' => __( 'EDD', 'tutor' ),
86
87 'sections' => array(
88 'general' => array(
89 'label' => __( 'General', 'tutor' ),
90 'desc' => __( 'Tutor Course Attachments Settings', 'tutor' ),
91 'fields' => array(
92 'enable_tutor_edd' => array(
93 'type' => 'checkbox',
94 'label' => __( 'Enable EDD', 'tutor' ),
95 'desc' => __( 'This will enable sell your product via EDD', 'tutor' ),
96 ),
97 ),
98 ),
99 ),
100 );
101 return $attr;
102 }
103
104 /**
105 * Returning monetization options
106 *
107 * @since 1.3.5
108 *
109 * @param array $arr monetization attrs.
110 *
111 * @return array
112 */
113 public function tutor_monetization_options( $arr ) {
114 $has_edd = tutils()->has_edd();
115 if ( $has_edd ) {
116 $arr['edd'] = __( 'Easy Digital Downloads', 'tutor' );
117 }
118 return $arr;
119 }
120
121 /**
122 * Save course meta
123 *
124 * @since 1.0.0
125 *
126 * @param int $post_ID post id.
127 *
128 * @return void
129 */
130 public function save_course_meta( $post_ID ) {
131
132 $product_id = Input::post( Course::COURSE_PRODUCT_ID_META, '' );
133
134 if ( '-1' !== $product_id ) {
135 $product_id = (int) $product_id;
136 if ( $product_id ) {
137 update_post_meta( $post_ID, Course::COURSE_PRODUCT_ID_META, $product_id );
138 update_post_meta( $product_id, '_tutor_product', 'yes' );
139 }
140 } else {
141 delete_post_meta( $post_ID, Course::COURSE_PRODUCT_ID_META );
142 }
143
144 do_action( 'save_tutor_course', $post_ID, get_post( $post_ID ) );
145 }
146
147 /**
148 * Check if course is purchase able
149 *
150 * @param bool $bool default value.
151 * @param int $course_id course id.
152 *
153 * @return boolean
154 */
155 public function is_course_purchasable( $bool, $course_id ) {
156 if ( ! tutor_utils()->has_edd() ) {
157 return false;
158 }
159
160 $course_id = tutor_utils()->get_post_id( $course_id );
161 $price_type = tutor_utils()->price_type( $course_id );
162 $has_product_id = tutor_utils()->get_course_product_id( $course_id );
163
164 if ( Course::PRICE_TYPE_PAID === $price_type && $has_product_id ) {
165 return true;
166 }
167
168 return false;
169 }
170
171 /**
172 * Get course price
173 *
174 * @since 1.0.0
175 *
176 * @param string $price course price.
177 * @param int $course_id course id.
178 *
179 * @return mixed
180 */
181 public function get_tutor_course_price( $price, $course_id ) {
182 $product_id = tutor_utils()->get_course_product_id( $course_id );
183 if ( tutils()->has_edd() ) {
184 return edd_price( $product_id, false );
185 }
186 }
187
188 /**
189 * Prepare enrollment status.
190 *
191 * @since 4.0.0
192 *
193 * @param string $order_status order status.
194 *
195 * @return string
196 */
197 private static function prepare_enrollment_status( $order_status ) {
198 return 'complete' === $order_status
199 ? EnrollmentModel::STATUS_COMPLETED
200 : ( 'pending' === $order_status ? EnrollmentModel::STATUS_PENDING : EnrollmentModel::STATUS_CANCEL );
201 }
202
203 /**
204 * Handle enrollment.
205 *
206 * @since 4.0.0
207 *
208 * @param int $order_id The order ID.
209 * @param string $enrollment_type The enrollment type.
210 * @param string $enrollment_status The enrollment status.
211 *
212 * @return void
213 */
214 private static function handle_enrollment( int $order_id, string $enrollment_type, string $enrollment_status ): void {
215 $order = edd_get_order( $order_id );
216 if ( ! $order || ! is_object( $order ) ) {
217 return;
218 }
219
220 $order_items = edd_get_order_items( array( 'order_id' => $order->id ) );
221 if ( empty( $order_items ) ) {
222 return;
223 }
224
225 $user_id = $order->user_id;
226
227 foreach ( $order_items as $order_item ) {
228 $product_id = $order_item->product_id;
229 $product_has_course = tutor_utils()->product_belongs_with_course( $product_id );
230
231 if ( ! $product_has_course ) {
232 continue;
233 }
234
235 $course_id = (int) $product_has_course->post_id;
236
237 if ( 'new' === $enrollment_type ) {
238 // New enrollment.
239 add_filter( 'tutor_enroll_data', fn( $data ) => array_merge( $data, array( 'post_status' => $enrollment_status ) ) );
240 EnrollmentModel::do_enroll( $course_id, $order_id, $user_id );
241 } else {
242 // Update enrollment.
243 $is_enrolled = EnrollmentModel::is_enrolled( $course_id, $user_id, false );
244 if ( $is_enrolled ) {
245 EnrollmentModel::update_enrollments( $enrollment_status, array( $is_enrolled->ID ) );
246 }
247 }
248 }
249 }
250
251 /**
252 * Handle enrollment when a new EDD order is created.
253 *
254 * Creates enrollment with appropriate status when EDD 3.0+ has fully
255 * built the order with its items. This avoids the issue where
256 * cart_details/order_items are empty during the initial
257 * edd_update_payment_status hook (old_status = 'new').
258 *
259 * @since 4.0.0
260 *
261 * @param int $order_id The order ID.
262 * @param array $order_data The order data array containing cart details.
263 *
264 * @return void
265 */
266 public function edd_order_created( $order_id, $order_data ) {
267 if ( ! $order_id ) {
268 return;
269 }
270
271 $enrollment_status = self::prepare_enrollment_status( $order_data['status'] );
272 self::handle_enrollment( $order_id, 'new', $enrollment_status );
273 }
274
275 /**
276 * Update enrollment status when EDD payment status changes.
277 *
278 * Skips the initial order creation (old_status = 'new') since enrollment
279 * is handled by edd_order_created. Uses EDD 3.0+ API to retrieve order
280 * items reliably.
281 *
282 * @since 1.0.0
283 * @since 4.0.0 Refactored to use EDD 3.0+ API and skip initial order creation.
284 *
285 * @param int $payment_id payment id.
286 * @param string $new_status payment's new status.
287 * @param string $old_status payment's old status.
288 *
289 * @return void
290 */
291 public function edd_update_payment_status( $payment_id, $new_status, $old_status ) {
292 if ( empty( $new_status ) || ! $payment_id ) {
293 return;
294 }
295
296 /**
297 * If 'new' === $old_status, EDD does not store cart_details.
298 * So skip the action, as enrollment is handled by edd_order_created method.
299 *
300 * @since 4.0.0
301 */
302 if ( 'new' === $old_status ) {
303 return;
304 }
305
306 $enrollment_status = self::prepare_enrollment_status( $new_status );
307 self::handle_enrollment( $payment_id, 'update', $enrollment_status );
308 }
309
310 /**
311 * Filter order history status options.
312 *
313 * @since 4.0.0
314 *
315 * @param array $options the status options.
316 * @param string $seleted the selected status.
317 *
318 * @return array<array{label: string, value: string, count: int, url: string, active: bool}>
319 */
320 public function filter_order_history_status_options( $options, $seleted ) {
321 $url = get_pagenum_link();
322 $user_id = get_current_user_id();
323
324 $statuses = array_merge( array( 'all' => 'All' ), edd_get_payment_statuses() );
325 $options = array();
326
327 foreach ( $statuses as $key => $status ) {
328 $params = array(
329 'meta_key' => Course::IS_TUTOR_ORDER_FOR_COURSE_META,
330 'user_id' => $user_id,
331 'status' => $key,
332 'count' => true,
333 );
334
335 $count_query = new \EDD_Payments_Query( $params );
336 $count = $count_query->get_payments();
337
338 $options[] = array(
339 'label' => $status,
340 'value' => $key,
341 'count' => $count,
342 'url' => UrlHelper::add_query_params( $url, array( 'data' => $key ) ),
343 'active' => $key === $seleted || ( empty( $key ) && 'all' === $seleted ),
344 );
345 }
346
347 return $options;
348 }
349
350 /**
351 * Filter tutor get orders by user id.
352 *
353 * @since 4.0.0
354 *
355 * @param object $data data.
356 * @param int $user_id the user id.
357 * @param array $args the query args.
358 *
359 * @return object
360 */
361 public function filter_tutor_get_orders_by_user_id( $data, $user_id, $args ) {
362 if ( ! $user_id ) {
363 return $data;
364 }
365
366 $status = Input::sanitize( $args['status'] ?? '' );
367 $start_date = Input::sanitize( $args['start_date'] ?? '' );
368 $end_date = Input::sanitize( $args['end_date'] ?? '' );
369 $order = QueryHelper::get_valid_sort_order( $args['order'] ?? 'DESC' );
370 $limit = intval( $args['limit'] ?? 0 );
371 $offset = intval( $args['offset'] ?? 0 );
372
373 $params = array(
374 'meta_key' => Course::IS_TUTOR_ORDER_FOR_COURSE_META,
375 'user' => $user_id,
376 'status' => $status,
377 'start_date' => $start_date,
378 'end_date' => $end_date,
379 'limit' => $limit,
380 'offset' => $offset,
381 'order' => $order,
382 );
383
384 if ( empty( $status ) || 'all' === $status ) {
385 unset( $params['status'] );
386 }
387
388 $edd_query = new \EDD_Payments_Query( $params );
389 $results = $edd_query->get_payments();
390
391 $params['count'] = true;
392 $total_count_query = new \EDD_Payments_Query( $params );
393 $total_count = $total_count_query->get_payments();
394
395 $data->results = $results;
396 $data->total_count = $total_count;
397
398 return $data;
399 }
400 }
401