$participant_settings['show_breadcrumbs'], 'current_page_id' => ! empty( $pages_settings['participant'] ) ? (int) $pages_settings['participant'] : 0, 'show_job_title' => isset( $participant_settings['display_metadata']['job_title'] ), 'show_company_name' => isset( $participant_settings['display_metadata']['company_name'] ), 'show_website_url' => isset( $participant_settings['display_metadata']['website_url'] ), 'show_podcasts_count' => isset( $participant_settings['display_metadata']['podcasts_count'] ), 'show_episodes_count' => isset( $participant_settings['display_metadata']['episodes_count'] ), 'show_follow_buttons' => $participant_settings['show_follow_buttons'], 'show_podcasts' => $participant_settings['show_podcasts'], 'podcasts_title' => $participant_settings['podcasts_title'], 'show_episodes' => $participant_settings['show_episodes'], 'episodes_title' => $participant_settings['episodes_title'] ); $attributes = shortcode_atts( $defaults, $atts, 'aiopod_participant' ); // Enqueue dependencies wp_enqueue_style( AIOPOD_PLUGIN_SLUG . '-public' ); wp_enqueue_script( AIOPOD_PLUGIN_SLUG . '-public' ); // Process output ob_start(); include apply_filters( 'aiopod_load_template', AIOPOD_PLUGIN_DIR . 'public/templates/single-participant.php', $attributes ); $content = ob_get_clean(); } else { $content = do_shortcode( '[aiopod_participants]' ); } return $content; } /** * Render the shortcode [aiopod_participants]. * * @since 1.0.0 * @param array $atts An associative array of attributes. */ public function render_shortcode_participants( $atts ) { $attributes = shortcode_atts( $this->get_defaults(), $atts, 'aiopod_participants' ); $attributes = $this->resolve_attributes( $attributes ); // Enqueue dependencies wp_enqueue_style( AIOPOD_PLUGIN_SLUG . '-public' ); wp_enqueue_script( AIOPOD_PLUGIN_SLUG . '-public' ); // Process output $content = $this->get_content( $attributes ); return $content; } /** * Load participants using Ajax. * * @since 1.0.0 */ public function ajax_callback_load_participants() { check_ajax_referer( 'aiopod_ajax_nonce', 'security' ); $attributes = $this->sanitize_ajax_attributes(); $content = $this->get_content( $attributes ); wp_send_json_success( array( 'html' => $content ) ); } /** * Return the allowed $_POST keys and their sanitization types for AJAX requests. * * Derived from aiopod_get_shortcode_fields() so premium-added fields are * automatically included. Internal/pagination state fields are appended below. * * @since 1.0.0 * @return array Map of attribute key to sanitization type string. */ private function get_allowed_attributes() { $type_map = array( 'checkbox' => 'bool', 'number' => 'int', 'select' => 'key', 'text' => 'text', 'podcasts' => 'int', 'categories' => 'array_int', 'participants' => 'array_int', 'tags' => 'array_int', ); $fields = aiopod_get_shortcode_fields(); $allowed = array(); foreach ( $fields['participants']['sections'] as $section ) { foreach ( $section['fields'] as $field ) { if ( ! isset( $type_map[ $field['type'] ] ) ) { continue; } $allowed[ $field['name'] ] = $type_map[ $field['type'] ]; } } // Internal / pagination state — not defined in aiopod_get_shortcode_fields() $allowed['source'] = 'key'; $allowed['title'] = 'text'; $allowed['count'] = 'int'; $allowed['paged'] = 'int'; $allowed['max_num_pages'] = 'int'; $allowed['include'] = 'text'; $allowed['exclude'] = 'text'; $allowed['hide_if_empty'] = 'int'; return $allowed; } /** * Sanitize AJAX $_POST attributes against the allowed list. * * @since 1.0.0 * @return array Sanitized attributes. */ private function sanitize_ajax_attributes() { $attributes = array(); foreach ( $this->get_allowed_attributes() as $key => $type ) { if ( ! isset( $_POST[ $key ] ) ) { continue; } switch ( $type ) { case 'int': $attributes[ $key ] = (int) wp_unslash( $_POST[ $key ] ); break; case 'bool': $raw = wp_unslash( $_POST[ $key ] ); if ( 'false' === $raw ) { $attributes[ $key ] = 0; } elseif ( 'true' === $raw ) { $attributes[ $key ] = 1; } else { $attributes[ $key ] = (int) $raw; } break; case 'key': $attributes[ $key ] = sanitize_key( wp_unslash( $_POST[ $key ] ) ); break; case 'text': $attributes[ $key ] = sanitize_text_field( wp_unslash( $_POST[ $key ] ) ); break; case 'array_int': $attributes[ $key ] = array_map( 'intval', wp_unslash( (array) $_POST[ $key ] ) ); break; } } return $attributes; } /** * Get the html output. * * @since 1.0.0 * @param array $attributes An associative array of attributes. * @return string $content HTML output. */ public function get_content( $attributes ) { ob_start(); include apply_filters( 'aiopod_load_template', AIOPOD_PLUGIN_DIR . 'public/templates/participants/participants-grid.php', $attributes ); $content = ob_get_clean(); return $content; } /** * Resolves attributes into a valid, final configuration. * * This method guarantees that the returned attributes array is safe, * consistent, and ready for query building and rendering. * * @since 1.0.0 * @param array $attributes Raw attributes merged from defaults and user input. * @return array Fully resolved attributes array. */ private function resolve_attributes( $attributes ) { // Pagination if ( empty( $attributes['pagination'] ) ) { $attributes['pagination'] = $this->defaults['pagination']; } // Return resolved array return $attributes; } /** * Get the default shortcode attribute values. * * @since 1.0.0 * @return array An associative array of attributes. */ public function get_defaults() { if ( empty( $this->defaults ) ) { $participants_settings = aiopod_get_option( 'aiopod_participants_settings' ); $pagination_settings = aiopod_get_option( 'aiopod_pagination_settings' ); $fields = aiopod_get_shortcode_fields(); foreach ( $fields['participants']['sections'] as $section ) { foreach ( $section['fields'] as $field ) { $this->defaults[ $field['name'] ] = $field['value']; } } $this->defaults['source'] = 'participants'; $this->defaults['title'] = ''; $this->defaults['count'] = 0; $this->defaults['paged'] = aiopod_get_page_number(); $this->defaults['max_num_pages'] = 1; $this->defaults['include'] = ''; $this->defaults['exclude'] = ''; $this->defaults['pagination'] = ! empty( $participants_settings['pagination'] ) ? $participants_settings['pagination'] : $pagination_settings['layout']; $this->defaults['hide_if_empty'] = 0; } return $this->defaults; } }