';
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 '';
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 );
}
}