>|null */ private static $properties_definitions; /** * Shortcode-only attributes (not exposed in Page Settings). * * @var string[] */ private static $shortcode_only_listing_keys = []; /** * Listing attribute keys saved under Page Settings. * * @return string[] */ public static function get_admin_listing_setting_keys(): array { return array_values(array_diff( array_keys(self::get_properties_attribute_definitions()), self::$shortcode_only_listing_keys )); } /** * Return admin listing setting field definitions. * * @return array> */ public static function get_admin_listing_setting_definitions(): array { $definitions = self::get_properties_attribute_definitions(); $admin = []; foreach (self::get_admin_listing_setting_keys() as $key) { if (isset($definitions[$key])) { $admin[$key] = $definitions[$key]; } } return $admin; } /** * Whether a listing attribute is managed in Page Settings. */ public static function is_admin_listing_setting(string $key): bool { return in_array($key, self::get_admin_listing_setting_keys(), true); } /** * All supported shortcodes for the admin builder. * * @return array> */ public static function get_shortcode_types(): array { return [ 'bizdex_uplisting_properties' => [ 'label' => __('Property Listing', 'advanced-connector-for-uplisting'), 'tag' => 'bizdex_uplisting_properties', 'description' => __('Display a searchable property archive with grid or list view.', 'advanced-connector-for-uplisting'), 'attributes' => self::get_properties_attribute_definitions(), ], ]; } /** * Return attribute definitions for the properties shortcode and block. * * @return array> */ public static function get_properties_attribute_definitions(): array { if (null !== self::$properties_definitions) { return self::$properties_definitions; } $default_limit = absint(get_option('bizdex_uplisting_properties_per_page', 12)); if ($default_limit < 1) { $default_limit = 12; } self::$properties_definitions = [ 'show_filter' => [ 'label' => __('Show Filter', 'advanced-connector-for-uplisting'), 'type' => 'select', 'default' => 'yes', 'options' => [ '' => __('No', 'advanced-connector-for-uplisting'), 'yes' => __('Yes', 'advanced-connector-for-uplisting'), 'no' => __('No (explicit)', 'advanced-connector-for-uplisting'), ], ], 'show_pagination' => [ 'label' => __('Show Pagination', 'advanced-connector-for-uplisting'), 'type' => 'select', 'default' => 'yes', 'options' => [ '' => __('Default (yes)', 'advanced-connector-for-uplisting'), 'yes' => __('Yes', 'advanced-connector-for-uplisting'), 'no' => __('No', 'advanced-connector-for-uplisting'), ], ], 'orderby' => [ 'label' => __('Order By', 'advanced-connector-for-uplisting'), 'type' => 'select', 'default' => 'date', 'options' => [ 'date' => __('Date', 'advanced-connector-for-uplisting'), 'title' => __('Title', 'advanced-connector-for-uplisting'), 'menu_order' => __('Menu Order', 'advanced-connector-for-uplisting'), 'rand' => __('Random', 'advanced-connector-for-uplisting'), ], ], 'order' => [ 'label' => __('Sort Order', 'advanced-connector-for-uplisting'), 'type' => 'select', 'default' => 'DESC', 'options' => [ 'DESC' => 'DESC', 'ASC' => 'ASC', ], ], 'limit' => [ 'label' => __('Properties Per Page', 'advanced-connector-for-uplisting'), 'type' => 'number', 'default' => (string) $default_limit, 'min' => 1, 'max' => 100, ], 'columns' => [ 'label' => __('Grid Columns', 'advanced-connector-for-uplisting'), 'type' => 'number', 'default' => '2', 'min' => 1, 'max' => 6, ], 'view' => [ 'label' => __('View', 'advanced-connector-for-uplisting'), 'type' => 'select', 'default' => 'list', 'options' => [ 'grid' => __('Grid', 'advanced-connector-for-uplisting'), 'list' => __('List', 'advanced-connector-for-uplisting'), ], ], ]; return self::$properties_definitions; } /** * WordPress option name for a listing shortcode attribute. */ public static function get_listing_setting_option_name(string $key): string { if ($key === 'limit') { return 'bizdex_uplisting_properties_per_page'; } return 'bizdex_uplisting_listing_' . $key; } /** * Saved listing defaults for the archive page and shortcode. * * @return array */ public static function get_listing_settings(): array { $settings = []; foreach (self::get_properties_attribute_definitions() as $key => $definition) { $default = (string) ($definition['default'] ?? ''); if ($key === 'limit' && $default === '') { $default = (string) max(1, absint(get_option('bizdex_uplisting_properties_per_page', 12))); } if (self::is_admin_listing_setting($key)) { $settings[$key] = (string) get_option( self::get_listing_setting_option_name($key), $default ); continue; } $settings[$key] = $default; } return $settings; } /** * Sanitize a single listing setting value on save. * * @param mixed $value Raw submitted value. */ public static function sanitize_listing_setting(string $key, $value): string { $definitions = self::get_properties_attribute_definitions(); $definition = $definitions[$key] ?? null; if (!is_array($definition)) { return sanitize_text_field((string) $value); } switch ($definition['type']) { case 'number': $min = (int) ($definition['min'] ?? 0); $max = (int) ($definition['max'] ?? 9999); $num = absint($value); return (string) max($min, min($max, $num)); case 'select': $options = $definition['options'] ?? []; $value = (string) $value; if (array_key_exists($value, $options)) { return $value; } return (string) ($definition['default'] ?? ''); default: return sanitize_text_field((string) $value); } } /** * Render a listing setting field for the Page Settings admin screen. * * @param array $definition Field definition. * @param string $value Current saved value. */ public static function render_listing_setting_field(string $key, array $definition, string $value): void { $option_name = self::get_listing_setting_option_name($key); $field_id = sanitize_html_class(str_replace('_', '-', $option_name)); $type = (string) ($definition['type'] ?? 'text'); if ($type === 'select') { $options = $definition['options'] ?? []; echo ''; return; } if ($type === 'number') { echo ''; return; } echo ''; } /** * Human-readable help text for listing settings fields. */ public static function get_listing_setting_description(string $key): string { $descriptions = [ 'show_filter' => __('Show the search and filter bar on property archive pages.', 'advanced-connector-for-uplisting'), 'show_pagination' => __('Show pagination below the property grid/list on archive pages.', 'advanced-connector-for-uplisting'), 'orderby' => __('Default sort field for properties on archive pages.', 'advanced-connector-for-uplisting'), 'order' => __('Default sort direction for archive pages.', 'advanced-connector-for-uplisting'), 'limit' => __('Number of properties to display per page on archive pages (1-100).', 'advanced-connector-for-uplisting'), 'columns' => __('Number of columns in grid view on archive pages (1-6).', 'advanced-connector-for-uplisting'), 'view' => __('Default layout for listing, property archive, and amenity archive pages.', 'advanced-connector-for-uplisting'), ]; return $descriptions[$key] ?? ''; } /** * Return example shortcode configurations for the admin builder UI. * * * @return array> */ public static function get_builder_examples(): array { return [ [ 'label' => __('Basic Grid View', 'advanced-connector-for-uplisting'), 'shortcode' => 'bizdex_uplisting_properties', 'attributes' => [ 'view' => 'grid', 'columns' => '2', 'limit' => '6', ], ], [ 'label' => __('List View with Filter', 'advanced-connector-for-uplisting'), 'shortcode' => 'bizdex_uplisting_properties', 'attributes' => [ 'view' => 'list', 'show_filter' => 'yes', 'orderby' => 'title', 'order' => 'ASC', ], ], [ 'label' => __('With Filter', 'advanced-connector-for-uplisting'), 'shortcode' => 'bizdex_uplisting_properties', 'attributes' => [ 'view' => 'list', 'show_filter' => 'yes', ], ], ]; } /** * Sanitize properties shortcode/block attributes to string values. * * @param array $attributes * @return array */ public static function sanitize_properties_attributes(array $attributes): array { $definitions = self::get_properties_attribute_definitions(); $sanitized = []; foreach ($definitions as $key => $definition) { if (!array_key_exists($key, $attributes)) { continue; } $value = $attributes[$key]; if (null === $value || '' === $value) { continue; } switch ($definition['type']) { case 'number': $sanitized[$key] = (string) max( (int) ($definition['min'] ?? 0), min((int) ($definition['max'] ?? 9999), absint($value)) ); break; case 'select': $options = $definition['options'] ?? []; if (array_key_exists((string) $value, $options)) { $sanitized[$key] = (string) $value; } break; default: $sanitized[$key] = sanitize_text_field((string) $value); break; } if ('' === $sanitized[$key]) { unset($sanitized[$key]); } } return $sanitized; } /** * Build a copy-paste shortcode string. * * @param string $shortcode_tag * @param array $attributes */ public static function build_shortcode_string(string $shortcode_tag, array $attributes = []): string { $types = self::get_shortcode_types(); if (!isset($types[$shortcode_tag])) { return '[' . sanitize_key($shortcode_tag) . ']'; } $definitions = $types[$shortcode_tag]['attributes'] ?? []; $parts = []; foreach ($attributes as $key => $value) { if (null === $value || '' === $value || !isset($definitions[$key])) { continue; } $parts[] = sanitize_key($key) . '="' . esc_attr((string) $value) . '"'; } if (empty($parts)) { return '[' . $types[$shortcode_tag]['tag'] . ']'; } return '[' . $types[$shortcode_tag]['tag'] . ' ' . implode(' ', $parts) . ']'; } /** * Convert Gutenberg block attributes to property shortcode attributes. * * @param array $block_attributes * @return array */ public static function block_to_shortcode_attributes(array $block_attributes): array { $map = [ 'showFilter' => 'show_filter', 'showPagination' => 'show_pagination', 'orderby' => 'orderby', 'order' => 'order', 'limit' => 'limit', 'columns' => 'columns', 'view' => 'view', ]; $atts = []; foreach ($map as $block_key => $shortcode_key) { if (!array_key_exists($block_key, $block_attributes)) { continue; } $value = $block_attributes[$block_key]; if (null === $value || '' === $value) { continue; } $atts[$shortcode_key] = (string) $value; } $sanitized = self::sanitize_properties_attributes($atts); // Layout / view controls (block-only, pass through after sanitize). if (!empty($block_attributes['enabledViews']) && is_array($block_attributes['enabledViews'])) { $views = array_values(array_intersect( ['grid', 'list'], array_map('sanitize_key', $block_attributes['enabledViews']) )); if (!empty($views)) { $sanitized['enabled_views'] = implode(',', $views); } } return $sanitized; } /** * Convert Property Archive block attributes to archive shortcode attributes. * * Forces the search bar on and passes field visibility for the filter template. * * @param array $block_attributes * @return array */ public static function block_to_archive_attributes(array $block_attributes): array { $sanitized = self::block_to_shortcode_attributes($block_attributes); $sanitized['show_filter'] = 'yes'; $visible = []; $map = [ 'showLocation' => 'location', 'showDates' => 'date', 'showGuests' => 'guest', 'showMoreFilters' => 'more_filters', 'showClear' => 'clear', 'showSubmit' => 'submit', 'showModalBedrooms' => 'bedrooms', 'showModalBeds' => 'beds', 'showModalBathrooms' => 'bathrooms', ]; foreach ($map as $attr => $field_key) { if (!array_key_exists($attr, $block_attributes)) { $visible[$field_key] = true; continue; } $visible[$field_key] = !empty($block_attributes[$attr]); } $sanitized['search_visible'] = $visible; if (!empty($block_attributes['listingUrl'])) { $sanitized['search_listing_url'] = esc_url_raw((string) $block_attributes['listingUrl']); } return $sanitized; } /** * Builder config for admin JavaScript. * * @return array */ public static function get_builder_config(): array { $types = []; foreach (self::get_shortcode_types() as $key => $type) { $types[$key] = [ 'label' => $type['label'], 'tag' => $type['tag'], 'description' => $type['description'], 'attributes' => $type['attributes'], ]; } return [ 'types' => $types, 'examples' => self::get_builder_examples(), 'i18n' => [ 'selectType' => __('Select shortcode type', 'advanced-connector-for-uplisting'), 'configure' => __('Configure Attributes', 'advanced-connector-for-uplisting'), 'generated' => __('Generated Shortcode', 'advanced-connector-for-uplisting'), 'copy' => __('Copy Shortcode', 'advanced-connector-for-uplisting'), 'copied' => __('Copied!', 'advanced-connector-for-uplisting'), 'quickExamples' => __('Quick Examples', 'advanced-connector-for-uplisting'), 'noAttributes' => __('This shortcode has no attributes.', 'advanced-connector-for-uplisting'), 'pasteInstructions'=> __('Copy the shortcode above and paste it into any page or post.', 'advanced-connector-for-uplisting'), ], ]; } }