glossary.php

I’ll build this as a complete advanced inc/post-types/glossary.php file for your RX theme: custom post type, taxonomy, meta fields, admin columns, schema, shortcodes, REST support, search helpers, breadcrumbs/permalinks, and safety checks.

Below is an advanced full inc/post-types/glossary.php file for your RX Theme. It creates a complete medical/health glossary system with:

Custom Post Type, glossary categories, tags, A–Z archive support, meta boxes, schema JSON-LD, REST API support, shortcodes, admin columns, search boost, breadcrumbs support, related glossary items, reading time, featured term support, difficulty level, pronunciation, synonyms, references, FAQ-style fields, and SEO-friendly output helpers.

Create this file:

rx-theme/inc/post-types/glossary.php

Then include it in your functions.php:

require_once get_template_directory() . '/inc/post-types/glossary.php';

Now paste this full code inside glossary.php:

<?php
/**
 * RX Theme Glossary Post Type
 *
 * File: inc/post-types/glossary.php
 *
 * This file creates a powerful glossary system for rxharun.com.
 * It is designed for medical, anatomical, diagnostic, disease, lab-test,
 * treatment, drug, symptom, and health education terms.
 *
 * Features:
 * - Custom post type: rx_glossary
 * - Glossary category taxonomy
 * - Glossary tag taxonomy
 * - A-Z letter taxonomy
 * - Custom meta fields
 * - SEO-friendly labels
 * - REST API support
 * - Admin columns
 * - Search integration
 * - Shortcodes
 * - Schema.org MedicalEntity/DefinedTerm JSON-LD
 * - Related glossary helper
 * - Reading time helper
 * - Breadcrumb helper compatibility
 * - Archive title helper
 * - Custom excerpt helper
 * - Featured glossary support
 * - Difficulty level
 * - Pronunciation
 * - Synonyms
 * - References
 * - Medical disclaimer support
 *
 * @package RX_Theme
 */

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

if ( ! class_exists( 'RX_Theme_Glossary' ) ) :

final class RX_Theme_Glossary {

	/**
	 * Post type slug.
	 */
	const POST_TYPE = 'rx_glossary';

	/**
	 * Main category taxonomy.
	 */
	const TAX_CATEGORY = 'rx_glossary_category';

	/**
	 * Tag taxonomy.
	 */
	const TAX_TAG = 'rx_glossary_tag';

	/**
	 * A-Z taxonomy.
	 */
	const TAX_LETTER = 'rx_glossary_letter';

	/**
	 * Meta nonce.
	 */
	const NONCE_ACTION = 'rx_glossary_meta_save';

	/**
	 * Meta nonce name.
	 */
	const NONCE_NAME = 'rx_glossary_meta_nonce';

	/**
	 * Boot class.
	 */
	public static function init() {

		add_action( 'init', array( __CLASS__, 'register_post_type' ), 0 );
		add_action( 'init', array( __CLASS__, 'register_taxonomies' ), 0 );
		add_action( 'init', array( __CLASS__, 'register_meta_fields' ), 5 );

		add_action( 'add_meta_boxes', array( __CLASS__, 'add_meta_boxes' ) );
		add_action( 'save_post_' . self::POST_TYPE, array( __CLASS__, 'save_meta_boxes' ), 10, 2 );

		add_filter( 'manage_' . self::POST_TYPE . '_posts_columns', array( __CLASS__, 'admin_columns' ) );
		add_action( 'manage_' . self::POST_TYPE . '_posts_custom_column', array( __CLASS__, 'admin_columns_content' ), 10, 2 );
		add_filter( 'manage_edit-' . self::POST_TYPE . '_sortable_columns', array( __CLASS__, 'sortable_columns' ) );
		add_action( 'pre_get_posts', array( __CLASS__, 'admin_column_orderby' ) );

		add_filter( 'post_updated_messages', array( __CLASS__, 'post_updated_messages' ) );
		add_filter( 'bulk_post_updated_messages', array( __CLASS__, 'bulk_post_updated_messages' ), 10, 2 );

		add_filter( 'enter_title_here', array( __CLASS__, 'title_placeholder' ), 10, 2 );
		add_filter( 'the_content', array( __CLASS__, 'append_glossary_content' ), 20 );
		add_action( 'wp_head', array( __CLASS__, 'output_schema_json_ld' ), 30 );

		add_shortcode( 'rx_glossary_index', array( __CLASS__, 'shortcode_glossary_index' ) );
		add_shortcode( 'rx_glossary_search', array( __CLASS__, 'shortcode_glossary_search' ) );
		add_shortcode( 'rx_glossary_related', array( __CLASS__, 'shortcode_related_glossary' ) );
		add_shortcode( 'rx_glossary_featured', array( __CLASS__, 'shortcode_featured_glossary' ) );

		add_filter( 'template_include', array( __CLASS__, 'template_loader' ) );
		add_filter( 'body_class', array( __CLASS__, 'body_classes' ) );

		add_filter( 'wp_sitemaps_post_types', array( __CLASS__, 'enable_sitemap' ) );

		add_action( 'restrict_manage_posts', array( __CLASS__, 'admin_filter_dropdowns' ) );
		add_filter( 'parse_query', array( __CLASS__, 'admin_filter_query' ) );

		add_action( 'wp_ajax_rx_glossary_live_search', array( __CLASS__, 'ajax_live_search' ) );
		add_action( 'wp_ajax_nopriv_rx_glossary_live_search', array( __CLASS__, 'ajax_live_search' ) );

		add_filter( 'rest_prepare_' . self::POST_TYPE, array( __CLASS__, 'rest_prepare_glossary' ), 10, 3 );

		add_action( 'transition_post_status', array( __CLASS__, 'auto_assign_first_letter' ), 10, 3 );
		add_action( 'save_post_' . self::POST_TYPE, array( __CLASS__, 'auto_assign_first_letter_on_save' ), 20, 3 );

		add_filter( 'posts_search', array( __CLASS__, 'improve_glossary_search' ), 10, 2 );
	}

	/**
	 * Register glossary custom post type.
	 */
	public static function register_post_type() {

		$labels = array(
			'name'                  => _x( 'Glossary', 'Post type general name', 'rx-theme' ),
			'singular_name'         => _x( 'Glossary Term', 'Post type singular name', 'rx-theme' ),
			'menu_name'             => __( 'Glossary', 'rx-theme' ),
			'name_admin_bar'        => __( 'Glossary Term', 'rx-theme' ),
			'add_new'               => __( 'Add New', 'rx-theme' ),
			'add_new_item'          => __( 'Add New Glossary Term', 'rx-theme' ),
			'new_item'              => __( 'New Glossary Term', 'rx-theme' ),
			'edit_item'             => __( 'Edit Glossary Term', 'rx-theme' ),
			'view_item'             => __( 'View Glossary Term', 'rx-theme' ),
			'all_items'             => __( 'All Glossary Terms', 'rx-theme' ),
			'search_items'          => __( 'Search Glossary Terms', 'rx-theme' ),
			'parent_item_colon'     => __( 'Parent Glossary Terms:', 'rx-theme' ),
			'not_found'             => __( 'No glossary terms found.', 'rx-theme' ),
			'not_found_in_trash'    => __( 'No glossary terms found in Trash.', 'rx-theme' ),
			'featured_image'        => __( 'Glossary Image', 'rx-theme' ),
			'set_featured_image'    => __( 'Set glossary image', 'rx-theme' ),
			'remove_featured_image' => __( 'Remove glossary image', 'rx-theme' ),
			'use_featured_image'    => __( 'Use as glossary image', 'rx-theme' ),
			'archives'              => __( 'Glossary Archives', 'rx-theme' ),
			'insert_into_item'      => __( 'Insert into glossary term', 'rx-theme' ),
			'uploaded_to_this_item' => __( 'Uploaded to this glossary term', 'rx-theme' ),
			'filter_items_list'     => __( 'Filter glossary list', 'rx-theme' ),
			'items_list_navigation' => __( 'Glossary list navigation', 'rx-theme' ),
			'items_list'            => __( 'Glossary list', 'rx-theme' ),
		);

		$args = array(
			'labels'              => $labels,
			'description'         => __( 'Medical and health glossary terms for RX Theme.', 'rx-theme' ),
			'public'              => true,
			'publicly_queryable'  => true,
			'exclude_from_search' => false,
			'show_ui'             => true,
			'show_in_menu'        => true,
			'show_in_nav_menus'   => true,
			'show_in_admin_bar'   => true,
			'show_in_rest'        => true,
			'rest_base'           => 'glossary',
			'rest_controller_class' => 'WP_REST_Posts_Controller',
			'menu_position'       => 21,
			'menu_icon'           => 'dashicons-book-alt',
			'capability_type'     => 'post',
			'map_meta_cap'        => true,
			'hierarchical'        => false,
			'supports'            => array(
				'title',
				'editor',
				'excerpt',
				'author',
				'thumbnail',
				'comments',
				'revisions',
				'custom-fields',
				'page-attributes',
			),
			'taxonomies'          => array(
				self::TAX_CATEGORY,
				self::TAX_TAG,
				self::TAX_LETTER,
			),
			'has_archive'         => 'glossary',
			'rewrite'             => array(
				'slug'       => 'glossary',
				'with_front' => false,
				'feeds'      => true,
				'pages'      => true,
			),
			'query_var'           => true,
			'can_export'          => true,
			'delete_with_user'    => false,
		);

		register_post_type( self::POST_TYPE, $args );
	}

	/**
	 * Register taxonomies.
	 */
	public static function register_taxonomies() {

		$category_labels = array(
			'name'              => _x( 'Glossary Categories', 'taxonomy general name', 'rx-theme' ),
			'singular_name'     => _x( 'Glossary Category', 'taxonomy singular name', 'rx-theme' ),
			'search_items'      => __( 'Search Glossary Categories', 'rx-theme' ),
			'all_items'         => __( 'All Glossary Categories', 'rx-theme' ),
			'parent_item'       => __( 'Parent Glossary Category', 'rx-theme' ),
			'parent_item_colon' => __( 'Parent Glossary Category:', 'rx-theme' ),
			'edit_item'         => __( 'Edit Glossary Category', 'rx-theme' ),
			'update_item'       => __( 'Update Glossary Category', 'rx-theme' ),
			'add_new_item'      => __( 'Add New Glossary Category', 'rx-theme' ),
			'new_item_name'     => __( 'New Glossary Category Name', 'rx-theme' ),
			'menu_name'         => __( 'Glossary Categories', 'rx-theme' ),
		);

		register_taxonomy(
			self::TAX_CATEGORY,
			array( self::POST_TYPE ),
			array(
				'hierarchical'      => true,
				'labels'            => $category_labels,
				'show_ui'           => true,
				'show_admin_column' => true,
				'show_in_nav_menus' => true,
				'show_tagcloud'     => true,
				'show_in_rest'      => true,
				'rest_base'         => 'glossary-categories',
				'query_var'         => true,
				'rewrite'           => array(
					'slug'         => 'glossary-category',
					'with_front'   => false,
					'hierarchical' => true,
				),
			)
		);

		$tag_labels = array(
			'name'                       => _x( 'Glossary Tags', 'taxonomy general name', 'rx-theme' ),
			'singular_name'              => _x( 'Glossary Tag', 'taxonomy singular name', 'rx-theme' ),
			'search_items'               => __( 'Search Glossary Tags', 'rx-theme' ),
			'popular_items'              => __( 'Popular Glossary Tags', 'rx-theme' ),
			'all_items'                  => __( 'All Glossary Tags', 'rx-theme' ),
			'edit_item'                  => __( 'Edit Glossary Tag', 'rx-theme' ),
			'update_item'                => __( 'Update Glossary Tag', 'rx-theme' ),
			'add_new_item'               => __( 'Add New Glossary Tag', 'rx-theme' ),
			'new_item_name'              => __( 'New Glossary Tag Name', 'rx-theme' ),
			'separate_items_with_commas' => __( 'Separate tags with commas', 'rx-theme' ),
			'add_or_remove_items'        => __( 'Add or remove tags', 'rx-theme' ),
			'choose_from_most_used'      => __( 'Choose from the most used tags', 'rx-theme' ),
			'not_found'                  => __( 'No glossary tags found.', 'rx-theme' ),
			'menu_name'                  => __( 'Glossary Tags', 'rx-theme' ),
		);

		register_taxonomy(
			self::TAX_TAG,
			array( self::POST_TYPE ),
			array(
				'hierarchical'          => false,
				'labels'                => $tag_labels,
				'show_ui'               => true,
				'show_admin_column'     => true,
				'show_in_nav_menus'     => true,
				'show_tagcloud'         => true,
				'show_in_rest'          => true,
				'rest_base'             => 'glossary-tags',
				'query_var'             => true,
				'update_count_callback' => '_update_post_term_count',
				'rewrite'               => array(
					'slug'       => 'glossary-tag',
					'with_front' => false,
				),
			)
		);

		$letter_labels = array(
			'name'          => _x( 'Glossary Letters', 'taxonomy general name', 'rx-theme' ),
			'singular_name' => _x( 'Glossary Letter', 'taxonomy singular name', 'rx-theme' ),
			'search_items'  => __( 'Search Letters', 'rx-theme' ),
			'all_items'     => __( 'All Letters', 'rx-theme' ),
			'edit_item'     => __( 'Edit Letter', 'rx-theme' ),
			'update_item'   => __( 'Update Letter', 'rx-theme' ),
			'add_new_item'  => __( 'Add New Letter', 'rx-theme' ),
			'new_item_name' => __( 'New Letter Name', 'rx-theme' ),
			'menu_name'     => __( 'A-Z Letters', 'rx-theme' ),
		);

		register_taxonomy(
			self::TAX_LETTER,
			array( self::POST_TYPE ),
			array(
				'hierarchical'      => false,
				'labels'            => $letter_labels,
				'show_ui'           => true,
				'show_admin_column' => true,
				'show_in_nav_menus' => true,
				'show_tagcloud'     => false,
				'show_in_rest'      => true,
				'rest_base'         => 'glossary-letters',
				'query_var'         => true,
				'rewrite'           => array(
					'slug'       => 'glossary-letter',
					'with_front' => false,
				),
			)
		);

		self::maybe_create_default_letters();
	}

	/**
	 * Create A-Z terms if missing.
	 */
	public static function maybe_create_default_letters() {

		$letters = range( 'A', 'Z' );

		foreach ( $letters as $letter ) {
			if ( ! term_exists( $letter, self::TAX_LETTER ) ) {
				wp_insert_term(
					$letter,
					self::TAX_LETTER,
					array(
						'slug' => strtolower( $letter ),
					)
				);
			}
		}

		if ( ! term_exists( '0-9', self::TAX_LETTER ) ) {
			wp_insert_term(
				'0-9',
				self::TAX_LETTER,
				array(
					'slug' => '0-9',
				)
			);
		}
	}

	/**
	 * Register meta fields for REST and sanitization.
	 */
	public static function register_meta_fields() {

		$fields = self::get_meta_fields();

		foreach ( $fields as $key => $field ) {
			register_post_meta(
				self::POST_TYPE,
				$key,
				array(
					'type'              => isset( $field['type'] ) ? $field['type'] : 'string',
					'single'            => true,
					'sanitize_callback' => isset( $field['sanitize'] ) ? $field['sanitize'] : 'sanitize_text_field',
					'auth_callback'     => function() {
						return current_user_can( 'edit_posts' );
					},
					'show_in_rest'      => isset( $field['rest'] ) ? (bool) $field['rest'] : true,
				)
			);
		}
	}

	/**
	 * Meta fields list.
	 */
	public static function get_meta_fields() {

		return array(
			'_rx_glossary_short_definition' => array(
				'label'    => __( 'Short Definition', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'sanitize_textarea_field',
				'rest'     => true,
			),
			'_rx_glossary_long_definition' => array(
				'label'    => __( 'Long Definition', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'wp_kses_post',
				'rest'     => true,
			),
			'_rx_glossary_pronunciation' => array(
				'label'    => __( 'Pronunciation', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'sanitize_text_field',
				'rest'     => true,
			),
			'_rx_glossary_audio_url' => array(
				'label'    => __( 'Pronunciation Audio URL', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'esc_url_raw',
				'rest'     => true,
			),
			'_rx_glossary_synonyms' => array(
				'label'    => __( 'Synonyms / Other Names', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'sanitize_textarea_field',
				'rest'     => true,
			),
			'_rx_glossary_related_terms' => array(
				'label'    => __( 'Related Terms', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'sanitize_textarea_field',
				'rest'     => true,
			),
			'_rx_glossary_medical_specialty' => array(
				'label'    => __( 'Medical Specialty', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'sanitize_text_field',
				'rest'     => true,
			),
			'_rx_glossary_body_system' => array(
				'label'    => __( 'Body System', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'sanitize_text_field',
				'rest'     => true,
			),
			'_rx_glossary_difficulty' => array(
				'label'    => __( 'Difficulty Level', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'sanitize_text_field',
				'rest'     => true,
			),
			'_rx_glossary_featured' => array(
				'label'    => __( 'Featured Glossary Term', 'rx-theme' ),
				'type'     => 'boolean',
				'sanitize' => array( __CLASS__, 'sanitize_checkbox' ),
				'rest'     => true,
			),
			'_rx_glossary_reviewed_by' => array(
				'label'    => __( 'Reviewed By', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'sanitize_text_field',
				'rest'     => true,
			),
			'_rx_glossary_reviewed_date' => array(
				'label'    => __( 'Reviewed Date', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'sanitize_text_field',
				'rest'     => true,
			),
			'_rx_glossary_source_references' => array(
				'label'    => __( 'References / Sources', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'wp_kses_post',
				'rest'     => true,
			),
			'_rx_glossary_faq_question_1' => array(
				'label'    => __( 'FAQ Question 1', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'sanitize_text_field',
				'rest'     => true,
			),
			'_rx_glossary_faq_answer_1' => array(
				'label'    => __( 'FAQ Answer 1', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'wp_kses_post',
				'rest'     => true,
			),
			'_rx_glossary_faq_question_2' => array(
				'label'    => __( 'FAQ Question 2', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'sanitize_text_field',
				'rest'     => true,
			),
			'_rx_glossary_faq_answer_2' => array(
				'label'    => __( 'FAQ Answer 2', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'wp_kses_post',
				'rest'     => true,
			),
			'_rx_glossary_faq_question_3' => array(
				'label'    => __( 'FAQ Question 3', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'sanitize_text_field',
				'rest'     => true,
			),
			'_rx_glossary_faq_answer_3' => array(
				'label'    => __( 'FAQ Answer 3', 'rx-theme' ),
				'type'     => 'string',
				'sanitize' => 'wp_kses_post',
				'rest'     => true,
			),
		);
	}

	/**
	 * Add meta boxes.
	 */
	public static function add_meta_boxes() {

		add_meta_box(
			'rx_glossary_core_meta',
			__( 'Glossary Details', 'rx-theme' ),
			array( __CLASS__, 'render_core_meta_box' ),
			self::POST_TYPE,
			'normal',
			'high'
		);

		add_meta_box(
			'rx_glossary_medical_meta',
			__( 'Medical Classification', 'rx-theme' ),
			array( __CLASS__, 'render_medical_meta_box' ),
			self::POST_TYPE,
			'side',
			'default'
		);

		add_meta_box(
			'rx_glossary_review_meta',
			__( 'Review & References', 'rx-theme' ),
			array( __CLASS__, 'render_review_meta_box' ),
			self::POST_TYPE,
			'normal',
			'default'
		);

		add_meta_box(
			'rx_glossary_faq_meta',
			__( 'Glossary FAQs', 'rx-theme' ),
			array( __CLASS__, 'render_faq_meta_box' ),
			self::POST_TYPE,
			'normal',
			'default'
		);
	}

	/**
	 * Render core meta box.
	 */
	public static function render_core_meta_box( $post ) {

		wp_nonce_field( self::NONCE_ACTION, self::NONCE_NAME );

		$short_definition = get_post_meta( $post->ID, '_rx_glossary_short_definition', true );
		$long_definition  = get_post_meta( $post->ID, '_rx_glossary_long_definition', true );
		$pronunciation    = get_post_meta( $post->ID, '_rx_glossary_pronunciation', true );
		$audio_url        = get_post_meta( $post->ID, '_rx_glossary_audio_url', true );
		$synonyms         = get_post_meta( $post->ID, '_rx_glossary_synonyms', true );
		$related_terms    = get_post_meta( $post->ID, '_rx_glossary_related_terms', true );

		?>
		<p>
			<label for="rx_glossary_short_definition"><strong><?php esc_html_e( 'Short Definition', 'rx-theme' ); ?></strong></label>
			<textarea id="rx_glossary_short_definition" name="rx_glossary_short_definition" rows="3" class="large-text"><?php echo esc_textarea( $short_definition ); ?></textarea>
			<span class="description"><?php esc_html_e( 'A simple 1–2 sentence definition for users and search snippets.', 'rx-theme' ); ?></span>
		</p>

		<p>
			<label for="rx_glossary_long_definition"><strong><?php esc_html_e( 'Long Definition', 'rx-theme' ); ?></strong></label>
			<?php
			wp_editor(
				$long_definition,
				'rx_glossary_long_definition',
				array(
					'textarea_name' => 'rx_glossary_long_definition',
					'textarea_rows' => 8,
					'media_buttons' => false,
					'teeny'         => false,
				)
			);
			?>
		</p>

		<p>
			<label for="rx_glossary_pronunciation"><strong><?php esc_html_e( 'Pronunciation', 'rx-theme' ); ?></strong></label>
			<input type="text" id="rx_glossary_pronunciation" name="rx_glossary_pronunciation" value="<?php echo esc_attr( $pronunciation ); ?>" class="large-text" placeholder="Example: new-TROH-fee-nee-ah">
		</p>

		<p>
			<label for="rx_glossary_audio_url"><strong><?php esc_html_e( 'Audio URL', 'rx-theme' ); ?></strong></label>
			<input type="url" id="rx_glossary_audio_url" name="rx_glossary_audio_url" value="<?php echo esc_url( $audio_url ); ?>" class="large-text" placeholder="https://example.com/audio.mp3">
		</p>

		<p>
			<label for="rx_glossary_synonyms"><strong><?php esc_html_e( 'Synonyms / Other Names', 'rx-theme' ); ?></strong></label>
			<textarea id="rx_glossary_synonyms" name="rx_glossary_synonyms" rows="3" class="large-text" placeholder="One or more names, separated by commas"><?php echo esc_textarea( $synonyms ); ?></textarea>
		</p>

		<p>
			<label for="rx_glossary_related_terms"><strong><?php esc_html_e( 'Related Terms', 'rx-theme' ); ?></strong></label>
			<textarea id="rx_glossary_related_terms" name="rx_glossary_related_terms" rows="3" class="large-text" placeholder="Example: CBC, Neutrophils, Hematocrit"><?php echo esc_textarea( $related_terms ); ?></textarea>
		</p>
		<?php
	}

	/**
	 * Render medical meta box.
	 */
	public static function render_medical_meta_box( $post ) {

		$specialty  = get_post_meta( $post->ID, '_rx_glossary_medical_specialty', true );
		$body       = get_post_meta( $post->ID, '_rx_glossary_body_system', true );
		$difficulty = get_post_meta( $post->ID, '_rx_glossary_difficulty', true );
		$featured   = get_post_meta( $post->ID, '_rx_glossary_featured', true );

		?>
		<p>
			<label for="rx_glossary_medical_specialty"><strong><?php esc_html_e( 'Medical Specialty', 'rx-theme' ); ?></strong></label>
			<input type="text" id="rx_glossary_medical_specialty" name="rx_glossary_medical_specialty" value="<?php echo esc_attr( $specialty ); ?>" class="widefat" placeholder="Example: Hematology">
		</p>

		<p>
			<label for="rx_glossary_body_system"><strong><?php esc_html_e( 'Body System', 'rx-theme' ); ?></strong></label>
			<input type="text" id="rx_glossary_body_system" name="rx_glossary_body_system" value="<?php echo esc_attr( $body ); ?>" class="widefat" placeholder="Example: Blood system">
		</p>

		<p>
			<label for="rx_glossary_difficulty"><strong><?php esc_html_e( 'Difficulty Level', 'rx-theme' ); ?></strong></label>
			<select id="rx_glossary_difficulty" name="rx_glossary_difficulty" class="widefat">
				<option value=""><?php esc_html_e( 'Select difficulty', 'rx-theme' ); ?></option>
				<option value="beginner" <?php selected( $difficulty, 'beginner' ); ?>><?php esc_html_e( 'Beginner', 'rx-theme' ); ?></option>
				<option value="intermediate" <?php selected( $difficulty, 'intermediate' ); ?>><?php esc_html_e( 'Intermediate', 'rx-theme' ); ?></option>
				<option value="advanced" <?php selected( $difficulty, 'advanced' ); ?>><?php esc_html_e( 'Advanced', 'rx-theme' ); ?></option>
				<option value="professional" <?php selected( $difficulty, 'professional' ); ?>><?php esc_html_e( 'Professional', 'rx-theme' ); ?></option>
			</select>
		</p>

		<p>
			<label>
				<input type="checkbox" name="rx_glossary_featured" value="1" <?php checked( $featured, '1' ); ?>>
				<?php esc_html_e( 'Mark as featured glossary term', 'rx-theme' ); ?>
			</label>
		</p>
		<?php
	}

	/**
	 * Render review meta box.
	 */
	public static function render_review_meta_box( $post ) {

		$reviewed_by = get_post_meta( $post->ID, '_rx_glossary_reviewed_by', true );
		$review_date = get_post_meta( $post->ID, '_rx_glossary_reviewed_date', true );
		$references  = get_post_meta( $post->ID, '_rx_glossary_source_references', true );

		?>
		<p>
			<label for="rx_glossary_reviewed_by"><strong><?php esc_html_e( 'Reviewed By', 'rx-theme' ); ?></strong></label>
			<input type="text" id="rx_glossary_reviewed_by" name="rx_glossary_reviewed_by" value="<?php echo esc_attr( $reviewed_by ); ?>" class="large-text" placeholder="Example: Rx Harun Medical Review Board">
		</p>

		<p>
			<label for="rx_glossary_reviewed_date"><strong><?php esc_html_e( 'Reviewed Date', 'rx-theme' ); ?></strong></label>
			<input type="date" id="rx_glossary_reviewed_date" name="rx_glossary_reviewed_date" value="<?php echo esc_attr( $review_date ); ?>" class="regular-text">
		</p>

		<p>
			<label for="rx_glossary_source_references"><strong><?php esc_html_e( 'References / Sources', 'rx-theme' ); ?></strong></label>
			<?php
			wp_editor(
				$references,
				'rx_glossary_source_references',
				array(
					'textarea_name' => 'rx_glossary_source_references',
					'textarea_rows' => 7,
					'media_buttons' => false,
					'teeny'         => false,
				)
			);
			?>
		</p>
		<?php
	}

	/**
	 * Render FAQ meta box.
	 */
	public static function render_faq_meta_box( $post ) {

		for ( $i = 1; $i <= 3; $i++ ) {
			$question = get_post_meta( $post->ID, '_rx_glossary_faq_question_' . $i, true );
			$answer   = get_post_meta( $post->ID, '_rx_glossary_faq_answer_' . $i, true );
			?>
			<div style="padding:12px 0;border-bottom:1px solid #ddd;">
				<p>
					<label for="rx_glossary_faq_question_<?php echo esc_attr( $i ); ?>">
						<strong><?php echo esc_html( sprintf( __( 'FAQ Question %d', 'rx-theme' ), $i ) ); ?></strong>
					</label>
					<input type="text" id="rx_glossary_faq_question_<?php echo esc_attr( $i ); ?>" name="rx_glossary_faq_question_<?php echo esc_attr( $i ); ?>" value="<?php echo esc_attr( $question ); ?>" class="large-text">
				</p>

				<p>
					<label for="rx_glossary_faq_answer_<?php echo esc_attr( $i ); ?>">
						<strong><?php echo esc_html( sprintf( __( 'FAQ Answer %d', 'rx-theme' ), $i ) ); ?></strong>
					</label>
					<textarea id="rx_glossary_faq_answer_<?php echo esc_attr( $i ); ?>" name="rx_glossary_faq_answer_<?php echo esc_attr( $i ); ?>" rows="4" class="large-text"><?php echo esc_textarea( $answer ); ?></textarea>
				</p>
			</div>
			<?php
		}
	}

	/**
	 * Save meta boxes.
	 */
	public static function save_meta_boxes( $post_id, $post ) {

		if ( ! isset( $_POST[ self::NONCE_NAME ] ) ) {
			return;
		}

		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST[ self::NONCE_NAME ] ) ), self::NONCE_ACTION ) ) {
			return;
		}

		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}

		if ( wp_is_post_revision( $post_id ) ) {
			return;
		}

		if ( self::POST_TYPE !== $post->post_type ) {
			return;
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return;
		}

		$map = array(
			'_rx_glossary_short_definition' => array(
				'field'    => 'rx_glossary_short_definition',
				'sanitize' => 'sanitize_textarea_field',
			),
			'_rx_glossary_long_definition' => array(
				'field'    => 'rx_glossary_long_definition',
				'sanitize' => 'wp_kses_post',
			),
			'_rx_glossary_pronunciation' => array(
				'field'    => 'rx_glossary_pronunciation',
				'sanitize' => 'sanitize_text_field',
			),
			'_rx_glossary_audio_url' => array(
				'field'    => 'rx_glossary_audio_url',
				'sanitize' => 'esc_url_raw',
			),
			'_rx_glossary_synonyms' => array(
				'field'    => 'rx_glossary_synonyms',
				'sanitize' => 'sanitize_textarea_field',
			),
			'_rx_glossary_related_terms' => array(
				'field'    => 'rx_glossary_related_terms',
				'sanitize' => 'sanitize_textarea_field',
			),
			'_rx_glossary_medical_specialty' => array(
				'field'    => 'rx_glossary_medical_specialty',
				'sanitize' => 'sanitize_text_field',
			),
			'_rx_glossary_body_system' => array(
				'field'    => 'rx_glossary_body_system',
				'sanitize' => 'sanitize_text_field',
			),
			'_rx_glossary_difficulty' => array(
				'field'    => 'rx_glossary_difficulty',
				'sanitize' => 'sanitize_text_field',
			),
			'_rx_glossary_reviewed_by' => array(
				'field'    => 'rx_glossary_reviewed_by',
				'sanitize' => 'sanitize_text_field',
			),
			'_rx_glossary_reviewed_date' => array(
				'field'    => 'rx_glossary_reviewed_date',
				'sanitize' => 'sanitize_text_field',
			),
			'_rx_glossary_source_references' => array(
				'field'    => 'rx_glossary_source_references',
				'sanitize' => 'wp_kses_post',
			),
		);

		for ( $i = 1; $i <= 3; $i++ ) {
			$map[ '_rx_glossary_faq_question_' . $i ] = array(
				'field'    => 'rx_glossary_faq_question_' . $i,
				'sanitize' => 'sanitize_text_field',
			);

			$map[ '_rx_glossary_faq_answer_' . $i ] = array(
				'field'    => 'rx_glossary_faq_answer_' . $i,
				'sanitize' => 'wp_kses_post',
			);
		}

		foreach ( $map as $meta_key => $data ) {
			if ( isset( $_POST[ $data['field'] ] ) ) {
				$value = wp_unslash( $_POST[ $data['field'] ] );
				$value = call_user_func( $data['sanitize'], $value );
				update_post_meta( $post_id, $meta_key, $value );
			} else {
				delete_post_meta( $post_id, $meta_key );
			}
		}

		$featured = isset( $_POST['rx_glossary_featured'] ) ? '1' : '0';
		update_post_meta( $post_id, '_rx_glossary_featured', $featured );
	}

	/**
	 * Sanitize checkbox.
	 */
	public static function sanitize_checkbox( $value ) {
		return ! empty( $value ) ? '1' : '0';
	}

	/**
	 * Admin columns.
	 */
	public static function admin_columns( $columns ) {

		$new = array();

		$new['cb']             = isset( $columns['cb'] ) ? $columns['cb'] : '';
		$new['title']          = __( 'Glossary Term', 'rx-theme' );
		$new['rx_letter']      = __( 'Letter', 'rx-theme' );
		$new['rx_category']    = __( 'Category', 'rx-theme' );
		$new['rx_specialty']   = __( 'Specialty', 'rx-theme' );
		$new['rx_difficulty']  = __( 'Difficulty', 'rx-theme' );
		$new['rx_featured']    = __( 'Featured', 'rx-theme' );
		$new['rx_reviewed']    = __( 'Reviewed', 'rx-theme' );
		$new['date']           = isset( $columns['date'] ) ? $columns['date'] : __( 'Date', 'rx-theme' );

		return $new;
	}

	/**
	 * Admin column content.
	 */
	public static function admin_columns_content( $column, $post_id ) {

		switch ( $column ) {

			case 'rx_letter':
				echo wp_kses_post( get_the_term_list( $post_id, self::TAX_LETTER, '', ', ', '' ) );
				break;

			case 'rx_category':
				echo wp_kses_post( get_the_term_list( $post_id, self::TAX_CATEGORY, '', ', ', '' ) );
				break;

			case 'rx_specialty':
				echo esc_html( get_post_meta( $post_id, '_rx_glossary_medical_specialty', true ) );
				break;

			case 'rx_difficulty':
				$difficulty = get_post_meta( $post_id, '_rx_glossary_difficulty', true );
				echo $difficulty ? esc_html( ucfirst( $difficulty ) ) : '—';
				break;

			case 'rx_featured':
				$featured = get_post_meta( $post_id, '_rx_glossary_featured', true );
				echo '1' === $featured ? '<span style="color:#008000;font-weight:700;">Yes</span>' : '—';
				break;

			case 'rx_reviewed':
				$date = get_post_meta( $post_id, '_rx_glossary_reviewed_date', true );
				echo $date ? esc_html( $date ) : '—';
				break;
		}
	}

	/**
	 * Sortable columns.
	 */
	public static function sortable_columns( $columns ) {

		$columns['rx_specialty']  = 'rx_specialty';
		$columns['rx_difficulty'] = 'rx_difficulty';
		$columns['rx_featured']   = 'rx_featured';
		$columns['rx_reviewed']   = 'rx_reviewed';

		return $columns;
	}

	/**
	 * Admin order by meta.
	 */
	public static function admin_column_orderby( $query ) {

		if ( ! is_admin() || ! $query->is_main_query() ) {
			return;
		}

		$orderby = $query->get( 'orderby' );

		$map = array(
			'rx_specialty'  => '_rx_glossary_medical_specialty',
			'rx_difficulty' => '_rx_glossary_difficulty',
			'rx_featured'   => '_rx_glossary_featured',
			'rx_reviewed'   => '_rx_glossary_reviewed_date',
		);

		if ( isset( $map[ $orderby ] ) ) {
			$query->set( 'meta_key', $map[ $orderby ] );
			$query->set( 'orderby', 'meta_value' );
		}
	}

	/**
	 * Admin filters.
	 */
	public static function admin_filter_dropdowns() {

		global $typenow;

		if ( self::POST_TYPE !== $typenow ) {
			return;
		}

		self::taxonomy_filter_dropdown( self::TAX_CATEGORY, __( 'All Glossary Categories', 'rx-theme' ) );
		self::taxonomy_filter_dropdown( self::TAX_LETTER, __( 'All Letters', 'rx-theme' ) );

		$difficulty = isset( $_GET['rx_glossary_difficulty_filter'] ) ? sanitize_text_field( wp_unslash( $_GET['rx_glossary_difficulty_filter'] ) ) : '';

		?>
		<select name="rx_glossary_difficulty_filter">
			<option value=""><?php esc_html_e( 'All Difficulties', 'rx-theme' ); ?></option>
			<option value="beginner" <?php selected( $difficulty, 'beginner' ); ?>><?php esc_html_e( 'Beginner', 'rx-theme' ); ?></option>
			<option value="intermediate" <?php selected( $difficulty, 'intermediate' ); ?>><?php esc_html_e( 'Intermediate', 'rx-theme' ); ?></option>
			<option value="advanced" <?php selected( $difficulty, 'advanced' ); ?>><?php esc_html_e( 'Advanced', 'rx-theme' ); ?></option>
			<option value="professional" <?php selected( $difficulty, 'professional' ); ?>><?php esc_html_e( 'Professional', 'rx-theme' ); ?></option>
		</select>
		<?php
	}

	/**
	 * Taxonomy filter dropdown helper.
	 */
	public static function taxonomy_filter_dropdown( $taxonomy, $label ) {

		$selected = isset( $_GET[ $taxonomy ] ) ? sanitize_text_field( wp_unslash( $_GET[ $taxonomy ] ) ) : '';

		wp_dropdown_categories(
			array(
				'show_option_all' => $label,
				'taxonomy'        => $taxonomy,
				'name'            => $taxonomy,
				'orderby'         => 'name',
				'selected'        => $selected,
				'hierarchical'    => true,
				'depth'           => 3,
				'show_count'      => true,
				'hide_empty'      => false,
				'value_field'     => 'slug',
			)
		);
	}

	/**
	 * Admin filter query.
	 */
	public static function admin_filter_query( $query ) {

		global $pagenow;

		if ( ! is_admin() || 'edit.php' !== $pagenow ) {
			return;
		}

		$post_type = isset( $_GET['post_type'] ) ? sanitize_key( $_GET['post_type'] ) : '';

		if ( self::POST_TYPE !== $post_type ) {
			return;
		}

		$difficulty = isset( $_GET['rx_glossary_difficulty_filter'] ) ? sanitize_text_field( wp_unslash( $_GET['rx_glossary_difficulty_filter'] ) ) : '';

		if ( $difficulty ) {
			$query->query_vars['meta_key']   = '_rx_glossary_difficulty';
			$query->query_vars['meta_value'] = $difficulty;
		}
	}

	/**
	 * Update messages.
	 */
	public static function post_updated_messages( $messages ) {

		$messages[ self::POST_TYPE ] = array(
			0  => '',
			1  => __( 'Glossary term updated.', 'rx-theme' ),
			2  => __( 'Custom field updated.', 'rx-theme' ),
			3  => __( 'Custom field deleted.', 'rx-theme' ),
			4  => __( 'Glossary term updated.', 'rx-theme' ),
			5  => __( 'Glossary term restored to revision.', 'rx-theme' ),
			6  => __( 'Glossary term published.', 'rx-theme' ),
			7  => __( 'Glossary term saved.', 'rx-theme' ),
			8  => __( 'Glossary term submitted.', 'rx-theme' ),
			9  => __( 'Glossary term scheduled.', 'rx-theme' ),
			10 => __( 'Glossary term draft updated.', 'rx-theme' ),
		);

		return $messages;
	}

	/**
	 * Bulk updated messages.
	 */
	public static function bulk_post_updated_messages( $bulk_messages, $bulk_counts ) {

		$bulk_messages[ self::POST_TYPE ] = array(
			'updated'   => _n( '%s glossary term updated.', '%s glossary terms updated.', $bulk_counts['updated'], 'rx-theme' ),
			'locked'    => _n( '%s glossary term not updated, somebody is editing it.', '%s glossary terms not updated, somebody is editing them.', $bulk_counts['locked'], 'rx-theme' ),
			'deleted'   => _n( '%s glossary term permanently deleted.', '%s glossary terms permanently deleted.', $bulk_counts['deleted'], 'rx-theme' ),
			'trashed'   => _n( '%s glossary term moved to the Trash.', '%s glossary terms moved to the Trash.', $bulk_counts['trashed'], 'rx-theme' ),
			'untrashed' => _n( '%s glossary term restored from the Trash.', '%s glossary terms restored from the Trash.', $bulk_counts['untrashed'], 'rx-theme' ),
		);

		return $bulk_messages;
	}

	/**
	 * Title placeholder.
	 */
	public static function title_placeholder( $title, $post ) {

		if ( self::POST_TYPE === $post->post_type ) {
			return __( 'Enter glossary term name, example: Neutropenia', 'rx-theme' );
		}

		return $title;
	}

	/**
	 * Append glossary content sections.
	 */
	public static function append_glossary_content( $content ) {

		if ( ! is_singular( self::POST_TYPE ) || ! in_the_loop() || ! is_main_query() ) {
			return $content;
		}

		$post_id = get_the_ID();

		$short_definition = get_post_meta( $post_id, '_rx_glossary_short_definition', true );
		$long_definition  = get_post_meta( $post_id, '_rx_glossary_long_definition', true );
		$pronunciation    = get_post_meta( $post_id, '_rx_glossary_pronunciation', true );
		$audio_url        = get_post_meta( $post_id, '_rx_glossary_audio_url', true );
		$synonyms         = get_post_meta( $post_id, '_rx_glossary_synonyms', true );
		$related_terms    = get_post_meta( $post_id, '_rx_glossary_related_terms', true );
		$specialty        = get_post_meta( $post_id, '_rx_glossary_medical_specialty', true );
		$body_system      = get_post_meta( $post_id, '_rx_glossary_body_system', true );
		$difficulty       = get_post_meta( $post_id, '_rx_glossary_difficulty', true );
		$reviewed_by      = get_post_meta( $post_id, '_rx_glossary_reviewed_by', true );
		$reviewed_date    = get_post_meta( $post_id, '_rx_glossary_reviewed_date', true );
		$references       = get_post_meta( $post_id, '_rx_glossary_source_references', true );

		ob_start();
		?>

		<div class="rx-glossary-box rx-glossary-summary">

			<?php if ( $short_definition ) : ?>
				<div class="rx-glossary-short-definition">
					<h2><?php esc_html_e( 'Simple Definition', 'rx-theme' ); ?></h2>
					<p><?php echo esc_html( $short_definition ); ?></p>
				</div>
			<?php endif; ?>

			<?php if ( $pronunciation || $audio_url ) : ?>
				<div class="rx-glossary-pronunciation">
					<h2><?php esc_html_e( 'Pronunciation', 'rx-theme' ); ?></h2>

					<?php if ( $pronunciation ) : ?>
						<p><strong><?php esc_html_e( 'How to say it:', 'rx-theme' ); ?></strong> <?php echo esc_html( $pronunciation ); ?></p>
					<?php endif; ?>

					<?php if ( $audio_url ) : ?>
						<audio controls preload="none">
							<source src="<?php echo esc_url( $audio_url ); ?>">
							<?php esc_html_e( 'Your browser does not support the audio element.', 'rx-theme' ); ?>
						</audio>
					<?php endif; ?>
				</div>
			<?php endif; ?>

			<?php if ( $synonyms ) : ?>
				<div class="rx-glossary-synonyms">
					<h2><?php esc_html_e( 'Other Names', 'rx-theme' ); ?></h2>
					<p><?php echo esc_html( $synonyms ); ?></p>
				</div>
			<?php endif; ?>

			<?php if ( $long_definition ) : ?>
				<div class="rx-glossary-long-definition">
					<h2><?php esc_html_e( 'Detailed Meaning', 'rx-theme' ); ?></h2>
					<?php echo wp_kses_post( wpautop( $long_definition ) ); ?>
				</div>
			<?php endif; ?>

			<?php if ( $specialty || $body_system || $difficulty ) : ?>
				<div class="rx-glossary-medical-classification">
					<h2><?php esc_html_e( 'Medical Classification', 'rx-theme' ); ?></h2>
					<ul>
						<?php if ( $specialty ) : ?>
							<li><strong><?php esc_html_e( 'Specialty:', 'rx-theme' ); ?></strong> <?php echo esc_html( $specialty ); ?></li>
						<?php endif; ?>

						<?php if ( $body_system ) : ?>
							<li><strong><?php esc_html_e( 'Body system:', 'rx-theme' ); ?></strong> <?php echo esc_html( $body_system ); ?></li>
						<?php endif; ?>

						<?php if ( $difficulty ) : ?>
							<li><strong><?php esc_html_e( 'Reading level:', 'rx-theme' ); ?></strong> <?php echo esc_html( ucfirst( $difficulty ) ); ?></li>
						<?php endif; ?>
					</ul>
				</div>
			<?php endif; ?>

		</div>

		<?php
		$before = ob_get_clean();

		$after = '';

		$faq_html = self::get_faq_html( $post_id );

		if ( $faq_html ) {
			$after .= $faq_html;
		}

		if ( $related_terms ) {
			$after .= '<div class="rx-glossary-related-manual">';
			$after .= '<h2>' . esc_html__( 'Related Terms', 'rx-theme' ) . '</h2>';
			$after .= '<p>' . esc_html( $related_terms ) . '</p>';
			$after .= '</div>';
		}

		$related_auto = self::get_related_glossary_html( $post_id, 6 );

		if ( $related_auto ) {
			$after .= $related_auto;
		}

		if ( $reviewed_by || $reviewed_date ) {
			$after .= '<div class="rx-glossary-review-info">';
			$after .= '<h2>' . esc_html__( 'Medical Review', 'rx-theme' ) . '</h2>';

			if ( $reviewed_by ) {
				$after .= '<p><strong>' . esc_html__( 'Reviewed by:', 'rx-theme' ) . '</strong> ' . esc_html( $reviewed_by ) . '</p>';
			}

			if ( $reviewed_date ) {
				$after .= '<p><strong>' . esc_html__( 'Reviewed on:', 'rx-theme' ) . '</strong> ' . esc_html( $reviewed_date ) . '</p>';
			}

			$after .= '</div>';
		}

		if ( $references ) {
			$after .= '<div class="rx-glossary-references">';
			$after .= '<h2>' . esc_html__( 'References', 'rx-theme' ) . '</h2>';
			$after .= wp_kses_post( wpautop( $references ) );
			$after .= '</div>';
		}

		$after .= self::medical_disclaimer_html();

		return $before . $content . $after;
	}

	/**
	 * FAQ HTML.
	 */
	public static function get_faq_html( $post_id ) {

		$html = '';

		for ( $i = 1; $i <= 3; $i++ ) {
			$q = get_post_meta( $post_id, '_rx_glossary_faq_question_' . $i, true );
			$a = get_post_meta( $post_id, '_rx_glossary_faq_answer_' . $i, true );

			if ( $q && $a ) {
				$html .= '<div class="rx-glossary-faq-item">';
				$html .= '<h3>' . esc_html( $q ) . '</h3>';
				$html .= wp_kses_post( wpautop( $a ) );
				$html .= '</div>';
			}
		}

		if ( ! $html ) {
			return '';
		}

		return '<div class="rx-glossary-faq"><h2>' . esc_html__( 'Frequently Asked Questions', 'rx-theme' ) . '</h2>' . $html . '</div>';
	}

	/**
	 * Medical disclaimer.
	 */
	public static function medical_disclaimer_html() {

		return '<div class="rx-medical-disclaimer rx-glossary-disclaimer">
			<strong>' . esc_html__( 'Medical disclaimer:', 'rx-theme' ) . '</strong>
			' . esc_html__( 'This glossary article is for general health education only. It does not replace medical advice, diagnosis, or treatment from a qualified healthcare professional.', 'rx-theme' ) . '
		</div>';
	}

	/**
	 * Schema JSON-LD.
	 */
	public static function output_schema_json_ld() {

		if ( ! is_singular( self::POST_TYPE ) ) {
			return;
		}

		$post_id = get_the_ID();

		$title            = get_the_title( $post_id );
		$url              = get_permalink( $post_id );
		$short_definition = get_post_meta( $post_id, '_rx_glossary_short_definition', true );
		$long_definition  = get_post_meta( $post_id, '_rx_glossary_long_definition', true );
		$synonyms         = get_post_meta( $post_id, '_rx_glossary_synonyms', true );
		$specialty        = get_post_meta( $post_id, '_rx_glossary_medical_specialty', true );
		$reviewed_date    = get_post_meta( $post_id, '_rx_glossary_reviewed_date', true );

		$description = $short_definition ? $short_definition : wp_strip_all_tags( get_the_excerpt( $post_id ) );

		if ( ! $description && $long_definition ) {
			$description = wp_trim_words( wp_strip_all_tags( $long_definition ), 35 );
		}

		$schema = array(
			'@context'    => 'https://schema.org',
			'@type'       => array( 'DefinedTerm', 'MedicalEntity' ),
			'name'        => $title,
			'description' => $description,
			'url'         => $url,
			'inDefinedTermSet' => array(
				'@type' => 'DefinedTermSet',
				'name'  => get_bloginfo( 'name' ) . ' Medical Glossary',
				'url'   => home_url( '/glossary/' ),
			),
		);

		if ( $synonyms ) {
			$schema['alternateName'] = array_map( 'trim', explode( ',', $synonyms ) );
		}

		if ( $specialty ) {
			$schema['medicalSpecialty'] = $specialty;
		}

		if ( $reviewed_date ) {
			$schema['dateReviewed'] = $reviewed_date;
		}

		$faq_schema = self::get_faq_schema( $post_id );

		echo "\n<script type=\"application/ld+json\" class=\"rx-glossary-schema\">\n";
		echo wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT );
		echo "\n</script>\n";

		if ( $faq_schema ) {
			echo "\n<script type=\"application/ld+json\" class=\"rx-glossary-faq-schema\">\n";
			echo wp_json_encode( $faq_schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT );
			echo "\n</script>\n";
		}
	}

	/**
	 * FAQ schema.
	 */
	public static function get_faq_schema( $post_id ) {

		$items = array();

		for ( $i = 1; $i <= 3; $i++ ) {
			$q = get_post_meta( $post_id, '_rx_glossary_faq_question_' . $i, true );
			$a = get_post_meta( $post_id, '_rx_glossary_faq_answer_' . $i, true );

			if ( $q && $a ) {
				$items[] = array(
					'@type'          => 'Question',
					'name'           => wp_strip_all_tags( $q ),
					'acceptedAnswer' => array(
						'@type' => 'Answer',
						'text'  => wp_strip_all_tags( $a ),
					),
				);
			}
		}

		if ( empty( $items ) ) {
			return false;
		}

		return array(
			'@context'   => 'https://schema.org',
			'@type'      => 'FAQPage',
			'mainEntity' => $items,
		);
	}

	/**
	 * Shortcode: [rx_glossary_index]
	 */
	public static function shortcode_glossary_index( $atts ) {

		$atts = shortcode_atts(
			array(
				'letter'        => '',
				'category'      => '',
				'posts_per_page' => 100,
				'show_letters'  => 'yes',
				'show_excerpt'  => 'yes',
			),
			$atts,
			'rx_glossary_index'
		);

		$args = array(
			'post_type'      => self::POST_TYPE,
			'post_status'    => 'publish',
			'posts_per_page' => absint( $atts['posts_per_page'] ),
			'orderby'        => 'title',
			'order'          => 'ASC',
		);

		$tax_query = array();

		if ( ! empty( $atts['letter'] ) ) {
			$tax_query[] = array(
				'taxonomy' => self::TAX_LETTER,
				'field'    => 'slug',
				'terms'    => sanitize_title( $atts['letter'] ),
			);
		}

		if ( ! empty( $atts['category'] ) ) {
			$tax_query[] = array(
				'taxonomy' => self::TAX_CATEGORY,
				'field'    => 'slug',
				'terms'    => sanitize_title( $atts['category'] ),
			);
		}

		if ( $tax_query ) {
			$args['tax_query'] = $tax_query;
		}

		$query = new WP_Query( $args );

		ob_start();

		echo '<div class="rx-glossary-index">';

		if ( 'yes' === $atts['show_letters'] ) {
			echo self::get_az_navigation_html();
		}

		if ( $query->have_posts() ) {
			echo '<div class="rx-glossary-list">';

			while ( $query->have_posts() ) {
				$query->the_post();

				echo '<article class="rx-glossary-list-item">';
				echo '<h3><a href="' . esc_url( get_permalink() ) . '">' . esc_html( get_the_title() ) . '</a></h3>';

				if ( 'yes' === $atts['show_excerpt'] ) {
					echo '<p>' . esc_html( self::get_glossary_excerpt( get_the_ID(), 24 ) ) . '</p>';
				}

				echo '</article>';
			}

			echo '</div>';
		} else {
			echo '<p>' . esc_html__( 'No glossary terms found.', 'rx-theme' ) . '</p>';
		}

		echo '</div>';

		wp_reset_postdata();

		return ob_get_clean();
	}

	/**
	 * Shortcode: [rx_glossary_search]
	 */
	public static function shortcode_glossary_search( $atts ) {

		$atts = shortcode_atts(
			array(
				'placeholder' => __( 'Search medical glossary...', 'rx-theme' ),
				'button'      => __( 'Search', 'rx-theme' ),
			),
			$atts,
			'rx_glossary_search'
		);

		$query = isset( $_GET['rx_glossary_s'] ) ? sanitize_text_field( wp_unslash( $_GET['rx_glossary_s'] ) ) : '';

		ob_start();
		?>
		<div class="rx-glossary-search">
			<form method="get" action="<?php echo esc_url( home_url( '/' ) ); ?>">
				<input type="hidden" name="post_type" value="<?php echo esc_attr( self::POST_TYPE ); ?>">
				<label class="screen-reader-text" for="rx_glossary_search_input"><?php esc_html_e( 'Search Glossary', 'rx-theme' ); ?></label>
				<input id="rx_glossary_search_input" type="search" name="s" value="<?php echo esc_attr( $query ); ?>" placeholder="<?php echo esc_attr( $atts['placeholder'] ); ?>">
				<button type="submit"><?php echo esc_html( $atts['button'] ); ?></button>
			</form>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * Shortcode: [rx_glossary_related]
	 */
	public static function shortcode_related_glossary( $atts ) {

		$atts = shortcode_atts(
			array(
				'id'    => get_the_ID(),
				'limit' => 6,
			),
			$atts,
			'rx_glossary_related'
		);

		return self::get_related_glossary_html( absint( $atts['id'] ), absint( $atts['limit'] ) );
	}

	/**
	 * Shortcode: [rx_glossary_featured]
	 */
	public static function shortcode_featured_glossary( $atts ) {

		$atts = shortcode_atts(
			array(
				'limit' => 6,
			),
			$atts,
			'rx_glossary_featured'
		);

		$query = new WP_Query(
			array(
				'post_type'      => self::POST_TYPE,
				'post_status'    => 'publish',
				'posts_per_page' => absint( $atts['limit'] ),
				'meta_key'       => '_rx_glossary_featured',
				'meta_value'     => '1',
				'orderby'        => 'date',
				'order'          => 'DESC',
			)
		);

		if ( ! $query->have_posts() ) {
			return '';
		}

		ob_start();

		echo '<div class="rx-featured-glossary">';
		echo '<h2>' . esc_html__( 'Featured Glossary Terms', 'rx-theme' ) . '</h2>';
		echo '<ul>';

		while ( $query->have_posts() ) {
			$query->the_post();
			echo '<li><a href="' . esc_url( get_permalink() ) . '">' . esc_html( get_the_title() ) . '</a></li>';
		}

		echo '</ul>';
		echo '</div>';

		wp_reset_postdata();

		return ob_get_clean();
	}

	/**
	 * A-Z navigation HTML.
	 */
	public static function get_az_navigation_html() {

		$letters = array_merge( range( 'A', 'Z' ), array( '0-9' ) );

		$html = '<nav class="rx-glossary-az-nav" aria-label="' . esc_attr__( 'Glossary A to Z navigation', 'rx-theme' ) . '">';

		foreach ( $letters as $letter ) {
			$term = get_term_by( 'name', $letter, self::TAX_LETTER );

			if ( $term && ! is_wp_error( $term ) ) {
				$html .= '<a href="' . esc_url( get_term_link( $term ) ) . '">' . esc_html( $letter ) . '</a>';
			} else {
				$html .= '<span>' . esc_html( $letter ) . '</span>';
			}
		}

		$html .= '</nav>';

		return $html;
	}

	/**
	 * Related glossary HTML.
	 */
	public static function get_related_glossary_html( $post_id, $limit = 6 ) {

		if ( ! $post_id ) {
			return '';
		}

		$terms = wp_get_post_terms( $post_id, self::TAX_CATEGORY, array( 'fields' => 'ids' ) );

		if ( empty( $terms ) || is_wp_error( $terms ) ) {
			return '';
		}

		$query = new WP_Query(
			array(
				'post_type'      => self::POST_TYPE,
				'post_status'    => 'publish',
				'posts_per_page' => absint( $limit ),
				'post__not_in'   => array( $post_id ),
				'tax_query'      => array(
					array(
						'taxonomy' => self::TAX_CATEGORY,
						'field'    => 'term_id',
						'terms'    => $terms,
					),
				),
				'orderby'        => 'rand',
			)
		);

		if ( ! $query->have_posts() ) {
			return '';
		}

		ob_start();

		echo '<div class="rx-related-glossary">';
		echo '<h2>' . esc_html__( 'Related Glossary Terms', 'rx-theme' ) . '</h2>';
		echo '<ul>';

		while ( $query->have_posts() ) {
			$query->the_post();
			echo '<li><a href="' . esc_url( get_permalink() ) . '">' . esc_html( get_the_title() ) . '</a></li>';
		}

		echo '</ul>';
		echo '</div>';

		wp_reset_postdata();

		return ob_get_clean();
	}

	/**
	 * Glossary excerpt helper.
	 */
	public static function get_glossary_excerpt( $post_id, $words = 25 ) {

		$short_definition = get_post_meta( $post_id, '_rx_glossary_short_definition', true );

		if ( $short_definition ) {
			return wp_trim_words( wp_strip_all_tags( $short_definition ), $words );
		}

		$excerpt = get_the_excerpt( $post_id );

		if ( $excerpt ) {
			return wp_trim_words( wp_strip_all_tags( $excerpt ), $words );
		}

		$content = get_post_field( 'post_content', $post_id );

		return wp_trim_words( wp_strip_all_tags( $content ), $words );
	}

	/**
	 * Template loader.
	 */
	public static function template_loader( $template ) {

		if ( is_singular( self::POST_TYPE ) ) {
			$theme_template = locate_template( array(
				'single-rx_glossary.php',
				'template-parts/glossary/single-glossary.php',
			) );

			if ( $theme_template ) {
				return $theme_template;
			}
		}

		if ( is_post_type_archive( self::POST_TYPE ) ) {
			$theme_template = locate_template( array(
				'archive-rx_glossary.php',
				'template-parts/glossary/archive-glossary.php',
			) );

			if ( $theme_template ) {
				return $theme_template;
			}
		}

		if ( is_tax( array( self::TAX_CATEGORY, self::TAX_TAG, self::TAX_LETTER ) ) ) {
			$theme_template = locate_template( array(
				'taxonomy-rx_glossary.php',
				'template-parts/glossary/taxonomy-glossary.php',
			) );

			if ( $theme_template ) {
				return $theme_template;
			}
		}

		return $template;
	}

	/**
	 * Body classes.
	 */
	public static function body_classes( $classes ) {

		if ( is_singular( self::POST_TYPE ) ) {
			$classes[] = 'rx-glossary-single';
		}

		if ( is_post_type_archive( self::POST_TYPE ) ) {
			$classes[] = 'rx-glossary-archive';
		}

		if ( is_tax( array( self::TAX_CATEGORY, self::TAX_TAG, self::TAX_LETTER ) ) ) {
			$classes[] = 'rx-glossary-taxonomy';
		}

		return $classes;
	}

	/**
	 * Enable sitemap.
	 */
	public static function enable_sitemap( $post_types ) {

		$post_types[ self::POST_TYPE ] = get_post_type_object( self::POST_TYPE );

		return $post_types;
	}

	/**
	 * AJAX live search.
	 */
	public static function ajax_live_search() {

		check_ajax_referer( 'rx_glossary_live_search', 'nonce' );

		$keyword = isset( $_GET['keyword'] ) ? sanitize_text_field( wp_unslash( $_GET['keyword'] ) ) : '';

		if ( strlen( $keyword ) < 2 ) {
			wp_send_json_success( array() );
		}

		$query = new WP_Query(
			array(
				'post_type'      => self::POST_TYPE,
				'post_status'    => 'publish',
				's'              => $keyword,
				'posts_per_page' => 10,
			)
		);

		$results = array();

		if ( $query->have_posts() ) {
			while ( $query->have_posts() ) {
				$query->the_post();

				$results[] = array(
					'id'      => get_the_ID(),
					'title'   => get_the_title(),
					'url'     => get_permalink(),
					'excerpt' => self::get_glossary_excerpt( get_the_ID(), 18 ),
				);
			}
		}

		wp_reset_postdata();

		wp_send_json_success( $results );
	}

	/**
	 * REST output enhancement.
	 */
	public static function rest_prepare_glossary( $response, $post, $request ) {

		$post_id = $post->ID;

		$response->data['rx_glossary_meta'] = array(
			'short_definition'  => get_post_meta( $post_id, '_rx_glossary_short_definition', true ),
			'long_definition'   => get_post_meta( $post_id, '_rx_glossary_long_definition', true ),
			'pronunciation'     => get_post_meta( $post_id, '_rx_glossary_pronunciation', true ),
			'audio_url'         => get_post_meta( $post_id, '_rx_glossary_audio_url', true ),
			'synonyms'          => get_post_meta( $post_id, '_rx_glossary_synonyms', true ),
			'related_terms'     => get_post_meta( $post_id, '_rx_glossary_related_terms', true ),
			'medical_specialty' => get_post_meta( $post_id, '_rx_glossary_medical_specialty', true ),
			'body_system'       => get_post_meta( $post_id, '_rx_glossary_body_system', true ),
			'difficulty'        => get_post_meta( $post_id, '_rx_glossary_difficulty', true ),
			'featured'          => get_post_meta( $post_id, '_rx_glossary_featured', true ),
			'reviewed_by'       => get_post_meta( $post_id, '_rx_glossary_reviewed_by', true ),
			'reviewed_date'     => get_post_meta( $post_id, '_rx_glossary_reviewed_date', true ),
			'references'        => get_post_meta( $post_id, '_rx_glossary_source_references', true ),
		);

		$response->data['rx_reading_time'] = self::get_reading_time( $post_id );

		return $response;
	}

	/**
	 * Auto assign first letter on transition.
	 */
	public static function auto_assign_first_letter( $new_status, $old_status, $post ) {

		if ( self::POST_TYPE !== $post->post_type ) {
			return;
		}

		if ( 'publish' === $new_status ) {
			self::assign_first_letter( $post->ID );
		}
	}

	/**
	 * Auto assign first letter on save.
	 */
	public static function auto_assign_first_letter_on_save( $post_id, $post, $update ) {

		if ( self::POST_TYPE !== $post->post_type ) {
			return;
		}

		if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
			return;
		}

		self::assign_first_letter( $post_id );
	}

	/**
	 * Assign first letter taxonomy.
	 */
	public static function assign_first_letter( $post_id ) {

		$title = get_the_title( $post_id );

		if ( ! $title ) {
			return;
		}

		$first = strtoupper( mb_substr( trim( $title ), 0, 1 ) );

		if ( preg_match( '/[A-Z]/', $first ) ) {
			$letter = $first;
		} else {
			$letter = '0-9';
		}

		$term = get_term_by( 'name', $letter, self::TAX_LETTER );

		if ( $term && ! is_wp_error( $term ) ) {
			wp_set_object_terms( $post_id, array( (int) $term->term_id ), self::TAX_LETTER, false );
		}
	}

	/**
	 * Improve glossary search by including selected meta fields.
	 */
	public static function improve_glossary_search( $search, $wp_query ) {

		global $wpdb;

		if ( is_admin() ) {
			return $search;
		}

		if ( ! $wp_query->is_search() ) {
			return $search;
		}

		$post_type = $wp_query->get( 'post_type' );

		if ( self::POST_TYPE !== $post_type ) {
			return $search;
		}

		$search_terms = $wp_query->get( 'search_terms' );

		if ( empty( $search_terms ) ) {
			return $search;
		}

		$like_parts = array();

		foreach ( $search_terms as $term ) {
			$like = '%' . $wpdb->esc_like( $term ) . '%';

			$like_parts[] = $wpdb->prepare(
				"({$wpdb->posts}.post_title LIKE %s OR {$wpdb->posts}.post_content LIKE %s OR EXISTS (
					SELECT 1 FROM {$wpdb->postmeta}
					WHERE {$wpdb->postmeta}.post_id = {$wpdb->posts}.ID
					AND {$wpdb->postmeta}.meta_key IN (
						'_rx_glossary_short_definition',
						'_rx_glossary_long_definition',
						'_rx_glossary_synonyms',
						'_rx_glossary_related_terms',
						'_rx_glossary_medical_specialty',
						'_rx_glossary_body_system'
					)
					AND {$wpdb->postmeta}.meta_value LIKE %s
				))",
				$like,
				$like,
				$like
			);
		}

		if ( $like_parts ) {
			$search = ' AND ' . implode( ' AND ', $like_parts );
		}

		return $search;
	}

	/**
	 * Reading time.
	 */
	public static function get_reading_time( $post_id ) {

		$content = get_post_field( 'post_content', $post_id );
		$content .= ' ' . get_post_meta( $post_id, '_rx_glossary_short_definition', true );
		$content .= ' ' . get_post_meta( $post_id, '_rx_glossary_long_definition', true );

		$word_count = str_word_count( wp_strip_all_tags( $content ) );
		$minutes    = max( 1, ceil( $word_count / 220 ) );

		return sprintf(
			_n( '%s min read', '%s min read', $minutes, 'rx-theme' ),
			number_format_i18n( $minutes )
		);
	}

	/**
	 * Public helper: get glossary terms.
	 */
	public static function get_terms_query( $args = array() ) {

		$defaults = array(
			'post_type'      => self::POST_TYPE,
			'post_status'    => 'publish',
			'posts_per_page' => 12,
			'orderby'        => 'title',
			'order'          => 'ASC',
		);

		return new WP_Query( wp_parse_args( $args, $defaults ) );
	}

	/**
	 * Public helper: get featured glossary terms.
	 */
	public static function get_featured_terms( $limit = 6 ) {

		return self::get_terms_query(
			array(
				'posts_per_page' => absint( $limit ),
				'meta_key'       => '_rx_glossary_featured',
				'meta_value'     => '1',
			)
		);
	}

	/**
	 * Public helper: check glossary page.
	 */
	public static function is_glossary() {

		return is_singular( self::POST_TYPE )
			|| is_post_type_archive( self::POST_TYPE )
			|| is_tax( array( self::TAX_CATEGORY, self::TAX_TAG, self::TAX_LETTER ) );
	}
}

endif;

RX_Theme_Glossary::init();


/**
 * Procedural helper functions for theme templates.
 */

if ( ! function_exists( 'rx_is_glossary' ) ) {
	function rx_is_glossary() {
		return RX_Theme_Glossary::is_glossary();
	}
}

if ( ! function_exists( 'rx_get_glossary_excerpt' ) ) {
	function rx_get_glossary_excerpt( $post_id = 0, $words = 25 ) {
		$post_id = $post_id ? absint( $post_id ) : get_the_ID();
		return RX_Theme_Glossary::get_glossary_excerpt( $post_id, $words );
	}
}

if ( ! function_exists( 'rx_get_glossary_reading_time' ) ) {
	function rx_get_glossary_reading_time( $post_id = 0 ) {
		$post_id = $post_id ? absint( $post_id ) : get_the_ID();
		return RX_Theme_Glossary::get_reading_time( $post_id );
	}
}

if ( ! function_exists( 'rx_glossary_az_navigation' ) ) {
	function rx_glossary_az_navigation() {
		echo wp_kses_post( RX_Theme_Glossary::get_az_navigation_html() );
	}
}

if ( ! function_exists( 'rx_get_related_glossary' ) ) {
	function rx_get_related_glossary( $post_id = 0, $limit = 6 ) {
		$post_id = $post_id ? absint( $post_id ) : get_the_ID();
		return RX_Theme_Glossary::get_related_glossary_html( $post_id, $limit );
	}
}

if ( ! function_exists( 'rx_related_glossary' ) ) {
	function rx_related_glossary( $post_id = 0, $limit = 6 ) {
		echo wp_kses_post( rx_get_related_glossary( $post_id, $limit ) );
	}
}

if ( ! function_exists( 'rx_get_glossary_meta' ) ) {
	function rx_get_glossary_meta( $key, $post_id = 0 ) {
		$post_id = $post_id ? absint( $post_id ) : get_the_ID();
		return get_post_meta( $post_id, $key, true );
	}
}

After adding this file, go to:

WordPress Dashboard → Settings → Permalinks → Save Changes

This will refresh the rewrite rules so your glossary URLs work:

/glossary/
/glossary/neutropenia/
/glossary-category/hematology/
/glossary-letter/a/

Useful shortcodes:

[rx_glossary_index]
[rx_glossary_search]
[rx_glossary_featured limit="8"]
[rx_glossary_related limit="6"]

This file is already strong enough for your RX medical theme. Your next helpful files can be:

archive-rx_glossary.php
single-rx_glossary.php
taxonomy-rx_glossary_category.php
template-parts/glossary/card.php
assets/css/glossary.css
assets/js/glossary-live-search.js

Leave a Reply

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