'pfbc-select pfbc-post-select'); /** * The form instance is serialized into a transient at render time and * restored on submit. The parent OptionElement::__sleep() whitelist would * drop this element's own properties, so submit-time validation would see * the defaults (post_type 'page', no extra_args) instead of the configured * values. Keep them in the serialized representation. * * @since 2.2.27 */ public function __sleep() { return array('attributes', 'label', 'validation', 'options', 'post_type', 'extra_args', 'ajax_url', 'nonce'); } /** * Constructor * * @param string $label The field label. * @param string $name The field name attribute. * @param string $post_type Post type to query (default: 'page'). * @param string $extra_args Extra query arguments in query string format. * @param array|null $properties Optional element properties. */ public function __construct($label, $name, $post_type = 'page', $extra_args = '', ?array $properties = null) { // Initialize with empty options - will be loaded via AJAX parent::__construct($label, $name, array(), $properties); $this->post_type = $post_type; $this->extra_args = $extra_args; $this->ajax_url = admin_url('admin-ajax.php'); $this->nonce = wp_create_nonce('accua_forms_get_posts'); // Add data attributes for JavaScript $this->attributes['data-post-type'] = $this->post_type; $this->attributes['data-ajax-url'] = $this->ajax_url; $this->attributes['data-nonce'] = $this->nonce; if (!empty($this->extra_args)) { $this->attributes['data-extra-args'] = $this->extra_args; } } /** * Render the post select element * * Outputs a native select that will be enhanced by JavaScript for search/AJAX. * Falls back to a working select if JavaScript is disabled (with initial options). */ public function render() { $this->applyAriaAttributes(); // Get current value $value = ''; if (isset($this->attributes['value'])) { $value = is_array($this->attributes['value']) ? reset($this->attributes['value']) : $this->attributes['value']; } // Store selected value for AJAX to include in first page if (!empty($value)) { $this->attributes['data-selected'] = $value; } // Generate unique ID for this element (access attributes array directly) $id = isset($this->attributes['id']) ? $this->attributes['id'] : ''; if (empty($id)) { $id = 'pfbc-post-select-' . uniqid(); $this->attributes['id'] = $id; } // Start rendering echo '
'; // Native select element (will be hidden when JS enhances it) echo 'getAttributes(array('value', 'selected', 'data-post-type', 'data-ajax-url', 'data-nonce', 'data-extra-args', 'data-selected')), '>'; // Empty option first - empty text like Country field does (floating label shows the field name) echo ''; // If we have a selected value, fetch and render that post. // Only posts of the configured type and of a status the field may expose // (publish, plus private when explicitly configured): the value can come // from user-submitted data (form re-render after a validation error), so // this must not disclose titles of other draft/private posts. if (!empty($value) && is_numeric($value)) { $selected_post = get_post(absint($value)); if ($selected_post && $selected_post->post_type === $this->getEffectivePostType() && in_array($selected_post->post_status, $this->getAllowedPostStatuses(), true)) { echo ''; } } echo ''; // Hidden data attributes for JS echo 'extra_args)) { echo ' data-extra-args="', esc_attr($this->extra_args), '"'; } if (!empty($value)) { echo ' data-selected="', esc_attr($value), '"'; } echo ' />'; echo '
'; } /** * Get the post type for this select * * @return string */ public function getPostType() { return $this->post_type; } /** * Get the post type actually queried, honoring the post_type override * that extra_args may contain (mirrors accua_forms_ajax_get_posts()). * * @since 2.2.27 * @return string */ public function getEffectivePostType() { if (!empty($this->extra_args)) { $extra = array(); wp_parse_str($this->extra_args, $extra); if (!empty($extra['post_type'])) { $override = sanitize_text_field($extra['post_type']); $valid_post_types = get_post_types(array('public' => true)); if (isset($valid_post_types[$override])) { return $override; } } } return $this->post_type; } /** * Get the post statuses this field may expose, honoring an explicit * post_status in extra_args (limited to publish/private). * * @since 2.2.27 * @return array */ public function getAllowedPostStatuses() { if (!empty($this->extra_args) && function_exists('accua_forms_filter_field_post_status')) { $extra = array(); wp_parse_str($this->extra_args, $extra); if (!empty($extra['post_status'])) { $statuses = accua_forms_filter_field_post_status($extra['post_status']); if (!empty($statuses)) { return $statuses; } } } return array('publish'); } /** * Get the extra query arguments * * @return string */ public function getExtraArgs() { return $this->extra_args; } } // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped