PluginProbe
Image Optimizer – Compress Images and Convert to WebP or AVIF / 1.7.6
Image Optimizer – Compress Images and Convert to WebP or AVIF v1.7.6
1.7.7 1.7.6 1.7.5 1.7.4 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.2.0 1.2.1 1.3.0 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 All 33 releases
image-optimization / vendor / elementor / wp-one-package / src / Admin / Services / Migration.php

Migration.php in Image Optimizer – Compress Images and Convert to WebP or AVIF 1.7.6, at vendor/elementor/wp-one-package/src/Admin/Services/Migration.php

408 lines 12.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ElementorOne\Admin\Services;
4
5 use ElementorOne\Admin\Config;
6 use ElementorOne\Admin\Components\Fields;
7 use ElementorOne\Admin\Exceptions\MigrationException;
8 use ElementorOne\Admin\Helpers\Utils;
9 use ElementorOne\Common\SupportedPlugins;
10 use ElementorOne\Connect\Classes\GrantTypes;
11 use ElementorOne\Connect\Facade;
12 use ElementorOne\Logger;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit; // Exit if accessed directly
16 }
17
18 /**
19 * Class Migration
20 */
21 class Migration {
22
23 const SETTING_MIGRATED_PLUGINS = Fields::SETTING_PREFIX . 'migrated_plugins';
24
25 /**
26 * Logger instance
27 * @var Logger
28 */
29 private Logger $logger;
30
31 /**
32 * Instance
33 * @var Migration|null
34 */
35 private static ?Migration $instance = null;
36
37 /**
38 * Get instance
39 * @return Migration|null
40 */
41 public static function instance(): ?Migration {
42 if ( ! self::$instance ) {
43 self::$instance = new self();
44 }
45 return self::$instance;
46 }
47
48 /**
49 * Constructor
50 */
51 private function __construct() {
52 $this->logger = new Logger( self::class );
53
54 add_filter( 'elementor_pro/license/api/use_home_url', [ $this, 'filter_license_api_use_home_url' ] );
55
56 add_action( 'elementor_one/get_connect_instance', [ $this, 'get_migrated_instance' ], 10, 3 );
57 add_action( 'elementor_one/elementor_one_switched_domain', [ $this, 'after_switch_domain' ] );
58 add_action( 'elementor_one/elementor_one_before_disconnect', [ $this, 'on_disconnect' ] );
59 add_action( 'elementor_one/elementor_one_connected', [ $this, 'on_connect' ] );
60 add_action( 'activated_plugin', [ $this, 'handle_plugin_activation' ], 10, 1 );
61 add_action( 'shutdown', [ $this, 'on_shutdown' ] );
62 }
63
64 /**
65 * Trigger editor data update on shutdown
66 * @return void
67 */
68 public function on_shutdown(): void {
69 do_action( 'elementor_one/update_editor_data' );
70 }
71
72 /**
73 * Handle plugin activation
74 *
75 * This action is triggered when a plugin is activated.
76 * We make a blocking HTTP request to migrate the plugin
77 * to ensure that the plugin is migrated before the user can use it.
78 *
79 * @param string $plugin Path to the plugin file relative to the plugins directory
80 * @param bool $network_wide Whether the plugin is being network-activated
81 * @return void
82 */
83 public function handle_plugin_activation( string $plugin ): void {
84 $plugin_slug = Utils::filter_plugin_slug( dirname( $plugin ) );
85
86 $supported_plugins = array_filter( self::get_supported_plugins(), function ( $plugin_slug ) {
87 return SupportedPlugins::ELEMENTOR !== $plugin_slug;
88 } );
89
90 if ( ! in_array( $plugin_slug, $supported_plugins, true ) ) {
91 return;
92 }
93
94 $this->http_migrate_plugin( $plugin_slug );
95 }
96
97 /**
98 * Get supported plugins
99 * @return array
100 */
101 public static function get_supported_plugins(): array {
102 return array_filter( SupportedPlugins::get_values(), function ( $plugin_slug ) {
103 return SupportedPlugins::ANGIE !== $plugin_slug;
104 } );
105 }
106
107 /**
108 * Make a blocking HTTP request to migrate a plugin
109 * @param string $plugin_slug The plugin slug to migrate
110 * @return void
111 */
112 private function http_migrate_plugin( string $plugin_slug ): void {
113 $url = rest_url( 'elementor-one/v1/plugins/' . $plugin_slug . '/migration/run' );
114
115 $args = [
116 'method' => \WP_REST_Server::CREATABLE,
117 'timeout' => 30,
118 'body' => [
119 'force' => false,
120 ],
121 'headers' => [
122 'X-WP-Nonce' => wp_create_nonce( 'wp_rest' ),
123 ],
124 'cookies' => array_map(
125 function ( $name ) {
126 return new \WP_Http_Cookie( [
127 'name' => wp_unslash( $name ),
128 'value' => sanitize_text_field( wp_unslash( $_COOKIE[ $name ] ?? '' ) ),
129 ] );
130 },
131 array_keys( $_COOKIE )
132 ),
133 ];
134
135 wp_remote_request( $url, $args );
136 }
137
138 /**
139 * On connect
140 * @return void
141 */
142 public function on_connect(): void {
143 foreach ( self::get_supported_plugins() as $plugin_slug ) {
144 $plugin_data = Utils::get_plugin_data( $plugin_slug );
145 if ( ! $plugin_data ) {
146 continue;
147 }
148
149 if ( ! is_plugin_active( $plugin_data['_file'] ) ) {
150 continue;
151 }
152
153 try {
154 $this->run( $plugin_slug );
155 } catch ( \Throwable $th ) {
156 $this->logger->error( $th->getMessage() );
157 }
158 }
159 }
160
161 /**
162 * Get facade instance
163 *
164 * When a plugin is migrated to ONE, this filter redirects facade lookups
165 * to use the ONE instance instead of the app's own instance.
166 *
167 * @param Facade|null $instance The originally requested facade instance
168 * @param string $plugin_slug The plugin slug being requested
169 * @param array $instances All registered facade instances
170 * @return Facade|null
171 */
172 public function get_migrated_instance( ?Facade $instance, string $plugin_slug, array $instances ): ?Facade {
173 // Not a migrated plugin - return the original instance
174 if ( ! self::is_migrated( $plugin_slug ) ) {
175 return $instance;
176 }
177
178 // Special case: AI app for non-owner users should use their own instance
179 if ( $this->should_use_original_ai_instance( $plugin_slug, $instance ) ) {
180 return $instance;
181 }
182
183 // Use the ONE instance for migrated plugins
184 return $instances[ Config::PLUGIN_SLUG ] ?? null;
185 }
186
187 /**
188 * Check if we should use the original instance instead of the migrated instance
189 *
190 * Non-owner users should continue using their own AI instance
191 * to maintain their individual connection state.
192 *
193 * @param string $plugin_slug The plugin slug
194 * @param Facade|null $instance The original facade instance
195 * @return bool
196 */
197 private function should_use_original_ai_instance( string $plugin_slug, ?Facade $instance ): bool {
198 if ( ! in_array( $plugin_slug, [ SupportedPlugins::ANGIE, SupportedPlugins::ELEMENTOR ], true ) || ! $instance ) {
199 return false;
200 }
201
202 return ! $instance->data()->user_is_subscription_owner();
203 }
204
205 /**
206 * Update editor data
207 * @param string $connect_type The connect type to update
208 * @return void
209 */
210 private static function update_editor_data( string $connect_type, $deactivate_license = false ): void {
211 remove_all_actions( 'elementor_one/update_editor_data' );
212 add_action( 'elementor_one/update_editor_data', function () use ( $connect_type, $deactivate_license ) {
213 Editor::instance()->update_editor_data( $connect_type, $deactivate_license );
214 } );
215 }
216
217 /**
218 * Add ONE migrated plugin
219 * @param string $plugin_slug The plugin slug to migrate
220 * @param bool $force Whether to force migration even if the plugin is already connected
221 * @return void
222 */
223 public function run( string $plugin_slug, bool $force = false ): void {
224 $migrated_plugins = self::get_migrated_plugins();
225
226 // If the plugin is already migrated, do nothing
227 if ( self::is_migrated( $plugin_slug, $migrated_plugins ) ) {
228 return;
229 }
230
231 switch ( $plugin_slug ) {
232 case SupportedPlugins::ELEMENTOR:
233 $is_connected = (bool) Editor::instance()->get_site_owner_connect_data();
234 if ( ! $is_connected ) {
235 self::update_editor_data( Editor::CONNECT_APP_LIBRARY );
236 }
237 break;
238 case SupportedPlugins::ELEMENTOR_PRO:
239 $is_connected = (bool) Editor::get_active_license_key();
240 if ( $is_connected && ! $force ) {
241 throw new MigrationException( 'Plugin is already connected.', \WP_Http::UNPROCESSABLE_ENTITY );
242 }
243 self::update_editor_data( Editor::CONNECT_APP_ACTIVATE, true );
244 break;
245 case SupportedPlugins::MANAGE:
246 // Do nothing with the existing manage instance
247 break;
248 default:
249 $facade = Facade::get( $plugin_slug );
250 if ( ! $facade ) {
251 throw new MigrationException( 'Plugin version not supported.', \WP_Http::CONFLICT );
252 }
253 $is_using_original_instance = $facade->get_config( 'plugin_slug' ) === $plugin_slug;
254 $is_connected = $facade->utils()->is_connected() && $is_using_original_instance;
255 if ( $is_connected ) {
256 if ( ! $force ) {
257 throw new MigrationException( 'Plugin is already connected.', \WP_Http::UNPROCESSABLE_ENTITY );
258 }
259 try {
260 $facade->service()->deactivate_license();
261 } catch ( \Throwable $th ) {
262 $facade->data()->clear_session();
263 $this->logger->error( $th->getMessage() );
264 }
265 }
266 break;
267 }
268
269 // Get the app prefix for the plugin
270 $app_prefix = self::get_app_prefix( $plugin_slug );
271
272 // Add the plugin slug to the migrated plugins
273 $migrated_plugins = array_merge( $migrated_plugins, [ $plugin_slug ] );
274 self::set_migrated_plugins( $migrated_plugins );
275
276 // Trigger the migration run event for the plugin slug
277 do_action( 'elementor_one/' . $app_prefix . '_migration_run' );
278 }
279
280 /**
281 * Rollback ONE migrated plugin
282 * @param string $plugin_slug The plugin slug to rollback
283 * @return array
284 */
285 public function rollback( string $plugin_slug ): array {
286 $migrated_plugins = self::get_migrated_plugins();
287
288 if ( self::is_migrated( $plugin_slug, $migrated_plugins ) ) {
289 $app_prefix = self::get_app_prefix( $plugin_slug );
290
291 if ( SupportedPlugins::ELEMENTOR_PRO === $plugin_slug ) {
292 Editor::instance()->update_editor_data( Editor::CONNECT_APP_LIBRARY, true );
293 }
294
295 // Get the app prefix for the plugin
296 $app_prefix = self::get_app_prefix( $plugin_slug );
297
298 // Remove the plugin slug from the migrated plugins
299 $migrated_plugins = array_values( array_diff( $migrated_plugins, [ $plugin_slug ] ) );
300 self::set_migrated_plugins( $migrated_plugins );
301
302 // Trigger the migration rollback event for the plugin slug
303 do_action( 'elementor_one/' . $app_prefix . '_migration_rollback' );
304 }
305
306 return $migrated_plugins;
307 }
308
309 /**
310 * Check if a plugin is migrated
311 * @param string $plugin_slug The plugin slug to check
312 * @return bool
313 */
314 public static function is_migrated( string $plugin_slug, array $migrated_plugins = [] ): bool {
315 if ( empty( $migrated_plugins ) ) {
316 $migrated_plugins = self::get_migrated_plugins();
317 }
318 return in_array( $plugin_slug, $migrated_plugins, true );
319 }
320
321 /**
322 * Get migrated plugins
323 * @return array
324 */
325 public static function get_migrated_plugins(): array {
326 $migrated_plugins = get_option( self::SETTING_MIGRATED_PLUGINS );
327 return ! empty( $migrated_plugins ) ? $migrated_plugins : [];
328 }
329
330 /**
331 * On disconnect
332 * @return void
333 */
334 public function on_disconnect(): void {
335 $migrated_plugins = self::get_migrated_plugins();
336
337 foreach ( $migrated_plugins as $plugin_slug ) {
338 if ( SupportedPlugins::ELEMENTOR_PRO === $plugin_slug ) {
339 Editor::instance()->delete_editor_data();
340 }
341 do_action( 'elementor_one/' . self::get_app_prefix( $plugin_slug ) . '_migration_rollback' );
342 }
343
344 delete_option( self::SETTING_MIGRATED_PLUGINS );
345 }
346
347 /**
348 * Set migrated plugins
349 * @param array $migrated_plugins
350 * @return void
351 */
352 private static function set_migrated_plugins( array $migrated_plugins ): void {
353 update_option( self::SETTING_MIGRATED_PLUGINS, $migrated_plugins, false );
354 }
355
356 /**
357 * Get app prefix for a plugin slug
358 * @param string $plugin_slug The plugin slug
359 * @return string The app prefix or plugin slug as fallback
360 */
361 private static function get_app_prefix( string $plugin_slug ): string {
362 if ( SupportedPlugins::ELEMENTOR_PRO === $plugin_slug ) {
363 return Editor::PRO_APP_PREFIX;
364 }
365
366 if ( SupportedPlugins::ELEMENTOR === $plugin_slug ) {
367 return Editor::CORE_APP_PREFIX;
368 }
369
370 $facade = Facade::get( $plugin_slug );
371 return $facade ? $facade->get_config( 'app_prefix' ) : $plugin_slug;
372 }
373
374 /**
375 * Handle domain switch for migrated Elementor Pro
376 *
377 * When a site's domain changes, we need to renew the access token
378 * and reset the editor activation state to ensure proper connectivity.
379 *
380 * @param Facade $facade The elementor-one facade instance
381 * @return void
382 */
383 public function after_switch_domain( Facade $facade ): void {
384 if ( ! self::is_migrated( SupportedPlugins::ELEMENTOR_PRO ) ) {
385 return;
386 }
387
388 try {
389 $facade->service()->renew_access_token( GrantTypes::REFRESH_TOKEN );
390 self::update_editor_data( Editor::CONNECT_APP_ACTIVATE, true );
391 } catch ( \Throwable $th ) {
392 $this->logger->error( $th->getMessage() );
393 }
394 }
395
396 /**
397 * Filter the license API use home URL to use the site URL instead of the home URL
398 * @param bool $use_home_url
399 * @return bool
400 */
401 public function filter_license_api_use_home_url( bool $use_home_url ): bool {
402 if ( self::is_migrated( SupportedPlugins::ELEMENTOR_PRO ) ) {
403 return false;
404 }
405 return $use_home_url;
406 }
407 }
408