get_supported_taxonomies() as $taxonomy ) { add_action( "{$taxonomy}_edit_form_fields", array( $this, 'render_term_fields' ) ); add_action( "edited_{$taxonomy}", function ( $term_id ) use ( $taxonomy ): void { $this->save_term_meta( (int) $term_id, $taxonomy ); } ); } } /** * Get the list of taxonomies the OG fields are exposed on. * * @since 1.2.0 * * @return string[] Taxonomy slugs. */ protected function get_supported_taxonomies(): array { $taxonomies = array( 'category', 'post_tag' ); /** * Filter the taxonomies that expose per-term Open Graph fields. * * @since 1.2.0 * * @param string[] $taxonomies Taxonomy slugs. */ return apply_filters( 'robotstxt_og_term_meta_taxonomies', $taxonomies ); } /** * Register term meta for REST API / block editor access. * * @since 1.2.0 * * @return void */ public function register_term_meta(): void { $fields = array( self::META_TITLE => 'sanitize_text_field', self::META_IMAGE => 'sanitize_url', self::META_IMAGE_ALT => 'sanitize_text_field', self::META_DESCRIPTION => 'sanitize_textarea_field', ); foreach ( $this->get_supported_taxonomies() as $taxonomy ) { $cap = $this->get_edit_cap( $taxonomy ); foreach ( $fields as $key => $sanitize ) { register_term_meta( $taxonomy, $key, array( 'type' => 'string', 'single' => true, 'sanitize_callback' => $sanitize, 'auth_callback' => static function () use ( $cap ) { return current_user_can( $cap ); }, 'show_in_rest' => true, ) ); } } } /** * Resolve the edit capability for a taxonomy. * * @since 1.2.0 * * @param string $taxonomy Taxonomy slug. * @return string Capability name. */ protected function get_edit_cap( string $taxonomy ): string { $tax_obj = get_taxonomy( $taxonomy ); if ( $tax_obj && isset( $tax_obj->cap->edit_terms ) && '' !== $tax_obj->cap->edit_terms ) { return $tax_obj->cap->edit_terms; } return 'manage_categories'; } /** * Supply the stored term image URL during taxonomy archive resolution. * * Hooked to `robotstxt_og_taxonomy_image`. The resolver still validates the * URL and resolves a compatible format, so a raw stored value is fine. * * @since 1.2.0 * * @param string $url Default URL (empty string). * @param int $term_id Term ID. * @return string */ public function filter_taxonomy_image( string $url, $term_id ): string { $term_id = (int) $term_id; if ( $term_id <= 0 ) { return $url; } $stored = get_term_meta( $term_id, self::META_IMAGE, true ); $stored = is_string( $stored ) ? $stored : ''; return '' !== $stored ? $stored : $url; } /** * Render the OG fields on the term edit page. * * Fires inside the existing term-edit form-table, so rows are emitted * directly (no wrapping table). * * @since 1.2.0 * * @param WP_Term $term The term being edited. * @return void */ public function render_term_fields( WP_Term $term ): void { wp_nonce_field( self::NONCE_ACTION, self::NONCE_NAME ); $title = $this->get_meta_string( (int) $term->term_id, self::META_TITLE ); $image = $this->get_meta_string( (int) $term->term_id, self::META_IMAGE ); $alt = $this->get_meta_string( (int) $term->term_id, self::META_IMAGE_ALT ); $desc = $this->get_meta_string( (int) $term->term_id, self::META_DESCRIPTION ); ?>