plugin_basename = plugin_basename( $plugin_file ); $this->plugin_slug = dirname( $this->plugin_basename ); $this->current_version = $current_version; self::$instance = $this; } /** * Get the singleton instance. * * @return Updater|null */ public static function get_instance(): ?Updater { return self::$instance; } /** * Initialize update hooks. * * @return void */ public function init(): void { // Allow complete disable via constant. if ( defined( 'WP_BNB_DISABLE_AUTO_UPDATE' ) && WP_BNB_DISABLE_AUTO_UPDATE ) { return; } // Hook into WordPress update system. add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_for_updates' ) ); add_filter( 'plugins_api', array( $this, 'plugin_info' ), 10, 3 ); add_action( 'upgrader_process_complete', array( $this, 'after_update' ), 10, 2 ); // Auto-install filter for WordPress background updates. add_filter( 'auto_update_plugin', array( $this, 'auto_update_plugin' ), 10, 2 ); // Clear update cache when license settings change. add_action( 'update_option_' . Manager::OPTION_LICENSE_KEY, array( $this, 'clear_cache' ) ); add_action( 'update_option_' . Manager::OPTION_SERVER_URL, array( $this, 'clear_cache' ) ); // AJAX handler for manual update check. add_action( 'wp_ajax_wp_bnb_check_updates', array( $this, 'ajax_check_updates' ) ); } /** * Check if update notifications are enabled. * * @return bool */ public static function is_notifications_enabled(): bool { return 'yes' === get_option( self::OPTION_NOTIFICATIONS_ENABLED, 'yes' ); } /** * Check if auto-install is enabled. * * @return bool */ public static function is_auto_install_enabled(): bool { return 'yes' === get_option( self::OPTION_AUTO_INSTALL_ENABLED, 'no' ); } /** * Get the update check frequency in hours. * * @return int */ public static function get_check_frequency(): int { $frequency = (int) get_option( self::OPTION_CHECK_FREQUENCY, self::DEFAULT_CHECK_FREQUENCY ); // Clamp between 1 and 168 hours (1 week). return max( 1, min( 168, $frequency ) ); } /** * Get cache duration in seconds based on check frequency. * * @return int */ private function get_cache_duration(): int { return self::get_check_frequency() * 3600; } /** * Filter for WordPress auto-update system. * * @param bool|null $update Whether to update the plugin. * @param object $item The plugin update object. * @return bool|null */ public function auto_update_plugin( $update, object $item ) { // Only affect our plugin. if ( ! isset( $item->plugin ) || $item->plugin !== $this->plugin_basename ) { return $update; } // Check if auto-install is enabled and license is valid. if ( self::is_auto_install_enabled() && Manager::is_license_valid() ) { return true; } return $update; } /** * Get current plugin version. * * @return string */ public function get_current_version(): string { return $this->current_version; } /** * Get last update check timestamp. * * @return int */ public static function get_last_check(): int { return (int) get_option( self::LAST_CHECK_KEY, 0 ); } /** * AJAX handler: Check for updates. * * @return void */ public function ajax_check_updates(): void { check_ajax_referer( 'wp_bnb_admin_nonce', 'nonce' ); if ( ! current_user_can( 'update_plugins' ) ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to check for updates.', 'wp-bnb' ), ) ); } $update_info = $this->get_cached_update_info( true ); if ( null === $update_info ) { wp_send_json_success( array( 'update_available' => false, 'current_version' => $this->current_version, 'message' => __( 'Could not check for updates. Please verify your license configuration.', 'wp-bnb' ), ) ); } $response = array( 'update_available' => $update_info->updateAvailable && version_compare( $this->current_version, $update_info->version ?? '', '<' ), 'current_version' => $this->current_version, 'latest_version' => $update_info->version ?? $this->current_version, 'last_check' => time(), ); if ( $response['update_available'] ) { $response['message'] = sprintf( /* translators: %s: New version number */ __( 'A new version (%s) is available.', 'wp-bnb' ), $update_info->version ); $response['changelog'] = $update_info->changelog ?? ''; } else { $response['message'] = __( 'You are running the latest version.', 'wp-bnb' ); } wp_send_json_success( $response ); } /** * Initialize the license client. * * @return bool */ private function init_client(): bool { if ( null !== $this->client ) { return true; } $server_url = Manager::get_server_url(); $server_secret = Manager::get_server_secret(); if ( empty( $server_url ) ) { return false; } try { if ( ! empty( $server_secret ) ) { $this->client = new SecureLicenseClient( httpClient: HttpClient::create(), baseUrl: $server_url, serverSecret: $server_secret, ); } else { $this->client = new LicenseClient( httpClient: HttpClient::create(), baseUrl: $server_url, ); } return true; } catch ( \Throwable $e ) { return false; } } /** * Check for plugin updates. * * @param object $transient The update_plugins transient. * @return object Modified transient. */ public function check_for_updates( object $transient ): object { if ( empty( $transient->checked ) ) { return $transient; } // Respect notifications enabled setting. if ( ! self::is_notifications_enabled() ) { return $transient; } $update_info = $this->get_update_info(); if ( null === $update_info || ! $update_info->updateAvailable ) { return $transient; } // Compare versions. if ( version_compare( $this->current_version, $update_info->version ?? '', '>=' ) ) { return $transient; } // Add to update response. $transient->response[ $this->plugin_basename ] = (object) array( 'slug' => $update_info->slug ?? $this->plugin_slug, 'plugin' => $this->plugin_basename, 'new_version' => $update_info->version, 'url' => $update_info->homepage ?? '', 'package' => $update_info->downloadUrl, 'icons' => $update_info->icons ?? array(), 'tested' => $update_info->tested ?? '', 'requires' => $update_info->requires ?? '', 'requires_php' => $update_info->requiresPhp ?? '', ); return $transient; } /** * Provide plugin information for the details modal. * * @param false|object|array $result The result object or array. * @param string $action The API action being performed. * @param object $args Plugin API arguments. * @return false|object */ public function plugin_info( $result, string $action, object $args ) { if ( 'plugin_information' !== $action ) { return $result; } if ( ! isset( $args->slug ) || $args->slug !== $this->plugin_slug ) { return $result; } $update_info = $this->get_update_info(); if ( null === $update_info ) { return $result; } $plugin_info = (object) array( 'name' => $update_info->name ?? 'WP BnB Manager', 'slug' => $update_info->slug ?? $this->plugin_slug, 'version' => $update_info->version ?? $this->current_version, 'author' => 'Marco Graetsch', 'homepage' => $update_info->homepage ?? 'https://src.bundespruefstelle.ch/magdev/wp-bnb', 'requires' => $update_info->requires ?? '6.0', 'tested' => $update_info->tested ?? '', 'requires_php' => $update_info->requiresPhp ?? '8.3', 'last_updated' => $update_info->lastUpdated?->format( 'Y-m-d' ) ?? '', 'download_link' => $update_info->downloadUrl ?? '', 'sections' => $update_info->sections ?? array( 'description' => __( 'A comprehensive Bed & Breakfast management plugin for WordPress.', 'wp-bnb' ), 'changelog' => $update_info->changelog ?? '', ), ); if ( ! empty( $update_info->icons ) ) { $plugin_info->icons = $update_info->icons; } return $plugin_info; } /** * Clear update cache after upgrade. * * @param \WP_Upgrader $upgrader WP_Upgrader instance. * @param array $hook_extra Extra arguments passed to hooked filters. * @return void */ public function after_update( \WP_Upgrader $upgrader, array $hook_extra ): void { if ( ! isset( $hook_extra['plugins'] ) || ! is_array( $hook_extra['plugins'] ) ) { return; } if ( in_array( $this->plugin_basename, $hook_extra['plugins'], true ) ) { $this->clear_cache(); } } /** * Get update info from cache or server. * * @param bool $force_refresh Force refresh from server. * @return UpdateInfo|null */ public function get_cached_update_info( bool $force_refresh = false ): ?UpdateInfo { if ( ! $force_refresh ) { $cached = get_transient( self::CACHE_KEY ); if ( false !== $cached && $cached instanceof UpdateInfo ) { return $cached; } } // Check if license is configured. $license_key = Manager::get_license_key(); if ( empty( $license_key ) ) { return null; } if ( ! $this->init_client() ) { return null; } try { $domain = $this->get_current_domain(); $update_info = $this->client->checkForUpdates( licenseKey: $license_key, domain: $domain, pluginSlug: $this->plugin_slug, currentVersion: $this->current_version, ); // Cache the result and update last check timestamp. set_transient( self::CACHE_KEY, $update_info, $this->get_cache_duration() ); update_option( self::LAST_CHECK_KEY, time() ); return $update_info; } catch ( \Throwable $e ) { // Silently fail and return null - don't break WordPress. return null; } } /** * Get update info from cache or server (alias for WordPress update system). * * @param bool $force_refresh Force refresh from server. * @return UpdateInfo|null */ private function get_update_info( bool $force_refresh = false ): ?UpdateInfo { return $this->get_cached_update_info( $force_refresh ); } /** * Get current domain. * * @return string */ private function get_current_domain(): string { $site_url = get_site_url(); $parsed = wp_parse_url( $site_url ); return $parsed['host'] ?? ''; } /** * Clear the update cache. * * @return void */ public function clear_cache(): void { delete_transient( self::CACHE_KEY ); } }