'; echo ''; echo '

' . esc_html__( 'Custom SEO title for this taxonomy page. Leave blank to use the default value from settings.', 'airygen-seo' ) . '

'; echo '
'; echo ''; echo '

' . esc_html__( 'Short summary used in search results. Leave blank to use the default value from settings.', 'airygen-seo' ) . '

'; echo '
'; echo ''; echo '

' . esc_html__( 'Leave empty to use the default taxonomy archive URL.', 'airygen-seo' ) . '

'; echo '
'; echo ''; echo '
'; } /** * Render edit-term fields. * * @param \WP_Term $term Term object. * @param string $taxonomy Taxonomy slug. * * @return void */ public static function render_edit_fields( \WP_Term $term, string $taxonomy ): void { if ( ! self::is_taxonomy_enabled( $taxonomy ) ) { return; } wp_nonce_field( self::NONCE_ACTION, self::NONCE_FIELD ); $title = (string) get_term_meta( $term->term_id, Constants::META_TERM_TITLE, true ); $description = (string) get_term_meta( $term->term_id, Constants::META_TERM_DESCRIPTION, true ); $canonical = (string) get_term_meta( $term->term_id, Constants::META_TERM_CANONICAL, true ); $robots = (string) get_term_meta( $term->term_id, Constants::META_TERM_ROBOTS, true ); echo ''; echo ''; echo ''; echo '

' . esc_html__( 'Custom SEO title for this taxonomy page. Leave blank to use the default value from settings.', 'airygen-seo' ) . '

'; echo ''; echo ''; echo ''; echo '

' . esc_html__( 'Short summary used in search results. Leave blank to use the default value from settings.', 'airygen-seo' ) . '

'; echo ''; echo ''; echo ''; echo '

' . esc_html__( 'Leave empty to use the default taxonomy archive URL.', 'airygen-seo' ) . '

'; echo ''; echo ''; echo ''; do_action( Constants::HOOK_TAXONOMY_SEO_EDIT_FIELDS, $term, $taxonomy ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Hook constant is already plugin-prefixed. } /** * Dispatch taxonomy SEO extension assets for the current term screen. * * @param string $hook Current admin hook. * * @return void */ public static function dispatch_extension_assets( string $hook ): void { $term = self::current_term_from_screen( $hook ); if ( ! $term instanceof \WP_Term ) { return; } do_action( Constants::HOOK_TAXONOMY_SEO_ASSETS, $term, (string) $term->taxonomy ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Hook constant is already plugin-prefixed. } /** * Save term meta values. * * @param int $term_id Term ID. * * @return void */ public static function save_term_meta( int $term_id ): void { $term = get_term( $term_id ); if ( ! $term instanceof \WP_Term ) { return; } if ( ! self::is_taxonomy_enabled( $term->taxonomy ) ) { return; } if ( ! isset( $_POST[ self::NONCE_FIELD ] ) ) { return; } $nonce = sanitize_text_field( wp_unslash( (string) $_POST[ self::NONCE_FIELD ] ) ); if ( ! wp_verify_nonce( $nonce, self::NONCE_ACTION ) ) { return; } $taxonomy_object = get_taxonomy( $term->taxonomy ); if ( ! $taxonomy_object instanceof WP_Taxonomy ) { return; } $capability = isset( $taxonomy_object->cap->manage_terms ) ? (string) $taxonomy_object->cap->manage_terms : 'manage_categories'; if ( ! current_user_can( $capability ) ) { return; } $title = self::posted_text( 'airygen_term_title' ); $description = self::posted_textarea( 'airygen_term_description' ); $canonical = self::posted_url( 'airygen_term_canonical' ); $robots = self::posted_robots( 'airygen_term_robots' ); self::persist_term_meta( $term_id, Constants::META_TERM_TITLE, $title ); self::persist_term_meta( $term_id, Constants::META_TERM_DESCRIPTION, $description ); self::persist_term_meta( $term_id, Constants::META_TERM_CANONICAL, $canonical ); self::persist_term_meta( $term_id, Constants::META_TERM_ROBOTS, $robots ); update_term_meta( $term_id, Constants::META_TERM_LASTMOD, gmdate( 'c' ) ); } /** * Determine whether a taxonomy is enabled in settings. * * @param string $taxonomy Taxonomy slug. * * @return bool */ private static function is_taxonomy_enabled( string $taxonomy ): bool { $settings = Settings::get(); if ( empty( $settings['enabled'] ) ) { return false; } if ( ! isset( $settings['enabled_taxonomies'] ) || ! is_array( $settings['enabled_taxonomies'] ) ) { return false; } return in_array( $taxonomy, $settings['enabled_taxonomies'], true ); } /** * Resolve the current term from a taxonomy edit screen. * * @param string $hook Current admin hook. * * @return \WP_Term|null */ private static function current_term_from_screen( string $hook ): ?\WP_Term { if ( 'term.php' !== $hook ) { return null; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin screen context. $taxonomy = isset( $_GET['taxonomy'] ) ? sanitize_key( wp_unslash( (string) $_GET['taxonomy'] ) ) : ''; if ( '' === $taxonomy || ! self::is_taxonomy_enabled( $taxonomy ) ) { return null; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin screen context. $term_id = isset( $_GET['tag_ID'] ) ? absint( wp_unslash( (string) $_GET['tag_ID'] ) ) : 0; if ( $term_id <= 0 ) { return null; } $term = get_term( $term_id, $taxonomy ); return $term instanceof \WP_Term ? $term : null; } /** * Render select option. * * @param string $value Option value. * @param string $selected Selected value. * @param string $label Label text. * * @return void */ private static function render_option( string $value, string $selected, string $label ): void { printf( '', esc_attr( $value ), selected( $selected, $value, false ), esc_html( $label ) ); } /** * Read and sanitize plain text input. * * @param string $key POST key. * * @return string */ private static function posted_text( string $key ): string { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in save_term_meta(). if ( ! isset( $_POST[ $key ] ) ) { return ''; } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in save_term_meta(). return mb_substr( sanitize_text_field( wp_unslash( (string) $_POST[ $key ] ) ), 0, 180 ); } /** * Read and sanitize textarea input. * * @param string $key POST key. * * @return string */ private static function posted_textarea( string $key ): string { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in save_term_meta(). if ( ! isset( $_POST[ $key ] ) ) { return ''; } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in save_term_meta(). return mb_substr( sanitize_textarea_field( wp_unslash( (string) $_POST[ $key ] ) ), 0, 220 ); } /** * Read and sanitize URL input. * * @param string $key POST key. * * @return string */ private static function posted_url( string $key ): string { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in save_term_meta(). if ( ! isset( $_POST[ $key ] ) ) { return ''; } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in save_term_meta(). return esc_url_raw( trim( sanitize_text_field( wp_unslash( (string) $_POST[ $key ] ) ) ) ); } /** * Read and sanitize robots directive input. * * @param string $key POST key. * * @return string */ private static function posted_robots( string $key ): string { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in save_term_meta(). if ( ! isset( $_POST[ $key ] ) ) { return ''; } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in save_term_meta(). $value = strtolower( trim( sanitize_text_field( wp_unslash( (string) $_POST[ $key ] ) ) ) ); $allowed = array( '', 'index,follow', 'noindex,follow', 'index,nofollow', 'noindex,nofollow', ); if ( ! in_array( $value, $allowed, true ) ) { return ''; } return $value; } /** * Persist term meta, deleting empty values. * * @param int $term_id Term ID. * @param string $key Meta key. * @param string $value Meta value. * * @return void */ private static function persist_term_meta( int $term_id, string $key, string $value ): void { if ( '' === $value ) { delete_term_meta( $term_id, $key ); return; } update_term_meta( $term_id, $key, $value ); } }