Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/tutor/classes/Earnings.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + <?php
2 + /**
3 + * Manage earnings
4 + *
5 + * @package Tutor\Ecommerce
6 + * @author Themeum <support@themeum.com>
7 + * @link https://themeum.com
8 + * @since 3.0.0
9 + */
10 +
11 + namespace TUTOR;
12 +
13 + use Tutor\Ecommerce\Ecommerce;
14 + use TUTOR\Singleton;
15 + use Tutor\Models\OrderModel;
16 + use Tutor\Helpers\QueryHelper;
17 +
18 + /**
19 + * Manage earnings
20 + */
21 + class Earnings extends Singleton {
22 + /**
23 + * Constant for earning process by
24 + *
25 + * @since 3.8.2
26 + */
27 + const PROCESS_BY_TUTOR = 'tutor';
28 + const PROCESS_BY_WOOCOMMERCE = 'woocommerce';
29 +
30 + /**
31 + * Error message for the invalid earning data
32 + *
33 + * @since 1.0.0
34 + *
35 + * @var string
36 + */
37 + const INVALID_DATA_MSG = 'Invalid earning data';
38 +
39 + /**
40 + * Earning table name
41 + *
42 + * @since 3.0.0
43 + *
44 + * @var string
45 + */
46 + private $earning_table;
47 +
48 + /**
49 + * Order id
50 + *
51 + * @since 3.0.0
52 + *
53 + * @var int
54 + */
55 + private $order_id;
56 +
57 + /**
58 + * Keep earning data here
59 + *
60 + * @since 3.0.0
61 + *
62 + * @var array
63 + */
64 + public $earning_data = array();
65 +
66 + /**
67 + * Set table name prop
68 + *
69 + * @since 3.0.0
70 + */
71 + protected function __construct() {
72 + global $wpdb;
73 + $this->earning_table = $wpdb->prefix . 'tutor_earnings';
74 + }
75 +
76 + /**
77 + * Prepare earnings from this order to store it as
78 + * earning & commission data.
79 + *
80 + * @since 3.0.0
81 + *
82 + * @param int $order_id Order id.
83 + *
84 + * @return mixed
85 + */
86 + public function prepare_order_earnings( int $order_id ) {
87 + $this->order_id = $order_id;
88 +
89 + $order_model = new OrderModel();
90 + $order_details = $order_model->get_order_by_id( $order_id );
91 + $items = is_object( $order_details ) && property_exists( $order_details, 'items' ) ? $order_details->items : array();
92 +
93 + $deducted_amount = $order_details->refund_amount + $order_details->coupon_amount;
94 + if ( $order_details->discount_amount ) {
95 + $discount_amount = $order_model->calculate_discount_amount( $order_details->discount_type, $order_details->discount_amount, $order_details->subtotal_price );
96 + $deducted_amount += $discount_amount;
97 + }
98 +
99 + if ( is_array( $items ) && count( $items ) ) {
100 +
101 + foreach ( $items as $item ) {
102 +
103 + $subtotal_price = $item->regular_price;
104 + $item_sold_price = $order_model->get_item_sold_price( $item->id, false );
105 +
106 + try {
107 + $per_earning_refund = $order_details->subtotal_price
108 + ? ( $deducted_amount * $subtotal_price ) / $order_details->subtotal_price
109 + : 0;
110 + } catch ( \Throwable $th ) {
111 + tutor_log( $th );
112 + $per_earning_refund = 0;
113 + }
114 +
115 + // Split deduct amount fro admin & instructor.
116 + $split_deduction = tutor_split_amounts( $per_earning_refund );
117 +
118 + // Split earnings.
119 + $split_earnings = tutor_split_amounts( $subtotal_price );
120 +
121 + // Deduct earnings.
122 + $admin_amount = $split_earnings['admin'] - $split_deduction['admin'];
123 + $instructor_amount = $split_earnings['instructor'] - $split_deduction['instructor'];
124 +
125 + $course_id = $item->id;
126 +
127 + if ( OrderModel::TYPE_SINGLE_ORDER !== $order_details->order_type ) {
128 + $plan_info = apply_filters( 'tutor_get_plan_info', null, $course_id );
129 + if ( $plan_info && isset( $plan_info->is_membership_plan ) && $plan_info->is_membership_plan ) {
130 + $course_id = null;
131 + } else {
132 + $course_id = apply_filters( 'tutor_subscription_course_by_plan', $item->id, $order_details );
133 + }
134 + }
135 +
136 + $this->earning_data[] = $this->prepare_earning_data( $item_sold_price, $course_id, $order_id, $order_details->order_status, $admin_amount, $instructor_amount );
137 + }
138 + }
139 + }
140 +
141 + /**
142 + * Get process by
143 + *
144 + * @since 3.8.2
145 + *
146 + * @return string
147 + */
148 + private function get_process_by() {
149 + $monetize_by = tutor_utils()->get_option( 'monetize_by' );
150 +
151 + switch ( $monetize_by ) {
152 + case Ecommerce::MONETIZE_BY:
153 + $process_by = self::PROCESS_BY_TUTOR;
154 + break;
155 + case WooCommerce::MONETIZE_BY:
156 + $process_by = self::PROCESS_BY_WOOCOMMERCE;
157 + break;
158 + default:
159 + $process_by = '';
160 + break;
161 + }
162 +
163 + return $process_by;
164 + }
165 +
166 + /**
167 + * Prepare earning data
168 + *
169 + * @since 3.0.0
170 + *
171 + * @param mixed $total_price Total price of an item.
172 + * @param int $course_id Connected course id.
173 + * @param int $order_id Order id.
174 + * @param string $order_status Order status.
175 + * @param string $admin_amount Admin amount.
176 + * @param string $instructor_amount Instructor status.
177 + *
178 + * @return array
179 + */
180 + public function prepare_earning_data( $total_price, $course_id, $order_id, $order_status, $admin_amount, $instructor_amount ) {
181 + $fees_deduct_data = array();
182 + $tutor_earning_fees = tutor_utils()->get_option( 'fee_amount_type' );
183 + $enable_fees_deducting = tutor_utils()->get_option( 'enable_fees_deducting' );
184 +
185 + $course_price_grand_total = $total_price;
186 +
187 + // Site maintenance fees.
188 + $fees_amount = 0;
189 +
190 + // Deduct predefined amount (percent or fixed).
191 + if ( $enable_fees_deducting ) {
192 + $fees_name = tutor_utils()->get_option( 'fees_name', '' );
193 + $fees_amount = (float) tutor_utils()->avalue_dot( 'fees_amount', $tutor_earning_fees );
194 + $fees_type = tutor_utils()->avalue_dot( 'fees_type', $tutor_earning_fees );
195 +
196 + if ( $fees_amount > 0 ) {
197 + if ( 'percent' === $fees_type ) {
198 + $fees_amount = ( $total_price * $fees_amount ) / 100;
199 + }
200 +
201 + $course_price_grand_total = max( $total_price - $fees_amount, 0 );
202 + }
203 +
204 + $fees_deduct_data = array(
205 + 'deduct_fees_amount' => $fees_amount,
206 + 'deduct_fees_name' => $fees_name,
207 + 'deduct_fees_type' => $fees_type,
208 + );
209 + }
210 +
211 + if ( $fees_amount ) {
212 + list( $admin_fees, $instructor_fees ) = array_values( tutor_split_amounts( $fees_amount ) );
213 +
214 + // Deduct fees.
215 + $admin_amount -= $admin_fees;
216 + $instructor_amount -= $instructor_fees;
217 + }
218 +
219 + // Distribute amount between admin and instructor.
220 + $sharing_enabled = tutor_utils()->get_option( 'enable_revenue_sharing' );
221 + $instructor_rate = $sharing_enabled ? tutor_utils()->get_option( 'earning_instructor_commission' ) : 0;
222 + $admin_rate = $sharing_enabled ? tutor_utils()->get_option( 'earning_admin_commission' ) : 100;
223 + $commission_type = 'percent';
224 +
225 + // Course author id.
226 + $user_id = get_post_field( 'post_author', $course_id );
227 +
228 + // (Use Pro Filter - Start)
229 + // The response must be same array structure.
230 + // Do not change used variable names here, or change in both of here and pro plugin
231 + $pro_arg = array(
232 + 'user_id' => $user_id,
233 + 'instructor_rate' => $instructor_rate,
234 + 'admin_rate' => $admin_rate,
235 + 'instructor_amount' => max( 0, $instructor_amount ),
236 + 'admin_amount' => max( 0, $admin_amount ),
237 + 'course_price_grand_total' => $course_price_grand_total,
238 + 'commission_type' => $commission_type,
239 + );
240 +
241 + $pro_calculation = apply_filters( 'tutor_pro_earning_calculator', $pro_arg );
242 + extract( $pro_calculation ); //phpcs:ignore
243 + // (Use Pro Filter - End).
244 +
245 + // Prepare insertable earning data.
246 + $earning_data = array(
247 + 'user_id' => $user_id,
248 + 'course_id' => $course_id,
249 + 'order_id' => $order_id,
250 + 'order_status' => $order_status,
251 + 'course_price_total' => $total_price,
252 + 'course_price_grand_total' => $course_price_grand_total,
253 +
254 + 'instructor_amount' => $instructor_amount,
255 + 'instructor_rate' => $instructor_rate,
256 + 'admin_amount' => $admin_amount,
257 + 'admin_rate' => $admin_rate,
258 +
259 + 'commission_type' => $commission_type,
260 + 'process_by' => self::get_process_by(),
261 + 'created_at' => current_time( 'mysql', true ),
262 + );
263 + $earning_data = apply_filters( 'tutor_new_earning_data', array_merge( $earning_data, $fees_deduct_data ) );
264 +
265 + return $earning_data;
266 + }
267 +
268 + /**
269 + * Get order earnings
270 + *
271 + * @since 3.0.0
272 + *
273 + * @param int $order_id Order id.
274 + *
275 + * @return mixed Array of objects on success
276 + */
277 + public function get_order_earnings( int $order_id ) {
278 + return QueryHelper::get_all(
279 + $this->earning_table,
280 + array( 'order_id' => $order_id ),
281 + 'earning_id'
282 + );
283 + }
284 +
285 + /**
286 + * Store earnings
287 + *
288 + * @since 3.0.0
289 + *
290 + * @throws \Exception If earning_data is empty.
291 + *
292 + * @return int On success inserted id will be returned
293 + */
294 + public function store_earnings() {
295 + if ( empty( $this->earning_data ) ) {
296 + throw new \Exception( self::INVALID_DATA_MSG );
297 + }
298 +
299 + $inserted_id = 0;
300 + try {
301 + foreach ( $this->earning_data as $earning ) {
302 + $inserted_id = QueryHelper::insert( $this->earning_table, $earning );
303 + }
304 + } catch ( \Throwable $th ) {
305 + throw new \Exception( $th->getMessage() );
306 + }
307 +
308 + return $inserted_id;
309 + }
310 +
311 + /**
312 + * Check if earning for a order already exists
313 + *
314 + * @since 3.0.0
315 + *
316 + * @param int $order_id Order id.
317 + *
318 + * @return mixed Earning row if exists, false|null otherwise.
319 + */
320 + public function is_exist_order_earning( $order_id ) {
321 + $row = QueryHelper::get_row(
322 + $this->earning_table,
323 + array(
324 + 'order_id' => $order_id,
325 + ),
326 + 'earning_id'
327 + );
328 +
329 + return $row;
330 + }
331 +
332 + /**
333 + * Update earning data
334 + *
335 + * Use prepare_order_earnings before updating
336 + *
337 + * @since 3.0.0
338 + *
339 + * @param int $earning_id Earning id.
340 + *
341 + * @throws \Exception If earning_data is empty.
342 + *
343 + * @return bool true|false
344 + */
345 + public function update_earning( $earning_id ) {
346 + if ( empty( $this->earning_data ) ) {
347 + throw new \Exception( self::INVALID_DATA_MSG );
348 + }
349 +
350 + $update = QueryHelper::update(
351 + $this->earning_table,
352 + $this->earning_data,
353 + array( 'earning_id' => $earning_id )
354 + );
355 +
356 + if ( $update ) {
357 + $this->earning_data = null;
358 + }
359 +
360 + return $update;
361 + }
362 +
363 + /**
364 + * Delete earning
365 + *
366 + * @since 3.0.0
367 + *
368 + * @param int $earning_id Earning id.
369 + *
370 + * @return bool true|false
371 + */
372 + public function delete_earning( $earning_id ) {
373 + return QueryHelper::delete(
374 + $this->earning_table,
375 + array( 'earning_id' => $earning_id )
376 + );
377 + }
378 +
379 + /**
380 + * Delete earning by order id
381 + *
382 + * @since 3.0.0
383 + *
384 + * @param int $order_id Order id.
385 + *
386 + * @return bool true|false
387 + */
388 + public function delete_earning_by_order( $order_id ) {
389 + return QueryHelper::delete(
390 + $this->earning_table,
391 + array( 'order_id' => $order_id )
392 + );
393 + }
394 +
395 + /**
396 + * Before storing earning this method will check if
397 + * earning exist for the given order id. If found it will
398 + * remove then store.
399 + *
400 + * @since 3.0.0
401 + *
402 + * @throws \Exception If earning_data is empty.
403 + *
404 + * @return int On success inserted id will be returned
405 + */
406 + public function remove_before_store_earnings() {
407 + if ( empty( $this->earning_data ) ) {
408 + throw new \Exception( self::INVALID_DATA_MSG );
409 + }
410 +
411 + if ( $this->is_exist_order_earning( $this->order_id ) ) {
412 + $this->delete_earning_by_order( $this->order_id );
413 + }
414 +
415 + try {
416 + return $this->store_earnings();
417 + } catch ( \Throwable $th ) {
418 + tutor_log( $th );
419 + }
420 + }
421 + }
422 +