admin_url('admin-ajax.php'), 'i18n' => FrontendManager::get_admin_script_i18n(), ]); } if (strpos($hook, 'bizdex-uplisting-import') !== false) { wp_register_style( 'bizdex-uplisting-admin-import', BIZDEX_UPLISTING_PLUGIN_URL . 'assets/css/admin-import.css', [], BIZDEX_UPLISTING_VERSION ); wp_enqueue_style('bizdex-uplisting-admin-import'); wp_localize_script('bizdex-uplisting-admin-js', 'bizdex_uplisting_ajax', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('bizdex_uplisting_import_property'), 'importing_text' => __('Importing...', 'advanced-connector-for-uplisting'), 'updating_text' => __('Updating...', 'advanced-connector-for-uplisting'), 'success_text' => __('Imported Successfully!', 'advanced-connector-for-uplisting'), 'updated_text' => __('Updated Successfully!', 'advanced-connector-for-uplisting'), 'error_text' => __('Failed!', 'advanced-connector-for-uplisting'), 'i18n' => FrontendManager::get_admin_script_i18n(), ]); } if (strpos($hook, 'bizdex-uplisting-settings') !== false) { wp_enqueue_media(); wp_register_style( 'bizdex-uplisting-payment-settings', BIZDEX_UPLISTING_PLUGIN_URL . 'assets/css/payment-settings.css', [], BIZDEX_UPLISTING_VERSION ); wp_enqueue_style('bizdex-uplisting-payment-settings'); wp_register_style( 'bizdex-uplisting-email-settings', BIZDEX_UPLISTING_PLUGIN_URL . 'assets/css/email-settings.css', [], BIZDEX_UPLISTING_VERSION ); wp_enqueue_style('bizdex-uplisting-email-settings'); wp_localize_script('bizdex-uplisting-admin-js', 'bizdex_uplisting_ajax', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('bizdex_uplisting_test_api_nonce'), 'i18n' => FrontendManager::get_admin_script_i18n(), ]); } } /** * Register Uplisting submenus under the main Uplisting menu. * * Parent menu `bizdex-uplisting-bookings` is registered by {@see Bookings}. * Submenus (capability: `manage_options`): * - Settings ({@see render_settings_page()}) * - Import Properties ({@see render_import_page()}) * * @return void */ public function add_admin_menus() { $parent = Bookings::PAGE_SLUG; add_submenu_page( $parent, 'Uplisting Settings', 'Settings', 'manage_options', 'bizdex-uplisting-settings', [$this, 'render_settings_page'] ); add_submenu_page( $parent, 'Import Properties', 'Import Properties', 'manage_options', 'bizdex-uplisting-import', [$this, 'render_import_page'] ); } /** * Register all plugin options with the Settings API. * * Options belong to settings group `bizdex_uplisting_settings_group`. * Payment gateway keys are registered separately by {@see Reservation_Manager}. * Includes archive, booking, email, customizer, API, page, and currency options. * * @return void */ public function register_settings() { $options = [ 'bizdex_uplisting_properties_per_page', 'bizdex_uplisting_gallery_images_count', 'bizdex_uplisting_enable_carousel', 'bizdex_uplisting_admin_email', 'bizdex_uplisting_booking_from_name', 'bizdex_uplisting_booking_from_email', 'bizdex_uplisting_customizer_font_size', 'bizdex_uplisting_customizer_heading_font_size', 'bizdex_uplisting_api_key', 'bizdex_uplisting_page_type', 'bizdex_uplisting_listing_page', 'bizdex_uplisting_reservation_page', 'bizdex_uplisting_reservation_success_page', 'bizdex_uplisting_currency', 'bizdex_uplisting_decimal_separator', 'bizdex_uplisting_thousand_separator', 'bizdex_uplisting_decimals', 'bizdex_uplisting_price_format', // NOTE: Payment gateway options are registered by each gateway via ReservationService. ]; $page_options = [ 'bizdex_uplisting_listing_page', 'bizdex_uplisting_reservation_page', 'bizdex_uplisting_reservation_success_page', ]; foreach (ShortcodeRegistry::get_admin_listing_setting_keys() as $listing_key) { $listing_option = ShortcodeRegistry::get_listing_setting_option_name($listing_key); if (!in_array($listing_option, $options, true)) { $options[] = $listing_option; } } foreach ($options as $option) { if (in_array($option, $page_options, true)) { register_setting(BIZDEX_UPLISTING_SETTINGS_GROUP, $option, [ 'sanitize_callback' => 'absint', ]); continue; } $listing_key = self::get_listing_setting_key_for_option($option); if ($listing_key !== '') { register_setting(BIZDEX_UPLISTING_SETTINGS_GROUP, $option, [ 'sanitize_callback' => static function ($value) use ($listing_key) { return ShortcodeRegistry::sanitize_listing_setting($listing_key, $value); }, ]); continue; } register_setting(BIZDEX_UPLISTING_SETTINGS_GROUP, $option, [ 'sanitize_callback' => static function ($value) use ($option) { return self::sanitize_setting($value, $option); }, ]); } } /** * Sanitize a plugin setting value based on option name. * * @param mixed $value Raw submitted value. * @param string $option Option name (passed by Settings API sanitize callback). * @return mixed Sanitized value. */ public static function sanitize_setting($value, string $option = '') { $email_options = [ BIZDEX_UPLISTING_ADMIN_EMAIL, BIZDEX_UPLISTING_BOOKING_FROM_EMAIL, ]; $int_options = [ BIZDEX_UPLISTING_GALLERY_IMAGES_COUNT, BIZDEX_UPLISTING_PROPERTIES_PER_PAGE, BIZDEX_UPLISTING_CUSTOMIZER_FONT_SIZE, BIZDEX_UPLISTING_CUSTOMIZER_HEADING_FONT_SIZE, BIZDEX_UPLISTING_DECIMALS, ]; $checkbox_options = [ BIZDEX_UPLISTING_ENABLE_CAROUSEL, ]; if (in_array($option, $email_options, true)) { return sanitize_email((string) $value); } if (in_array($option, $int_options, true)) { return (string) absint($value); } if (in_array($option, $checkbox_options, true)) { return $value ? '1' : '0'; } if ($option === BIZDEX_UPLISTING_PRICE_FORMAT) { return bizdex_uplisting_sanitize_price_format((string) $value); } return sanitize_text_field((string) $value); } /** * Sanitize permalink slug options and schedule rewrite flush when changed. * * @param mixed $value Raw submitted value. * @param string $option Option name. * @return string Sanitized slug. */ public static function sanitize_permalink_setting($value, string $option): string { $default = $option === BIZDEX_UPLISTING_AMENITY_REWRITE_SLUG ? BIZDEX_UPLISTING_DEFAULT_AMENITY_SLUG : BIZDEX_UPLISTING_DEFAULT_PROPERTY_SLUG; $old_value = (string) get_option($option, $default); $sanitized = bizdex_uplisting_sanitize_slug((string) $value, $default); if ($sanitized !== bizdex_uplisting_sanitize_slug($old_value, $default)) { bizdex_uplisting_schedule_rewrite_flush(); } return $sanitized; } /** * Map a stored option name back to a listing attribute key. * * @param string $option WordPress option name. * @return string Listing setting key, or empty string when unknown. */ private function get_listing_setting_key_for_option(string $option): string { foreach (ShortcodeRegistry::get_admin_listing_setting_keys() as $key) { if (ShortcodeRegistry::get_listing_setting_option_name($key) === $option) { return $key; } } return ''; } /** * Render the tabbed main settings shell. * * View: `templates/admin/settings-page.php` (includes sub-tab partials). * * @since 1.0.0 * * @return void */ public function render_settings_page() { if (!current_user_can('manage_options')) { return; } $this->load_view('settings-page', [ 'admin_manager' => $this, ]); } /** * Render the integration settings tab. * * View: `integration.php`, or falls back to `page-settings.php` if missing. * * @return void */ public function render_integration_page() { $view_file = BIZDEX_UPLISTING_PLUGIN_DIR . 'templates/admin/integration.php'; if (!file_exists($view_file)) { $this->load_view('page-settings', $this->get_page_settings_view_data()); return; } $this->load_view('integration', $this->get_integration_view_data()); } /** * Render listing page, reservation page, and display settings. * * View: `templates/admin/page-settings.php`. * * @return void */ public function render_page_settings_page() { $this->load_view('page-settings', $this->get_page_settings_view_data()); } /** * Render shortcode attribute reference and usage examples. * * View: `templates/admin/shortcode-settings.php`. * * @return void */ public function render_shortcode_settings_page() { $this->load_view('shortcode-settings', $this->get_shortcode_settings_view_data()); } /** * Render booking email and notification settings. * * View: `templates/admin/booking-settings.php`. * * @return void */ public function render_booking_settings_page() { $this->load_view('booking-settings', $this->get_booking_settings_view_data()); } /** * Render Uplisting API key and connection settings. * * View: `templates/admin/uplisting-api.php`. * * @return void */ public function render_uplisting_api_settings() { $this->load_view('uplisting-api', $this->get_uplisting_api_view_data()); } /** * Render payment gateway selector and per-gateway settings panels. * * Instantiates {@see Reservation_Manager} to collect registered gateways for the view. * View: `templates/admin/payment-settings.php`. * * @return void */ public function render_payment_settings_page() { $this->load_view('payment-settings', $this->get_payment_settings_view_data()); } /** * Render general plugin settings (currency formatting, customizer colors, etc.). * * View: `templates/admin/general-settings.php`. * * @return void */ public function render_general_settings() { $this->load_view('general-settings', $this->get_general_settings_view_data()); } /** * Add a Settings link on the plugins list screen. * * @param array $links Existing plugin action links. * @return array */ public function plugin_action_links(array $links): array { $settings_link = sprintf( '%s', esc_url(admin_url('admin.php?page=bizdex-uplisting-settings')), esc_html__('Settings', 'advanced-connector-for-uplisting') ); array_unshift($links, $settings_link); return $links; } /** * Render the property import screen with live Uplisting property list. * * Fetches properties via {@see Integration_Manager::get_properties()}. * Sets `$api_response` error text when the API key is missing or the call fails. * View: `templates/admin/import-page.php`. * * @return void */ public function render_import_page() { if (!current_user_can('manage_options')) { return; } $api_response = ''; $integration_manager = new IntegrationService(); $properties_data = $integration_manager->get_properties(); if ($properties_data === false) { $api_response = 'No API key configured or unable to fetch properties.'; $properties_data = []; } $this->load_view('import-page', get_defined_vars()); } /** * Build template data for the payment settings tab. * * @return array{ * gateways: array, * manual_payment: mixed, * active_online: string, * uses_manual: bool * } */ private function get_payment_settings_view_data(): array { $reservation_service = ReservationService::instance(); return [ 'gateways' => $reservation_service->get_gateways(), 'manual_payment' => $reservation_service->get_manual_payment(), 'active_online' => $reservation_service->get_active_online_gateway_id(), 'uses_manual' => $reservation_service->uses_manual_payment(), ]; } /** * Build template data for the page / display settings tab. * * @return array */ private function get_page_settings_view_data(): array { $listing_settings = ShortcodeRegistry::get_listing_settings(); $listing_definitions = ShortcodeRegistry::get_admin_listing_setting_definitions(); $listing_fields = []; foreach ($listing_definitions as $setting_key => $definition) { $field_value = (string) ($listing_settings[$setting_key] ?? ''); ob_start(); ShortcodeRegistry::render_listing_setting_field($setting_key, $definition, $field_value); $field_html = (string) ob_get_clean(); $listing_fields[] = [ 'key' => $setting_key, 'field_id' => ShortcodeRegistry::get_listing_setting_option_name($setting_key), 'field_value' => $field_value, 'label' => (string) ($definition['label'] ?? $setting_key), 'description' => ShortcodeRegistry::get_listing_setting_description($setting_key), 'field_html' => $field_html, ]; } return [ 'listing_page_id' => absint(get_option('bizdex_uplisting_listing_page', 0)), 'reservation_page_id' => absint(get_option('bizdex_uplisting_reservation_page', 0)), 'reservation_success_page_id' => absint(get_option('bizdex_uplisting_reservation_success_page', 0)), 'gallery_count' => get_option('bizdex_uplisting_gallery_images_count', '10'), 'enable_carousel' => get_option('bizdex_uplisting_enable_carousel', '1'), 'listing_page_url' => FrontendManager::get_listing_page_url(), 'listing_fields' => $listing_fields, ]; } /** * Build template data for the integration settings tab. * * @return array */ private function get_integration_view_data(): array { return []; } /** * Build template data for the shortcode settings tab. * * @return array */ private function get_shortcode_settings_view_data(): array { return [ 'examples' => ShortcodeRegistry::get_builder_examples(), 'properties_attrs' => ShortcodeRegistry::get_properties_attribute_definitions(), ]; } /** * Build template data for the booking / email settings tab. * * @return array */ private function get_booking_settings_view_data(): array { $email_data = EmailManager::instance()->get_settings_view_data(); return array_merge( [ 'admin_email' => get_option(BIZDEX_UPLISTING_ADMIN_EMAIL, get_option('admin_email')), 'from_name' => get_option('bizdex_uplisting_booking_from_name', get_bloginfo('name')), 'from_email' => get_option('bizdex_uplisting_booking_from_email', get_option('admin_email')), ], $email_data ); } /** * Build template data for the Uplisting API settings tab. * * @return array{api_key: string} */ private function get_uplisting_api_view_data(): array { return [ 'api_key' => get_option('bizdex_uplisting_api_key', ''), ]; } /** * Build template data for the general settings tab. * * @return array */ private function get_general_settings_view_data(): array { return [ 'currency' => get_option('bizdex_uplisting_currency', 'USD'), 'decimal_separator' => get_option('bizdex_uplisting_decimal_separator', '.'), 'thousand_separator' => get_option('bizdex_uplisting_thousand_separator', ','), 'decimals' => get_option('bizdex_uplisting_decimals', '2'), 'price_format' => get_option('bizdex_uplisting_price_format', '%1$s%2$s'), ]; } /** * Include an admin view partial with template variables. * * @since 1.0.0 * * @param string $view Basename of the file in `templates/admin/`. * @param array $args Variables available as `$view_args` in the template. * * @return void */ private function load_view($view, $args = []) { if (!current_user_can('manage_options')) { return; } $view_file = BIZDEX_UPLISTING_PLUGIN_DIR . 'templates/admin/' . $view . '.php'; if (file_exists($view_file)) { $view_args = $args; include $view_file; } else { echo '

Error: View file not found at ' . esc_html($view_file) . '

'; } } /** * Find an existing WordPress property imported from Uplisting. * * Queries the `property` CPT by meta key `property_source_id`. * Used on the import page to show Import vs Update actions. * * @param string $source_id Uplisting property ID (stored in post meta). * @return \WP_Post|null Matching post object, or null if not imported yet. */ public function check_property_exists($source_id) { $query = new \WP_Query([ 'post_type' => 'bizdexul_property', 'post_status' => 'any', 'posts_per_page' => 1, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Lookup by Uplisting source id. 'meta_query' => [ 'relation' => 'OR', [ 'key' => bizdex_uplisting_meta_key('property_source_id'), 'value' => sanitize_text_field($source_id), ], [ 'key' => 'property_source_id', 'value' => sanitize_text_field($source_id), ], ], ]); return $query->have_posts() ? $query->posts[0] : null; } }