... render()`, un echo de * valeur que PHPCS ne peut pas suivre jusqu'à l'échappement fait * À L'INTÉRIEUR de render(). D'où une annotation de mise en sourdine du sniff * par extension, refusé par wordpress.org. Le moteur enveloppe * désormais l'appel dans ob_start()/ob_end_flush() : le try/catch * garde son effet, et plus rien n'est masqué. * ⚠ Le contrat est PARTAGÉ par les deux catalogues (.org et premium) * et le socle est chargé « premier arrivé gagne » (class_exists). * Un onglet resté en `: string` sur un site qui a les deux = * Fatal error au chargement. Mesuré, pas déduit. * * Le LLM peut implémenter optionnellement : * - register() : enregistre les hooks WP (settings, AJAX, cron) * - assets() : déclare CSS/JS spécifique chargé QUE si tab actif * - capability() : capability WP requise (défaut: manage_options) * * Le LLM N'A PAS LE DROIT de : * - Ajouter un menu admin (c'est le moteur qui le fait via le Hub) * - Enqueue des assets globaux (utiliser assets() pour scope onglet) * - Faire `new` sur un autre Tab (utiliser le moteur si besoin de cross-tab) * - Déclarer des constantes ou fonctions globales depuis la classe Tab * - Faire `require_once` depuis ce fichier * * @package Aiforya_Vendor * @since 1.0.0 (28/04/2026) */ if ( ! defined( 'ABSPATH' ) ) { exit; } if ( class_exists( 'Aiforya_Faq_Tab', false ) ) { return; // Idempotent : inclus par chaque plugin via require_once. } abstract class Aiforya_Faq_Tab { /** * Identifiant unique de l'onglet, utilisé comme query-string ?tab={slug}. * Doit matcher /^[a-z0-9-]+$/. */ abstract public function slug(): string; /** * Titre affiché dans la nav. Doit utiliser __() pour i18n. */ abstract public function label(): string; /** * Position d'affichage dans la nav (croissant = plus à gauche). * Convention : 10-90 = onglets métier ; 100 = Support ; 110 = Avis. */ abstract public function order(): int; /** * Rend le HTML de l'onglet. NE PAS PRINT — retourner string. * Utiliser ob_start() + ob_get_clean() si besoin d'output buffering. * * @param array $args Contexte fourni par le moteur (slug plugin, etc). */ abstract public function render( array $args ): void; /** * Hook optionnel pour enregistrer les actions/filtres WP de cet onglet. * Appelé par le moteur sur plugins_loaded prio 20. * * Exemples : add_action('admin_init', ...) pour Settings API, * add_action('wp_ajax_my_action', ...), add_filter('cron_schedules', ...). */ public function register(): void { // Aucun comportement par défaut — à override si besoin. } /** * Déclare les CSS/JS spécifiques à cet onglet. Le moteur enqueue * conditionnellement quand l'onglet est actif (?tab={slug}). * * @return array{css?: array, ver?: string}>, js?: array, ver?: string, in_footer?: bool}>} */ public function assets(): array { return []; } /** * Capability WP requise pour voir cet onglet. Permet de cacher des onglets * admin-only à des rôles inférieurs. */ public function capability(): string { return 'manage_options'; } }