__( 'After content', 'trustbio' ),
'before' => __( 'Before content', 'trustbio' ),
'both' => __( 'Before and after', 'trustbio' ),
'manual' => __( 'Manual only (shortcode / block)', 'trustbio' ),
)
);
self::render_toggle( 'auto_insert_posts', $s['auto_insert_posts'],
__( 'Auto-Insert on Posts', 'trustbio' ), __( 'Automatically append the author box to all posts.', 'trustbio' ) );
self::render_toggle( 'auto_insert_pages', $s['auto_insert_pages'],
__( 'Auto-Insert on Pages', 'trustbio' ), __( 'Automatically append the author box to all pages.', 'trustbio' ) );
self::render_text( 'exclude_post_ids', $s['exclude_post_ids'],
__( 'Exclude Post IDs', 'trustbio' ), __( 'Comma-separated IDs where the author box should not appear.', 'trustbio' ) );
?>
__( 'Inline in author box', 'trustbio' ),
'bar' => __( 'Separate review bar', 'trustbio' ),
'both' => __( 'Both inline and bar', 'trustbio' ),
'none' => __( 'Hidden (meta only)', 'trustbio' ),
)
);
self::render_text( 'review_default_label', $s['review_default_label'],
__( 'Default Review Label', 'trustbio' ), __( 'e.g. "Medically reviewed by" or "Fact-checked by"', 'trustbio' ) );
self::render_number( 'review_stale_months', $s['review_stale_months'],
__( 'Stale After (months)', 'trustbio' ), __( 'Content without a review in this many months is flagged as stale.', 'trustbio' ), 1, 60 );
self::render_toggle( 'review_show_column', $s['review_show_column'],
__( 'Show Column in Posts List', 'trustbio' ), __( 'Add a "Last Reviewed" column to the WP posts list table.', 'trustbio' ) );
self::render_select( 'review_bar_position', $s['review_bar_position'],
__( 'Review Bar Position', 'trustbio' ), __( 'Where the review bar appears within post content.', 'trustbio' ),
array(
'before_content' => __( 'Before content', 'trustbio' ),
'after_content' => __( 'After content', 'trustbio' ),
)
);
self::render_toggle( 'review_with_author_box', $s['review_with_author_box'],
__( 'Attach Bar to Author Box', 'trustbio' ), __( 'Render the review bar immediately adjacent to the author box.', 'trustbio' ) );
?>
$settings Current settings array.
*/
do_action( 'trustbio_settings_advanced_sections', $s );
?>
__( 'Author Box', 'trustbio' ),
'items' => array(
array(
'tag' => '[trustbio]',
'desc' => __( 'Outputs the author box for the current post. Place anywhere inside post content.', 'trustbio' ),
'attrs' => array(
'id="5"' => __( 'Show a specific author by their TrustBio author ID.', 'trustbio' ),
'slug="dr-jane-smith"' => __( 'Show a specific author by their URL slug.', 'trustbio' ),
'box="Compact"' => __( 'Use a specific author box by its title.', 'trustbio' ),
'category="medical-review"' => __( 'Only show authors in a specific author category.', 'trustbio' ),
),
),
),
),
array(
'title' => __( 'Last Review Bar', 'trustbio' ),
'items' => array(
array(
'tag' => '[trustbio_review]',
'desc' => __( 'Inserts the last-reviewed bar. Useful when auto-insert is disabled.', 'trustbio' ),
'attrs' => array(
'post_id="42"' => __( 'Show the review data for a different post.', 'trustbio' ),
'style="inline"' => __( 'Render as inline text instead of a bar.', 'trustbio' ),
'label="Fact-checked by"' => __( 'Override the review label for this instance.', 'trustbio' ),
),
),
),
),
);
/**
* Filters the documented shortcode reference groups.
*
* The TrustBio Pro add-on documents its own shortcodes here.
*
* @since 1.0.0
* @param array $groups Shortcode reference groups.
*/
$tbs_groups = (array) apply_filters( 'trustbio_shortcode_reference', $tbs_groups );
/*
* Skip anything that is not shaped like a group. An add-on that
* gets this filter's structure wrong should not be able to print
* PHP warnings across a user's Settings screen.
*/
$tbs_groups = array_filter(
$tbs_groups,
static function ( $tbs_candidate ) {
return is_array( $tbs_candidate )
&& ! empty( $tbs_candidate['title'] )
&& ! empty( $tbs_candidate['items'] )
&& is_array( $tbs_candidate['items'] );
}
);
foreach ( $tbs_groups as $tbs_group ) : ?>
$tbs_attr_desc ) : ?>
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 {
?>
'',
'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'];
}
}