array( array( 'handle' => 'aiforya-sm-tab-types', 'src' => AIFORYA_SM_URL . 'assets/css/tab-types.css', 'ver' => AIFORYA_SM_VERSION, ), ), ); } public function render( array $args = [] ): void { /* * V786-B — le comportement part par wp_add_inline_script() (grief n°2 de wordpress.org, * compté ERREUR par le Plugin Check). NOWDOC obligatoire : le corps contient $form, * $button, $spinner, $result, $row, $op, $btn, $card, $r, $ — un heredoc les viderait * tous, sans qu aucun contrôle ne crie. * * ⭐ GAIN COLLATÉRAL : trois de ces textes passaient par esc_html_e() ÉCHO DIRECT dans * une chaîne JS — le mauvais échappement pour ce contexte. wp_localize_script les * encode en JSON, ce qui est le bon. */ $handle = 'aiforya-sm-tab-types-js'; wp_register_script( $handle, false, array( 'jquery' ), AIFORYA_SM_VERSION, true ); wp_enqueue_script( $handle ); wp_localize_script( $handle, 'aiforyaSmTypesL10n', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'rulesNonce' => wp_create_nonce( 'aiforya_sm_admin_nonce' ), 'mappingsSaved' => __( 'Mappings saved successfully!', 'aiforya-schema-markup' ), 'saveError' => __( 'Error while saving.', 'aiforya-schema-markup' ), 'commError' => __( 'Communication error with the server.', 'aiforya-schema-markup' ), 'condPostType' => __( 'Post type', 'aiforya-schema-markup' ), 'condCategory' => __( 'Category', 'aiforya-schema-markup' ), 'condTag' => __( 'Tag', 'aiforya-schema-markup' ), 'condAuthor' => __( 'Author (ID)', 'aiforya-schema-markup' ), 'condUrlRegex' => __( 'URL regex', 'aiforya-schema-markup' ), 'condTitleContains' => __( 'Title contains', 'aiforya-schema-markup' ), 'condMetaField' => __( 'ACF/Meta field', 'aiforya-schema-markup' ), 'valuePlaceholder' => __( 'value', 'aiforya-schema-markup' ), 'editRule' => __( 'Edit rule', 'aiforya-schema-markup' ), 'newRule' => __( 'New Rule', 'aiforya-schema-markup' ), 'unnamedRule' => __( 'Unnamed rule', 'aiforya-schema-markup' ), 'confirmDelete' => __( 'Delete this rule?', 'aiforya-schema-markup' ), 'testing' => __( 'Running test on 30 recent posts…', 'aiforya-schema-markup' ), 'failed' => __( 'Failed.', 'aiforya-schema-markup' ), ) ); $js = <<<'JS' jQuery(document).ready(function($) { var L = window.aiforyaSmTypesL10n || {}; var ajaxurl = window.ajaxurl || L.ajaxUrl; $('#aiforya-sm-types-form').on('submit', function(e) { e.preventDefault(); var $form = $(this); var $button = $('#aiforya-sm-save-types'); var $spinner = $form.find('.spinner'); var $result = $form.find('.aiforya-sm-save-result'); $button.prop('disabled', true); $spinner.addClass('is-active'); $result.removeClass('success error').text(''); // Serialize form natively (PHP $_POST receives schema_type[post]=Article + auto_generate[post]=1). var formData = $form.serialize() + '&action=aiforya_sm_save_type_mappings' + '&nonce=' + encodeURIComponent($form.find('#aiforya_sm_types_nonce').val()); $.ajax({ url: ajaxurl, type: 'POST', data: formData, success: function(response) { $spinner.removeClass('is-active'); $button.prop('disabled', false); if (response.success) { $result.addClass('success').text(response.data.message || L.mappingsSaved); } else { $result.addClass('error').text(response.data.message || L.saveError); } setTimeout(function() { $result.fadeOut(function() { $(this).text('').show(); }); }, 3000); }, error: function() { $spinner.removeClass('is-active'); $button.prop('disabled', false); $result.addClass('error').text(L.commError); } }); }); // ============================================================ // F06 Règles avancées — JS handlers // ============================================================ var rulesNonce = L.rulesNonce; $('#aiforya-sm-rules-toast-host').appendTo('body'); $('#aiforya-sm-rule-modal').appendTo('body'); function smToast(message, type) { type = type || 'info'; var $host = $('.aiforya-sm-toast-host').last(); if (!$host.length) $host = $('
').appendTo('body'); var $t = $('
', { 'class': 'aiforya-sm-toast aiforya-sm-toast--' + type, role: 'status' }).text(message); $host.append($t); setTimeout(function(){ $t.addClass('is-visible'); }, 10); setTimeout(function(){ $t.removeClass('is-visible'); setTimeout(function(){ $t.remove(); }, 300); }, 4500); } var conditionTypes = [ { value: 'post_type', label: L.condPostType, ops: ['is', 'is_not', 'in'] }, { value: 'category', label: L.condCategory, ops: ['is', 'is_not'] }, { value: 'tag', label: L.condTag, ops: ['has', 'has_not'] }, { value: 'author', label: L.condAuthor, ops: ['is'] }, { value: 'url_regex', label: L.condUrlRegex, ops: ['matches'] }, { value: 'title_contains', label: L.condTitleContains, ops: ['contains', 'starts_with', 'ends_with'] }, { value: 'meta_field', label: L.condMetaField, ops: ['exists', 'not_empty', 'equals', 'contains'] } ]; function escHtml(s){ return String(s == null ? '' : s).replace(/[&<>"']/g, function(c){ return ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]; }); } function buildConditionRow(c) { c = c || { type: 'post_type', op: 'is', value: '', field: '' }; var typeOptions = conditionTypes.map(function(t){ return ''; }).join(''); var typeDef = conditionTypes.find(function(t){ return t.value === c.type; }) || conditionTypes[0]; var opOptions = typeDef.ops.map(function(op){ return ''; }).join(''); var fieldHtml = (c.type === 'meta_field') ? '' : ''; return '
' + '' + '' + fieldHtml + '' + '' + '
'; } function refreshConditionRow($row) { var type = $row.find('.aiforya-sm-cond-type').val(); var typeDef = conditionTypes.find(function(t){ return t.value === type; }) || conditionTypes[0]; var $op = $row.find('.aiforya-sm-cond-op').empty(); typeDef.ops.forEach(function(op){ $op.append(''); }); // Field input only for meta_field $row.find('.aiforya-sm-cond-field').remove(); if (type === 'meta_field') { $row.find('.aiforya-sm-cond-op').after(''); } } $(document).on('change', '.aiforya-sm-cond-type', function(){ refreshConditionRow($(this).closest('.aiforya-sm-rule-cond-row')); }); function openRuleModal(rule) { rule = rule || { id: 0, name: '', enabled: 1, priority: 10, schema_type: 'Article', operator: 'AND', conditions: [{ type: 'post_type', op: 'is', value: 'post' }] }; $('.aiforya-sm-rule-modal-title').text(rule.id ? L.editRule : L.newRule); $('#aiforya-sm-rule-name').val(rule.name); $('#aiforya-sm-rule-schema-type').val(rule.schema_type); $('#aiforya-sm-rule-priority').val(rule.priority); $('#aiforya-sm-rule-operator').val(rule.operator); $('#aiforya-sm-rule-enabled').prop('checked', !!parseInt(rule.enabled, 10)); var html = ''; (rule.conditions || []).forEach(function(c){ html += buildConditionRow(c); }); $('#aiforya-sm-rule-conditions').html(html); $('#aiforya-sm-rule-modal').data('editing-id', rule.id || 0).addClass('is-open').fadeIn(120); } function closeRuleModal() { $('#aiforya-sm-rule-modal').removeClass('is-open').fadeOut(120); } $(document).on('click', '.aiforya-sm-rule-add', function(){ openRuleModal(); }); $(document).on('click', '.aiforya-sm-rule-edit', function(){ var data = $(this).closest('.aiforya-sm-rule-card').data('rule'); openRuleModal(data); }); $(document).on('click', '.aiforya-sm-rule-modal-close, .aiforya-sm-rule-modal-backdrop', closeRuleModal); $(document).on('click', '.aiforya-sm-rule-add-cond', function(){ $('#aiforya-sm-rule-conditions').append(buildConditionRow()); }); $(document).on('click', '.aiforya-sm-cond-remove', function(){ $(this).closest('.aiforya-sm-rule-cond-row').remove(); }); $(document).on('click', '.aiforya-sm-rule-save', function(){ var $btn = $(this); var conditions = []; $('#aiforya-sm-rule-conditions .aiforya-sm-rule-cond-row').each(function(){ var $r = $(this); conditions.push({ type: $r.find('.aiforya-sm-cond-type').val(), op: $r.find('.aiforya-sm-cond-op').val(), field: String($r.find('.aiforya-sm-cond-field').val() || ''), value: String($r.find('.aiforya-sm-cond-value').val() || '') }); }); var rule = { id: parseInt($('#aiforya-sm-rule-modal').data('editing-id'), 10) || 0, name: String($('#aiforya-sm-rule-name').val() || '').trim() || L.unnamedRule, schema_type: $('#aiforya-sm-rule-schema-type').val(), priority: parseInt($('#aiforya-sm-rule-priority').val(), 10) || 10, operator: $('#aiforya-sm-rule-operator').val(), enabled: $('#aiforya-sm-rule-enabled').prop('checked') ? 1 : 0, conditions: conditions }; $btn.prop('disabled', true); $.post(ajaxurl, { action: 'aiforya_sm_rules_save', nonce: rulesNonce, rule: JSON.stringify(rule) }).done(function(r){ if (r && r.success) { smToast((r.data && r.data.message) || 'OK', 'success'); closeRuleModal(); setTimeout(function(){ location.reload(); }, 600); } else { smToast((r && r.data && r.data.message) || L.failed, 'error'); $btn.prop('disabled', false); } }); }); $(document).on('click', '.aiforya-sm-rule-delete', function(){ var $card = $(this).closest('.aiforya-sm-rule-card'); var id = $card.data('rule-id'); window.AiforyaConfirm.ask(L.confirmDelete, function(){ $.post(ajaxurl, { action: 'aiforya_sm_rules_delete', nonce: rulesNonce, rule_id: id }).done(function(r){ if (r && r.success) { smToast((r.data && r.data.message) || 'OK', 'success'); $card.fadeOut(220, function(){ $(this).remove(); }); } else { smToast((r && r.data && r.data.message) || L.failed, 'error'); } }); }); }); $(document).on('click', '.aiforya-sm-rules-test', function(){ var $btn = $(this); var $result = $('.aiforya-sm-rules-test-result'); $btn.prop('disabled', true); $result.text(L.testing).css('color', '#94a3b8'); $.post(ajaxurl, { action: 'aiforya_sm_rules_test', nonce: rulesNonce }).done(function(r){ if (r && r.success) { var d = r.data || {}; $result.css('color', '#a5b4fc').text(d.matched + ' / ' + d.tested_posts + ' posts match ' + d.rules_count + ' rule(s).'); } else { $result.css('color', '#f87171').text(L.failed); } }).always(function(){ $btn.prop('disabled', false); }); }); }); JS; wp_add_inline_script( $handle, $js ); // Récupération des mappings actuels $mappings = get_option( 'aiforya_sm_type_mappings', array() ); // Liste des types Schema.org supportés $schema_types = array( 'Article' => __( 'Article', 'aiforya-schema-markup' ), 'BlogPosting' => __( 'Blog Posting', 'aiforya-schema-markup' ), 'NewsArticle' => __( 'News Article', 'aiforya-schema-markup' ), 'Product' => __( 'Product', 'aiforya-schema-markup' ), 'Recipe' => __( 'Recipe', 'aiforya-schema-markup' ), 'Event' => __( 'Event', 'aiforya-schema-markup' ), 'FAQ' => __( 'FAQ', 'aiforya-schema-markup' ), 'HowTo' => __( 'How-To', 'aiforya-schema-markup' ), 'Review' => __( 'Review', 'aiforya-schema-markup' ), 'VideoObject' => __( 'Video Object', 'aiforya-schema-markup' ), 'LocalBusiness' => __( 'Local Business', 'aiforya-schema-markup' ), 'Organization' => __( 'Organization', 'aiforya-schema-markup' ), 'Person' => __( 'Person', 'aiforya-schema-markup' ), 'WebPage' => __( 'Web Page', 'aiforya-schema-markup' ), ); // Récupération de tous les post types publics $post_types = get_post_types( array( 'public' => true ), 'objects' ); // Filtrer les types WordPress système (attachments, nav_menu_item, etc.) $excluded_types = array( 'attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'oembed_cache', 'user_request', 'wp_block' ); // Helper D2 : pastille d'aide « ? » charte (AiforyaTooltip). $text déjà traduit. $tip = static function ( $text ) { printf( '', esc_attr( $text ) ); }; ?>

'Article', 'page' => 'WebPage', ...] // (cohérent avec Activator + Detector::detect_schema_type_for_post). $auto_generate_map = get_option( 'aiforya_sm_auto_generate', array( 'post' => 1, 'page' => 1, 'product' => 1 ) ); if ( ! is_array( $auto_generate_map ) ) { $auto_generate_map = array(); } ?> $post_type_obj ) : ?>

get_rules() : array(); $meta_keys = $rules_service ? $rules_service->discover_meta_keys( 80 ) : array(); $valid_types = class_exists( 'Aiforya_Schema_Markup_Detector' ) ? Aiforya_Schema_Markup_Detector::supported_schema_types() : array(); // $rules_nonce retiré V786-B : le nonce part par wp_localize_script(). ?>

#

  • Article :
  • Product :
  • Recipe :
  • Event :
  • FAQ :
  • HowTo :