PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.1.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.1.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.1.0, at includes/addons.php

245 lines 7.1 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 'multi-vendor' => 'MultiVendor',
60 'eu-compliance' => 'EuCompliance',
61 'seo' => 'Seo',
62 'ai' => 'Ai',
63 'gdpr' => 'Gdpr',
64 'seeder' => 'Seeder',
65 'brand' => 'Brand',
66 'funnel-builder' => 'FunnelBuilder',
67 ] );
68
69 foreach ( $addons as $addon_name => $addon_class_name ) {
70 $addon_root_path = STOREENGINE_ADDONS_DIR_PATH . $addon_name . '/';
71 $addon_namespace = 'StoreEngine\\Addons\\' . $addon_class_name;
72
73 // Register the addon's root namespace and path.
74 Autoload::get_instance()->add_namespace_directory( $addon_namespace, $addon_root_path );
75
76 /**
77 * Initialize the addon's main class.
78 *
79 * @var AbstractAddon $class Addon's main class.
80 */
81 $class = $addon_namespace . '\\' . $addon_class_name;
82 $class = $class::init();
83 $class->run();
84
85 // Only active addons participate in schema syncing.
86 if ( Helper::get_addon_active_status( $addon_name ) ) {
87 $this->loaded_addons[ $addon_name ] = $class;
88 }
89 }
90
91 // Single hook drives table creation/migration for every active addon.
92 add_action( 'init', [ $this, 'sync_addon_schemas' ], 4 );
93
94 do_action( 'storeengine/addons/loaded' );
95 }
96
97 /**
98 * Sync the schema of every active core addon loaded this request.
99 */
100 public function sync_addon_schemas(): void {
101 self::sync_schema_for( $this->loaded_addons );
102 }
103
104 /**
105 * Compare each addon's declared DB version against the stored map and
106 * rerun install_tables() where they differ. Writes the map once at the end.
107 *
108 * Reusable so other loaders (e.g. storeengine-pro) can sync their own
109 * addon instances into the same `storeengine_addons_db_version` option.
110 *
111 * @param AbstractAddon[] $addons Addon instances keyed by slug.
112 */
113 public static function sync_schema_for( array $addons ): void {
114 $stored = (array) get_option( self::DB_VERSION_OPTION, [] );
115 $changed = false;
116
117 foreach ( $addons as $slug => $addon ) {
118 if ( ! $addon instanceof AbstractAddon ) {
119 continue;
120 }
121
122 $declared = $addon->get_db_version();
123 if ( '' === $declared ) {
124 // Addon has no tables of its own.
125 continue;
126 }
127
128 if ( isset( $stored[ $slug ] ) && $stored[ $slug ] === $declared ) {
129 // Already up to date.
130 continue;
131 }
132
133 $addon->install_tables();
134 $stored[ $slug ] = $declared;
135 $changed = true;
136 }
137
138 if ( $changed ) {
139 update_option( self::DB_VERSION_OPTION, $stored, true );
140 }
141 }
142
143 /**
144 * Hard inter-addon dependency map.
145 *
146 * Activating an addon requires every slug in its list to already be active.
147 * Deactivating an addon is blocked while anything that lists it here is
148 * still active. Filter `storeengine/addons/dependencies` lets pro plugins
149 * extend the map without editing core.
150 */
151 public static function get_dependencies(): array {
152 return apply_filters( 'storeengine/addons/dependencies', [
153 'inventory-pro' => [ 'inventory' ],
154 // POS only hard-requires inventory. inventory-pro is a soft
155 // dependency — when active, registers can be bound to locations
156 // and stock is decremented per location; when inactive, POS
157 // falls back to core's global StockManager.
158 'pos' => [ 'inventory' ],
159 'returns' => [ 'inventory' ],
160 ] );
161 }
162
163 /**
164 * Slugs that currently depend on $addon_slug AND are themselves active.
165 */
166 public static function get_active_dependents( string $addon_slug ): array {
167 global $storeengine_addons;
168 $active = (array) $storeengine_addons;
169 $dependents = [];
170 foreach ( self::get_dependencies() as $consumer => $requires ) {
171 if ( ! in_array( $addon_slug, $requires, true ) ) {
172 continue;
173 }
174 if ( ! empty( $active[ $consumer ] ) ) {
175 $dependents[] = $consumer;
176 }
177 }
178 return $dependents;
179 }
180
181 /**
182 * Slugs that $addon_slug requires but are NOT currently active.
183 */
184 public static function get_missing_requirements( string $addon_slug ): array {
185 global $storeengine_addons;
186 $active = (array) $storeengine_addons;
187 $required = self::get_dependencies()[ $addon_slug ] ?? [];
188 $missing = [];
189 foreach ( $required as $req ) {
190 if ( empty( $active[ $req ] ) ) {
191 $missing[] = $req;
192 }
193 }
194 return $missing;
195 }
196
197 public static function activate( string $addon_slug ) {
198 global $storeengine_addons;
199 if ( ! isset( $storeengine_addons ) ) {
200 Settings::load_settings();
201 }
202
203 $data = (array) $storeengine_addons;
204
205 if ( array_key_exists( $addon_slug, $data ) ) {
206 if ( $data[$addon_slug] ) {
207 // translators: %s: Addon slug.
208 return new WP_Error( 'addon_already_activated', sprintf( __( 'Addon %s already activated', 'storeengine' ), $addon_slug ) );
209 }
210 }
211
212 $storeengine_addons->{$addon_slug} = true;
213
214 update_option( STOREENGINE_ADDONS_SETTINGS_NAME, wp_json_encode( $storeengine_addons ) );
215
216 do_action( "storeengine/addons/activated_{$addon_slug}" );
217
218 return true;
219 }
220
221 public static function deactivate( string $addon_slug ) {
222 global $storeengine_addons;
223 if ( ! isset( $storeengine_addons ) ) {
224 Settings::load_settings();
225 }
226
227 $data = (array) $storeengine_addons;
228
229 if ( array_key_exists( $addon_slug, $data ) ) {
230 if ( !$data[$addon_slug] ) {
231 // translators: %s: Addon slug.
232 return new WP_Error( 'addon_already_deactivated', sprintf( __( 'Addon %s already deactivated', 'storeengine' ), $addon_slug ) );
233 }
234 }
235
236 $storeengine_addons->{$addon_slug} = false;
237
238 update_option( STOREENGINE_ADDONS_SETTINGS_NAME, wp_json_encode( $storeengine_addons ) );
239
240 do_action( "storeengine/addons/deactivated_{$addon_slug}" );
241
242 return true;
243 }
244 }
245