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 / addons.php

addons.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.2.0, at includes/addons.php

250 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace StoreEngine;
4
5 use StoreEngine\Admin\Settings;
6 use StoreEngine\Classes\AbstractAddon;
7 use StoreEngine\Utils\Helper;
8 use WP_Error;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit;
12 }
13
14 class Addons {
15
16 /**
17 * Single option holding every addon's installed DB schema version, as a
18 * map of `addon-slug => version`. The central manager compares each active
19 * addon's get_db_version() against this and reruns install_tables() on a
20 * mismatch — no per-addon option or per-addon `init` hook needed.
21 */
22 const DB_VERSION_OPTION = 'storeengine_addons_db_version';
23
24 /**
25 * Active addon instances collected during loading, keyed by slug.
26 *
27 * @var AbstractAddon[]
28 */
29 private array $loaded_addons = [];
30
31 public static function init() {
32 $self = new self();
33 // Load all addons
34 $self->addons_loader();
35 }
36
37 private function addons_loader() {
38 $addons = apply_filters( 'storeengine/addons/loader_args', [
39 'paypal' => 'Paypal',
40 'stripe' => 'Stripe',
41 'affiliate' => 'Affiliate',
42 'membership' => 'Membership',
43 'subscription' => 'Subscription',
44 'invoice' => 'Invoice',
45 'email' => 'Email',
46 'webhooks' => 'Webhooks',
47 'migration-tool' => 'MigrationTool',
48 'csv' => 'Csv',
49 'catalog-mode' => 'CatalogMode',
50 'razorpay' => 'Razorpay',
51 // 'paystack' moved to the storeengine-payments plugin.
52 'paddle' => 'Paddle',
53 'multi-currency' => 'MultiCurrency',
54 'eu-vat' => 'EuVat',
55 'multi-currency' => 'MultiCurrency',
56 'instant-checkout' => 'InstantCheckout',
57 'embeddable-checkout' => 'EmbeddableCheckout',
58 'inventory' => 'Inventory',
59 // Courier framework (shipment model, status sync, Shipments admin).
60 // Third-party provider integrations ship in the storeengine-courier
61 // satellite and register via the `storeengine/couriers/providers`
62 // filter — same split as payment gateways / storeengine-payments.
63 'couriers' => 'Couriers',
64 'multi-vendor' => 'MultiVendor',
65 'eu-compliance' => 'EuCompliance',
66 'seo' => 'Seo',
67 'ai' => 'Ai',
68 'gdpr' => 'Gdpr',
69 'seeder' => 'Seeder',
70 'brand' => 'Brand',
71 'funnel-builder' => 'FunnelBuilder',
72 ] );
73
74 foreach ( $addons as $addon_name => $addon_class_name ) {
75 $addon_root_path = STOREENGINE_ADDONS_DIR_PATH . $addon_name . '/';
76 $addon_namespace = 'StoreEngine\\Addons\\' . $addon_class_name;
77
78 // Register the addon's root namespace and path.
79 Autoload::get_instance()->add_namespace_directory( $addon_namespace, $addon_root_path );
80
81 /**
82 * Initialize the addon's main class.
83 *
84 * @var AbstractAddon $class Addon's main class.
85 */
86 $class = $addon_namespace . '\\' . $addon_class_name;
87 $class = $class::init();
88 $class->run();
89
90 // Only active addons participate in schema syncing.
91 if ( Helper::get_addon_active_status( $addon_name ) ) {
92 $this->loaded_addons[ $addon_name ] = $class;
93 }
94 }
95
96 // Single hook drives table creation/migration for every active addon.
97 add_action( 'init', [ $this, 'sync_addon_schemas' ], 4 );
98
99 do_action( 'storeengine/addons/loaded' );
100 }
101
102 /**
103 * Sync the schema of every active core addon loaded this request.
104 */
105 public function sync_addon_schemas(): void {
106 self::sync_schema_for( $this->loaded_addons );
107 }
108
109 /**
110 * Compare each addon's declared DB version against the stored map and
111 * rerun install_tables() where they differ. Writes the map once at the end.
112 *
113 * Reusable so other loaders (e.g. storeengine-pro) can sync their own
114 * addon instances into the same `storeengine_addons_db_version` option.
115 *
116 * @param AbstractAddon[] $addons Addon instances keyed by slug.
117 */
118 public static function sync_schema_for( array $addons ): void {
119 $stored = (array) get_option( self::DB_VERSION_OPTION, [] );
120 $changed = false;
121
122 foreach ( $addons as $slug => $addon ) {
123 if ( ! $addon instanceof AbstractAddon ) {
124 continue;
125 }
126
127 $declared = $addon->get_db_version();
128 if ( '' === $declared ) {
129 // Addon has no tables of its own.
130 continue;
131 }
132
133 if ( isset( $stored[ $slug ] ) && $stored[ $slug ] === $declared ) {
134 // Already up to date.
135 continue;
136 }
137
138 $addon->install_tables();
139 $stored[ $slug ] = $declared;
140 $changed = true;
141 }
142
143 if ( $changed ) {
144 update_option( self::DB_VERSION_OPTION, $stored, true );
145 }
146 }
147
148 /**
149 * Hard inter-addon dependency map.
150 *
151 * Activating an addon requires every slug in its list to already be active.
152 * Deactivating an addon is blocked while anything that lists it here is
153 * still active. Filter `storeengine/addons/dependencies` lets pro plugins
154 * extend the map without editing core.
155 */
156 public static function get_dependencies(): array {
157 return apply_filters( 'storeengine/addons/dependencies', [
158 'inventory-pro' => [ 'inventory' ],
159 // POS only hard-requires inventory. inventory-pro is a soft
160 // dependency — when active, registers can be bound to locations
161 // and stock is decremented per location; when inactive, POS
162 // falls back to core's global StockManager.
163 'pos' => [ 'inventory' ],
164 'returns' => [ 'inventory' ],
165 ] );
166 }
167
168 /**
169 * Slugs that currently depend on $addon_slug AND are themselves active.
170 */
171 public static function get_active_dependents( string $addon_slug ): array {
172 global $storeengine_addons;
173 $active = (array) $storeengine_addons;
174 $dependents = [];
175 foreach ( self::get_dependencies() as $consumer => $requires ) {
176 if ( ! in_array( $addon_slug, $requires, true ) ) {
177 continue;
178 }
179 if ( ! empty( $active[ $consumer ] ) ) {
180 $dependents[] = $consumer;
181 }
182 }
183 return $dependents;
184 }
185
186 /**
187 * Slugs that $addon_slug requires but are NOT currently active.
188 */
189 public static function get_missing_requirements( string $addon_slug ): array {
190 global $storeengine_addons;
191 $active = (array) $storeengine_addons;
192 $required = self::get_dependencies()[ $addon_slug ] ?? [];
193 $missing = [];
194 foreach ( $required as $req ) {
195 if ( empty( $active[ $req ] ) ) {
196 $missing[] = $req;
197 }
198 }
199 return $missing;
200 }
201
202 public static function activate( string $addon_slug ) {
203 global $storeengine_addons;
204 if ( ! isset( $storeengine_addons ) ) {
205 Settings::load_settings();
206 }
207
208 $data = (array) $storeengine_addons;
209
210 if ( array_key_exists( $addon_slug, $data ) ) {
211 if ( $data[$addon_slug] ) {
212 // translators: %s: Addon slug.
213 return new WP_Error( 'addon_already_activated', sprintf( __( 'Addon %s already activated', 'storeengine' ), $addon_slug ) );
214 }
215 }
216
217 $storeengine_addons->{$addon_slug} = true;
218
219 update_option( STOREENGINE_ADDONS_SETTINGS_NAME, wp_json_encode( $storeengine_addons ) );
220
221 do_action( "storeengine/addons/activated_{$addon_slug}" );
222
223 return true;
224 }
225
226 public static function deactivate( string $addon_slug ) {
227 global $storeengine_addons;
228 if ( ! isset( $storeengine_addons ) ) {
229 Settings::load_settings();
230 }
231
232 $data = (array) $storeengine_addons;
233
234 if ( array_key_exists( $addon_slug, $data ) ) {
235 if ( !$data[$addon_slug] ) {
236 // translators: %s: Addon slug.
237 return new WP_Error( 'addon_already_deactivated', sprintf( __( 'Addon %s already deactivated', 'storeengine' ), $addon_slug ) );
238 }
239 }
240
241 $storeengine_addons->{$addon_slug} = false;
242
243 update_option( STOREENGINE_ADDONS_SETTINGS_NAME, wp_json_encode( $storeengine_addons ) );
244
245 do_action( "storeengine/addons/deactivated_{$addon_slug}" );
246
247 return true;
248 }
249 }
250