true ), 'objects' ); $wp_roles = wp_roles()->get_names(); trustbio_layout_start( 'trustbio-settings' ); ?>
$settings Current settings array. */ do_action( 'trustbio_settings_panels', $s ); ?>

__( 'Active Authors', 'trustbio' ), 'value' => $stats['authors'], 'color' => '#4f46e5' ), array( 'label' => __( 'Author Boxes', 'trustbio' ), 'value' => $stats['boxes'], 'color' => '#0891b2' ), array( 'label' => __( 'Review Bars', 'trustbio' ), 'value' => $stats['review_bars'], 'color' => '#059669' ), array( 'label' => __( 'Posts w/ Author', 'trustbio' ), 'value' => $stats['posts'], 'color' => '#d97706' ), ); foreach ( $stat_items as $si ) : ?>

$fi ) : $trustbio_active = $all_on || in_array( $slug, $feat_arr, true ); ?>
trustbio_get_enabled_post_types(), 'allowed_roles' => (array) get_option( 'trustbio_allowed_roles', array( 'administrator', 'editor', 'author', 'contributor' ) ), 'show_powered_by' => (bool) get_option( 'trustbio_show_powered_by', 0 ), 'allow_guest_no_email' => (bool) get_option( 'trustbio_allow_guest_no_email', 0 ), 'show_contribution_count' => (bool) get_option( 'trustbio_show_contribution_count', 0 ), 'contribution_noun_plural' => (string) get_option( 'trustbio_contribution_noun_plural', 'articles' ), 'contribution_min_count' => (int) get_option( 'trustbio_contribution_min_count', 1 ), 'archive_enabled' => (bool) get_option( 'trustbio_archive_enabled', 0 ), 'archive_per_page' => (int) get_option( 'trustbio_archive_per_page', 10 ), 'archive_hero_color' => (string) get_option( 'trustbio_archive_hero_color', '#312e81' ), 'archive_accent_color' => (string) get_option( 'trustbio_archive_accent_color', '#4f46e5' ), 'enable_archive_registered' => (bool) get_option( 'trustbio_enable_archive_registered', 1 ), 'enable_archive_guest' => (bool) get_option( 'trustbio_enable_archive_guest', 1 ), 'author_url_base' => (string) get_option( 'trustbio_author_url_base', 'authors' ), 'gravatar_default' => (string) get_option( 'trustbio_gravatar_default', 'mystery' ), 'box_position' => (string) get_option( 'trustbio_box_position', 'after' ), 'auto_insert_posts' => (bool) get_option( 'trustbio_auto_insert_posts', 1 ), 'auto_insert_pages' => (bool) get_option( 'trustbio_auto_insert_pages', 0 ), 'exclude_post_ids' => (string) get_option( 'trustbio_exclude_post_ids', '' ), 'review_display' => (string) get_option( 'trustbio_review_display', 'inline' ), 'review_default_label' => (string) get_option( 'trustbio_review_default_label', 'Medically reviewed by' ), 'review_stale_months' => (int) get_option( 'trustbio_review_stale_months', 12 ), 'review_show_column' => (bool) get_option( 'trustbio_review_show_column', 1 ), 'review_bar_position' => (string) get_option( 'trustbio_review_bar_position', 'before_content' ), 'review_with_author_box' => (bool) get_option( 'trustbio_review_with_author_box', 0 ), 'enable_schema' => (bool) get_option( 'trustbio_enable_schema', 1 ), 'schema_compat_mode' => (bool) get_option( 'trustbio_schema_compat_mode', 1 ), 'debug_mode' => (bool) get_option( 'trustbio_debug_mode', 0 ), 'enable_rest_api' => (bool) get_option( 'trustbio_enable_rest_api', 1 ), 'delete_data_on_uninstall' => (bool) get_option( 'trustbio_delete_data_on_uninstall', 0 ), ); } private static function get_stats(): array { $trustbio_cached = wp_cache_get( 'trustbio_sp_stats', 'trustbio' ); if ( false !== $trustbio_cached ) { return (array) $trustbio_cached; } global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared $trustbio_stats = array( 'authors' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}trustbio_authors WHERE is_active = 1" ), 'boxes' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}trustbio_author_boxes" ), 'review_bars' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}trustbio_review_bars" ), 'posts' => (int) $wpdb->get_var( "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} WHERE meta_key = '_trustbio_authors'" ), ); // phpcs:enable wp_cache_set( 'trustbio_sp_stats', $trustbio_stats, 'trustbio', 5 * MINUTE_IN_SECONDS ); return $trustbio_stats; } public static function get_feature_info(): array { $trustbio_features = array( 'review_bars' => array( 'name' => __( 'Last Review Bars', 'trustbio' ), 'short' => __( 'Show last-reviewed dates on posts.', 'trustbio' ), 'desc' => __( 'Expert review dates with bar layouts and stale tracking.', 'trustbio' ), 'icon' => 'review' ), 'schema' => array( 'name' => __( 'Schema Output', 'trustbio' ), 'short' => __( 'JSON-LD author & FAQ schema in head.', 'trustbio' ), 'desc' => __( 'Structured data Google uses for E-E-A-T evaluation.', 'trustbio' ), 'icon' => 'schema' ), ); /** * Filters the togglable TrustBio feature modules. * * The TrustBio Pro add-on registers its own modules here so they get * their own on/off toggle on the Features screen. * * @since 1.0.0 * @param array $features Feature slug => name/short/desc/icon. */ return (array) apply_filters( 'trustbio_feature_modules', $trustbio_features ); } // ========================================================= // AJAX: Save settings // ========================================================= /** * Recursively clean a posted settings payload. * * Strings go through sanitize_textarea_field(), which strips tags and * invalid UTF-8 while keeping the newlines a multi-line setting needs. * Scalars are cast, and array keys are sanitised unless they are numeric, * which would otherwise turn a list into a set of empty-string keys. * * @param array $trustbio_input Raw, unslashed payload. * @param int $trustbio_depth Guard against a pathological nesting depth. * @return array */ private static function sanitize_payload( array $trustbio_input, int $trustbio_depth = 0 ): array { if ( $trustbio_depth > 5 ) { return array(); } $trustbio_clean = array(); foreach ( $trustbio_input as $trustbio_key => $trustbio_value ) { $trustbio_key = is_int( $trustbio_key ) ? $trustbio_key : sanitize_key( (string) $trustbio_key ); if ( is_array( $trustbio_value ) ) { $trustbio_clean[ $trustbio_key ] = self::sanitize_payload( $trustbio_value, $trustbio_depth + 1 ); continue; } if ( is_bool( $trustbio_value ) || is_int( $trustbio_value ) || is_float( $trustbio_value ) ) { $trustbio_clean[ $trustbio_key ] = $trustbio_value; continue; } $trustbio_clean[ $trustbio_key ] = sanitize_textarea_field( (string) $trustbio_value ); } return $trustbio_clean; } public function ajax_save(): void { check_ajax_referer( 'trustbio_save_settings', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Permission denied.', 'trustbio' ) ), 403 ); } /* * Sanitised once, here, rather than only at each point of use. * * Every setting this method stores is validated again below — as a * key, an absint, a hex colour or a boolean — so the plugin itself was * never at risk. The payload is also handed to the * trustbio_settings_saved action further down, though, and an add-on * listening there received whatever was posted, untouched. Cleaning it * at the door means no listener can be the weak point. */ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitised by sanitize_payload() on the next line. $trustbio_raw = isset( $_POST['settings'] ) && is_array( $_POST['settings'] ) ? wp_unslash( $_POST['settings'] ) : array(); $s = self::sanitize_payload( (array) $trustbio_raw ); // ── General ── $trustbio_post_types = isset( $s['enabled_post_types'] ) && is_array( $s['enabled_post_types'] ) ? array_map( 'sanitize_key', (array) $s['enabled_post_types'] ) : array( 'post', 'page' ); update_option( 'trustbio_enabled_post_types', $trustbio_post_types ); $trustbio_roles = isset( $s['allowed_roles'] ) && is_array( $s['allowed_roles'] ) ? array_map( 'sanitize_key', (array) $s['allowed_roles'] ) : array( 'administrator', 'editor', 'author', 'contributor' ); update_option( 'trustbio_allowed_roles', $trustbio_roles ); update_option( 'trustbio_show_powered_by', ! empty( $s['show_powered_by'] ) ? 1 : 0 ); update_option( 'trustbio_allow_guest_no_email', ! empty( $s['allow_guest_no_email'] ) ? 1 : 0 ); update_option( 'trustbio_show_contribution_count', ! empty( $s['show_contribution_count'] ) ? 1 : 0 ); update_option( 'trustbio_contribution_noun_plural', sanitize_text_field( $s['contribution_noun_plural'] ?? 'articles' ) ); update_option( 'trustbio_contribution_min_count', absint( $s['contribution_min_count'] ?? 1 ) ); update_option( 'trustbio_archive_enabled', ! empty( $s['archive_enabled'] ) ? 1 : 0 ); update_option( 'trustbio_archive_per_page', absint( $s['archive_per_page'] ?? 10 ) ); update_option( 'trustbio_enable_archive_registered', ! empty( $s['enable_archive_registered'] ) ? 1 : 0 ); update_option( 'trustbio_enable_archive_guest', ! empty( $s['enable_archive_guest'] ) ? 1 : 0 ); $trustbio_hero = sanitize_hex_color( $s['archive_hero_color'] ?? '#312e81' ); update_option( 'trustbio_archive_hero_color', $trustbio_hero ?: '#312e81' ); $trustbio_accent = sanitize_hex_color( $s['archive_accent_color'] ?? '#4f46e5' ); update_option( 'trustbio_archive_accent_color', $trustbio_accent ?: '#4f46e5' ); $trustbio_url_base = sanitize_title( $s['author_url_base'] ?? 'authors' ); update_option( 'trustbio_author_url_base', $trustbio_url_base ?: 'authors' ); $trustbio_grav = sanitize_key( $s['gravatar_default'] ?? 'mystery' ); if ( ! in_array( $trustbio_grav, array( 'mystery', 'identicon', 'monsterid', 'wavatar', 'retro', 'blank' ), true ) ) { $trustbio_grav = 'mystery'; } update_option( 'trustbio_gravatar_default', $trustbio_grav ); // ── Author Boxes ── $trustbio_box_pos = sanitize_key( $s['box_position'] ?? 'after' ); if ( ! in_array( $trustbio_box_pos, array( 'after', 'before', 'both', 'manual' ), true ) ) { $trustbio_box_pos = 'after'; } update_option( 'trustbio_box_position', $trustbio_box_pos ); update_option( 'trustbio_auto_insert_posts', ! empty( $s['auto_insert_posts'] ) ? 1 : 0 ); update_option( 'trustbio_auto_insert_pages', ! empty( $s['auto_insert_pages'] ) ? 1 : 0 ); update_option( 'trustbio_exclude_post_ids', preg_replace( '/[^0-9,]/', '', sanitize_text_field( $s['exclude_post_ids'] ?? '' ) ) ); // ── Review Bars ── $trustbio_rev_disp = sanitize_key( $s['review_display'] ?? 'inline' ); if ( ! in_array( $trustbio_rev_disp, array( 'inline', 'bar', 'both', 'none' ), true ) ) { $trustbio_rev_disp = 'inline'; } update_option( 'trustbio_review_display', $trustbio_rev_disp ); update_option( 'trustbio_review_default_label', sanitize_text_field( $s['review_default_label'] ?? 'Medically reviewed by' ) ); update_option( 'trustbio_review_stale_months', absint( $s['review_stale_months'] ?? 12 ) ); update_option( 'trustbio_review_show_column', ! empty( $s['review_show_column'] ) ? 1 : 0 ); $trustbio_bar_pos = sanitize_key( $s['review_bar_position'] ?? 'before_content' ); if ( ! in_array( $trustbio_bar_pos, array( 'before_content', 'after_content' ), true ) ) { $trustbio_bar_pos = 'before_content'; } update_option( 'trustbio_review_bar_position', $trustbio_bar_pos ); update_option( 'trustbio_review_with_author_box', ! empty( $s['review_with_author_box'] ) ? 1 : 0 ); // ── Advanced ── update_option( 'trustbio_enable_schema', ! empty( $s['enable_schema'] ) ? 1 : 0 ); update_option( 'trustbio_schema_compat_mode', ! empty( $s['schema_compat_mode'] ) ? 1 : 0 ); update_option( 'trustbio_debug_mode', ! empty( $s['debug_mode'] ) ? 1 : 0 ); update_option( 'trustbio_enable_rest_api', ! empty( $s['enable_rest_api'] ) ? 1 : 0 ); /** * Fires while TrustBio settings are being saved. * * The TrustBio Pro add-on persists its own settings here. The array is * the posted settings payload, unslashed and recursively sanitised — * strings through sanitize_textarea_field(), keys through * sanitize_key(). A listener should still validate each value for its * own purpose, but it is never handed raw request data. * * @since 1.0.0 * @param array $posted Sanitised settings payload. */ do_action( 'trustbio_settings_saved', $s ); update_option( 'trustbio_delete_data_on_uninstall', ! empty( $s['delete_data_on_uninstall'] ) ? 1 : 0 ); wp_send_json_success( array( 'message' => __( 'Settings saved successfully.', 'trustbio' ) ) ); } // ========================================================= // AJAX: Save features // ========================================================= public function ajax_save_features(): void { check_ajax_referer( 'trustbio_save_features', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Permission denied.', 'trustbio' ) ), 403 ); } $trustbio_raw = isset( $_POST['features'] ) && is_array( $_POST['features'] ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ? array_map( 'sanitize_key', wp_unslash( $_POST['features'] ) ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized : array(); $trustbio_enabled = array_values( array_intersect( $trustbio_raw, self::feature_slugs() ) ); update_option( 'trustbio_enabled_features', $trustbio_enabled ); wp_send_json_success( array( 'message' => __( 'Feature settings saved. Reload the page to see menu changes.', 'trustbio' ), ) ); } // ========================================================= // AJAX: Flush rewrites // ========================================================= public function ajax_flush_rewrites(): void { check_ajax_referer( 'trustbio_flush_rewrites', 'nonce' ); trustbio_verify_ajax_request( 'trustbio_flush_rewrites' ); $trustbio_base = sanitize_title( wp_unslash( $_POST['author_url_base'] ?? 'authors' ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized update_option( 'trustbio_author_url_base', $trustbio_base ); add_rewrite_rule( '^' . preg_quote( $trustbio_base, '/' ) . '/([^/]+)/?$', 'index.php?trustbio_author=$matches[1]', 'top' ); flush_rewrite_rules(); wp_send_json_success( array( 'message' => __( 'Rewrite rules flushed. Author URLs are now active.', 'trustbio' ), 'example' => trailingslashit( home_url( $trustbio_base . '/author-slug' ) ), ) ); } // ========================================================= // AJAX: Clear cache // ========================================================= public function ajax_clear_cache(): void { check_ajax_referer( 'trustbio_clear_cache', 'nonce' ); trustbio_verify_ajax_request( 'trustbio_clear_cache' ); global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", $wpdb->esc_like( '_transient_trustbio_' ) . '%', $wpdb->esc_like( '_transient_timeout_trustbio_' ) . '%' ) ); wp_send_json_success( array( 'message' => __( 'Cache cleared successfully.', 'trustbio' ) ) ); } // ========================================================= // AJAX: Export data // ========================================================= public function ajax_export_data(): void { check_ajax_referer( 'trustbio_export_data', 'nonce' ); trustbio_verify_ajax_request( 'trustbio_export_data' ); global $wpdb; $trustbio_export = array( 'version' => TRUSTBIO_VERSION, 'exported' => gmdate( 'Y-m-d H:i:s' ), 'categories' => $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}trustbio_author_categories", ARRAY_A ), // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared 'fields' => $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}trustbio_author_fields", ARRAY_A ), // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared 'authors' => $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}trustbio_authors", ARRAY_A ), // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared 'boxes' => $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}trustbio_author_boxes", ARRAY_A ), // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared 'field_values' => $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}trustbio_author_field_values", ARRAY_A ), // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared 'credentials' => $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}trustbio_author_credentials", ARRAY_A ), // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared 'review_bars' => $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}trustbio_review_bars", ARRAY_A ), // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared ); wp_send_json_success( array( 'data' => wp_json_encode( $trustbio_export, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE ), 'filename' => 'trustbio-export-' . gmdate( 'Y-m-d' ) . '.json', ) ); } // ========================================================= // AJAX: Import data // ========================================================= public function ajax_import_data(): void { check_ajax_referer( 'trustbio_save_settings', 'nonce' ); trustbio_verify_ajax_request( 'trustbio_save_settings' ); if ( empty( $_POST['import_json'] ) ) { wp_send_json_error( array( 'message' => __( 'No data provided.', 'trustbio' ) ) ); } $trustbio_raw = sanitize_textarea_field( wp_unslash( $_POST['import_json'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $trustbio_data = json_decode( $trustbio_raw, true ); if ( ! is_array( $trustbio_data ) || empty( $trustbio_data['version'] ) ) { wp_send_json_error( array( 'message' => __( 'Invalid import file. Please use a file exported from TrustBio.', 'trustbio' ) ) ); } global $wpdb; $trustbio_counts = array( 'authors' => 0, 'boxes' => 0, 'review_bars' => 0 ); if ( ! empty( $trustbio_data['authors'] ) && is_array( $trustbio_data['authors'] ) ) { foreach ( $trustbio_data['authors'] as $trustbio_author ) { if ( empty( $trustbio_author['display_name'] ) ) { continue; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->replace( $wpdb->prefix . 'trustbio_authors', array( 'id' => absint( $trustbio_author['id'] ?? 0 ) ?: null, 'author_type' => in_array( $trustbio_author['author_type'] ?? '', array( 'registered', 'guest' ), true ) ? $trustbio_author['author_type'] : 'guest', 'wp_user_id' => ! empty( $trustbio_author['wp_user_id'] ) ? absint( $trustbio_author['wp_user_id'] ) : null, 'display_name' => sanitize_text_field( $trustbio_author['display_name'] ), 'slug' => sanitize_title( $trustbio_author['slug'] ?? $trustbio_author['display_name'] ), 'email' => sanitize_email( $trustbio_author['email'] ?? '' ), 'website' => esc_url_raw( $trustbio_author['website'] ?? '' ), 'job_title' => sanitize_text_field( $trustbio_author['job_title'] ?? '' ), 'bio' => wp_kses_post( $trustbio_author['bio'] ?? '' ), 'avatar_url' => esc_url_raw( $trustbio_author['avatar_url'] ?? '' ), 'is_active' => 1, ) ); ++$trustbio_counts['authors']; } } if ( ! empty( $trustbio_data['boxes'] ) && is_array( $trustbio_data['boxes'] ) ) { foreach ( $trustbio_data['boxes'] as $trustbio_row ) { if ( empty( $trustbio_row['title'] ) ) { continue; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->replace( $wpdb->prefix . 'trustbio_author_boxes', array( 'id' => absint( $trustbio_row['id'] ?? 0 ) ?: null, 'title' => sanitize_text_field( $trustbio_row['title'] ), 'layout' => sanitize_key( $trustbio_row['layout'] ?? 'classic' ), 'is_default' => (int) ( $trustbio_row['is_default'] ?? 0 ), 'settings' => wp_json_encode( json_decode( $trustbio_row['settings'] ?? '{}', true ) ), ) ); ++$trustbio_counts['boxes']; } } if ( ! empty( $trustbio_data['review_bars'] ) && is_array( $trustbio_data['review_bars'] ) ) { foreach ( $trustbio_data['review_bars'] as $trustbio_row ) { if ( empty( $trustbio_row['title'] ) ) { continue; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->replace( $wpdb->prefix . 'trustbio_review_bars', array( 'id' => absint( $trustbio_row['id'] ?? 0 ) ?: null, 'title' => sanitize_text_field( $trustbio_row['title'] ), 'layout' => sanitize_key( $trustbio_row['layout'] ?? 'classic' ), 'is_default' => (int) ( $trustbio_row['is_default'] ?? 0 ), 'settings' => wp_json_encode( json_decode( $trustbio_row['settings'] ?? '{}', true ) ), ) ); ++$trustbio_counts['review_bars']; } } wp_send_json_success( array( 'message' => sprintf( /* translators: %1$d: total, %2$d: authors, %3$d: boxes, %4$d: review bars */ __( 'Import complete. %1$d items: %2$d authors, %3$d boxes, %4$d review bars.', 'trustbio' ), array_sum( $trustbio_counts ), $trustbio_counts['authors'], $trustbio_counts['boxes'], $trustbio_counts['review_bars'] ) ) ); } // ========================================================= // Render helpers // ========================================================= /* * These are public on purpose. * * An add-on renders its own panels into this screen on the * trustbio_settings_panels action, and those panels have to look like the * rest of it. The alternative — each add-on writing its own copy of the * row markup — is what happened before, and it drifted immediately: the * panels came out as bare paragraphs with the label and its explanation * running together as one sentence, nothing lined up with the free rows, * and the markup had to be propped up by a stylesheet rule written for * that one wrong shape. * * The free plugin owns the design system, so it owns the row markup too. * The $key passed here is the key the value arrives under in the * trustbio_settings_saved payload, so an add-on should prefix its own. * * @since 1.0.0 */ /** * The label-and-explanation half of a settings row. * * The description is skipped when empty rather than rendered as an empty * element, because not every control has something worth adding — several * of the add-on's say everything they need to in the label. * * @param string $label Row label. * @param string $desc Explanation, or '' for none. * @return void */ private static function render_setting_info( string $label, string $desc ): void { ?>
>
post type object or slug => plain label; both * turn up here, and which it is used to be told by a boolean argument the * caller had to remember to pass. * * @param string $key Key the array is posted under. * @param array $checked Currently selected slugs. * @param array $options Available choices. * @param string $label Row label. * @param string $desc Row description. * @return void */ public static function render_checkbox_group( string $key, array $checked, $options, string $label, string $desc ): void { ?>
$obj ) : $trustbio_lbl = is_object( $obj ) ? ( $obj->labels->singular_name ?? $obj->labels->name ?? $slug ) : (string) $obj; $trustbio_checked = in_array( (string) $slug, array_map( 'strval', $checked ), true ); ?>

'', 'overview' => '', 'general' => '', 'boxes' => '', 'review' => '', 'advanced' => '', 'features' => '', 'maintenance' => '', 'ai-readiness'=> '', 'shortcodes' => '', 'stat' => '', 'check' => '', 'off' => '', 'sidebar' => '', 'qa' => '', 'gap' => '', 'ai' => '', 'schema' => '', 'llms' => '', 'validate' => '', 'email' => '', /* * Names the add-on's settings tabs ask for. They existed only * in trustbio_ap_svg_icon() (the admin shell's map), so six of * the eight add-on tabs rendered with no icon at all beside a * row of core tabs that had one. * * Added here rather than by delegating to that function: its * icons are filled 20x20 glyphs and these are 24x24 strokes, * so borrowing them would have put two icon styles in one * strip. The free plugin owns the design system, so the names * an add-on may use have to be defined in it. */ 'shield' => '', 'shield-check' => '', 'authors' => '', 'fields' => '', ); } /* * Fall back to a real icon, never to nothing. An unknown name used to * return '', which leaves a labelled tab sitting flush against its * neighbours' icons -- the ragged row described above. An add-on can * only pass a name it hopes exists, so a miss should degrade to a * neutral glyph, exactly as trustbio_ap_svg_icon() already does. */ return $icons[ $name ] ?? $icons['general']; } }