237 lines
6.4 KiB
PHP
237 lines
6.4 KiB
PHP
<?php
|
||
/**
|
||
* Handles saving and retrieving SEO meta for taxonomy terms
|
||
* (categories, tags, and all public custom taxonomies).
|
||
*
|
||
* @package S1_SEO_Meta
|
||
*/
|
||
|
||
if ( ! defined( 'ABSPATH' ) ) {
|
||
exit;
|
||
}
|
||
|
||
class S1_SEO_Meta_Term {
|
||
|
||
/**
|
||
* Option-table key prefix used to store term meta (WP < 4.4 fallback aware).
|
||
* From WP 4.4+ term meta is stored in wp_termmeta via get/update_term_meta().
|
||
*/
|
||
const META_TITLE = 's1_seo_title';
|
||
const META_DESC = 's1_seo_description';
|
||
|
||
public function __construct() {
|
||
add_action( 'init', array( $this, 'register_term_hooks' ) );
|
||
}
|
||
|
||
/**
|
||
* Register add/edit/save hooks for every public taxonomy.
|
||
*/
|
||
public function register_term_hooks() {
|
||
$taxonomies = get_taxonomies( array( 'public' => true ), 'names' );
|
||
|
||
foreach ( $taxonomies as $taxonomy ) {
|
||
// Fields on the "Add new term" form.
|
||
add_action( "{$taxonomy}_add_form_fields", array( $this, 'add_form_fields' ) );
|
||
// Fields on the "Edit term" form.
|
||
add_action( "{$taxonomy}_edit_form_fields", array( $this, 'edit_form_fields' ), 10, 2 );
|
||
// Save on create.
|
||
add_action( "created_{$taxonomy}", array( $this, 'save_term_meta' ), 10, 2 );
|
||
// Save on update.
|
||
add_action( "edited_{$taxonomy}", array( $this, 'save_term_meta' ), 10, 2 );
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Render fields on the "Add new term" screen.
|
||
*
|
||
* @param string $taxonomy Current taxonomy slug.
|
||
*/
|
||
public function add_form_fields( $taxonomy ) {
|
||
wp_nonce_field( 's1_seo_term_save', 's1_seo_term_nonce' );
|
||
?>
|
||
<div class="form-field s1-seo-term-wrap">
|
||
<h3 class="s1-seo-term-heading"><?php esc_html_e( 'S1 SEO', 's1-seo-meta' ); ?></h3>
|
||
|
||
<div class="form-field">
|
||
<label for="s1_seo_title"><?php esc_html_e( 'SEO Title', 's1-seo-meta' ); ?></label>
|
||
<input
|
||
type="text"
|
||
id="s1_seo_title"
|
||
name="s1_seo_title"
|
||
value=""
|
||
maxlength="666"
|
||
/>
|
||
<p class="s1-seo-counter">
|
||
<span id="s1_seo_title_count">0</span>/60 <?php esc_html_e( 'characters', 's1-seo-meta' ); ?>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-field">
|
||
<label for="s1_seo_description"><?php esc_html_e( 'SEO Description', 's1-seo-meta' ); ?></label>
|
||
<?php
|
||
$settings = array(
|
||
'media_buttons' => false,
|
||
'textarea_rows' => 5,
|
||
'teeny' => true,
|
||
'tinymce' => array(
|
||
'toolbar1' => 'bold,italic,underline,link,unlink',
|
||
'toolbar2' => '',
|
||
),
|
||
);
|
||
wp_editor( '', 's1_seo_description', $settings );
|
||
?>
|
||
<p class="s1-seo-counter">
|
||
<span id="s1_seo_desc_count">0</span>/160 <?php esc_html_e( 'characters (recommended: 120–160)', 's1-seo-meta' ); ?>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Render fields on the "Edit term" screen.
|
||
*
|
||
* @param WP_Term $term Current term object.
|
||
* @param string $taxonomy Current taxonomy slug.
|
||
*/
|
||
public function edit_form_fields( $term, $taxonomy ) {
|
||
wp_nonce_field( 's1_seo_term_save', 's1_seo_term_nonce' );
|
||
|
||
$seo_title = get_term_meta( $term->term_id, self::META_TITLE, true );
|
||
$seo_desc = get_term_meta( $term->term_id, self::META_DESC, true );
|
||
?>
|
||
<tr class="form-field s1-seo-term-row">
|
||
<td colspan="2">
|
||
<h3 class="s1-seo-term-heading"><?php esc_html_e( 'S1 SEO', 's1-seo-meta' ); ?></h3>
|
||
</td>
|
||
</tr>
|
||
|
||
<tr class="form-field">
|
||
<th scope="row">
|
||
<label for="s1_seo_title"><?php esc_html_e( 'SEO Title', 's1-seo-meta' ); ?></label>
|
||
</th>
|
||
<td>
|
||
<input
|
||
type="text"
|
||
id="s1_seo_title"
|
||
name="s1_seo_title"
|
||
value="<?php echo esc_attr( $seo_title ); ?>"
|
||
placeholder="<?php echo esc_attr( $term->name ); ?>"
|
||
maxlength="60"
|
||
/>
|
||
<p class="description s1-seo-counter">
|
||
<span id="s1_seo_title_count"><?php echo esc_html( mb_strlen( $seo_title ) ); ?></span>/60
|
||
<?php esc_html_e( 'characters (recommended: 50–60)', 's1-seo-meta' ); ?>
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
|
||
<tr class="form-field">
|
||
<th scope="row">
|
||
<label for="s1_seo_description"><?php esc_html_e( 'SEO Description', 's1-seo-meta' ); ?></label>
|
||
</th>
|
||
<td>
|
||
<?php
|
||
$settings = array(
|
||
'media_buttons' => false,
|
||
'textarea_rows' => 5,
|
||
'teeny' => true,
|
||
'tinymce' => array(
|
||
'toolbar1' => 'bold,italic,underline,link,unlink',
|
||
'toolbar2' => '',
|
||
),
|
||
);
|
||
wp_editor( $seo_desc, 's1_seo_description', $settings );
|
||
?>
|
||
<p class="description s1-seo-counter">
|
||
<span id="s1_seo_desc_count"><?php echo esc_html( mb_strlen( wp_strip_all_tags( $seo_desc ) ) ); ?></span>/160
|
||
<?php esc_html_e( 'characters (recommended: 120–160)', 's1-seo-meta' ); ?>
|
||
</p>
|
||
</td>
|
||
</tr>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Save term SEO meta.
|
||
*
|
||
* @param int $term_id Term ID.
|
||
*/
|
||
public function save_term_meta( $term_id ) {
|
||
if (
|
||
! isset( $_POST['s1_seo_term_nonce'] ) ||
|
||
! wp_verify_nonce( sanitize_key( $_POST['s1_seo_term_nonce'] ), 's1_seo_term_save' )
|
||
) {
|
||
return;
|
||
}
|
||
|
||
if ( ! current_user_can( 'manage_categories' ) ) {
|
||
return;
|
||
}
|
||
|
||
if ( isset( $_POST['s1_seo_title'] ) ) {
|
||
update_term_meta(
|
||
$term_id,
|
||
self::META_TITLE,
|
||
sanitize_text_field( wp_unslash( $_POST['s1_seo_title'] ) )
|
||
);
|
||
}
|
||
|
||
if ( isset( $_POST['s1_seo_description'] ) ) {
|
||
$description = wp_unslash( $_POST['s1_seo_description'] );
|
||
|
||
// Разрешаем безопасные HTML теги для SEO описания
|
||
$allowed_tags = array(
|
||
'a' => array(
|
||
'href' => array(),
|
||
'title' => array(),
|
||
'target' => array(),
|
||
),
|
||
'b' => array(),
|
||
'strong' => array(),
|
||
'i' => array(),
|
||
'em' => array(),
|
||
'br' => array(),
|
||
'p' => array(),
|
||
'span' => array(),
|
||
'ul' => array(),
|
||
'ol' => array(),
|
||
'li' => array(),
|
||
);
|
||
|
||
// Для администраторов разрешаем все теги, для остальных - только безопасные
|
||
if ( current_user_can( 'unfiltered_html' ) ) {
|
||
$sanitized = $description;
|
||
} else {
|
||
$sanitized = wp_kses( $description, $allowed_tags );
|
||
}
|
||
|
||
update_term_meta(
|
||
$term_id,
|
||
self::META_DESC,
|
||
$sanitized
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Static helper: retrieve SEO title for a term.
|
||
*
|
||
* @param int $term_id Term ID.
|
||
* @return string
|
||
*/
|
||
public static function get_title( $term_id ) {
|
||
return (string) get_term_meta( $term_id, self::META_TITLE, true );
|
||
}
|
||
|
||
/**
|
||
* Static helper: retrieve SEO description for a term.
|
||
*
|
||
* @param int $term_id Term ID.
|
||
* @return string
|
||
*/
|
||
public static function get_description( $term_id ) {
|
||
return (string) get_term_meta( $term_id, self::META_DESC, true );
|
||
}
|
||
}
|