diff --git a/CHANGELOG.md b/CHANGELOG.md index 61f3bcf..1cfaaa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,74 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.1] - 2026-02-03 + +### Added + +- Auto-Update System: + - New `src/License/Updater.php` class for WordPress update integration + - Hooks into `pre_set_site_transient_update_plugins` for update detection + - Plugin info modal via `plugins_api` filter + - Configurable update check frequency (1-168 hours) + - Option to enable/disable update notifications + - Option to enable/disable automatic updates + - AJAX endpoint for manual update check + - Automatic cache clearing when license settings change +- Updates Tab in Settings: + - Enable/disable update notifications toggle + - Enable/disable automatic updates toggle + - Update check frequency setting + - Manual "Check for Updates" button + - Display of last check timestamp and current version +- Localhost Development Mode: + - License bypass for local development environments + - Detects: localhost, 127.0.0.1, ::1, .local/.test/.localhost/.dev/.ddev.site domains + - Private IP range detection (10.x.x.x, 172.16-31.x.x, 192.168.x.x) + - "Development Mode" notice on Dashboard and License settings page +- Extended General Settings: + - Business address fields (street, city, postal code, country) + - Contact fields (email, phone, website) + - Social media fields (Facebook, Instagram, X/Twitter, LinkedIn, TripAdvisor) +- Pricing Settings Subtabs: + - Split into three subtabs: Pricing Tiers, Weekend Days, Seasons + - Each subtab has its own save button + - Seasons subtab shows priority column and link to Seasons Manager +- Guest Data Encryption: + - AES-256-CBC encryption for sensitive data (ID/passport numbers) + - Uses WordPress AUTH_KEY for encryption key derivation + - `encrypt()` and `decrypt()` methods in Guest class + - Backward compatible with legacy unencrypted data + - Security notice displayed in Identification meta box +- Guest Auto-Creation from Booking: + - When new guest data is entered in booking form, guest record is automatically created + - Links booking to the new guest via guest_id meta + - Prevents duplicate guest entries + +### Changed + +- Admin submenu reordered for better organization: + - Dashboard at top, Settings at bottom + - Logical grouping: Buildings, Rooms, Bookings, Guests, Services, Calendar, Seasons +- Booking title auto-generates with guest name and dates (room number removed) +- Disabled Gutenberg block editor for form-based post types: + - Service, Guest, and Booking now use classic editor + - Meta boxes display properly instead of being hidden at bottom + - Form-based interfaces more appropriate than block editor for data entry +- Settings tabs now flush with tab content (no gap) + +### Fixed + +- Fixed Booking admin issues with auto-draft status causing type errors +- Fixed guest dropdown to always load existing guests +- Fixed booking history display on Guest edit page +- Fixed service pricing meta box not displaying radio buttons (Gutenberg hiding meta boxes) + +### Security + +- Guest ID/passport numbers encrypted at rest using AES-256-CBC +- Random IV generation for each encryption operation +- Secure key derivation from WordPress AUTH_KEY + ## [0.6.0] - 2026-02-02 ### Added @@ -358,6 +426,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Input sanitization and output escaping - Server secret masking in license settings +[0.6.1]: https://src.bundespruefstelle.ch/magdev/wp-bnb/releases/tag/v0.6.1 [0.6.0]: https://src.bundespruefstelle.ch/magdev/wp-bnb/releases/tag/v0.6.0 [0.5.0]: https://src.bundespruefstelle.ch/magdev/wp-bnb/releases/tag/v0.5.0 [0.4.0]: https://src.bundespruefstelle.ch/magdev/wp-bnb/releases/tag/v0.4.0 diff --git a/README.md b/README.md index 5fc6b15..2330018 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,15 @@ WP BnB Management enables WordPress to act as a full management system for B&B h - **Multi-Property Support**: Manage multiple buildings, each with multiple rooms - **Flexible Pricing**: Configure short-term (nights), mid-term (weeks), and long-term (months) pricing +- **Seasonal Pricing**: Set price modifiers for high/low seasons - **Booking Management**: Track reservations from inquiry to checkout - **Guest Management**: Store guest information securely with GDPR compliance +- **Data Encryption**: Sensitive guest data (ID/passport) encrypted at rest - **Additional Services**: Offer extras like breakfast, parking, or tours - **Frontend Integration**: Gutenberg blocks, widgets, and shortcodes -- **Contact Form 7 Integration**: Accept booking requests through forms +- **Auto-Updates**: Automatic update checks and installation from license server +- **Development Mode**: License bypass for local development environments +- **Contact Form 7 Integration**: Accept booking requests through forms (planned) ### Requirements @@ -44,6 +48,23 @@ WP BnB Management enables WordPress to act as a full management system for B&B h - **Business Name**: Your B&B business name - **Currency**: Select your preferred currency (CHF, EUR, USD, GBP) +- **Business Address**: Street, city, postal code, country +- **Contact Information**: Email, phone, website +- **Social Media**: Facebook, Instagram, X (Twitter), LinkedIn, TripAdvisor + +### Update Settings + +- **Update Notifications**: Enable/disable update notifications in WordPress +- **Automatic Updates**: Enable/disable automatic plugin updates +- **Check Frequency**: How often to check for updates (1-168 hours) + +### Development Mode + +The plugin automatically detects local development environments and bypasses license validation. Supported environments: + +- localhost, 127.0.0.1, ::1 +- Domains ending in .local, .test, .localhost, .dev, .ddev.site +- Private IP ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x) ## Usage @@ -81,26 +102,46 @@ WP BnB Management enables WordPress to act as a full management system for B&B h Display buildings and rooms on your site using shortcodes: ```txt -[wp_bnb_buildings] -[wp_bnb_rooms building="123"] -[wp_bnb_room_search] +[bnb_buildings] - List all buildings (grid/list layout) +[bnb_rooms building="123"] - List rooms, optionally filtered by building +[bnb_room_search] - Interactive room search form +[bnb_building id="123"] - Display a single building +[bnb_room id="456"] - Display a single room with availability ``` +### Shortcode Attributes + +**`[bnb_buildings]`** and **`[bnb_rooms]`**: + +- `layout` - "grid" or "list" (default: grid) +- `columns` - 1-4 columns (default: 3) +- `limit` - Number of items (default: 12) +- `orderby` - title, date, price, capacity (default: title) +- `order` - ASC or DESC (default: ASC) + +**`[bnb_rooms]`** additional attributes: + +- `building` - Building ID to filter by +- `room_type` - Room type slug to filter by +- `amenities` - Comma-separated amenity slugs + ## Gutenberg Blocks The following blocks are available in the block editor: -- **Building** - Display a single building -- **Room** - Display a single room -- **Room Search** - Search and filter rooms -- **Booking Form** - Accept booking requests +- **Building** - Display a single building with details +- **Room** - Display a single room with availability form +- **Room Search** - Interactive search form with filters +- **Buildings List** - Display buildings grid/list +- **Rooms List** - Display rooms grid/list with filters ## Widgets Available sidebar widgets: -- **Similar Rooms** - Show rooms similar to the current one +- **Similar Rooms** - Show rooms from same building or room type - **Building Rooms** - List all rooms in a building +- **Availability Calendar** - Mini calendar showing booking status ## Hooks and Filters @@ -123,7 +164,7 @@ add_action( 'wp_bnb_before_booking_create', function( $booking_data ) { ### Do I need a license to use this plugin? -Yes, a valid license is required to use the frontend features. The admin functionality works without a license for evaluation purposes. +Yes, a valid license is required to use the frontend features in production. The admin functionality works without a license for evaluation purposes. Local development environments (localhost, .local, .test, .dev domains) automatically bypass license validation. ### Can I manage multiple properties? @@ -137,6 +178,10 @@ Yes, guest data can be exported and deleted on request, and consent is tracked a WooCommerce integration for payments is planned for a future release. +### How is guest data secured? + +Sensitive guest data like passport/ID numbers are encrypted using AES-256-CBC encryption before storage. The encryption key is derived from your WordPress AUTH_KEY, ensuring data is secure at rest. + ## Changelog See [CHANGELOG.md](CHANGELOG.md) for a detailed list of changes. diff --git a/assets/css/admin.css b/assets/css/admin.css index d807e7e..e8294c4 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -54,7 +54,8 @@ /* Settings Tabs */ .nav-tab-wrapper { - margin-bottom: 20px; + margin-bottom: 0; + border-bottom: 1px solid #c3c4c7; } .tab-content { @@ -64,6 +65,57 @@ padding: 20px; } +/* Settings Subtabs */ +.wp-bnb-subtabs { + display: flex; + gap: 0; + margin-bottom: 20px; + border-bottom: 1px solid #c3c4c7; +} + +.wp-bnb-subtab { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 10px 16px; + text-decoration: none; + color: #50575e; + background: #f6f7f7; + border: 1px solid #c3c4c7; + border-bottom: none; + margin-bottom: -1px; + margin-right: -1px; + font-size: 13px; + transition: background 0.15s ease, color 0.15s ease; +} + +.wp-bnb-subtab:first-child { + border-top-left-radius: 4px; +} + +.wp-bnb-subtab:last-child { + border-top-right-radius: 4px; + margin-right: 0; +} + +.wp-bnb-subtab:hover { + background: #fff; + color: #135e96; +} + +.wp-bnb-subtab.active { + background: #fff; + color: #1d2327; + font-weight: 600; + border-bottom-color: #fff; +} + +.wp-bnb-subtab .dashicons { + font-size: 16px; + width: 16px; + height: 16px; +} + /* Form Tables */ .form-table th { width: 200px; diff --git a/assets/js/admin.js b/assets/js/admin.js index 4f0a71b..6738259 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -91,6 +91,91 @@ } } + /** + * Initialize update check functionality. + */ + function initUpdateCheck() { + var $checkBtn = $('#wp-bnb-check-updates'); + var $spinner = $('#wp-bnb-update-spinner'); + var $message = $('#wp-bnb-update-message'); + var $latestVersion = $('#wp-bnb-latest-version'); + var $lastCheck = $('#wp-bnb-update-last-check'); + + if (!$checkBtn.length) { + return; + } + + $checkBtn.on('click', function(e) { + e.preventDefault(); + + // Disable button and show spinner. + $checkBtn.prop('disabled', true); + $spinner.addClass('is-active'); + $message.hide(); + + $.ajax({ + url: wpBnbAdmin.ajaxUrl, + type: 'POST', + data: { + action: 'wp_bnb_check_updates', + nonce: wpBnbAdmin.nonce + }, + success: function(response) { + $spinner.removeClass('is-active'); + $checkBtn.prop('disabled', false); + + if (response.success) { + var data = response.data; + + // Update last check time. + $lastCheck.text(wpBnbAdmin.i18n.justNow || 'Just now'); + + // Update version display. + if (data.update_available) { + $latestVersion.html( + '' + + data.latest_version + + ' ' + + ' ' + + '' + (wpBnbAdmin.i18n.updateAvailable || 'Update available!') + '' + ); + showUpdateMessage('success', data.message); + } else { + $latestVersion.html( + data.latest_version + + ' ' + + (wpBnbAdmin.i18n.upToDate || '(You are up to date)') + + '' + ); + showUpdateMessage('success', data.message); + } + } else { + showUpdateMessage('error', response.data.message || wpBnbAdmin.i18n.error); + } + }, + error: function() { + $spinner.removeClass('is-active'); + $checkBtn.prop('disabled', false); + showUpdateMessage('error', wpBnbAdmin.i18n.error); + } + }); + }); + + /** + * Show an update message. + * + * @param {string} type Message type (success or error). + * @param {string} message Message text. + */ + function showUpdateMessage(type, message) { + $message + .removeClass('success error') + .addClass(type) + .text(message) + .fadeIn(); + } + } + /** * Initialize room gallery functionality. */ @@ -768,7 +853,7 @@ return; } - $pricingTypeInputs.on('change', function() { + function updatePriceRowVisibility() { var pricingType = $('input[name="bnb_service_pricing_type"]:checked').val(); if (pricingType === 'included') { @@ -784,7 +869,12 @@ $priceDescription.text(wpBnbAdmin.i18n.perBookingDescription || 'This price will be charged once for the booking.'); } } - }); + } + + $pricingTypeInputs.on('change', updatePriceRowVisibility); + + // Set initial visibility state on page load. + updatePriceRowVisibility(); } /** @@ -937,6 +1027,7 @@ // Initialize on document ready. $(document).ready(function() { initLicenseManagement(); + initUpdateCheck(); initRoomGallery(); initPricingSettings(); initSeasonForm(); diff --git a/src/License/Manager.php b/src/License/Manager.php index bbc6576..2e3b0f0 100644 --- a/src/License/Manager.php +++ b/src/License/Manager.php @@ -126,13 +126,60 @@ final class Manager { /** * Check if license is valid. * + * Localhost environments bypass the license check to allow + * full functionality during development. + * * @return bool */ public static function is_license_valid(): bool { + // Bypass license check for localhost environments. + if ( self::is_localhost() ) { + return true; + } + $status = get_option( self::OPTION_LICENSE_STATUS, 'unchecked' ); return 'valid' === $status; } + /** + * Check if running on localhost. + * + * Detects common local development environments: + * - localhost / 127.0.0.1 / ::1 + * - .local, .test, .localhost domains + * - Private IP ranges (192.168.x.x, 10.x.x.x, 172.16-31.x.x) + * + * @return bool + */ + public static function is_localhost(): bool { + $site_url = get_site_url(); + $parsed = wp_parse_url( $site_url ); + $host = $parsed['host'] ?? ''; + + // Check for localhost variations. + if ( in_array( $host, array( 'localhost', '127.0.0.1', '::1' ), true ) ) { + return true; + } + + // Check for common local development TLDs. + $local_tlds = array( '.local', '.test', '.localhost', '.dev', '.ddev.site' ); + foreach ( $local_tlds as $tld ) { + if ( str_ends_with( $host, $tld ) ) { + return true; + } + } + + // Check for private IP ranges. + if ( filter_var( $host, FILTER_VALIDATE_IP ) ) { + // 10.x.x.x, 172.16-31.x.x, 192.168.x.x. + if ( ! filter_var( $host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE ) ) { + return true; + } + } + + return false; + } + /** * Get license key. * diff --git a/src/License/Updater.php b/src/License/Updater.php new file mode 100644 index 0000000..090270b --- /dev/null +++ b/src/License/Updater.php @@ -0,0 +1,473 @@ +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 ); + } +} diff --git a/src/Plugin.php b/src/Plugin.php index d76bc87..144e132 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -20,6 +20,8 @@ use Magdev\WpBnb\Frontend\Widgets\AvailabilityCalendar; use Magdev\WpBnb\Frontend\Widgets\BuildingRooms; use Magdev\WpBnb\Frontend\Widgets\SimilarRooms; use Magdev\WpBnb\License\Manager as LicenseManager; +use Magdev\WpBnb\License\Updater as LicenseUpdater; +use Magdev\WcLicensedProductClient\Dto\UpdateInfo; use Magdev\WpBnb\PostTypes\Booking; use Magdev\WpBnb\PostTypes\Building; use Magdev\WpBnb\PostTypes\Guest; @@ -128,6 +130,9 @@ final class Plugin { // Initialize License Manager (always active for admin). LicenseManager::get_instance(); + // Initialize auto-updater (requires license configuration). + $this->init_updater(); + // Initialize admin components. if ( is_admin() ) { $this->init_admin(); @@ -139,6 +144,19 @@ final class Plugin { } } + /** + * Initialize the plugin auto-updater. + * + * @return void + */ + private function init_updater(): void { + $updater = new LicenseUpdater( + plugin_file: WP_BNB_PATH . 'wp-bnb.php', + current_version: WP_BNB_VERSION, + ); + $updater->init(); + } + /** * Initialize admin components. * @@ -147,6 +165,7 @@ final class Plugin { private function init_admin(): void { // Admin menu and settings will be added here. add_action( 'admin_menu', array( $this, 'register_admin_menu' ) ); + add_action( 'admin_menu', array( $this, 'reorder_admin_menu' ), 99 ); add_action( 'admin_init', array( $this, 'register_settings' ) ); // Initialize seasons admin page. @@ -280,6 +299,10 @@ final class Plugin { 'guestBlocked' => __( 'Blocked', 'wp-bnb' ), 'perNightDescription' => __( 'This price will be charged per night of the stay.', 'wp-bnb' ), 'perBookingDescription' => __( 'This price will be charged once for the booking.', 'wp-bnb' ), + 'justNow' => __( 'Just now', 'wp-bnb' ), + 'updateAvailable' => __( 'Update available!', 'wp-bnb' ), + 'upToDate' => __( '(You are up to date)', 'wp-bnb' ), + 'checkingUpdates' => __( 'Checking for updates...', 'wp-bnb' ), ), ) ); @@ -392,6 +415,59 @@ final class Plugin { ); } + /** + * Reorder the admin submenu items. + * + * Places Dashboard at top, Settings at bottom, and organizes + * the remaining items in logical order. + * + * @return void + */ + public function reorder_admin_menu(): void { + global $submenu; + + if ( ! isset( $submenu['wp-bnb'] ) ) { + return; + } + + // Define the desired order of menu slugs. + $desired_order = array( + 'wp-bnb', // Dashboard. + 'edit.php?post_type=bnb_building', // Buildings. + 'edit.php?post_type=bnb_room', // Rooms. + 'edit.php?post_type=bnb_booking', // Bookings. + 'edit.php?post_type=bnb_guest', // Guests. + 'edit.php?post_type=bnb_service', // Services. + 'wp-bnb-calendar', // Calendar. + 'wp-bnb-seasons', // Seasons. + 'wp-bnb-settings', // Settings (always last). + ); + + $current_menu = $submenu['wp-bnb']; + $ordered_menu = array(); + $index = 0; + + // Add items in the desired order. + foreach ( $desired_order as $slug ) { + foreach ( $current_menu as $key => $item ) { + if ( $item[2] === $slug ) { + $ordered_menu[ $index ] = $item; + unset( $current_menu[ $key ] ); + ++$index; + break; + } + } + } + + // Append any remaining items not in the desired order. + foreach ( $current_menu as $item ) { + $ordered_menu[ $index ] = $item; + ++$index; + } + + $submenu['wp-bnb'] = $ordered_menu; + } + /** * Register plugin settings. * @@ -408,12 +484,21 @@ final class Plugin { * @return void */ public function render_dashboard_page(): void { - $license_status = LicenseManager::get_cached_status(); + $license_valid = LicenseManager::is_license_valid(); + $is_localhost = LicenseManager::is_localhost(); ?>
+ + + +
+"> + + +