author-box-widget.php

I’ll build this as a full advanced WordPress widget file for inc/widgets/author-box-widget.php, including admin fields, sanitization, schema-ready output, social links, avatar controls, author meta, CTA buttons, styling options, and safe fallbacks.

Below is a complete advanced inc/widgets/author-box-widget.php file for your RX Theme.

<?php
/**
 * RX Theme - Advanced Author Box Widget
 *
 * File: inc/widgets/author-box-widget.php
 *
 * Features:
 * - Current post author or selected author
 * - Avatar, name, role, bio, website, email, post count
 * - Social links
 * - Latest author posts
 * - CTA button
 * - Follow button/link
 * - Schema.org Person JSON-LD
 * - Layout/style options
 * - Safe escaping and sanitization
 *
 * @package RX_Theme
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! class_exists( 'RX_Author_Box_Widget' ) ) :

class RX_Author_Box_Widget extends WP_Widget {

	/**
	 * Default widget settings.
	 */
	private function defaults() {
		return array(
			'title'                => esc_html__( 'About the Author', 'rx-theme' ),
			'author_source'        => 'current_post_author', // current_post_author, selected_author, current_user
			'author_id'            => 0,

			'show_avatar'          => 1,
			'avatar_size'          => 120,
			'avatar_shape'         => 'circle', // circle, rounded, square

			'show_name'            => 1,
			'show_display_role'    => 1,
			'custom_role_label'    => '',

			'show_bio'             => 1,
			'bio_limit'            => 280,

			'show_website'         => 1,
			'show_email'           => 0,
			'show_post_count'      => 1,
			'show_registered_date' => 0,

			'show_socials'         => 1,
			'social_style'         => 'text', // text, button

			'show_latest_posts'    => 0,
			'latest_posts_count'   => 3,

			'show_cta'             => 0,
			'cta_text'             => esc_html__( 'View Author Profile', 'rx-theme' ),
			'cta_url'              => '',

			'show_follow'          => 0,
			'follow_text'          => esc_html__( 'Follow Author', 'rx-theme' ),
			'follow_url'           => '',

			'layout'               => 'center', // center, left, compact
			'box_style'            => 'card', // card, minimal, border
			'background_color'     => '#ffffff',
			'text_color'           => '#222222',
			'accent_color'         => '#0073aa',

			'enable_schema'        => 1,
			'extra_class'          => '',
		);
	}

	/**
	 * Constructor.
	 */
	public function __construct() {
		parent::__construct(
			'rx_author_box_widget',
			esc_html__( 'RX Advanced Author Box', 'rx-theme' ),
			array(
				'classname'                   => 'rx_author_box_widget',
				'description'                 => esc_html__( 'Advanced author profile box with avatar, bio, social links, CTA, latest posts, and schema.', 'rx-theme' ),
				'customize_selective_refresh' => true,
			)
		);
	}

	/**
	 * Get setting with default.
	 */
	private function get_setting( $instance, $key ) {
		$defaults = $this->defaults();
		return isset( $instance[ $key ] ) ? $instance[ $key ] : $defaults[ $key ];
	}

	/**
	 * Validate checkbox value.
	 */
	private function checkbox( $value ) {
		return ! empty( $value ) ? 1 : 0;
	}

	/**
	 * Sanitize color.
	 */
	private function sanitize_color( $color, $fallback = '#ffffff' ) {
		$color = sanitize_hex_color( $color );
		return $color ? $color : $fallback;
	}

	/**
	 * Trim text by character length.
	 */
	private function trim_text( $text, $limit = 280 ) {
		$text  = wp_strip_all_tags( $text );
		$limit = absint( $limit );

		if ( $limit < 1 ) {
			return $text;
		}

		if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_substr' ) ) {
			if ( mb_strlen( $text ) > $limit ) {
				return mb_substr( $text, 0, $limit ) . '...';
			}
			return $text;
		}

		if ( strlen( $text ) > $limit ) {
			return substr( $text, 0, $limit ) . '...';
		}

		return $text;
	}

	/**
	 * Get author ID based on widget source.
	 */
	private function get_author_id( $instance ) {
		$source    = $this->get_setting( $instance, 'author_source' );
		$author_id = absint( $this->get_setting( $instance, 'author_id' ) );

		if ( 'selected_author' === $source && $author_id > 0 ) {
			return $author_id;
		}

		if ( 'current_user' === $source && is_user_logged_in() ) {
			return get_current_user_id();
		}

		if ( 'current_post_author' === $source && is_singular() ) {
			$post_id = get_the_ID();
			if ( $post_id ) {
				$post_author = absint( get_post_field( 'post_author', $post_id ) );
				if ( $post_author > 0 ) {
					return $post_author;
				}
			}
		}

		if ( $author_id > 0 ) {
			return $author_id;
		}

		$admin_users = get_users(
			array(
				'role__in' => array( 'administrator', 'editor', 'author' ),
				'number'   => 1,
				'fields'   => array( 'ID' ),
			)
		);

		if ( ! empty( $admin_users[0]->ID ) ) {
			return absint( $admin_users[0]->ID );
		}

		return 0;
	}

	/**
	 * Get author role.
	 */
	private function get_author_role( $user ) {
		if ( empty( $user->roles ) || ! is_array( $user->roles ) ) {
			return '';
		}

		$roles = wp_roles();

		$role_key = $user->roles[0];

		if ( isset( $roles->roles[ $role_key ]['name'] ) ) {
			return translate_user_role( $roles->roles[ $role_key ]['name'] );
		}

		return ucfirst( str_replace( '_', ' ', $role_key ) );
	}

	/**
	 * Get social links from user meta.
	 */
	private function get_social_links( $author_id ) {
		$socials = array(
			'facebook'  => array(
				'label' => esc_html__( 'Facebook', 'rx-theme' ),
				'keys'  => array( 'facebook', 'facebook_url', 'rx_facebook' ),
			),
			'twitter'   => array(
				'label' => esc_html__( 'X / Twitter', 'rx-theme' ),
				'keys'  => array( 'twitter', 'twitter_url', 'x_url', 'rx_twitter' ),
			),
			'linkedin'  => array(
				'label' => esc_html__( 'LinkedIn', 'rx-theme' ),
				'keys'  => array( 'linkedin', 'linkedin_url', 'rx_linkedin' ),
			),
			'instagram' => array(
				'label' => esc_html__( 'Instagram', 'rx-theme' ),
				'keys'  => array( 'instagram', 'instagram_url', 'rx_instagram' ),
			),
			'youtube'   => array(
				'label' => esc_html__( 'YouTube', 'rx-theme' ),
				'keys'  => array( 'youtube', 'youtube_url', 'rx_youtube' ),
			),
			'github'    => array(
				'label' => esc_html__( 'GitHub', 'rx-theme' ),
				'keys'  => array( 'github', 'github_url', 'rx_github' ),
			),
			'website'   => array(
				'label' => esc_html__( 'Website', 'rx-theme' ),
				'keys'  => array( 'website', 'user_url' ),
			),
		);

		$output = array();

		foreach ( $socials as $slug => $data ) {
			$url = '';

			foreach ( $data['keys'] as $key ) {
				if ( 'user_url' === $key ) {
					$user_data = get_userdata( $author_id );
					$value     = $user_data ? $user_data->user_url : '';
				} else {
					$value = get_user_meta( $author_id, $key, true );
				}

				if ( ! empty( $value ) ) {
					$url = esc_url_raw( $value );
					break;
				}
			}

			if ( ! empty( $url ) ) {
				$output[ $slug ] = array(
					'label' => $data['label'],
					'url'   => $url,
				);
			}
		}

		return $output;
	}

	/**
	 * Widget front-end output.
	 */
	public function widget( $args, $instance ) {
		$instance = wp_parse_args( (array) $instance, $this->defaults() );

		$author_id = $this->get_author_id( $instance );

		if ( ! $author_id ) {
			return;
		}

		$user = get_userdata( $author_id );

		if ( ! $user ) {
			return;
		}

		$title = apply_filters( 'widget_title', $this->get_setting( $instance, 'title' ), $instance, $this->id_base );

		$display_name = $user->display_name ? $user->display_name : $user->user_login;
		$bio          = get_user_meta( $author_id, 'description', true );
		$website      = $user->user_url;
		$email        = $user->user_email;
		$posts_url    = get_author_posts_url( $author_id );
		$post_count   = count_user_posts( $author_id, 'post', true );

		$avatar_size  = absint( $this->get_setting( $instance, 'avatar_size' ) );
		$avatar_size  = $avatar_size ? $avatar_size : 120;

		$layout       = sanitize_html_class( $this->get_setting( $instance, 'layout' ) );
		$box_style    = sanitize_html_class( $this->get_setting( $instance, 'box_style' ) );
		$avatar_shape = sanitize_html_class( $this->get_setting( $instance, 'avatar_shape' ) );
		$extra_class  = sanitize_html_class( $this->get_setting( $instance, 'extra_class' ) );

		$bg_color     = $this->sanitize_color( $this->get_setting( $instance, 'background_color' ), '#ffffff' );
		$text_color   = $this->sanitize_color( $this->get_setting( $instance, 'text_color' ), '#222222' );
		$accent_color = $this->sanitize_color( $this->get_setting( $instance, 'accent_color' ), '#0073aa' );

		$classes = array(
			'rx-author-box',
			'rx-author-layout-' . $layout,
			'rx-author-style-' . $box_style,
			'rx-author-avatar-' . $avatar_shape,
		);

		if ( ! empty( $extra_class ) ) {
			$classes[] = $extra_class;
		}

		$style = sprintf(
			'--rx-author-bg:%1$s;--rx-author-text:%2$s;--rx-author-accent:%3$s;',
			esc_attr( $bg_color ),
			esc_attr( $text_color ),
			esc_attr( $accent_color )
		);

		echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

		if ( ! empty( $title ) ) {
			echo $args['before_title'] . esc_html( $title ) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		}

		?>
		<div class="<?php echo esc_attr( implode( ' ', $classes ) ); ?>" style="<?php echo esc_attr( $style ); ?>">
			<style>
				.rx-author-box {
					background: var(--rx-author-bg);
					color: var(--rx-author-text);
					padding: 24px;
					margin: 0 0 24px;
					box-sizing: border-box;
					position: relative;
					overflow: hidden;
				}

				.rx-author-style-card {
					border-radius: 18px;
					box-shadow: 0 10px 30px rgba(0,0,0,.08);
				}

				.rx-author-style-border {
					border: 1px solid rgba(0,0,0,.12);
					border-radius: 14px;
				}

				.rx-author-style-minimal {
					padding: 12px 0;
					background: transparent;
				}

				.rx-author-layout-center {
					text-align: center;
				}

				.rx-author-layout-left {
					text-align: left;
				}

				.rx-author-layout-compact {
					display: flex;
					gap: 16px;
					align-items: flex-start;
					text-align: left;
				}

				.rx-author-avatar-wrap {
					margin-bottom: 16px;
					line-height: 0;
				}

				.rx-author-layout-compact .rx-author-avatar-wrap {
					margin-bottom: 0;
					flex: 0 0 auto;
				}

				.rx-author-avatar-wrap img {
					max-width: 100%;
					height: auto;
					border: 4px solid rgba(255,255,255,.9);
					box-shadow: 0 6px 18px rgba(0,0,0,.12);
				}

				.rx-author-avatar-circle img {
					border-radius: 999px;
				}

				.rx-author-avatar-rounded img {
					border-radius: 18px;
				}

				.rx-author-avatar-square img {
					border-radius: 0;
				}

				.rx-author-name {
					font-size: 22px;
					line-height: 1.25;
					margin: 0 0 6px;
					color: var(--rx-author-text);
				}

				.rx-author-name a {
					color: inherit;
					text-decoration: none;
				}

				.rx-author-name a:hover {
					color: var(--rx-author-accent);
				}

				.rx-author-role {
					font-size: 14px;
					font-weight: 600;
					color: var(--rx-author-accent);
					margin-bottom: 10px;
				}

				.rx-author-bio {
					font-size: 15px;
					line-height: 1.7;
					margin: 10px 0 14px;
				}

				.rx-author-meta {
					display: flex;
					flex-wrap: wrap;
					gap: 8px;
					justify-content: center;
					margin: 14px 0;
					font-size: 13px;
				}

				.rx-author-layout-left .rx-author-meta,
				.rx-author-layout-compact .rx-author-meta {
					justify-content: flex-start;
				}

				.rx-author-meta span,
				.rx-author-meta a {
					display: inline-flex;
					align-items: center;
					gap: 6px;
					padding: 5px 10px;
					border-radius: 999px;
					background: rgba(0,0,0,.05);
					color: inherit;
					text-decoration: none;
				}

				.rx-author-meta a:hover {
					background: var(--rx-author-accent);
					color: #fff;
				}

				.rx-author-socials {
					display: flex;
					flex-wrap: wrap;
					gap: 8px;
					justify-content: center;
					margin: 16px 0;
				}

				.rx-author-layout-left .rx-author-socials,
				.rx-author-layout-compact .rx-author-socials {
					justify-content: flex-start;
				}

				.rx-author-socials a {
					text-decoration: none;
					color: var(--rx-author-accent);
					font-size: 14px;
					font-weight: 600;
				}

				.rx-author-socials.rx-social-button a {
					padding: 7px 12px;
					border-radius: 999px;
					background: var(--rx-author-accent);
					color: #fff;
				}

				.rx-author-actions {
					display: flex;
					flex-wrap: wrap;
					gap: 10px;
					justify-content: center;
					margin-top: 16px;
				}

				.rx-author-layout-left .rx-author-actions,
				.rx-author-layout-compact .rx-author-actions {
					justify-content: flex-start;
				}

				.rx-author-button {
					display: inline-flex;
					align-items: center;
					justify-content: center;
					padding: 10px 16px;
					border-radius: 999px;
					background: var(--rx-author-accent);
					color: #fff !important;
					text-decoration: none;
					font-size: 14px;
					font-weight: 700;
					transition: transform .2s ease, opacity .2s ease;
				}

				.rx-author-button:hover {
					opacity: .9;
					transform: translateY(-1px);
				}

				.rx-author-button.secondary {
					background: transparent;
					color: var(--rx-author-accent) !important;
					border: 1px solid var(--rx-author-accent);
				}

				.rx-author-latest {
					margin-top: 20px;
					padding-top: 16px;
					border-top: 1px solid rgba(0,0,0,.08);
				}

				.rx-author-latest-title {
					font-size: 16px;
					margin: 0 0 10px;
				}

				.rx-author-latest ul {
					list-style: none;
					margin: 0;
					padding: 0;
				}

				.rx-author-latest li {
					margin: 0 0 8px;
					padding: 0;
				}

				.rx-author-latest a {
					color: inherit;
					text-decoration: none;
					font-weight: 600;
				}

				.rx-author-latest a:hover {
					color: var(--rx-author-accent);
				}

				@media (max-width: 600px) {
					.rx-author-layout-compact {
						display: block;
						text-align: center;
					}

					.rx-author-layout-compact .rx-author-avatar-wrap {
						margin-bottom: 16px;
					}

					.rx-author-layout-compact .rx-author-meta,
					.rx-author-layout-compact .rx-author-socials,
					.rx-author-layout-compact .rx-author-actions {
						justify-content: center;
					}
				}
			</style>

			<?php if ( $this->checkbox( $this->get_setting( $instance, 'show_avatar' ) ) ) : ?>
				<div class="rx-author-avatar-wrap">
					<a href="<?php echo esc_url( $posts_url ); ?>" aria-label="<?php echo esc_attr( sprintf( __( 'View all posts by %s', 'rx-theme' ), $display_name ) ); ?>">
						<?php
						echo get_avatar(
							$author_id,
							$avatar_size,
							'',
							$display_name,
							array(
								'class' => 'rx-author-avatar-img',
							)
						);
						?>
					</a>
				</div>
			<?php endif; ?>

			<div class="rx-author-content">
				<?php if ( $this->checkbox( $this->get_setting( $instance, 'show_name' ) ) ) : ?>
					<h3 class="rx-author-name">
						<a href="<?php echo esc_url( $posts_url ); ?>">
							<?php echo esc_html( $display_name ); ?>
						</a>
					</h3>
				<?php endif; ?>

				<?php
				$role_label = $this->get_setting( $instance, 'custom_role_label' );
				$role_label = $role_label ? $role_label : $this->get_author_role( $user );
				?>

				<?php if ( $this->checkbox( $this->get_setting( $instance, 'show_display_role' ) ) && ! empty( $role_label ) ) : ?>
					<div class="rx-author-role">
						<?php echo esc_html( $role_label ); ?>
					</div>
				<?php endif; ?>

				<?php if ( $this->checkbox( $this->get_setting( $instance, 'show_bio' ) ) && ! empty( $bio ) ) : ?>
					<div class="rx-author-bio">
						<?php echo esc_html( $this->trim_text( $bio, $this->get_setting( $instance, 'bio_limit' ) ) ); ?>
					</div>
				<?php endif; ?>

				<div class="rx-author-meta">
					<?php if ( $this->checkbox( $this->get_setting( $instance, 'show_post_count' ) ) ) : ?>
						<span class="rx-author-post-count">
							<?php
							printf(
								esc_html( _n( '%s Article', '%s Articles', $post_count, 'rx-theme' ) ),
								esc_html( number_format_i18n( $post_count ) )
							);
							?>
						</span>
					<?php endif; ?>

					<?php if ( $this->checkbox( $this->get_setting( $instance, 'show_website' ) ) && ! empty( $website ) ) : ?>
						<a class="rx-author-website" href="<?php echo esc_url( $website ); ?>" target="_blank" rel="nofollow noopener noreferrer">
							<?php esc_html_e( 'Website', 'rx-theme' ); ?>
						</a>
					<?php endif; ?>

					<?php if ( $this->checkbox( $this->get_setting( $instance, 'show_email' ) ) && ! empty( $email ) ) : ?>
						<a class="rx-author-email" href="<?php echo esc_url( 'mailto:' . antispambot( $email ) ); ?>">
							<?php esc_html_e( 'Email', 'rx-theme' ); ?>
						</a>
					<?php endif; ?>

					<?php if ( $this->checkbox( $this->get_setting( $instance, 'show_registered_date' ) ) ) : ?>
						<span class="rx-author-registered">
							<?php
							printf(
								esc_html__( 'Joined %s', 'rx-theme' ),
								esc_html( date_i18n( get_option( 'date_format' ), strtotime( $user->user_registered ) ) )
							);
							?>
						</span>
					<?php endif; ?>
				</div>

				<?php
				$social_links = $this->get_social_links( $author_id );
				$social_style = sanitize_html_class( $this->get_setting( $instance, 'social_style' ) );
				?>

				<?php if ( $this->checkbox( $this->get_setting( $instance, 'show_socials' ) ) && ! empty( $social_links ) ) : ?>
					<div class="rx-author-socials rx-social-<?php echo esc_attr( $social_style ); ?>">
						<?php foreach ( $social_links as $social_key => $social ) : ?>
							<a class="rx-social-link rx-social-<?php echo esc_attr( $social_key ); ?>" href="<?php echo esc_url( $social['url'] ); ?>" target="_blank" rel="nofollow noopener noreferrer">
								<?php echo esc_html( $social['label'] ); ?>
							</a>
						<?php endforeach; ?>
					</div>
				<?php endif; ?>

				<?php if ( $this->checkbox( $this->get_setting( $instance, 'show_cta' ) ) || $this->checkbox( $this->get_setting( $instance, 'show_follow' ) ) ) : ?>
					<div class="rx-author-actions">
						<?php
						$cta_url = $this->get_setting( $instance, 'cta_url' );
						$cta_url = $cta_url ? $cta_url : $posts_url;
						?>

						<?php if ( $this->checkbox( $this->get_setting( $instance, 'show_cta' ) ) ) : ?>
							<a class="rx-author-button" href="<?php echo esc_url( $cta_url ); ?>">
								<?php echo esc_html( $this->get_setting( $instance, 'cta_text' ) ); ?>
							</a>
						<?php endif; ?>

						<?php if ( $this->checkbox( $this->get_setting( $instance, 'show_follow' ) ) && $this->get_setting( $instance, 'follow_url' ) ) : ?>
							<a class="rx-author-button secondary" href="<?php echo esc_url( $this->get_setting( $instance, 'follow_url' ) ); ?>" target="_blank" rel="nofollow noopener noreferrer">
								<?php echo esc_html( $this->get_setting( $instance, 'follow_text' ) ); ?>
							</a>
						<?php endif; ?>
					</div>
				<?php endif; ?>

				<?php if ( $this->checkbox( $this->get_setting( $instance, 'show_latest_posts' ) ) ) : ?>
					<?php
					$latest_count = absint( $this->get_setting( $instance, 'latest_posts_count' ) );
					$latest_count = $latest_count ? $latest_count : 3;

					$latest_posts = new WP_Query(
						array(
							'author'              => $author_id,
							'post_type'           => 'post',
							'post_status'         => 'publish',
							'posts_per_page'      => $latest_count,
							'ignore_sticky_posts' => true,
							'no_found_rows'       => true,
						)
					);
					?>

					<?php if ( $latest_posts->have_posts() ) : ?>
						<div class="rx-author-latest">
							<h4 class="rx-author-latest-title">
								<?php esc_html_e( 'Latest Articles', 'rx-theme' ); ?>
							</h4>

							<ul>
								<?php while ( $latest_posts->have_posts() ) : ?>
									<?php $latest_posts->the_post(); ?>
									<li>
										<a href="<?php the_permalink(); ?>">
											<?php the_title(); ?>
										</a>
									</li>
								<?php endwhile; ?>
							</ul>
						</div>
					<?php endif; ?>

					<?php wp_reset_postdata(); ?>
				<?php endif; ?>
			</div>

			<?php if ( $this->checkbox( $this->get_setting( $instance, 'enable_schema' ) ) ) : ?>
				<?php
				$schema = array(
					'@context' => 'https://schema.org',
					'@type'    => 'Person',
					'name'     => $display_name,
					'url'      => $posts_url,
				);

				if ( ! empty( $bio ) ) {
					$schema['description'] = wp_strip_all_tags( $bio );
				}

				if ( ! empty( $website ) ) {
					$schema['sameAs'] = array_values(
						wp_list_pluck( $social_links, 'url' )
					);

					if ( empty( $schema['sameAs'] ) ) {
						$schema['sameAs'] = array( esc_url_raw( $website ) );
					}
				}

				$avatar_url = get_avatar_url( $author_id, array( 'size' => 300 ) );

				if ( $avatar_url ) {
					$schema['image'] = esc_url_raw( $avatar_url );
				}
				?>
				<script type="application/ld+json">
					<?php echo wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); ?>
				</script>
			<?php endif; ?>
		</div>
		<?php

		echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	}

	/**
	 * Widget admin form.
	 */
	public function form( $instance ) {
		$instance = wp_parse_args( (array) $instance, $this->defaults() );

		$users = get_users(
			array(
				'orderby' => 'display_name',
				'order'   => 'ASC',
				'fields'  => array( 'ID', 'display_name', 'user_login' ),
			)
		);
		?>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
				<?php esc_html_e( 'Title:', 'rx-theme' ); ?>
			</label>
			<input class="widefat"
				id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
				type="text"
				value="<?php echo esc_attr( $this->get_setting( $instance, 'title' ) ); ?>">
		</p>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'author_source' ) ); ?>">
				<?php esc_html_e( 'Author Source:', 'rx-theme' ); ?>
			</label>
			<select class="widefat"
				id="<?php echo esc_attr( $this->get_field_id( 'author_source' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'author_source' ) ); ?>">
				<option value="current_post_author" <?php selected( $this->get_setting( $instance, 'author_source' ), 'current_post_author' ); ?>>
					<?php esc_html_e( 'Current Post Author', 'rx-theme' ); ?>
				</option>
				<option value="selected_author" <?php selected( $this->get_setting( $instance, 'author_source' ), 'selected_author' ); ?>>
					<?php esc_html_e( 'Selected Author', 'rx-theme' ); ?>
				</option>
				<option value="current_user" <?php selected( $this->get_setting( $instance, 'author_source' ), 'current_user' ); ?>>
					<?php esc_html_e( 'Current Logged-in User', 'rx-theme' ); ?>
				</option>
			</select>
		</p>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'author_id' ) ); ?>">
				<?php esc_html_e( 'Select Author:', 'rx-theme' ); ?>
			</label>
			<select class="widefat"
				id="<?php echo esc_attr( $this->get_field_id( 'author_id' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'author_id' ) ); ?>">
				<option value="0"><?php esc_html_e( 'Auto / Default', 'rx-theme' ); ?></option>
				<?php foreach ( $users as $user ) : ?>
					<option value="<?php echo esc_attr( $user->ID ); ?>" <?php selected( absint( $this->get_setting( $instance, 'author_id' ) ), $user->ID ); ?>>
						<?php echo esc_html( $user->display_name ? $user->display_name : $user->user_login ); ?>
					</option>
				<?php endforeach; ?>
			</select>
		</p>

		<hr>

		<p>
			<input class="checkbox" type="checkbox"
				id="<?php echo esc_attr( $this->get_field_id( 'show_avatar' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'show_avatar' ) ); ?>"
				value="1" <?php checked( $this->get_setting( $instance, 'show_avatar' ), 1 ); ?>>
			<label for="<?php echo esc_attr( $this->get_field_id( 'show_avatar' ) ); ?>">
				<?php esc_html_e( 'Show Avatar', 'rx-theme' ); ?>
			</label>
		</p>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'avatar_size' ) ); ?>">
				<?php esc_html_e( 'Avatar Size:', 'rx-theme' ); ?>
			</label>
			<input class="small-text"
				id="<?php echo esc_attr( $this->get_field_id( 'avatar_size' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'avatar_size' ) ); ?>"
				type="number"
				min="40"
				max="300"
				value="<?php echo esc_attr( absint( $this->get_setting( $instance, 'avatar_size' ) ) ); ?>">
			<span><?php esc_html_e( 'px', 'rx-theme' ); ?></span>
		</p>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'avatar_shape' ) ); ?>">
				<?php esc_html_e( 'Avatar Shape:', 'rx-theme' ); ?>
			</label>
			<select class="widefat"
				id="<?php echo esc_attr( $this->get_field_id( 'avatar_shape' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'avatar_shape' ) ); ?>">
				<option value="circle" <?php selected( $this->get_setting( $instance, 'avatar_shape' ), 'circle' ); ?>><?php esc_html_e( 'Circle', 'rx-theme' ); ?></option>
				<option value="rounded" <?php selected( $this->get_setting( $instance, 'avatar_shape' ), 'rounded' ); ?>><?php esc_html_e( 'Rounded', 'rx-theme' ); ?></option>
				<option value="square" <?php selected( $this->get_setting( $instance, 'avatar_shape' ), 'square' ); ?>><?php esc_html_e( 'Square', 'rx-theme' ); ?></option>
			</select>
		</p>

		<hr>

		<?php
		$checkboxes = array(
			'show_name'            => esc_html__( 'Show Name', 'rx-theme' ),
			'show_display_role'    => esc_html__( 'Show Role / Label', 'rx-theme' ),
			'show_bio'             => esc_html__( 'Show Bio', 'rx-theme' ),
			'show_website'         => esc_html__( 'Show Website', 'rx-theme' ),
			'show_email'           => esc_html__( 'Show Email', 'rx-theme' ),
			'show_post_count'      => esc_html__( 'Show Post Count', 'rx-theme' ),
			'show_registered_date' => esc_html__( 'Show Registered Date', 'rx-theme' ),
			'show_socials'         => esc_html__( 'Show Social Links', 'rx-theme' ),
			'show_latest_posts'    => esc_html__( 'Show Latest Posts', 'rx-theme' ),
			'show_cta'             => esc_html__( 'Show CTA Button', 'rx-theme' ),
			'show_follow'          => esc_html__( 'Show Follow Button', 'rx-theme' ),
			'enable_schema'        => esc_html__( 'Enable Person Schema JSON-LD', 'rx-theme' ),
		);
		?>

		<?php foreach ( $checkboxes as $key => $label ) : ?>
			<p>
				<input class="checkbox" type="checkbox"
					id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"
					name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>"
					value="1" <?php checked( $this->get_setting( $instance, $key ), 1 ); ?>>
				<label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>">
					<?php echo esc_html( $label ); ?>
				</label>
			</p>
		<?php endforeach; ?>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'custom_role_label' ) ); ?>">
				<?php esc_html_e( 'Custom Role Label:', 'rx-theme' ); ?>
			</label>
			<input class="widefat"
				id="<?php echo esc_attr( $this->get_field_id( 'custom_role_label' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'custom_role_label' ) ); ?>"
				type="text"
				value="<?php echo esc_attr( $this->get_setting( $instance, 'custom_role_label' ) ); ?>"
				placeholder="<?php esc_attr_e( 'Example: Medical Author, Editor, Doctor', 'rx-theme' ); ?>">
		</p>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'bio_limit' ) ); ?>">
				<?php esc_html_e( 'Bio Character Limit:', 'rx-theme' ); ?>
			</label>
			<input class="small-text"
				id="<?php echo esc_attr( $this->get_field_id( 'bio_limit' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'bio_limit' ) ); ?>"
				type="number"
				min="50"
				max="2000"
				value="<?php echo esc_attr( absint( $this->get_setting( $instance, 'bio_limit' ) ) ); ?>">
		</p>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'latest_posts_count' ) ); ?>">
				<?php esc_html_e( 'Latest Posts Count:', 'rx-theme' ); ?>
			</label>
			<input class="small-text"
				id="<?php echo esc_attr( $this->get_field_id( 'latest_posts_count' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'latest_posts_count' ) ); ?>"
				type="number"
				min="1"
				max="10"
				value="<?php echo esc_attr( absint( $this->get_setting( $instance, 'latest_posts_count' ) ) ); ?>">
		</p>

		<hr>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'cta_text' ) ); ?>">
				<?php esc_html_e( 'CTA Text:', 'rx-theme' ); ?>
			</label>
			<input class="widefat"
				id="<?php echo esc_attr( $this->get_field_id( 'cta_text' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'cta_text' ) ); ?>"
				type="text"
				value="<?php echo esc_attr( $this->get_setting( $instance, 'cta_text' ) ); ?>">
		</p>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'cta_url' ) ); ?>">
				<?php esc_html_e( 'CTA URL:', 'rx-theme' ); ?>
			</label>
			<input class="widefat"
				id="<?php echo esc_attr( $this->get_field_id( 'cta_url' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'cta_url' ) ); ?>"
				type="url"
				value="<?php echo esc_url( $this->get_setting( $instance, 'cta_url' ) ); ?>">
		</p>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'follow_text' ) ); ?>">
				<?php esc_html_e( 'Follow Text:', 'rx-theme' ); ?>
			</label>
			<input class="widefat"
				id="<?php echo esc_attr( $this->get_field_id( 'follow_text' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'follow_text' ) ); ?>"
				type="text"
				value="<?php echo esc_attr( $this->get_setting( $instance, 'follow_text' ) ); ?>">
		</p>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'follow_url' ) ); ?>">
				<?php esc_html_e( 'Follow URL:', 'rx-theme' ); ?>
			</label>
			<input class="widefat"
				id="<?php echo esc_attr( $this->get_field_id( 'follow_url' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'follow_url' ) ); ?>"
				type="url"
				value="<?php echo esc_url( $this->get_setting( $instance, 'follow_url' ) ); ?>">
		</p>

		<hr>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'layout' ) ); ?>">
				<?php esc_html_e( 'Layout:', 'rx-theme' ); ?>
			</label>
			<select class="widefat"
				id="<?php echo esc_attr( $this->get_field_id( 'layout' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'layout' ) ); ?>">
				<option value="center" <?php selected( $this->get_setting( $instance, 'layout' ), 'center' ); ?>><?php esc_html_e( 'Center', 'rx-theme' ); ?></option>
				<option value="left" <?php selected( $this->get_setting( $instance, 'layout' ), 'left' ); ?>><?php esc_html_e( 'Left', 'rx-theme' ); ?></option>
				<option value="compact" <?php selected( $this->get_setting( $instance, 'layout' ), 'compact' ); ?>><?php esc_html_e( 'Compact', 'rx-theme' ); ?></option>
			</select>
		</p>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'box_style' ) ); ?>">
				<?php esc_html_e( 'Box Style:', 'rx-theme' ); ?>
			</label>
			<select class="widefat"
				id="<?php echo esc_attr( $this->get_field_id( 'box_style' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'box_style' ) ); ?>">
				<option value="card" <?php selected( $this->get_setting( $instance, 'box_style' ), 'card' ); ?>><?php esc_html_e( 'Card', 'rx-theme' ); ?></option>
				<option value="border" <?php selected( $this->get_setting( $instance, 'box_style' ), 'border' ); ?>><?php esc_html_e( 'Border', 'rx-theme' ); ?></option>
				<option value="minimal" <?php selected( $this->get_setting( $instance, 'box_style' ), 'minimal' ); ?>><?php esc_html_e( 'Minimal', 'rx-theme' ); ?></option>
			</select>
		</p>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'social_style' ) ); ?>">
				<?php esc_html_e( 'Social Style:', 'rx-theme' ); ?>
			</label>
			<select class="widefat"
				id="<?php echo esc_attr( $this->get_field_id( 'social_style' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'social_style' ) ); ?>">
				<option value="text" <?php selected( $this->get_setting( $instance, 'social_style' ), 'text' ); ?>><?php esc_html_e( 'Text', 'rx-theme' ); ?></option>
				<option value="button" <?php selected( $this->get_setting( $instance, 'social_style' ), 'button' ); ?>><?php esc_html_e( 'Button', 'rx-theme' ); ?></option>
			</select>
		</p>

		<p>
			<label><?php esc_html_e( 'Background Color:', 'rx-theme' ); ?></label>
			<input class="widefat"
				name="<?php echo esc_attr( $this->get_field_name( 'background_color' ) ); ?>"
				type="text"
				value="<?php echo esc_attr( $this->get_setting( $instance, 'background_color' ) ); ?>">
		</p>

		<p>
			<label><?php esc_html_e( 'Text Color:', 'rx-theme' ); ?></label>
			<input class="widefat"
				name="<?php echo esc_attr( $this->get_field_name( 'text_color' ) ); ?>"
				type="text"
				value="<?php echo esc_attr( $this->get_setting( $instance, 'text_color' ) ); ?>">
		</p>

		<p>
			<label><?php esc_html_e( 'Accent Color:', 'rx-theme' ); ?></label>
			<input class="widefat"
				name="<?php echo esc_attr( $this->get_field_name( 'accent_color' ) ); ?>"
				type="text"
				value="<?php echo esc_attr( $this->get_setting( $instance, 'accent_color' ) ); ?>">
		</p>

		<p>
			<label for="<?php echo esc_attr( $this->get_field_id( 'extra_class' ) ); ?>">
				<?php esc_html_e( 'Extra CSS Class:', 'rx-theme' ); ?>
			</label>
			<input class="widefat"
				id="<?php echo esc_attr( $this->get_field_id( 'extra_class' ) ); ?>"
				name="<?php echo esc_attr( $this->get_field_name( 'extra_class' ) ); ?>"
				type="text"
				value="<?php echo esc_attr( $this->get_setting( $instance, 'extra_class' ) ); ?>">
		</p>

		<?php
	}

	/**
	 * Save widget settings.
	 */
	public function update( $new_instance, $old_instance ) {
		$defaults = $this->defaults();
		$instance = array();

		$instance['title']         = sanitize_text_field( $new_instance['title'] ?? $defaults['title'] );
		$instance['author_source'] = sanitize_key( $new_instance['author_source'] ?? $defaults['author_source'] );
		$instance['author_id']     = absint( $new_instance['author_id'] ?? 0 );

		$allowed_sources = array( 'current_post_author', 'selected_author', 'current_user' );
		if ( ! in_array( $instance['author_source'], $allowed_sources, true ) ) {
			$instance['author_source'] = 'current_post_author';
		}

		$checkboxes = array(
			'show_avatar',
			'show_name',
			'show_display_role',
			'show_bio',
			'show_website',
			'show_email',
			'show_post_count',
			'show_registered_date',
			'show_socials',
			'show_latest_posts',
			'show_cta',
			'show_follow',
			'enable_schema',
		);

		foreach ( $checkboxes as $key ) {
			$instance[ $key ] = ! empty( $new_instance[ $key ] ) ? 1 : 0;
		}

		$instance['avatar_size'] = min( 300, max( 40, absint( $new_instance['avatar_size'] ?? $defaults['avatar_size'] ) ) );
		$instance['bio_limit']   = min( 2000, max( 50, absint( $new_instance['bio_limit'] ?? $defaults['bio_limit'] ) ) );

		$instance['latest_posts_count'] = min( 10, max( 1, absint( $new_instance['latest_posts_count'] ?? $defaults['latest_posts_count'] ) ) );

		$instance['avatar_shape'] = sanitize_key( $new_instance['avatar_shape'] ?? $defaults['avatar_shape'] );
		$allowed_avatar_shapes    = array( 'circle', 'rounded', 'square' );
		if ( ! in_array( $instance['avatar_shape'], $allowed_avatar_shapes, true ) ) {
			$instance['avatar_shape'] = 'circle';
		}

		$instance['layout'] = sanitize_key( $new_instance['layout'] ?? $defaults['layout'] );
		$allowed_layouts    = array( 'center', 'left', 'compact' );
		if ( ! in_array( $instance['layout'], $allowed_layouts, true ) ) {
			$instance['layout'] = 'center';
		}

		$instance['box_style'] = sanitize_key( $new_instance['box_style'] ?? $defaults['box_style'] );
		$allowed_styles        = array( 'card', 'border', 'minimal' );
		if ( ! in_array( $instance['box_style'], $allowed_styles, true ) ) {
			$instance['box_style'] = 'card';
		}

		$instance['social_style'] = sanitize_key( $new_instance['social_style'] ?? $defaults['social_style'] );
		$allowed_social_styles    = array( 'text', 'button' );
		if ( ! in_array( $instance['social_style'], $allowed_social_styles, true ) ) {
			$instance['social_style'] = 'text';
		}

		$instance['custom_role_label'] = sanitize_text_field( $new_instance['custom_role_label'] ?? '' );

		$instance['cta_text']   = sanitize_text_field( $new_instance['cta_text'] ?? $defaults['cta_text'] );
		$instance['cta_url']    = esc_url_raw( $new_instance['cta_url'] ?? '' );
		$instance['follow_text'] = sanitize_text_field( $new_instance['follow_text'] ?? $defaults['follow_text'] );
		$instance['follow_url']  = esc_url_raw( $new_instance['follow_url'] ?? '' );

		$instance['background_color'] = $this->sanitize_color( $new_instance['background_color'] ?? $defaults['background_color'], '#ffffff' );
		$instance['text_color']       = $this->sanitize_color( $new_instance['text_color'] ?? $defaults['text_color'], '#222222' );
		$instance['accent_color']     = $this->sanitize_color( $new_instance['accent_color'] ?? $defaults['accent_color'], '#0073aa' );

		$instance['extra_class'] = sanitize_html_class( $new_instance['extra_class'] ?? '' );

		return $instance;
	}
}

endif;

/**
 * Register RX Author Box Widget.
 */
if ( ! function_exists( 'rx_register_author_box_widget' ) ) {
	function rx_register_author_box_widget() {
		register_widget( 'RX_Author_Box_Widget' );
	}
	add_action( 'widgets_init', 'rx_register_author_box_widget' );
}

Add this in your theme functions.php if not already included:

/**
 * RX Theme widgets.
 */
$rx_author_widget_file = get_template_directory() . '/inc/widgets/author-box-widget.php';

if ( file_exists( $rx_author_widget_file ) ) {
	require_once $rx_author_widget_file;
}

For author social links, this widget checks user meta fields like:

facebook, facebook_url, rx_facebook
twitter, twitter_url, x_url, rx_twitter
linkedin, linkedin_url, rx_linkedin
instagram, instagram_url, rx_instagram
youtube, youtube_url, rx_youtube
github, github_url, rx_github

You can add those fields later in your user profile system or author meta plugin.

Leave a Reply

Your email address will not be published. Required fields are marked *