true], 'names') as $taxonomy) { add_action("{$taxonomy}_edit_form_fields", [$this, 'renderTermFields'], 10, 2); add_action("edited_{$taxonomy}", [$this, 'saveTerm']); // No add_form_fields hook: a term being created has no ID yet, and the // SEO fields are far more useful once there is content to describe. } add_action('show_user_profile', [$this, 'renderUserFields']); add_action('edit_user_profile', [$this, 'renderUserFields']); add_action('personal_options_update', [$this, 'saveUser']); add_action('edit_user_profile_update', [$this, 'saveUser']); } // ──────────────────────────────────────────────────────────── // Terms // ──────────────────────────────────────────────────────────── public function renderTermFields($term, $taxonomy = '') { if (!($term instanceof \WP_Term)) { return; } $context = new Context(); $context->type = Context::TYPE_TAXONOMY; $context->object = $term; $context->objectId = (int) $term->term_id; $context->taxonomy = $term->taxonomy; $context->settingsKey = 'tax_' . $term->taxonomy; $values = []; foreach (self::FIELDS as $field) { $stored = get_term_meta($term->term_id, PostMeta::metaKey($field), true); $values[$field] = is_string($stored) ? $stored : ''; } $inheritedTitle = Variables::resolve(Resolver::template($context, 'title'), $context); $inheritedDescription = Variables::resolve(Resolver::template($context, 'description'), $context); wp_nonce_field(self::NONCE_ACTION, self::NONCE_NAME); $this->renderRows($values, $inheritedTitle, $inheritedDescription, get_term_link($term)); } public function saveTerm($termId) { if (!isset($_POST[self::NONCE_NAME]) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST[self::NONCE_NAME])), self::NONCE_ACTION)) { return; } if (!current_user_can('manage_categories')) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above. $submitted = isset($_POST['rankify']) && is_array($_POST['rankify']) ? wp_unslash($_POST['rankify']) : []; foreach (self::FIELDS as $field) { $raw = array_key_exists($field, $submitted) ? $submitted[$field] : ''; $value = PostMeta::sanitize($field, $raw); // Delete rather than store '' — Resolver reads a missing key as "fall // back to the template", and an empty string would be indistinguishable // from a deliberate blank. if ('' === $value) { delete_term_meta((int) $termId, PostMeta::metaKey($field)); continue; } update_term_meta((int) $termId, PostMeta::metaKey($field), $value); } } // ──────────────────────────────────────────────────────────── // Authors // ──────────────────────────────────────────────────────────── public function renderUserFields($user) { if (!($user instanceof \WP_User) || !current_user_can('edit_user', $user->ID)) { return; } $context = new Context(); $context->type = Context::TYPE_AUTHOR; $context->object = $user; $context->objectId = (int) $user->ID; $context->settingsKey = 'author'; $values = []; foreach (self::FIELDS as $field) { $stored = get_user_meta($user->ID, PostMeta::metaKey($field), true); $values[$field] = is_string($stored) ? $stored : ''; } $inheritedTitle = Variables::resolve(Resolver::template($context, 'title'), $context); $inheritedDescription = Variables::resolve(Resolver::template($context, 'description'), $context); ?>

renderRows($values, $inheritedTitle, $inheritedDescription, get_author_posts_url($user->ID));?>