?php if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Handles actual install / update of plugins and themes. * * Uses WP_Filesystem + unzip_file() directly instead of WP_Upgrader, * so it works reliably from REST API / CLI context where is_admin()=false. */ class Lithops_Updater_Installer { /** * Install or update a package. * * @param string $download_url Absolute URL (from feed). * @param string $repo_name Repository name / folder slug. * @param string $label_slug 'plugin' or 'theme' * @param bool $reactivate Re-activate plugin after update. * @return array|WP_Error { ok, installedVersion, message } */ public static function run( string $download_url, string $repo_name, string $label_slug, bool $reactivate = false ) { require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/plugin.php'; require_once ABSPATH . 'wp-admin/includes/theme.php'; // Force direct filesystem no FTP prompts from REST/CRON context. add_filter( 'filesystem_method', [ __CLASS__, '_force_direct_fs' ], 99 ); WP_Filesystem(); remove_filter( 'filesystem_method', [ __CLASS__, '_force_direct_fs' ], 99 ); /** @var WP_Filesystem_Base $wp_filesystem */ global $wp_filesystem; if ( ! $wp_filesystem ) { return new WP_Error( 'lupdater_fs_error', __( 'Could not initialise filesystem (WP_Filesystem).', 'lithops-updater' ) ); } // -- 1. Download ZIP --------------------------------------------------- $zip_path = Lithops_Updater_API::download_zip( $download_url ); if ( is_wp_error( $zip_path ) ) { return $zip_path; } // -- 2. Unzip to a temp working directory ------------------------------ $work_dir = trailingslashit( get_temp_dir() ) . 'lupdater_' . uniqid() . '/'; $wp_filesystem->mkdir( $work_dir ); $unzip = unzip_file( $zip_path, $work_dir ); @unlink( $zip_path ); if ( is_wp_error( $unzip ) ) { $wp_filesystem->delete( $work_dir, true ); return $unzip; } // -- 3. Find the single extracted directory ---------------------------- $entries = array_filter( (array) glob( trailingslashit( $work_dir ) . '*' ), 'is_dir' ); if ( empty( $entries ) ) { $wp_filesystem->delete( $work_dir, true ); return new WP_Error( 'lupdater_empty_archive', __( 'The ZIP archive contains no directories.', 'lithops-updater' ) ); } $source_dir = trailingslashit( reset( $entries ) ); // -- 4. Determine destination ------------------------------------------ $destination = ( 'theme' === $label_slug ) ? trailingslashit( get_theme_root() ) . $repo_name : trailingslashit( WP_PLUGIN_DIR ) . $repo_name; // -- 5. Remember the currently active plugin file (for reactivation) -- $existing_plugin_file = null; if ( 'theme' !== $label_slug ) { wp_clean_plugins_cache( false ); foreach ( get_plugins() as $file => $data ) { if ( str_starts_with( $file, $repo_name . '/' ) ) { $existing_plugin_file = $file; break; } } } // -- 6. Delete old destination (update scenario) ----------------------- if ( $wp_filesystem->is_dir( $destination ) ) { if ( ! $wp_filesystem->delete( $destination, true ) ) { $wp_filesystem->delete( $work_dir, true ); return new WP_Error( 'lupdater_delete_failed', sprintf( /* translators: %s: directory path */ __( 'Could not remove existing directory: %s', 'lithops-updater' ), $destination ) ); } } // -- 7. Move extracted folder to destination --------------------------- // copy_dir() is used instead of rename/move_dir for cross-device safety. $copy_result = copy_dir( $source_dir, $destination ); $wp_filesystem->delete( $work_dir, true ); // clean up regardless if ( is_wp_error( $copy_result ) ) { return $copy_result; } // -- 8. Flush caches --------------------------------------------------- if ( 'theme' === $label_slug ) { wp_clean_themes_cache(); } else { wp_clean_plugins_cache( false ); } // -- 9. Activate theme / reactivate plugin -------------------------------- $self_slug = basename( rtrim( LUPDATER_DIR, '/\\' ) ); $new_plugin_file = null; if ( 'theme' === $label_slug ) { if ( $reactivate ) { switch_theme( $repo_name ); } } elseif ( $repo_name !== $self_slug ) { foreach ( get_plugins() as $file => $data ) { if ( str_starts_with( $file, $repo_name . '/' ) ) { $new_plugin_file = $file; break; } } if ( $reactivate && $new_plugin_file ) { $was_active = $existing_plugin_file && is_plugin_active( $existing_plugin_file ); if ( $was_active ) { deactivate_plugins( $existing_plugin_file ); } if ( $was_active || $reactivate ) { activate_plugin( $new_plugin_file ); } } } // -- 10. Read installed version ---------------------------------------- $installed_version = ''; if ( 'theme' === $label_slug ) { $theme = wp_get_theme( $repo_name ); $installed_version = $theme->exists() ? $theme->get( 'Version' ) : ''; } elseif ( $new_plugin_file ) { wp_clean_plugins_cache( false ); $all = get_plugins(); $installed_version = $all[ $new_plugin_file ]['Version'] ?? ''; } return [ 'ok' => true, 'installedVersion' => $installed_version, 'message' => sprintf( /* translators: %s: repo/package name */ __( '%s installed/updated successfully.', 'lithops-updater' ), $repo_name ), ]; } /** @internal Removable filter to force direct filesystem. */ public static function _force_direct_fs(): string { return 'direct'; } }