array(
array(
'handle' => 'aiforya-sm-tab-schemas',
'src' => AIFORYA_SM_URL . 'assets/css/tab-schemas.css',
'ver' => AIFORYA_SM_VERSION,
),
),
);
}
public function render( array $args = [] ): void {
// Récupération des paramètres de pagination et filtrage
$paged = isset( $_GET['paged'] ) ? max( 1, intval( $_GET['paged'] ) ) : 1;
$per_page = 20;
$offset = ( $paged - 1 ) * $per_page;
$filter_type = isset( $_GET['filter_type'] ) ? sanitize_text_field( wp_unslash( $_GET['filter_type'] ) ) : '';
$filter_status = isset( $_GET['filter_status'] ) ? sanitize_text_field( wp_unslash( $_GET['filter_status'] ) ) : '';
$search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
// Construction des arguments de requête
$query_args = array(
'limit' => $per_page,
'offset' => $offset,
);
if ( ! empty( $filter_type ) ) {
$query_args['schema_type'] = $filter_type;
}
if ( ! empty( $filter_status ) ) {
$query_args['status'] = $filter_status;
}
if ( ! empty( $search ) ) {
$query_args['search'] = $search;
}
// Récupération des données via Db
$db = Aiforya_Schema_Markup_Db::get_instance();
$schemas = $db->get_schemas_for_listing( $query_args );
$total_count = $db->count_schemas( $query_args );
$total_pages = ceil( $total_count / $per_page );
// Types de schémas disponibles pour le filtre
$schema_types = array( 'Article', 'Product', 'Recipe', 'Event', 'Organization', 'Person', 'Service', 'FAQ', 'HowTo' );
// Helper D2 : pastille d'aide « ? » charte (AiforyaTooltip). $text déjà traduit.
$tip = static function ( $text ) {
printf( '', esc_attr( $text ) );
};
?>
admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'aiforya_sm_admin_nonce' ),
'changerLeTypeDe' => __( 'Changing the schema type will regenerate the JSON-LD. Continue?', 'aiforya-schema-markup' ),
'regenererLeSchemaPour' => __( 'Regenerate the schema for this content?', 'aiforya-schema-markup' ),
'uneErreurEstSurvenue' => __( 'An error occurred.', 'aiforya-schema-markup' ),
'erreurReseau' => __( 'Network error.', 'aiforya-schema-markup' ),
'chargementDuJsonLd' => __( 'Loading JSON-LD…', 'aiforya-schema-markup' ),
'aucunSchemaStockePour' => __( 'No schema stored for this post. Please regenerate first.', 'aiforya-schema-markup' ),
'jsonLdCopieDans' => __( 'JSON-LD copied to clipboard.', 'aiforya-schema-markup' ),
'impossibleDeCopierSelection' => __( 'Unable to copy — manual selection available.', 'aiforya-schema-markup' ),
'validationLocale' => __( 'Local validation…', 'aiforya-schema-markup' ),
'regenerationEnCours' => __( 'Regenerating…', 'aiforya-schema-markup' ),
'schemaRegenereAvecSucces' => __( 'Schema successfully regenerated.', 'aiforya-schema-markup' ),
'ouvertureDuGoogleRich' => __( 'Opening Google Rich Results Test…', 'aiforya-schema-markup' ),
'confirmer' => __( 'Confirm', 'aiforya-schema-markup' ),
'annuler' => __( 'Cancel', 'aiforya-schema-markup' ),
'supprimerLeSchemaJson' => /* translators: %s : valeur insérée dynamiquement. */ __( 'Delete the JSON-LD schema for "%s"? The WordPress post itself is left untouched.', 'aiforya-schema-markup' ),
'aucuneLigneSelectionnee' => __( 'No rows selected.', 'aiforya-schema-markup' ),
'supprimerDefinitivementDSchema' => /* translators: %d : valeur insérée dynamiquement. */ __( 'Permanently delete %d schema(s)? WordPress content will not be affected.', 'aiforya-schema-markup' ),
'regenererDSchemaS' => /* translators: %d : valeur insérée dynamiquement. */ __( 'Regenerate %d schema(s)? This may take several seconds.', 'aiforya-schema-markup' ),
'appliquerLeTypeS' => /* translators: %1$s : valeur insérée dynamiquement, %2$d : valeur insérée dynamiquement. */ __( 'Apply type %1$s to %2$d schema(s)? JSON-LD will be regenerated.', 'aiforya-schema-markup' ),
'validerDSchemaS' => /* translators: %d : valeur insérée dynamiquement. */ __( 'Validate %d schema(s)?', 'aiforya-schema-markup' ),
'actionEnCours' => __( 'Action in progress…', 'aiforya-schema-markup' ),
'choisissezUnTypeSchema' => __( 'Choose a Schema.org type from the dropdown menu.', 'aiforya-schema-markup' ),
)
);
$js = <<<'JS'
var L = window.aiforyaSmSchemasL10n || {};
jQuery(function($){
var ajaxUrl = window.ajaxurl || L.ajaxUrl;
var nonce = L.nonce;
// FIX doctrine — shell AIFORYA wrap le tab content dans un parent avec transform/filter
// qui casse position:fixed des enfants. Solution : déplacer les modals à document.body
// au boot pour qu'ils sortent du containing block du parent transformé.
$('#aiforya-sm-json-modal, #aiforya-sm-toast-host').appendTo('body');
var i18n = {
changeTypeConfirm : L.changerLeTypeDe,
regenConfirm : L.regenererLeSchemaPour,
error : L.uneErreurEstSurvenue,
network : L.erreurReseau,
jsonLoading : L.chargementDuJsonLd,
jsonNotFound : L.aucunSchemaStockePour,
copied : L.jsonLdCopieDans,
copyFail : L.impossibleDeCopierSelection,
validatingLocal : L.validationLocale,
regenerating : L.regenerationEnCours,
regenOk : L.schemaRegenereAvecSucces,
openingGoogle : L.ouvertureDuGoogleRich,
yes : L.confirmer,
no : L.annuler
};
// ---------- Toast charte AIFORYA ----------
function smToast(message, type, ttl) {
type = type || 'info';
ttl = ttl || 4500;
var $host = $('#aiforya-sm-toast-host');
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);
}, ttl);
}
// ---------- Confirm charte AIFORYA (remplace window.confirm) ----------
function smConfirm(message, onYes, onNo) {
var $modal = $('' +
'
' +
'
' +
'
' +
'
' +
'' +
'' +
'
' +
'
' +
'
');
$modal.find('p').text(message);
$modal.find('.aiforya-sm-confirm-no').text(i18n.no);
$modal.find('.aiforya-sm-confirm-yes').text(i18n.yes);
$('body').append($modal);
setTimeout(function(){ $modal.addClass('is-open'); $modal.find('.aiforya-sm-confirm-yes').trigger('focus'); }, 10);
var settled = false;
function close(){ $modal.removeClass('is-open'); setTimeout(function(){ $modal.remove(); }, 200); }
function dismiss(){ if (settled) return; settled = true; close(); if (typeof onNo === 'function') onNo(); }
function accept(){ if (settled) return; settled = true; close(); if (typeof onYes === 'function') onYes(); }
$modal.on('click', '.aiforya-sm-confirm-no, .aiforya-sm-modal-backdrop', dismiss);
$modal.on('click', '.aiforya-sm-confirm-yes', accept);
$modal.on('keydown', function(e){ if (e.key === 'Escape') dismiss(); if (e.key === 'Enter') accept(); });
}
function setBtnLoading($btn, loading, originalHtml) {
if (loading) {
$btn.data('orig-html', originalHtml || $btn.html());
$btn.prop('disabled', true).html('');
} else {
$btn.prop('disabled', false).html($btn.data('orig-html') || $btn.html());
}
}
// ---------- Changement de type via select inline ----------
$(document).on('change', '.aiforya-sm-schema-type-selector', function(){
var $select = $(this);
var postId = $select.data('post-id');
var newType = $select.val();
var origType = $select.data('original');
if (newType === origType) return;
$select.prop('disabled', true);
smConfirm(i18n.changeTypeConfirm, function(){
$.post(ajaxUrl, {
action: 'aiforya_sm_override_schema_type',
nonce: nonce,
post_id: postId,
schema_type: newType
}).done(function(r){
if (r && r.success) {
$select.data('original', newType);
smToast((r.data && r.data.message) || 'OK', 'success');
setTimeout(function(){ location.reload(); }, 600);
} else {
smToast((r && r.data && r.data.message) || i18n.error, 'error');
$select.val(origType);
}
}).fail(function(){
smToast(i18n.network, 'error');
$select.val(origType);
}).always(function(){
$select.prop('disabled', false);
});
}, function(){
$select.val(origType).prop('disabled', false);
});
});
// ---------- Voir JSON-LD ----------
$(document).on('click', '.aiforya-sm-action-view', function(){
var $btn = $(this);
var postId = $btn.data('post-id');
var orig = $btn.html();
setBtnLoading($btn, true, orig);
$('#aiforya-sm-json-modal .aiforya-sm-json-display').text(i18n.jsonLoading);
openModal();
$.post(ajaxUrl, {
action: 'aiforya_sm_get_schema_json',
nonce: nonce,
post_id: postId
}).done(function(r){
if (r && r.success && r.data && r.data.json) {
var pretty;
try { pretty = JSON.stringify(JSON.parse(r.data.json), null, 2); }
catch(e) { pretty = String(r.data.json); }
$('#aiforya-sm-json-modal .aiforya-sm-json-display').text(pretty);
} else {
$('#aiforya-sm-json-modal .aiforya-sm-json-display').text((r && r.data && r.data.message) || i18n.jsonNotFound);
}
}).fail(function(){
$('#aiforya-sm-json-modal .aiforya-sm-json-display').text(i18n.network);
}).always(function(){
setBtnLoading($btn, false);
});
});
// ---------- Tester Google (validation locale + ouverture Rich Results Test) ----------
$(document).on('click', '.aiforya-sm-action-google', function(){
var $btn = $(this);
var postId = $btn.data('post-id');
var orig = $btn.html();
setBtnLoading($btn, true, orig);
smToast(i18n.validatingLocal, 'info', 2500);
$.post(ajaxUrl, {
action: 'aiforya_sm_validate_with_google',
nonce: nonce,
post_id: postId
}).done(function(r){
if (r && r.success) {
var data = r.data || {};
var msg = data.message || 'OK';
var status = data.status || 'valid';
var type = (status === 'valid') ? 'success' : (status === 'warning' ? 'warning' : 'error');
smToast(msg, type, 5500);
if (data.external_url) {
smToast(i18n.openingGoogle, 'info', 3500);
window.open(data.external_url, '_blank', 'noopener');
}
} else {
smToast((r && r.data && r.data.message) || i18n.error, 'error');
}
}).fail(function(){
smToast(i18n.network, 'error');
}).always(function(){
setBtnLoading($btn, false);
});
});
// ---------- Supprimer (efface le schema JSON-LD seulement, post intact) ----------
$(document).on('click', '.aiforya-sm-action-delete', function(){
var $btn = $(this);
var postId = $btn.data('post-id');
var title = String($btn.data('post-title') || '');
var msg = L.supprimerLeSchemaJson.replace('%s', title);
smConfirm(msg, function(){
var orig = $btn.html();
setBtnLoading($btn, true, orig);
$.post(ajaxUrl, {
action : 'aiforya_sm_delete_schema_for_post',
nonce : nonce,
post_id : postId
}).done(function(r){
if (r && r.success) {
smToast((r.data && r.data.message) || 'OK', 'success');
$btn.closest('tr').fadeOut(220, function(){ $(this).remove(); });
} else {
smToast((r && r.data && r.data.message) || i18n.error, 'error');
setBtnLoading($btn, false);
}
}).fail(function(){
smToast(i18n.network, 'error');
setBtnLoading($btn, false);
});
});
});
// ---------- Régénérer ----------
$(document).on('click', '.aiforya-sm-action-regen', function(){
var $btn = $(this);
var postId = $btn.data('post-id');
smConfirm(i18n.regenConfirm, function(){
var orig = $btn.html();
setBtnLoading($btn, true, orig);
smToast(i18n.regenerating, 'info', 4000);
$.post(ajaxUrl, {
action: 'aiforya_sm_generate_one',
nonce: nonce,
post_id: postId
}).done(function(r){
if (r && r.success) {
smToast((r.data && r.data.message) || i18n.regenOk, 'success');
setTimeout(function(){ location.reload(); }, 800);
} else {
smToast((r && r.data && r.data.message) || i18n.error, 'error');
}
}).fail(function(){
smToast(i18n.network, 'error');
}).always(function(){
setBtnLoading($btn, false);
});
});
});
// ---------- Modal helpers ----------
function openModal() { $('#aiforya-sm-json-modal').addClass('is-open').fadeIn(120); }
function closeModal() { $('#aiforya-sm-json-modal').removeClass('is-open').fadeOut(120); }
$(document).on('click', '#aiforya-sm-json-modal .aiforya-sm-modal-close, #aiforya-sm-json-modal .aiforya-sm-modal-backdrop', closeModal);
$(document).on('keydown', function(e){
if (e.key === 'Escape' && $('#aiforya-sm-json-modal').hasClass('is-open')) closeModal();
});
$(document).on('click', '#aiforya-sm-json-modal .aiforya-sm-modal-copy', function(){
var text = $('#aiforya-sm-json-modal .aiforya-sm-json-display').text() || '';
if (!text) return;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(
function(){ smToast(i18n.copied, 'success'); },
function(){ smToast(i18n.copyFail, 'warning'); }
);
} else {
var $ta = $('').val(text).css({ position:'fixed', top:'-9999px' }).appendTo('body');
$ta[0].select();
try { document.execCommand('copy'); smToast(i18n.copied, 'success'); }
catch(e) { smToast(i18n.copyFail, 'warning'); }
$ta.remove();
}
});
// ========================================================
// Actions groupées (bulk)
// ========================================================
var bulkI18n = {
noSelection : L.aucuneLigneSelectionnee,
confirmDelete : L.supprimerDefinitivementDSchema,
confirmRegen : L.regenererDSchemaS,
confirmChange : L.appliquerLeTypeS,
confirmValidate : L.validerDSchemaS,
bulkInProgress : L.actionEnCours,
pickType : L.choisissezUnTypeSchema
};
function selectedIds() {
return $('.aiforya-sm-cb:checked').map(function(){ return parseInt($(this).val(), 10); }).get().filter(Boolean);
}
function refreshBulkToolbar() {
var n = selectedIds().length;
var $bar = $('#aiforya-sm-bulk-toolbar');
$bar.find('.aiforya-sm-bulk-count-num').text(n);
if (n > 0) $bar.removeAttr('hidden'); else $bar.attr('hidden', 'hidden');
// Sync header checkbox state
var total = $('.aiforya-sm-cb').length;
$('.aiforya-sm-cb-all').prop('checked', total > 0 && n === total).prop('indeterminate', n > 0 && n < total);
}
$(document).on('change', '.aiforya-sm-cb', refreshBulkToolbar);
$(document).on('change', '.aiforya-sm-cb-all', function(){
var checked = $(this).prop('checked');
$('.aiforya-sm-cb').prop('checked', checked);
refreshBulkToolbar();
});
// ---------- Bulk action dispatch ----------
$(document).on('click', '.aiforya-sm-bulk-btn', function(){
var $btn = $(this);
var bulkAction = $btn.data('bulk-action');
var ids = selectedIds();
if (!ids.length) { smToast(bulkI18n.noSelection, 'warning'); return; }
var newType = '';
var msg = '';
if (bulkAction === 'regenerate') msg = bulkI18n.confirmRegen.replace('%d', ids.length);
if (bulkAction === 'validate') msg = bulkI18n.confirmValidate.replace('%d', ids.length);
if (bulkAction === 'delete') msg = bulkI18n.confirmDelete.replace('%d', ids.length);
if (bulkAction === 'change_type') {
newType = $('.aiforya-sm-bulk-type-select').val();
if (!newType) { smToast(bulkI18n.pickType, 'warning'); return; }
msg = bulkI18n.confirmChange.replace('%s', newType).replace('%d', ids.length);
}
smConfirm(msg, function(){
runBulk(bulkAction, ids, newType, $btn);
});
});
function runBulk(bulkAction, ids, newType, $btn) {
var orig = $btn ? $btn.html() : '';
if ($btn) setBtnLoading($btn, true, orig);
smToast(bulkI18n.bulkInProgress, 'info', 3000);
$.post(ajaxUrl, {
action : 'aiforya_sm_bulk_action',
nonce : nonce,
bulk_action : bulkAction,
post_ids : ids,
new_type : newType || ''
}).done(function(r){
if (r && r.success) {
smToast((r.data && r.data.message) || 'OK', 'success');
// Si deferred, on enchaîne automatiquement le batch suivant.
if (r.data && r.data.next_ids && r.data.next_ids.length) {
setTimeout(function(){ runBulk(bulkAction, r.data.next_ids, newType, $btn); }, 500);
return;
}
setTimeout(function(){ location.reload(); }, 800);
} else {
smToast((r && r.data && r.data.message) || i18n.error, 'error');
}
}).fail(function(){
smToast(i18n.network, 'error');
}).always(function(){
if ($btn) setBtnLoading($btn, false);
});
}
function escapeHtml(s) {
return String(s == null ? '' : s).replace(/[&<>"']/g, function(c){
return ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' })[c];
});
}
});
JS;
wp_add_inline_script( $handle, $js );
?>