diff --git a/flywp.php b/flywp.php index bb9d948..45d7ce5 100644 --- a/flywp.php +++ b/flywp.php @@ -53,6 +53,7 @@ private function __construct() { $this->add_action( 'plugins_loaded', 'init_plugin' ); register_activation_hook( __FILE__, [ $this, 'activate' ] ); + register_deactivation_hook( __FILE__, [ $this, 'deactivate' ] ); } /** @@ -84,6 +85,15 @@ public function activate() { flush_rewrite_rules( false ); } + /** + * Plugin activation hook. + * + * @return void + */ + public function deactivate() { + ( new FlyWP\Api\UpdatesData() )->deactivate(); + } + /** * Initialize plugin. * @@ -102,14 +112,15 @@ public function init_plugin() { $this->frontend = new FlyWP\Frontend(); } - $this->router = new FlyWP\Router(); - $this->rest = new FlyWP\Api(); - $this->fastcgi = new FlyWP\Fastcgi_Cache(); - $this->opcache = new FlyWP\Opcache(); - $this->flyapi = new FlyWP\FlyApi(); - $this->email = new FlyWP\Email(); - $this->optimize = new FlyWP\Optimizations(); - $this->litespeed = new FlyWP\Litespeed(); + $this->router = new FlyWP\Router(); + $this->rest = new FlyWP\Api(); + $this->fastcgi = new FlyWP\Fastcgi_Cache(); + $this->opcache = new FlyWP\Opcache(); + $this->flyapi = new FlyWP\FlyApi(); + $this->email = new FlyWP\Email(); + $this->optimize = new FlyWP\Optimizations(); + $this->litespeed = new FlyWP\Litespeed(); + $this->updates_data = new FlyWP\Api\UpdatesData(); } /** diff --git a/includes/Admin.php b/includes/Admin.php index 3118822..0207076 100644 --- a/includes/Admin.php +++ b/includes/Admin.php @@ -16,7 +16,7 @@ class Admin { */ public const SCREEN_NAME = 'dashboard_page_flywp'; - public $fastcgi = null; + public $fastcgi = null; public $litespeed = null; /** @@ -103,9 +103,10 @@ public function render_admin_page() { ]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $active_tab = isset( $_GET['tab'] ) && array_key_exists( $_GET['tab'], $tabs ) ? $_GET['tab'] : 'cache'; - $site_info = $this->fetch_site_info(); - $app_site_url = $this->get_site_url( $site_info ); + $tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : ''; + $active_tab = array_key_exists( $tab, $tabs ) ? $tab : 'cache'; + $site_info = $this->fetch_site_info(); + $app_site_url = $this->get_site_url( $site_info ); include FLYWP_PLUGIN_DIR . '/views/admin.php'; } @@ -145,8 +146,7 @@ private function get_site_url( $info ) { } return sprintf( - 'https://app.flywp.com/servers/%d/sites/%d', - $info['server_id'], + 'https://app.flywp.com/site/%d', $info['id'] ); } diff --git a/includes/Api.php b/includes/Api.php index 85f7570..608f502 100644 --- a/includes/Api.php +++ b/includes/Api.php @@ -27,6 +27,7 @@ public function __construct() { new Api\Updates(); new Api\Cache(); new Api\Health(); + new Api\UpdatesData(); } /** @@ -52,7 +53,7 @@ public function get_bearer_token() { return false; } - $auth_header = wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ); + $auth_header = sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ) ); if ( ! preg_match( '/Bearer\s(\S+)/', $auth_header, $matches ) ) { return false; diff --git a/includes/Api/UpdatesData.php b/includes/Api/UpdatesData.php new file mode 100644 index 0000000..26b33e8 --- /dev/null +++ b/includes/Api/UpdatesData.php @@ -0,0 +1,249 @@ +initialize_routes(); + $this->initialize_cron_job(); + } + + /** + * Initialize API routes. + */ + private function initialize_routes(): void { + flywp()->router->get( 'updates-data', [ $this, 'respond' ] ); + } + + /** + * Initialize cron job for sending updates data. + */ + private function initialize_cron_job(): void { + add_action( self::CRON_HOOK, [ $this, 'send_updates_data_to_api' ] ); + + if ( ! wp_next_scheduled( self::CRON_HOOK ) ) { + wp_schedule_event( time(), self::CRON_INTERVAL, self::CRON_HOOK ); + } + } + + /** + * Send updates data to the remote API. + */ + public function send_updates_data_to_api(): void { + $updates_data = $this->get_updates_data(); + flywp()->flyapi->post( '/updates-data', $updates_data ); + } + + /** + * Handle the API request. + */ + public function respond(): void { + wp_send_json( $this->get_updates_data() ); + } + + /** + * Get updates data. + * + * @return array + */ + private function get_updates_data(): array { + return [ + 'wp_version' => get_bloginfo( 'version' ), + 'updates' => $this->get_formatted_updates_data(), + ]; + } + + /** + * Get formatted updates data. + * + * @return array + */ + private function get_formatted_updates_data(): array { + return [ + 'core' => $this->get_formatted_core_updates(), + 'plugins' => $this->get_formatted_plugin_updates(), + 'themes' => $this->get_formatted_theme_updates(), + ]; + } + + /** + * Get formatted core updates data. + * + * @return array + */ + private function get_formatted_core_updates(): array { + $core_data = $this->get_core_updates(); + + if ( ! $core_data['update_available'] ) { + return []; + } + + return [ + 'installed_version' => $core_data['version'], + 'latest_version' => $core_data['new_version'], + ]; + } + + /** + * Get formatted plugin updates data. + * + * @return array + */ + private function get_formatted_plugin_updates(): array { + $this->load_required_files(); + + $all_plugins = get_plugins(); + $plugin_updates = get_plugin_updates(); + + $formatted_plugins = []; + + foreach ( $plugin_updates as $plugin_file => $plugin_data ) { + $plugin_info = $all_plugins[ $plugin_file ]; + $slug = dirname( $plugin_file ); + + $formatted_plugins[] = $this->format_plugin_data( $plugin_info, $plugin_data, $plugin_file, $slug ); + } + + return $formatted_plugins; + } + + /** + * Get formatted theme updates data. + * + * @return array + */ + private function get_formatted_theme_updates(): array { + $theme_updates = get_theme_updates(); + + $formatted_themes = []; + + foreach ( $theme_updates as $theme_slug => $theme_data ) { + $theme = wp_get_theme( $theme_slug ); + $formatted_themes[] = $this->format_theme_data( $theme, $theme_data, $theme_slug ); + } + + return $formatted_themes; + } + + /** + * Check if WordPress core has an update available. + * + * @return array + */ + private function get_core_updates(): array { + $current = get_bloginfo( 'version' ); + + $this->load_required_files(); + + $update = get_preferred_from_update_core(); + + $response = [ + 'version' => $current, + 'update_available' => false, + 'new_version' => null, + ]; + + if ( ! isset( $update->response ) || $update->response !== 'upgrade' ) { + return $response; + } + + $response['update_available'] = true; + $response['new_version'] = $update->current; + + return $response; + } + + /** + * Format plugin data. + * + * @param array $plugin_info + * @param object $plugin_data + * @param string $plugin_file + * @param string $slug + * + * @return array + */ + private function format_plugin_data( array $plugin_info, object $plugin_data, string $plugin_file, string $slug ): array { + $formatted_plugin = [ + 'slug' => $slug, + 'name' => $plugin_info['Name'], + 'installed_version' => $plugin_info['Version'], + 'latest_version' => $plugin_data->update->new_version, + 'is_active' => is_plugin_active( $plugin_file ), + ]; + + $extra = [ + 'url' => $plugin_info['PluginURI'] ?? '', + 'author' => $plugin_info['Author'] ?? '', + 'file' => $plugin_file, + 'textdomain' => $plugin_info['TextDomain'] ?? '', + 'description' => $plugin_info['Description'] ?? '', + 'php' => $plugin_info['RequiresPHP'] ?? '', + ]; + + if ( ! empty( array_filter( $extra ) ) ) { + $formatted_plugin['extra'] = array_filter( $extra ); + } + + return $formatted_plugin; + } + + /** + * Format theme data. + * + * @param \WP_Theme $theme + * @param object $theme_data + * @param string $theme_slug + * + * @return array + */ + private function format_theme_data( \WP_Theme $theme, object $theme_data, string $theme_slug ): array { + $formatted_theme = [ + 'slug' => $theme_slug, + 'name' => $theme->get( 'Name' ), + 'installed_version' => $theme->get( 'Version' ), + 'latest_version' => $theme_data->update['new_version'], + 'is_active' => ( get_stylesheet() === $theme_slug ), + ]; + + $extra = [ + 'url' => $theme->get( 'ThemeURI' ), + ]; + + if ( ! empty( array_filter( $extra ) ) ) { + $formatted_theme['extra'] = array_filter( $extra ); + } + + return $formatted_theme; + } + + /** + * Load required WordPress files. + */ + private function load_required_files(): void { + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + if ( ! function_exists( 'get_plugin_updates' ) ) { + require_once ABSPATH . 'wp-admin/includes/update.php'; + } + } + + /** + * Deactivate the scheduler when the plugin is deactivated. + */ + public function deactivate(): void { + $timestamp = wp_next_scheduled( self::CRON_HOOK ); + if ( $timestamp ) { + wp_unschedule_event( $timestamp, self::CRON_HOOK ); + } + } +} diff --git a/includes/FlyApi.php b/includes/FlyApi.php index 416c32c..4ea4afc 100644 --- a/includes/FlyApi.php +++ b/includes/FlyApi.php @@ -73,7 +73,7 @@ public function get( $path ) { * Send a POST request to the API. * * @param string $path - * @param array $data + * @param array $data * * @return array|false */ @@ -86,11 +86,13 @@ public function post( $path, $data = [] ) { 'headers' => [ 'Authorization' => 'Bearer ' . flywp()->get_key(), ], - 'body' => $data, + 'body' => $data, ] ); if ( is_wp_error( $response ) ) { + error_log( print_r( $response, true ) ); + return false; }