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 / abstract-integration.php

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

415 lines 9.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Abstract integration.
4 *
5 * @version 2.0
6 * @since StoreEngine v1.0.0
7 */
8
9 namespace StoreEngine\Integrations;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 die();
13 }
14
15 use StoreEngine\Addons\Subscription\Classes\Subscription;
16 use StoreEngine\Classes\AbstractOrder;
17 use StoreEngine\Classes\Data\IntegrationRepositoryData;
18 use StoreEngine\Classes\Exceptions\StoreEngineException;
19 use StoreEngine\Classes\Integration;
20 use StoreEngine\Classes\Order;
21 use StoreEngine\Classes\Order\OrderItemProduct;
22 use StoreEngine\Interfaces\IntegrationInterface;
23 use StoreEngine\Utils\Helper;
24 use StoreEnginePro\Addons\InstallmentPlan\Classes\InstallmentPlan;
25 use Throwable;
26
27 abstract class AbstractIntegration implements IntegrationInterface {
28 /**
29 * Integration instances.
30 *
31 * @var IntegrationInterface[]
32 */
33 protected static array $integrations = [];
34
35 /**
36 * Integration type id.
37 * @var string
38 */
39 protected string $id;
40
41 /**
42 * Integration display name.
43 * @var string
44 */
45 protected string $label;
46
47 /**
48 * Integration items label.
49 * @var string
50 */
51 protected string $items_label;
52
53 /**
54 * Integration logo/icon.
55 * @var string
56 */
57 protected string $logo;
58
59 /**
60 * Whether the integration is enabled (with all the dependencies).
61 * @var bool
62 */
63 protected bool $isEnabled = false;
64
65 public function __construct() {
66 if ( ! empty( self::$integrations[ static::ID ] ) ) {
67 _doing_it_wrong(
68 __METHOD__,
69 sprintf(
70 // translators: %1$s. The integration runner class name, %2$s. Integration type (e.g. storeengine/academylms, storeengine/membership, etc.).
71 esc_html__( 'Direct instantiation of integration runners is not allowed. Attempted to create an instance of %1$s. Please retrieve the integration using AbstractIntegration::get_integration( %2$s ) instead.', 'storeengine' ),
72 esc_html( get_called_class() ),
73 esc_html( static::ID )
74 ),
75 '1.8.0'
76 );
77
78 // Already booted.
79 return;
80 }
81
82 self::$integrations[ static::ID ] = $this;
83
84 $this->setup();
85 $this->dispatch_common_hooks();
86 }
87
88 /**
89 * Get integration class instance by type.
90 * Access integration singleton class instance.
91 *
92 * @param string $id
93 *
94 * @return ?IntegrationInterface
95 */
96 public static function get_integration( string $id ): ?IntegrationInterface {
97 return self::$integrations[ $id ] ?? null;
98 }
99
100 /**
101 * Sets-up class props before dispatch.
102 * @return void
103 */
104 abstract public function setup(): void;
105
106 /**
107 * Dispatch Hooks.
108 *
109 * @return void
110 */
111 abstract public function dispatch_hooks(): void;
112
113 /**
114 * Integration type id.
115 *
116 * @return string
117 */
118 public function get_id(): string {
119 return $this->id;
120 }
121
122 /**
123 * Display name/label.
124 *
125 * @return string
126 */
127 public function get_label(): string {
128 return $this->label;
129 }
130
131 /**
132 * Logo/icon.
133 *
134 * @return string
135 */
136 public function get_logo(): string {
137 return $this->logo;
138 }
139
140 /**
141 * Is enabled?.
142 *
143 * @return bool
144 */
145 public function enabled(): bool {
146 return $this->isEnabled;
147 }
148
149 /**
150 * Items label.
151 *
152 * @return string
153 */
154 public function get_items_label(): string {
155 return $this->items_label;
156 }
157
158 /**
159 * Search integration items.
160 *
161 * @param array $args Search params.
162 *
163 * @return array<object{value:string, label:string}> Search results.
164 */
165 abstract public function get_items( array $args = [] ): array;
166
167 /**
168 * Triggers a purchase event for integration.
169 *
170 * @param Integration $integration
171 * @param AbstractOrder $order
172 *
173 * @return void
174 */
175 abstract protected function purchase_created( Integration $integration, AbstractOrder $order );
176
177 /**
178 * Triggers a return/refund/void-sale event for integration.
179 *
180 * @param Integration $integration
181 * @param AbstractOrder $order
182 *
183 * @return void
184 */
185 abstract protected function purchase_removed( Integration $integration, AbstractOrder $order );
186
187 /**
188 * Common hooks.
189 *
190 * Triggers purchase & refund/refound related events from order status events.
191 *
192 * @return void
193 */
194 protected function dispatch_common_hooks(): void {
195 if ( ! $this->enabled() ) {
196 return;
197 }
198
199 add_action( 'storeengine/order/payment_status_changed', [ $this, 'order_payment_status_changed' ], 10, 2 );
200 add_action( 'storeengine/subscription/status_updated', [ $this, 'subscription_status_updated' ], 10, 3 );
201 add_action( 'storeengine_pro/installment-plan/status_updated', [ $this, 'installment_status_updated' ], 10, 3 );
202
203 $this->dispatch_hooks();
204 }
205
206 /**
207 * Checks if the integration should trigger its events.
208 *
209 * Third-party plugin/addon request not to trigger certain event and handle themselves.
210 *
211 * @param OrderItemProduct $order_item
212 *
213 * @return bool
214 */
215 protected function should_run( OrderItemProduct $order_item ): bool {
216 $should_run = true;
217
218 if ( has_filter( 'storeengine/integrations/run_integration_outside' ) ) {
219 $should_run = ! apply_filters_deprecated(
220 'storeengine/integrations/run_integration_outside',
221 [ false, null, $order_item ],
222 '1.8.0',
223 'storeengine/integrations/maybe_run_integration'
224 );
225 }
226
227 /**
228 * Flag whether integration needs to be running.
229 */
230 return (bool) apply_filters( 'storeengine/integrations/maybe_run_integration', $should_run, $order_item, $this );
231 }
232
233 /**
234 * Order payment status handler.
235 *
236 * @param Order $order
237 * @param string $status
238 *
239 * @return void
240 */
241 public function order_payment_status_changed( Order $order, string $status ) {
242 foreach ( $order->get_line_product_items() as $order_item ) {
243 if ( ! $this->should_run( $order_item ) || 'onetime' !== $order_item->get_price_type() ) {
244 continue;
245 }
246
247 if ( 'paid' === $status ) {
248 $this->run_integration( $order_item, $order );
249 continue;
250 }
251
252 $this->remove_integration( $order_item, $order );
253 }
254 }
255
256 /**
257 * Subscription active status handler.
258 *
259 * @param Subscription $subscription
260 * @param string $new_status
261 *
262 * @return void
263 */
264 public function subscription_status_updated( Subscription $subscription, string $new_status ) {
265 foreach ( $subscription->get_line_product_items() as $order_item ) {
266 if ( ! $this->should_run( $order_item ) ) {
267 continue;
268 }
269
270 if ( 'active' === $new_status ) {
271 $this->run_integration( $order_item, $subscription );
272 continue;
273 }
274
275 $this->remove_integration( $order_item, $subscription );
276 }
277 }
278
279 /**
280 * Installment plan status handler.
281 *
282 * @param InstallmentPlan $installment_plan
283 * @param string $new_status
284 *
285 * @return void
286 */
287 public function installment_status_updated( InstallmentPlan $installment_plan, string $new_status ) {
288 foreach ( $installment_plan->get_line_product_items() as $order_item ) {
289 if ( ! $this->should_run( $order_item ) ) {
290 continue;
291 }
292
293 if ( in_array( $new_status, [ 'active', 'completed' ], true ) ) {
294 $this->run_integration( $order_item, $installment_plan );
295 continue;
296 }
297
298 $this->remove_integration( $order_item, $installment_plan );
299 }
300 }
301
302 /**
303 * Triggers purchase event for order item.
304 *
305 * @param OrderItemProduct $order_item
306 * @param AbstractOrder $order
307 *
308 * @return void
309 */
310 public function run_integration( OrderItemProduct $order_item, AbstractOrder $order ): void {
311 try {
312 if ( 'bundled' === $order_item->get_product_type() ) {
313 $bundles = $order_item->get_meta( '_bundles' );
314 if ( ! $bundles || ! is_array( $bundles ) ) {
315 return;
316 }
317
318 foreach ( $bundles as ['price_id' => $price_id] ) {
319 $this->_run_integration( $price_id, $order );
320 }
321
322 return;
323 }
324
325 $this->_run_integration( $order_item->get_price_id(), $order );
326 } catch ( Throwable $e ) {
327 Helper::log_error( $e );
328 }
329 }
330
331 /**
332 * Triggers return/refund/void-salse event for order item.
333 *
334 * @param OrderItemProduct $order_item
335 * @param AbstractOrder $order
336 *
337 * @return void
338 */
339 public function remove_integration( OrderItemProduct $order_item, AbstractOrder $order ): void {
340 try {
341 if ( 'bundled' === $order_item->get_product_type() ) {
342 $bundles = $order_item->get_meta( '_bundles' );
343 if ( ! $bundles || ! is_array( $bundles ) ) {
344 return;
345 }
346
347 foreach ( $bundles as ['price_id' => $price_id] ) {
348 $this->_remove_integration( $price_id, $order );
349 }
350
351 return;
352 }
353
354 $this->_remove_integration( $order_item->get_price_id(), $order );
355 } catch ( Throwable $e ) {
356 Helper::log_error( $e );
357 }
358 }
359
360 /**
361 * Triggers purchase event by price-id for order.
362 *
363 * @param int $price_id
364 * @param AbstractOrder $order
365 *
366 * @return void
367 * @throws StoreEngineException
368 */
369 protected function _run_integration( int $price_id, AbstractOrder $order ): void {
370 $integrations = Helper::get_integrations_by_price_id( $price_id );
371
372 foreach ( $integrations as $integration ) {
373 if ( $integration->get_provider() !== $this->get_id() ) {
374 continue;
375 }
376
377 $this->purchase_created( $integration, $order );
378 }
379 }
380
381 /**
382 * Triggers return/refund/void-salse event by price-id for order.
383 *
384 * @param int $price_id
385 * @param AbstractOrder $order
386 *
387 * @return void
388 * @throws StoreEngineException
389 */
390 protected function _remove_integration( int $price_id, AbstractOrder $order ): void {
391 $integrations = Helper::get_integrations_by_price_id( $price_id );
392
393 if ( ! $integrations ) {
394 return;
395 }
396
397 foreach ( $integrations as $integration ) {
398 if ( $integration->get_provider() !== $this->get_id() ) {
399 continue;
400 }
401 $this->purchase_removed( $integration, $order );
402 }
403 }
404
405 /**
406 * Find integration objects by integration item (foreign) id.
407 * @param $integration_id
408 *
409 * @return array<IntegrationRepositoryData>
410 */
411 public function get_integration_repository( $integration_id ): array {
412 return Helper::get_integration_repository_by_id( $this->get_id(), $integration_id );
413 }
414 }
415