Changeset 3477168
- Timestamp:
- 03/07/2026 08:46:04 PM (4 weeks ago)
- Location:
- filikod
- Files:
-
- 48 added
- 6 edited
-
tags/1.0.7 (added)
-
tags/1.0.7/admin (added)
-
tags/1.0.7/admin/index.php (added)
-
tags/1.0.7/admin/views (added)
-
tags/1.0.7/admin/views/alt-audit.php (added)
-
tags/1.0.7/admin/views/dashboard.php (added)
-
tags/1.0.7/admin/views/index.php (added)
-
tags/1.0.7/admin/views/settings.php (added)
-
tags/1.0.7/assets (added)
-
tags/1.0.7/assets/css (added)
-
tags/1.0.7/assets/css/admin.css (added)
-
tags/1.0.7/assets/css/index.php (added)
-
tags/1.0.7/assets/filikod-picto.svg (added)
-
tags/1.0.7/assets/index.php (added)
-
tags/1.0.7/assets/js (added)
-
tags/1.0.7/assets/js/admin.js (added)
-
tags/1.0.7/assets/js/index.php (added)
-
tags/1.0.7/filikod.php (added)
-
tags/1.0.7/includes (added)
-
tags/1.0.7/includes/accessibility (added)
-
tags/1.0.7/includes/accessibility/class-filikod-accessibility.php (added)
-
tags/1.0.7/includes/accessibility/index.php (added)
-
tags/1.0.7/includes/admin (added)
-
tags/1.0.7/includes/admin/class-filikod-admin.php (added)
-
tags/1.0.7/includes/admin/index.php (added)
-
tags/1.0.7/includes/class-filikod-alt-audit.php (added)
-
tags/1.0.7/includes/dashboard (added)
-
tags/1.0.7/includes/dashboard/class-filikod-dashboard.php (added)
-
tags/1.0.7/includes/dashboard/index.php (added)
-
tags/1.0.7/includes/file-types (added)
-
tags/1.0.7/includes/file-types/class-filikod-file-types.php (added)
-
tags/1.0.7/includes/file-types/index.php (added)
-
tags/1.0.7/includes/index.php (added)
-
tags/1.0.7/includes/optimizations (added)
-
tags/1.0.7/includes/optimizations/class-filikod-image-resizer.php (added)
-
tags/1.0.7/includes/optimizations/index.php (added)
-
tags/1.0.7/includes/security (added)
-
tags/1.0.7/includes/security/class-filikod-svg-security.php (added)
-
tags/1.0.7/includes/security/index.php (added)
-
tags/1.0.7/includes/settings (added)
-
tags/1.0.7/includes/settings/class-filikod-settings.php (added)
-
tags/1.0.7/includes/settings/index.php (added)
-
tags/1.0.7/index.php (added)
-
tags/1.0.7/languages (added)
-
tags/1.0.7/languages/filikod-fr_FR.po (added)
-
tags/1.0.7/languages/filikod.pot (added)
-
tags/1.0.7/readme.txt (added)
-
tags/1.0.7/uninstall.php (added)
-
trunk/assets/js/admin.js (modified) (1 diff)
-
trunk/filikod.php (modified) (2 diffs)
-
trunk/includes/accessibility/class-filikod-accessibility.php (modified) (2 diffs)
-
trunk/includes/class-filikod-alt-audit.php (modified) (1 diff)
-
trunk/includes/dashboard/class-filikod-dashboard.php (modified) (1 diff)
-
trunk/readme.txt (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
-
filikod/trunk/assets/js/admin.js
r3462774 r3477168 1 1 /** 2 3 2 * JavaScript pour l'administration Filikod 4 5 3 * Gère le système d'onglets sans changement de page 6 7 4 */ 8 5 6 (function($) { 7 'use strict'; 8 9 $(document).ready(function() { 10 // Initialiser le système d'onglets 11 initTabs(); 12 13 // Initialiser les toggle switches 14 initToggleSwitches(); 15 16 // Initialiser le traitement des images existantes 17 initProcessExistingImages(); 18 19 // Initialiser le redimensionnement des images existantes 20 initProcessExistingImagesResize(); 21 22 // Gérer l'activation/désactivation du champ de taille max 23 initMaxWidthToggle(); 24 25 // ALT Audit : sauvegarde AJAX et mise à jour du tableau 26 initAltAuditSave(); 27 }); 28 29 /** 30 * Initialiser le système d'onglets 31 */ 32 function initTabs() { 33 // Gérer le clic sur les onglets Settings uniquement (data-tab = changement sans rechargement) 34 // Les .filikod-tab sans data-tab (ex. ALT Audit) restent des liens normaux 35 $('.filikod-tab[data-tab]').on('click', function(e) { 36 e.preventDefault(); 37 38 var tabKey = $(this).data('tab'); 39 var tabId = '#tab-' + tabKey; 40 41 // Retirer la classe active de tous les onglets dans ce wrapper 42 $(this).closest('.filikod-tabs-nav').find('.filikod-tab').removeClass('active'); 43 $(this).closest('.filikod-tabs-wrapper').find('.filikod-tab-content').removeClass('active'); 44 45 // Ajouter la classe active à l'onglet cliqué 46 $(this).addClass('active'); 47 $(tabId).addClass('active'); 48 49 // Mettre à jour l'URL sans recharger la page 50 var url = new URL(window.location.href); 51 url.searchParams.set('tab', tabKey); 52 window.history.pushState({}, '', url); 53 }); 54 55 // Restaurer l'onglet actif depuis l'URL au chargement (Settings uniquement) 56 var urlParams = new URLSearchParams(window.location.search); 57 var activeTab = urlParams.get('tab') || 'optimizations'; // Par défaut, onglet optimizations 58 59 var tabId = '#tab-' + activeTab; 60 var tabLink = $('.filikod-tab[data-tab="' + activeTab + '"]'); 61 62 if (tabLink.length && $(tabId).length) { 63 tabLink.closest('.filikod-tabs-nav').find('.filikod-tab').removeClass('active'); 64 tabLink.closest('.filikod-tabs-wrapper').find('.filikod-tab-content').removeClass('active'); 65 66 tabLink.addClass('active'); 67 $(tabId).addClass('active'); 68 } 69 } 70 71 /** 72 * Initialiser les toggle switches 73 * 74 * Cette fonction gère le clic sur les toggle switches 75 * pour cocher/décocher la checkbox associée 76 */ 77 function initToggleSwitches() { 78 // Fonction pour mettre à jour l'état du toggle 79 function updateToggleState(checkbox, label) { 80 var isChecked = checkbox.prop('checked'); 81 // Mettre à jour l'attribut aria-checked pour l'accessibilité 82 label.attr('aria-checked', isChecked ? 'true' : 'false'); 83 } 84 85 // Initialiser l'état de tous les toggles au chargement 86 $('.filikod-toggle-switch').each(function() { 87 var checkbox = $(this); 88 var label = checkbox.next('.filikod-toggle-label'); 89 updateToggleState(checkbox, label); 90 }); 91 92 // Gérer le clic sur le label du toggle 93 $('.filikod-toggle-label').on('click', function(e) { 94 // Empêcher le comportement par défaut du label 95 e.preventDefault(); 96 e.stopPropagation(); 97 98 // Trouver la checkbox associée 99 var checkbox = $(this).prev('.filikod-toggle-switch'); 100 var label = $(this); 101 102 // Inverser l'état de la checkbox 103 checkbox.prop('checked', !checkbox.prop('checked')); 104 105 // Mettre à jour l'état visuel et l'accessibilité 106 updateToggleState(checkbox, label); 107 108 // Déclencher l'événement change pour que les autres scripts puissent réagir 109 checkbox.trigger('change'); 110 }); 111 112 // Gérer le clic sur la carte (sauf le toggle lui-même) 113 $('.filikod-file-type-card').on('click', function(e) { 114 // Si on clique directement sur le toggle, ne pas faire double action 115 if ($(e.target).closest('.filikod-file-type-toggle').length) { 116 return; 117 } 118 119 // Si on clique sur le contenu de la carte, activer/désactiver le toggle 120 if ($(e.target).closest('.filikod-file-type-content').length) { 121 e.preventDefault(); 122 e.stopPropagation(); 123 var checkbox = $(this).find('.filikod-toggle-switch'); 124 var label = checkbox.next('.filikod-toggle-label'); 125 checkbox.prop('checked', !checkbox.prop('checked')); 126 updateToggleState(checkbox, label); 127 checkbox.trigger('change'); 128 } 129 }); 130 131 // Gérer les changements de la checkbox (au cas où elle serait modifiée autrement) 132 $('.filikod-toggle-switch').on('change', function() { 133 var checkbox = $(this); 134 var label = checkbox.next('.filikod-toggle-label'); 135 updateToggleState(checkbox, label); 136 }); 137 } 138 139 /** 140 * Initialiser le traitement des images existantes 141 */ 142 function initProcessExistingImages() { 143 $('#filikod-process-existing-images').on('click', function(e) { 144 e.preventDefault(); 145 146 var button = $(this); 147 var statusMessage = $('#filikod-processing-status'); 148 var progressContainer = $('#filikod-accessibility-progress-container'); 149 var progressBar = $('#filikod-accessibility-progress-bar'); 150 var progressText = $('#filikod-accessibility-progress-text'); 151 var warningMessage = $('#filikod-accessibility-warning-message'); 152 153 // Afficher le conteneur de progression et le message d'avertissement 154 progressContainer.show(); 155 warningMessage.show(); 156 157 // Désactiver le bouton et afficher le statut 158 button.prop('disabled', true); 159 statusMessage.removeClass('success error').addClass('processing'); 160 statusMessage.text(filikodAdmin.strings.processing || 'Processing...'); 161 162 // Variables pour suivre la progression 163 var totalImages = 0; 164 var currentProcessed = 0; 165 var currentSkipped = 0; 166 var offset = 0; 167 var batchSize = 50; 168 var isProcessing = true; 169 170 // Empêcher la navigation si l'utilisateur essaie de quitter la page 171 var beforeUnloadHandler = function(e) { 172 if (isProcessing) { 173 e.preventDefault(); 174 e.returnValue = ''; 175 return ''; 176 } 177 }; 178 window.addEventListener('beforeunload', beforeUnloadHandler); 179 180 // Fonction pour traiter un batch 181 function processBatch() { 182 if (!isProcessing) { 183 return; 184 } 185 186 $.ajax({ 187 url: filikodAdmin.ajaxUrl, 188 type: 'POST', 189 data: { 190 action: 'filikod_process_existing_images_accessibility_batch', 191 nonce: filikodAdmin.nonce, 192 offset: offset, 193 batch_size: batchSize, 194 total_processed: currentProcessed, 195 total_skipped: currentSkipped 196 }, 197 success: function(response) { 198 if (response.success) { 199 var data = response.data; 200 201 // Mettre à jour les compteurs 202 currentProcessed = data.total_processed; 203 currentSkipped = data.total_skipped; 204 offset = data.next_offset; 205 206 // Calculer le pourcentage de progression 207 var totalProcessed = currentProcessed + currentSkipped; 208 var percentage = totalImages > 0 ? Math.min(100, Math.round((totalProcessed / totalImages) * 100)) : 0; 209 210 // Mettre à jour la barre de progression 211 progressBar.css('width', percentage + '%'); 212 progressBar.attr('aria-valuenow', percentage); 213 214 // Mettre à jour le texte de progression 215 var processedText = currentProcessed + ' ' + (currentProcessed === 1 ? 216 filikodAdmin.strings.imageProcessed || 'image processed' : 217 filikodAdmin.strings.imagesProcessed || 'images processed'); 218 var skippedText = currentSkipped > 0 ? ', ' + currentSkipped + ' ' + (currentSkipped === 1 ? 219 filikodAdmin.strings.imageSkipped || 'skipped' : 220 filikodAdmin.strings.imagesSkipped || 'skipped') : ''; 221 progressText.text(processedText + skippedText + ' (' + percentage + '%)'); 222 223 // Si le traitement est terminé 224 if (data.finished) { 225 isProcessing = false; 226 window.removeEventListener('beforeunload', beforeUnloadHandler); 227 228 statusMessage.removeClass('processing').addClass('success'); 229 var finalMessage = sprintf( 230 filikodAdmin.strings.accessibilityComplete || 'Processing complete: %d images processed, %d skipped.', 231 currentProcessed, 232 currentSkipped 233 ); 234 statusMessage.text(finalMessage); 235 showNotice(finalMessage, 'success'); 236 237 // Masquer le conteneur de progression et le message d'avertissement 238 progressContainer.hide(); 239 warningMessage.hide(); 240 241 button.prop('disabled', false); 242 } else { 243 // Continuer avec le batch suivant 244 setTimeout(processBatch, 100); 245 } 246 } else { 247 isProcessing = false; 248 window.removeEventListener('beforeunload', beforeUnloadHandler); 249 250 statusMessage.removeClass('processing').addClass('error'); 251 statusMessage.text(response.data.message || filikodAdmin.strings.errorOccurred || 'An error occurred'); 252 showNotice(response.data.message || filikodAdmin.strings.errorOccurred || 'An error occurred', 'error'); 253 254 progressContainer.hide(); 255 warningMessage.hide(); 256 button.prop('disabled', false); 257 } 258 }, 259 error: function() { 260 isProcessing = false; 261 window.removeEventListener('beforeunload', beforeUnloadHandler); 262 263 statusMessage.removeClass('processing').addClass('error'); 264 statusMessage.text(filikodAdmin.strings.errorProcessing || 'Error processing images'); 265 showNotice(filikodAdmin.strings.errorProcessing || 'Error processing images', 'error'); 266 267 progressContainer.hide(); 268 warningMessage.hide(); 269 button.prop('disabled', false); 270 } 271 }); 272 } 273 274 // Obtenir d'abord le nombre total d'images 275 $.ajax({ 276 url: filikodAdmin.ajaxUrl, 277 type: 'POST', 278 data: { 279 action: 'filikod_get_total_images_count_accessibility', 280 nonce: filikodAdmin.nonce 281 }, 282 success: function(response) { 283 if (response.success) { 284 totalImages = response.data.total; 285 286 // Initialiser la barre de progression 287 progressBar.css('width', '0%'); 288 progressBar.attr('aria-valuenow', 0); 289 progressBar.attr('aria-valuemin', 0); 290 progressBar.attr('aria-valuemax', totalImages); 291 progressText.text(filikodAdmin.strings.startingProcessing || 'Starting processing...'); 292 293 // Commencer le traitement 294 processBatch(); 295 } else { 296 isProcessing = false; 297 window.removeEventListener('beforeunload', beforeUnloadHandler); 298 299 statusMessage.removeClass('processing').addClass('error'); 300 statusMessage.text(filikodAdmin.strings.errorGettingCount || 'Unable to retrieve image count'); 301 showNotice(filikodAdmin.strings.errorGettingCount || 'Unable to retrieve image count', 'error'); 302 303 progressContainer.hide(); 304 warningMessage.hide(); 305 button.prop('disabled', false); 306 } 307 }, 308 error: function() { 309 isProcessing = false; 310 window.removeEventListener('beforeunload', beforeUnloadHandler); 311 312 statusMessage.removeClass('processing').addClass('error'); 313 statusMessage.text(filikodAdmin.strings.errorGettingCountDesc || 'Error retrieving image count'); 314 showNotice(filikodAdmin.strings.errorGettingCountDesc || 'Error retrieving image count', 'error'); 315 316 progressContainer.hide(); 317 warningMessage.hide(); 318 button.prop('disabled', false); 319 } 320 }); 321 }); 322 } 323 324 /** 325 * Initialiser le traitement des images existantes pour redimensionnement 326 */ 327 function initProcessExistingImagesResize() { 328 $('#filikod-process-existing-images-resize').on('click', function(e) { 329 e.preventDefault(); 330 331 var button = $(this); 332 var statusMessage = $('#filikod-resize-processing-status'); 333 var progressContainer = $('#filikod-resize-progress-container'); 334 var progressBar = $('#filikod-resize-progress-bar'); 335 var progressText = $('#filikod-resize-progress-text'); 336 var warningMessage = $('#filikod-resize-warning-message'); 337 338 // Afficher le conteneur de progression et le message d'avertissement 339 progressContainer.show(); 340 warningMessage.show(); 341 342 // Désactiver le bouton et afficher le statut 343 button.prop('disabled', true); 344 statusMessage.removeClass('success error').addClass('processing'); 345 statusMessage.text(filikodAdmin.strings.processing || 'Processing...'); 346 347 // Variables pour suivre la progression 348 var totalImages = 0; 349 var currentProcessed = 0; 350 var currentSkipped = 0; 351 var totalSaved = 0; 352 var offset = 0; 353 var batchSize = 10; 354 var isProcessing = true; 355 356 // Empêcher la navigation si l'utilisateur essaie de quitter la page 357 var beforeUnloadHandler = function(e) { 358 if (isProcessing) { 359 e.preventDefault(); 360 e.returnValue = ''; 361 return ''; 362 } 363 }; 364 window.addEventListener('beforeunload', beforeUnloadHandler); 365 366 // Fonction pour traiter un batch 367 function processBatch() { 368 if (!isProcessing) { 369 return; 370 } 371 372 $.ajax({ 373 url: filikodAdmin.ajaxUrl, 374 type: 'POST', 375 data: { 376 action: 'filikod_process_existing_images_resize_batch', 377 nonce: filikodAdmin.nonce, 378 offset: offset, 379 batch_size: batchSize, 380 total_processed: currentProcessed, 381 total_skipped: currentSkipped, 382 total_saved: totalSaved 383 }, 384 success: function(response) { 385 if (response.success) { 386 var data = response.data; 387 388 // Mettre à jour les compteurs 389 currentProcessed = data.total_processed; 390 currentSkipped = data.total_skipped; 391 totalSaved = data.total_saved; 392 offset = data.next_offset; 393 394 // Calculer le pourcentage de progression 395 // Utiliser le nombre total d'images traitées (processed + skipped) pour une meilleure précision 396 var totalProcessed = currentProcessed + currentSkipped; 397 var percentage = totalImages > 0 ? Math.min(100, Math.round((totalProcessed / totalImages) * 100)) : 0; 398 399 // Mettre à jour la barre de progression 400 progressBar.css('width', percentage + '%'); 401 progressBar.attr('aria-valuenow', percentage); 402 403 // Mettre à jour le texte de progression 404 var imagesResizedText = currentProcessed === 1 ? 405 filikodAdmin.strings.imageResized : 406 filikodAdmin.strings.imagesResized; 407 var processedText = currentProcessed + ' ' + imagesResizedText; 408 var skippedText = currentSkipped > 0 ? ', ' + currentSkipped + ' ' + filikodAdmin.strings.imagesIgnored : ''; 409 var savedText = data.total_saved_formatted ? ' - ' + data.total_saved_formatted + ' ' + filikodAdmin.strings.spaceSaved : ''; 410 progressText.text(processedText + skippedText + savedText + ' (' + percentage + '%)'); 411 412 // Si le traitement est terminé 413 if (data.finished) { 414 isProcessing = false; 415 window.removeEventListener('beforeunload', beforeUnloadHandler); 416 417 statusMessage.removeClass('processing').addClass('success'); 418 var finalMessage = sprintf( 419 filikodAdmin.strings.resizeComplete || 'Resize complete: %d images resized, %d ignored. Space saved: %s', 420 currentProcessed, 421 currentSkipped, 422 data.total_saved_formatted 423 ); 424 statusMessage.text(finalMessage); 425 showNotice(finalMessage, 'success'); 426 427 // Masquer le conteneur de progression et le message d'avertissement 428 progressContainer.hide(); 429 warningMessage.hide(); 430 431 button.prop('disabled', false); 432 } else { 433 // Continuer avec le batch suivant 434 setTimeout(processBatch, 100); 435 } 436 } else { 437 isProcessing = false; 438 window.removeEventListener('beforeunload', beforeUnloadHandler); 439 440 statusMessage.removeClass('processing').addClass('error'); 441 statusMessage.text(response.data.message || filikodAdmin.strings.errorOccurred || 'An error occurred'); 442 showNotice(response.data.message || filikodAdmin.strings.errorOccurred || 'An error occurred', 'error'); 443 444 progressContainer.hide(); 445 warningMessage.hide(); 446 button.prop('disabled', false); 447 } 448 }, 449 error: function() { 450 isProcessing = false; 451 window.removeEventListener('beforeunload', beforeUnloadHandler); 452 453 statusMessage.removeClass('processing').addClass('error'); 454 statusMessage.text(filikodAdmin.strings.errorProcessing || 'Error processing images'); 455 showNotice(filikodAdmin.strings.errorProcessing || 'Error processing images', 'error'); 456 457 progressContainer.hide(); 458 warningMessage.hide(); 459 button.prop('disabled', false); 460 } 461 }); 462 } 463 464 // Obtenir d'abord le nombre total d'images 465 $.ajax({ 466 url: filikodAdmin.ajaxUrl, 467 type: 'POST', 468 data: { 469 action: 'filikod_get_total_images_count', 470 nonce: filikodAdmin.nonce 471 }, 472 success: function(response) { 473 if (response.success) { 474 totalImages = response.data.total; 475 476 // Initialiser la barre de progression 477 progressBar.css('width', '0%'); 478 progressBar.attr('aria-valuenow', 0); 479 progressBar.attr('aria-valuemin', 0); 480 progressBar.attr('aria-valuemax', totalImages); 481 progressText.text(filikodAdmin.strings.startingProcessing || 'Starting processing...'); 482 483 // Commencer le traitement 484 processBatch(); 485 } else { 486 isProcessing = false; 487 window.removeEventListener('beforeunload', beforeUnloadHandler); 488 489 statusMessage.removeClass('processing').addClass('error'); 490 statusMessage.text(filikodAdmin.strings.errorGettingCount || 'Unable to retrieve image count'); 491 showNotice(filikodAdmin.strings.errorGettingCount || 'Unable to retrieve image count', 'error'); 492 493 progressContainer.hide(); 494 warningMessage.hide(); 495 button.prop('disabled', false); 496 } 497 }, 498 error: function() { 499 isProcessing = false; 500 window.removeEventListener('beforeunload', beforeUnloadHandler); 501 502 statusMessage.removeClass('processing').addClass('error'); 503 statusMessage.text(filikodAdmin.strings.errorGettingCountDesc || 'Error retrieving image count'); 504 showNotice(filikodAdmin.strings.errorGettingCountDesc || 'Error retrieving image count', 'error'); 505 506 progressContainer.hide(); 507 warningMessage.hide(); 508 button.prop('disabled', false); 509 } 510 }); 511 }); 512 } 513 514 /** 515 * Fonction sprintf simplifiée pour formater les chaînes 516 * Supporte les placeholders WordPress ordonnés (%1$d, %2$s, etc.) 517 */ 518 function sprintf(format) { 519 var args = Array.prototype.slice.call(arguments, 1); 520 521 // D'abord, remplacer les placeholders ordonnés WordPress (%1$d, %2$s, etc.) 522 format = format.replace(/%(\d+)\$([sdj%])/g, function(match, index, type) { 523 if (type === '%') return '%'; 524 var argIndex = parseInt(index, 10) - 1; // Les indices WordPress commencent à 1 525 if (argIndex >= 0 && argIndex < args.length) { 526 return args[argIndex]; 527 } 528 return match; 529 }); 530 531 // Ensuite, remplacer les placeholders simples (%d, %s, etc.) pour compatibilité 532 return format.replace(/%[sdj%]/g, function(match) { 533 if (match === '%%') return '%'; 534 var arg = args.shift(); 535 if (arg === undefined) return match; 536 return arg; 537 }); 538 } 539 540 /** 541 * Gérer l'activation/désactivation du champ de taille maximum 542 */ 543 function initMaxWidthToggle() { 544 var checkbox = $('#filikod_auto_resize_enabled'); 545 var maxWidthCard = $('#filikod-max-width-card'); 546 var maxWidthInput = $('#filikod_max_image_width'); 547 548 // Fonction pour mettre à jour l'état du champ 549 function updateMaxWidthField() { 550 if (checkbox.is(':checked')) { 551 // Activer le champ 552 maxWidthCard.css({ 553 'opacity': '1', 554 'pointer-events': 'auto' 555 }); 556 maxWidthInput.prop('disabled', false); 557 maxWidthInput.removeAttr('readonly'); 558 } else { 559 // Désactiver le champ 560 maxWidthCard.css({ 561 'opacity': '0.6', 562 'pointer-events': 'auto' // Garder pointer-events pour que le CSS fonctionne 563 }); 564 maxWidthInput.prop('disabled', true); 565 } 566 } 567 568 // Initialiser l'état au chargement 569 updateMaxWidthField(); 570 571 // Mettre à jour lors du changement de la checkbox 572 checkbox.on('change', function() { 573 updateMaxWidthField(); 574 }); 575 576 // Mettre à jour aussi lors du clic sur le toggle label 577 $('.filikod-toggle-label[for="filikod_auto_resize_enabled"]').on('click', function() { 578 // Petit délai pour laisser le temps à la checkbox de changer 579 setTimeout(function() { 580 updateMaxWidthField(); 581 }, 10); 582 }); 583 } 584 585 /** 586 * ALT Audit : sauvegarde AJAX, mise à jour de la ligne (ou suppression) et des compteurs des onglets 587 */ 588 function initAltAuditSave() { 589 var $page = $('.filikod-alt-audit-page'); 590 if (!$page.length || !window.filikodAltAudit) { 591 return; 592 } 593 var altAudit = window.filikodAltAudit; 594 var saveNonce = altAudit.saveAltNonce || (typeof filikodAdmin !== 'undefined' && filikodAdmin.saveAltNonce); 595 var ajaxUrl = altAudit.ajaxUrl || (typeof filikodAdmin !== 'undefined' && filikodAdmin.ajaxUrl) || ''; 596 if (!saveNonce || !ajaxUrl) { 597 return; 598 } 599 function doSaveAlt(e) { 600 e.preventDefault(); 601 e.stopPropagation(); 602 var $btn = $(this); 603 var formId = $btn.attr('form'); 604 var $form = formId ? $('#' + formId) : $btn.closest('form'); 605 if (!$form.length || !$form.hasClass('filikod-alt-row-form')) { 606 return; 607 } 608 var $row = $form.closest('tr'); 609 var attachmentId = $form.find('input[name="attachment_id"]').val(); 610 var altValue = $form.find('input[name="filikod_alt_value"]').val(); 611 $btn.prop('disabled', true); 612 $.ajax({ 613 url: ajaxUrl, 614 type: 'POST', 615 data: { 616 action: 'filikod_save_alt', 617 nonce: saveNonce, 618 attachment_id: attachmentId, 619 filikod_alt_value: altValue 620 }, 621 success: function(response) { 622 if (response.success && response.data) { 623 var data = response.data; 624 var currentStatus = window.filikodAltAudit.currentStatus; 625 var currentInternal = window.filikodAltAudit.urlToInternal[currentStatus]; 9 626 10 11 (function($) { 12 13 'use strict'; 14 15 16 17 $(document).ready(function() { 18 19 // Initialiser le système d'onglets 20 21 initTabs(); 22 23 24 25 // Initialiser les toggle switches 26 27 initToggleSwitches(); 28 29 30 31 // Initialiser le traitement des images existantes 32 33 initProcessExistingImages(); 34 35 36 37 // Initialiser le redimensionnement des images existantes 38 39 initProcessExistingImagesResize(); 40 41 42 43 // Gérer l'activation/désactivation du champ de taille max 44 45 initMaxWidthToggle(); 46 47 48 49 // ALT Audit : sauvegarde AJAX et mise à jour du tableau 50 51 initAltAuditSave(); 52 53 }); 54 55 56 57 /** 58 59 * Initialiser le système d'onglets 60 61 */ 62 63 function initTabs() { 64 65 // Gérer le clic sur les onglets Settings uniquement (data-tab = changement sans rechargement) 66 67 // Les .filikod-tab sans data-tab (ex. ALT Audit) restent des liens normaux 68 69 $('.filikod-tab[data-tab]').on('click', function(e) { 70 71 e.preventDefault(); 72 73 74 75 var tabKey = $(this).data('tab'); 76 77 var tabId = '#tab-' + tabKey; 78 79 80 81 // Retirer la classe active de tous les onglets dans ce wrapper 82 83 $(this).closest('.filikod-tabs-nav').find('.filikod-tab').removeClass('active'); 84 85 $(this).closest('.filikod-tabs-wrapper').find('.filikod-tab-content').removeClass('active'); 86 87 88 89 // Ajouter la classe active à l'onglet cliqué 90 91 $(this).addClass('active'); 92 93 $(tabId).addClass('active'); 94 95 96 97 // Mettre à jour l'URL sans recharger la page 98 99 var url = new URL(window.location.href); 100 101 url.searchParams.set('tab', tabKey); 102 103 window.history.pushState({}, '', url); 104 105 }); 106 107 108 109 // Restaurer l'onglet actif depuis l'URL au chargement (Settings uniquement) 110 111 var urlParams = new URLSearchParams(window.location.search); 112 113 var activeTab = urlParams.get('tab') || 'optimizations'; // Par défaut, onglet optimizations 114 115 116 117 var tabId = '#tab-' + activeTab; 118 119 var tabLink = $('.filikod-tab[data-tab="' + activeTab + '"]'); 120 121 122 123 if (tabLink.length && $(tabId).length) { 124 125 tabLink.closest('.filikod-tabs-nav').find('.filikod-tab').removeClass('active'); 126 127 tabLink.closest('.filikod-tabs-wrapper').find('.filikod-tab-content').removeClass('active'); 128 129 130 131 tabLink.addClass('active'); 132 133 $(tabId).addClass('active'); 134 135 } 136 137 } 138 139 140 141 /** 142 143 * Initialiser les toggle switches 144 145 * 146 147 * Cette fonction gère le clic sur les toggle switches 148 149 * pour cocher/décocher la checkbox associée 150 151 */ 152 153 function initToggleSwitches() { 154 155 // Fonction pour mettre à jour l'état du toggle 156 157 function updateToggleState(checkbox, label) { 158 159 var isChecked = checkbox.prop('checked'); 160 161 // Mettre à jour l'attribut aria-checked pour l'accessibilité 162 163 label.attr('aria-checked', isChecked ? 'true' : 'false'); 164 165 } 166 167 168 169 // Initialiser l'état de tous les toggles au chargement 170 171 $('.filikod-toggle-switch').each(function() { 172 173 var checkbox = $(this); 174 175 var label = checkbox.next('.filikod-toggle-label'); 176 177 updateToggleState(checkbox, label); 178 179 }); 180 181 182 183 // Gérer le clic sur le label du toggle 184 185 $('.filikod-toggle-label').on('click', function(e) { 186 187 // Empêcher le comportement par défaut du label 188 189 e.preventDefault(); 190 191 e.stopPropagation(); 192 193 194 195 // Trouver la checkbox associée 196 197 var checkbox = $(this).prev('.filikod-toggle-switch'); 198 199 var label = $(this); 200 201 202 203 // Inverser l'état de la checkbox 204 205 checkbox.prop('checked', !checkbox.prop('checked')); 206 207 208 209 // Mettre à jour l'état visuel et l'accessibilité 210 211 updateToggleState(checkbox, label); 212 213 214 215 // Déclencher l'événement change pour que les autres scripts puissent réagir 216 217 checkbox.trigger('change'); 218 219 }); 220 221 222 223 // Gérer le clic sur la carte (sauf le toggle lui-même) 224 225 $('.filikod-file-type-card').on('click', function(e) { 226 227 // Si on clique directement sur le toggle, ne pas faire double action 228 229 if ($(e.target).closest('.filikod-file-type-toggle').length) { 230 231 return; 232 233 } 234 235 236 237 // Si on clique sur le contenu de la carte, activer/désactiver le toggle 238 239 if ($(e.target).closest('.filikod-file-type-content').length) { 240 241 e.preventDefault(); 242 243 e.stopPropagation(); 244 245 var checkbox = $(this).find('.filikod-toggle-switch'); 246 247 var label = checkbox.next('.filikod-toggle-label'); 248 249 checkbox.prop('checked', !checkbox.prop('checked')); 250 251 updateToggleState(checkbox, label); 252 253 checkbox.trigger('change'); 254 255 } 256 257 }); 258 259 260 261 // Gérer les changements de la checkbox (au cas où elle serait modifiée autrement) 262 263 $('.filikod-toggle-switch').on('change', function() { 264 265 var checkbox = $(this); 266 267 var label = checkbox.next('.filikod-toggle-label'); 268 269 updateToggleState(checkbox, label); 270 271 }); 272 273 } 274 275 276 277 /** 278 279 * Initialiser le traitement des images existantes 280 281 */ 282 283 function initProcessExistingImages() { 284 285 $('#filikod-process-existing-images').on('click', function(e) { 286 287 e.preventDefault(); 288 289 290 291 var button = $(this); 292 293 var statusMessage = $('#filikod-processing-status'); 294 295 var progressContainer = $('#filikod-accessibility-progress-container'); 296 297 var progressBar = $('#filikod-accessibility-progress-bar'); 298 299 var progressText = $('#filikod-accessibility-progress-text'); 300 301 var warningMessage = $('#filikod-accessibility-warning-message'); 302 303 304 305 // Afficher le conteneur de progression et le message d'avertissement 306 307 progressContainer.show(); 308 309 warningMessage.show(); 310 311 312 313 // Désactiver le bouton et afficher le statut 314 315 button.prop('disabled', true); 316 317 statusMessage.removeClass('success error').addClass('processing'); 318 319 statusMessage.text(filikodAdmin.strings.processing || 'Processing...'); 320 321 322 323 // Variables pour suivre la progression 324 325 var totalImages = 0; 326 327 var currentProcessed = 0; 328 329 var currentSkipped = 0; 330 331 var offset = 0; 332 333 var batchSize = 50; 334 335 var isProcessing = true; 336 337 338 339 // Empêcher la navigation si l'utilisateur essaie de quitter la page 340 341 var beforeUnloadHandler = function(e) { 342 343 if (isProcessing) { 344 345 e.preventDefault(); 346 347 e.returnValue = ''; 348 349 return ''; 350 351 } 352 353 }; 354 355 window.addEventListener('beforeunload', beforeUnloadHandler); 356 357 358 359 // Fonction pour traiter un batch 360 361 function processBatch() { 362 363 if (!isProcessing) { 364 365 return; 366 367 } 368 369 370 371 $.ajax({ 372 373 url: filikodAdmin.ajaxUrl, 374 375 type: 'POST', 376 377 data: { 378 379 action: 'filikod_process_existing_images_accessibility_batch', 380 381 nonce: filikodAdmin.nonce, 382 383 offset: offset, 384 385 batch_size: batchSize, 386 387 total_processed: currentProcessed, 388 389 total_skipped: currentSkipped 390 391 }, 392 393 success: function(response) { 394 395 if (response.success) { 396 397 var data = response.data; 398 399 400 401 // Mettre à jour les compteurs 402 403 currentProcessed = data.total_processed; 404 405 currentSkipped = data.total_skipped; 406 407 offset = data.next_offset; 408 409 410 411 // Calculer le pourcentage de progression 412 413 var totalProcessed = currentProcessed + currentSkipped; 414 415 var percentage = totalImages > 0 ? Math.min(100, Math.round((totalProcessed / totalImages) * 100)) : 0; 416 417 418 419 // Mettre à jour la barre de progression 420 421 progressBar.css('width', percentage + '%'); 422 423 progressBar.attr('aria-valuenow', percentage); 424 425 426 427 // Mettre à jour le texte de progression 428 429 var processedText = currentProcessed + ' ' + (currentProcessed === 1 ? 430 431 filikodAdmin.strings.imageProcessed || 'image processed' : 432 433 filikodAdmin.strings.imagesProcessed || 'images processed'); 434 435 var skippedText = currentSkipped > 0 ? ', ' + currentSkipped + ' ' + (currentSkipped === 1 ? 436 437 filikodAdmin.strings.imageSkipped || 'skipped' : 438 439 filikodAdmin.strings.imagesSkipped || 'skipped') : ''; 440 441 progressText.text(processedText + skippedText + ' (' + percentage + '%)'); 442 443 444 445 // Si le traitement est terminé 446 447 if (data.finished) { 448 449 isProcessing = false; 450 451 window.removeEventListener('beforeunload', beforeUnloadHandler); 452 453 454 455 statusMessage.removeClass('processing').addClass('success'); 456 457 var finalMessage = sprintf( 458 459 filikodAdmin.strings.accessibilityComplete || 'Processing complete: %d images processed, %d skipped.', 460 461 currentProcessed, 462 463 currentSkipped 464 465 ); 466 467 statusMessage.text(finalMessage); 468 469 showNotice(finalMessage, 'success'); 470 471 472 473 // Masquer le conteneur de progression et le message d'avertissement 474 475 progressContainer.hide(); 476 477 warningMessage.hide(); 478 479 480 481 button.prop('disabled', false); 482 483 } else { 484 485 // Continuer avec le batch suivant 486 487 setTimeout(processBatch, 100); 488 489 } 490 491 } else { 492 493 isProcessing = false; 494 495 window.removeEventListener('beforeunload', beforeUnloadHandler); 496 497 498 499 statusMessage.removeClass('processing').addClass('error'); 500 501 statusMessage.text(response.data.message || filikodAdmin.strings.errorOccurred || 'An error occurred'); 502 503 showNotice(response.data.message || filikodAdmin.strings.errorOccurred || 'An error occurred', 'error'); 504 505 506 507 progressContainer.hide(); 508 509 warningMessage.hide(); 510 511 button.prop('disabled', false); 512 627 // Onglet Duplicated : recharger la page pour que la liste se mette à jour 628 // (l’autre doublon disparaît s’il n’y a plus que 2 et qu’on en a modifié un ; idem si 3+ et qu’il ne reste qu’un seul avec cet ALT). 629 if (currentStatus === 'duplicated') { 630 window.location.reload(); 631 return; 513 632 } 514 633 515 }, 634 if (data.new_status !== currentInternal) { 635 $row.fadeOut(300, function() { $(this).remove(); }); 636 } else { 637 $form.find('input[name="filikod_alt_value"]').val(data.new_alt); 638 $row.find('.column-issue').text(window.filikodAltAudit.labels[data.new_status] || data.new_status); 639 } 640 if (data.counts) { 641 $page.find('.filikod-tab').each(function() { 642 var key = $(this).data('count-key'); 643 if (key && data.counts[key] !== undefined) { 644 $(this).find('.filikod-alt-tab-count').text(data.counts[key]); 645 } 646 }); 647 } 648 var msg = (filikodAdmin.strings && filikodAdmin.strings.altSaved) ? filikodAdmin.strings.altSaved : 'Saved.'; 649 var $notice = $('#filikod-alt-audit-notice'); 650 $notice.find('p').text(msg).end().show().delay(4000).fadeOut(200); 651 } else { 652 var errMsg = (response.data && response.data.message) ? response.data.message : ((filikodAdmin.strings && filikodAdmin.strings.altSaveError) ? filikodAdmin.strings.altSaveError : 'Error saving ALT.'); 653 showNotice(errMsg, 'error'); 654 } 655 }, 656 error: function() { 657 var errMsg = (filikodAdmin.strings && filikodAdmin.strings.altSaveError) ? filikodAdmin.strings.altSaveError : 'Error saving ALT.'; 658 showNotice(errMsg, 'error'); 659 }, 660 complete: function() { 661 $btn.prop('disabled', false); 662 } 663 }); 664 } 665 $page.on('click', 'button[name="filikod_save_alt"]', doSaveAlt); 666 $page.on('submit', '.filikod-alt-row-form', function(e) { 667 e.preventDefault(); 668 e.stopPropagation(); 669 }); 670 } 516 671 517 error: function() { 518 519 isProcessing = false; 520 521 window.removeEventListener('beforeunload', beforeUnloadHandler); 522 523 524 525 statusMessage.removeClass('processing').addClass('error'); 526 527 statusMessage.text(filikodAdmin.strings.errorProcessing || 'Error processing images'); 528 529 showNotice(filikodAdmin.strings.errorProcessing || 'Error processing images', 'error'); 530 531 532 533 progressContainer.hide(); 534 535 warningMessage.hide(); 536 537 button.prop('disabled', false); 538 539 } 540 541 }); 542 543 } 544 545 546 547 // Obtenir d'abord le nombre total d'images 548 549 $.ajax({ 550 551 url: filikodAdmin.ajaxUrl, 552 553 type: 'POST', 554 555 data: { 556 557 action: 'filikod_get_total_images_count_accessibility', 558 559 nonce: filikodAdmin.nonce 560 561 }, 562 563 success: function(response) { 564 565 if (response.success) { 566 567 totalImages = response.data.total; 568 569 570 571 // Initialiser la barre de progression 572 573 progressBar.css('width', '0%'); 574 575 progressBar.attr('aria-valuenow', 0); 576 577 progressBar.attr('aria-valuemin', 0); 578 579 progressBar.attr('aria-valuemax', totalImages); 580 581 progressText.text(filikodAdmin.strings.startingProcessing || 'Starting processing...'); 582 583 584 585 // Commencer le traitement 586 587 processBatch(); 588 589 } else { 590 591 isProcessing = false; 592 593 window.removeEventListener('beforeunload', beforeUnloadHandler); 594 595 596 597 statusMessage.removeClass('processing').addClass('error'); 598 599 statusMessage.text(filikodAdmin.strings.errorGettingCount || 'Unable to retrieve image count'); 600 601 showNotice(filikodAdmin.strings.errorGettingCount || 'Unable to retrieve image count', 'error'); 602 603 604 605 progressContainer.hide(); 606 607 warningMessage.hide(); 608 609 button.prop('disabled', false); 610 611 } 612 613 }, 614 615 error: function() { 616 617 isProcessing = false; 618 619 window.removeEventListener('beforeunload', beforeUnloadHandler); 620 621 622 623 statusMessage.removeClass('processing').addClass('error'); 624 625 statusMessage.text(filikodAdmin.strings.errorGettingCountDesc || 'Error retrieving image count'); 626 627 showNotice(filikodAdmin.strings.errorGettingCountDesc || 'Error retrieving image count', 'error'); 628 629 630 631 progressContainer.hide(); 632 633 warningMessage.hide(); 634 635 button.prop('disabled', false); 636 637 } 638 672 /** 673 * Fonction utilitaire pour afficher des notifications 674 */ 675 function showNotice(message, type) { 676 type = type || 'success'; 677 var noticeClass = type === 'success' ? 'notice-success' : 'notice-error'; 678 679 var notice = $('<div class="notice ' + noticeClass + ' is-dismissible"><p>' + message + '</p></div>'); 680 $('.wrap').first().prepend(notice); 681 682 // Auto-dismiss après 5 secondes 683 setTimeout(function() { 684 notice.fadeOut(function() { 685 $(this).remove(); 639 686 }); 640 641 });642 643 }644 645 646 647 /**648 649 * Initialiser le traitement des images existantes pour redimensionnement650 651 */652 653 function initProcessExistingImagesResize() {654 655 $('#filikod-process-existing-images-resize').on('click', function(e) {656 657 e.preventDefault();658 659 660 661 var button = $(this);662 663 var statusMessage = $('#filikod-resize-processing-status');664 665 var progressContainer = $('#filikod-resize-progress-container');666 667 var progressBar = $('#filikod-resize-progress-bar');668 669 var progressText = $('#filikod-resize-progress-text');670 671 var warningMessage = $('#filikod-resize-warning-message');672 673 674 675 // Afficher le conteneur de progression et le message d'avertissement676 677 progressContainer.show();678 679 warningMessage.show();680 681 682 683 // Désactiver le bouton et afficher le statut684 685 button.prop('disabled', true);686 687 statusMessage.removeClass('success error').addClass('processing');688 689 statusMessage.text(filikodAdmin.strings.processing || 'Processing...');690 691 692 693 // Variables pour suivre la progression694 695 var totalImages = 0;696 697 var currentProcessed = 0;698 699 var currentSkipped = 0;700 701 var totalSaved = 0;702 703 var offset = 0;704 705 var batchSize = 10;706 707 var isProcessing = true;708 709 710 711 // Empêcher la navigation si l'utilisateur essaie de quitter la page712 713 var beforeUnloadHandler = function(e) {714 715 if (isProcessing) {716 717 e.preventDefault();718 719 e.returnValue = '';720 721 return '';722 723 }724 725 };726 727 window.addEventListener('beforeunload', beforeUnloadHandler);728 729 730 731 // Fonction pour traiter un batch732 733 function processBatch() {734 735 if (!isProcessing) {736 737 return;738 739 }740 741 742 743 $.ajax({744 745 url: filikodAdmin.ajaxUrl,746 747 type: 'POST',748 749 data: {750 751 action: 'filikod_process_existing_images_resize_batch',752 753 nonce: filikodAdmin.nonce,754 755 offset: offset,756 757 batch_size: batchSize,758 759 total_processed: currentProcessed,760 761 total_skipped: currentSkipped,762 763 total_saved: totalSaved764 765 },766 767 success: function(response) {768 769 if (response.success) {770 771 var data = response.data;772 773 774 775 // Mettre à jour les compteurs776 777 currentProcessed = data.total_processed;778 779 currentSkipped = data.total_skipped;780 781 totalSaved = data.total_saved;782 783 offset = data.next_offset;784 785 786 787 // Calculer le pourcentage de progression788 789 // Utiliser le nombre total d'images traitées (processed + skipped) pour une meilleure précision790 791 var totalProcessed = currentProcessed + currentSkipped;792 793 var percentage = totalImages > 0 ? Math.min(100, Math.round((totalProcessed / totalImages) * 100)) : 0;794 795 796 797 // Mettre à jour la barre de progression798 799 progressBar.css('width', percentage + '%');800 801 progressBar.attr('aria-valuenow', percentage);802 803 804 805 // Mettre à jour le texte de progression806 807 var imagesResizedText = currentProcessed === 1 ?808 809 filikodAdmin.strings.imageResized :810 811 filikodAdmin.strings.imagesResized;812 813 var processedText = currentProcessed + ' ' + imagesResizedText;814 815 var skippedText = currentSkipped > 0 ? ', ' + currentSkipped + ' ' + filikodAdmin.strings.imagesIgnored : '';816 817 var savedText = data.total_saved_formatted ? ' - ' + data.total_saved_formatted + ' ' + filikodAdmin.strings.spaceSaved : '';818 819 progressText.text(processedText + skippedText + savedText + ' (' + percentage + '%)');820 821 822 823 // Si le traitement est terminé824 825 if (data.finished) {826 827 isProcessing = false;828 829 window.removeEventListener('beforeunload', beforeUnloadHandler);830 831 832 833 statusMessage.removeClass('processing').addClass('success');834 835 var finalMessage = sprintf(836 837 filikodAdmin.strings.resizeComplete || 'Resize complete: %d images resized, %d ignored. Space saved: %s',838 839 currentProcessed,840 841 currentSkipped,842 843 data.total_saved_formatted844 845 );846 847 statusMessage.text(finalMessage);848 849 showNotice(finalMessage, 'success');850 851 852 853 // Masquer le conteneur de progression et le message d'avertissement854 855 progressContainer.hide();856 857 warningMessage.hide();858 859 860 861 button.prop('disabled', false);862 863 } else {864 865 // Continuer avec le batch suivant866 867 setTimeout(processBatch, 100);868 869 }870 871 } else {872 873 isProcessing = false;874 875 window.removeEventListener('beforeunload', beforeUnloadHandler);876 877 878 879 statusMessage.removeClass('processing').addClass('error');880 881 statusMessage.text(response.data.message || filikodAdmin.strings.errorOccurred || 'An error occurred');882 883 showNotice(response.data.message || filikodAdmin.strings.errorOccurred || 'An error occurred', 'error');884 885 886 887 progressContainer.hide();888 889 warningMessage.hide();890 891 button.prop('disabled', false);892 893 }894 895 },896 897 error: function() {898 899 isProcessing = false;900 901 window.removeEventListener('beforeunload', beforeUnloadHandler);902 903 904 905 statusMessage.removeClass('processing').addClass('error');906 907 statusMessage.text(filikodAdmin.strings.errorProcessing || 'Error processing images');908 909 showNotice(filikodAdmin.strings.errorProcessing || 'Error processing images', 'error');910 911 912 913 progressContainer.hide();914 915 warningMessage.hide();916 917 button.prop('disabled', false);918 919 }920 921 });922 923 }924 925 926 927 // Obtenir d'abord le nombre total d'images928 929 $.ajax({930 931 url: filikodAdmin.ajaxUrl,932 933 type: 'POST',934 935 data: {936 937 action: 'filikod_get_total_images_count',938 939 nonce: filikodAdmin.nonce940 941 },942 943 success: function(response) {944 945 if (response.success) {946 947 totalImages = response.data.total;948 949 950 951 // Initialiser la barre de progression952 953 progressBar.css('width', '0%');954 955 progressBar.attr('aria-valuenow', 0);956 957 progressBar.attr('aria-valuemin', 0);958 959 progressBar.attr('aria-valuemax', totalImages);960 961 progressText.text(filikodAdmin.strings.startingProcessing || 'Starting processing...');962 963 964 965 // Commencer le traitement966 967 processBatch();968 969 } else {970 971 isProcessing = false;972 973 window.removeEventListener('beforeunload', beforeUnloadHandler);974 975 976 977 statusMessage.removeClass('processing').addClass('error');978 979 statusMessage.text(filikodAdmin.strings.errorGettingCount || 'Unable to retrieve image count');980 981 showNotice(filikodAdmin.strings.errorGettingCount || 'Unable to retrieve image count', 'error');982 983 984 985 progressContainer.hide();986 987 warningMessage.hide();988 989 button.prop('disabled', false);990 991 }992 993 },994 995 error: function() {996 997 isProcessing = false;998 999 window.removeEventListener('beforeunload', beforeUnloadHandler);1000 1001 1002 1003 statusMessage.removeClass('processing').addClass('error');1004 1005 statusMessage.text(filikodAdmin.strings.errorGettingCountDesc || 'Error retrieving image count');1006 1007 showNotice(filikodAdmin.strings.errorGettingCountDesc || 'Error retrieving image count', 'error');1008 1009 1010 1011 progressContainer.hide();1012 1013 warningMessage.hide();1014 1015 button.prop('disabled', false);1016 1017 }1018 1019 });1020 1021 });1022 1023 }1024 1025 1026 1027 /**1028 1029 * Fonction sprintf simplifiée pour formater les chaînes1030 1031 * Supporte les placeholders WordPress ordonnés (%1$d, %2$s, etc.)1032 1033 */1034 1035 function sprintf(format) {1036 1037 var args = Array.prototype.slice.call(arguments, 1);1038 1039 1040 1041 // D'abord, remplacer les placeholders ordonnés WordPress (%1$d, %2$s, etc.)1042 1043 format = format.replace(/%(\d+)\$([sdj%])/g, function(match, index, type) {1044 1045 if (type === '%') return '%';1046 1047 var argIndex = parseInt(index, 10) - 1; // Les indices WordPress commencent à 11048 1049 if (argIndex >= 0 && argIndex < args.length) {1050 1051 return args[argIndex];1052 1053 }1054 1055 return match;1056 1057 });1058 1059 1060 1061 // Ensuite, remplacer les placeholders simples (%d, %s, etc.) pour compatibilité1062 1063 return format.replace(/%[sdj%]/g, function(match) {1064 1065 if (match === '%%') return '%';1066 1067 var arg = args.shift();1068 1069 if (arg === undefined) return match;1070 1071 return arg;1072 1073 });1074 1075 }1076 1077 1078 1079 /**1080 1081 * Gérer l'activation/désactivation du champ de taille maximum1082 1083 */1084 1085 function initMaxWidthToggle() {1086 1087 var checkbox = $('#filikod_auto_resize_enabled');1088 1089 var maxWidthCard = $('#filikod-max-width-card');1090 1091 var maxWidthInput = $('#filikod_max_image_width');1092 1093 1094 1095 // Fonction pour mettre à jour l'état du champ1096 1097 function updateMaxWidthField() {1098 1099 if (checkbox.is(':checked')) {1100 1101 // Activer le champ1102 1103 maxWidthCard.css({1104 1105 'opacity': '1',1106 1107 'pointer-events': 'auto'1108 1109 });1110 1111 maxWidthInput.prop('disabled', false);1112 1113 maxWidthInput.removeAttr('readonly');1114 1115 } else {1116 1117 // Désactiver le champ1118 1119 maxWidthCard.css({1120 1121 'opacity': '0.6',1122 1123 'pointer-events': 'auto' // Garder pointer-events pour que le CSS fonctionne1124 1125 });1126 1127 maxWidthInput.prop('disabled', true);1128 1129 }1130 1131 }1132 1133 1134 1135 // Initialiser l'état au chargement1136 1137 updateMaxWidthField();1138 1139 1140 1141 // Mettre à jour lors du changement de la checkbox1142 1143 checkbox.on('change', function() {1144 1145 updateMaxWidthField();1146 1147 });1148 1149 1150 1151 // Mettre à jour aussi lors du clic sur le toggle label1152 1153 $('.filikod-toggle-label[for="filikod_auto_resize_enabled"]').on('click', function() {1154 1155 // Petit délai pour laisser le temps à la checkbox de changer1156 1157 setTimeout(function() {1158 1159 updateMaxWidthField();1160 1161 }, 10);1162 1163 });1164 1165 }1166 1167 1168 1169 /**1170 1171 * ALT Audit : sauvegarde AJAX, mise à jour de la ligne (ou suppression) et des compteurs des onglets1172 1173 */1174 1175 function initAltAuditSave() {1176 1177 var $page = $('.filikod-alt-audit-page');1178 1179 if (!$page.length || !window.filikodAltAudit) {1180 1181 return;1182 1183 }1184 1185 var altAudit = window.filikodAltAudit;1186 1187 var saveNonce = altAudit.saveAltNonce || (typeof filikodAdmin !== 'undefined' && filikodAdmin.saveAltNonce);1188 1189 var ajaxUrl = altAudit.ajaxUrl || (typeof filikodAdmin !== 'undefined' && filikodAdmin.ajaxUrl) || '';1190 1191 if (!saveNonce || !ajaxUrl) {1192 1193 return;1194 1195 }1196 1197 function doSaveAlt(e) {1198 1199 e.preventDefault();1200 1201 e.stopPropagation();1202 1203 var $btn = $(this);1204 1205 var formId = $btn.attr('form');1206 1207 var $form = formId ? $('#' + formId) : $btn.closest('form');1208 1209 if (!$form.length || !$form.hasClass('filikod-alt-row-form')) {1210 1211 return;1212 1213 }1214 1215 var $row = $form.closest('tr');1216 1217 var attachmentId = $form.find('input[name="attachment_id"]').val();1218 1219 var altValue = $form.find('input[name="filikod_alt_value"]').val();1220 1221 $btn.prop('disabled', true);1222 1223 $.ajax({1224 1225 url: ajaxUrl,1226 1227 type: 'POST',1228 1229 data: {1230 1231 action: 'filikod_save_alt',1232 1233 nonce: saveNonce,1234 1235 attachment_id: attachmentId,1236 1237 filikod_alt_value: altValue1238 1239 },1240 1241 success: function(response) {1242 1243 if (response.success && response.data) {1244 1245 var data = response.data;1246 1247 var currentInternal = window.filikodAltAudit.urlToInternal[window.filikodAltAudit.currentStatus];1248 1249 if (data.new_status !== currentInternal) {1250 1251 $row.fadeOut(300, function() { $(this).remove(); });1252 1253 } else {1254 1255 $form.find('input[name="filikod_alt_value"]').val(data.new_alt);1256 1257 $row.find('.column-issue').text(window.filikodAltAudit.labels[data.new_status] || data.new_status);1258 1259 }1260 1261 if (data.counts) {1262 1263 $page.find('.filikod-tab').each(function() {1264 1265 var key = $(this).data('count-key');1266 1267 if (key && data.counts[key] !== undefined) {1268 1269 $(this).find('.filikod-alt-tab-count').text(data.counts[key]);1270 1271 }1272 1273 });1274 1275 }1276 1277 var msg = (filikodAdmin.strings && filikodAdmin.strings.altSaved) ? filikodAdmin.strings.altSaved : 'Saved.';1278 1279 var $notice = $('#filikod-alt-audit-notice');1280 1281 $notice.find('p').text(msg).end().show().delay(4000).fadeOut(200);1282 1283 } else {1284 1285 var errMsg = (response.data && response.data.message) ? response.data.message : ((filikodAdmin.strings && filikodAdmin.strings.altSaveError) ? filikodAdmin.strings.altSaveError : 'Error saving ALT.');1286 1287 showNotice(errMsg, 'error');1288 1289 }1290 1291 },1292 1293 error: function() {1294 1295 var errMsg = (filikodAdmin.strings && filikodAdmin.strings.altSaveError) ? filikodAdmin.strings.altSaveError : 'Error saving ALT.';1296 1297 showNotice(errMsg, 'error');1298 1299 },1300 1301 complete: function() {1302 1303 $btn.prop('disabled', false);1304 1305 }1306 1307 });1308 1309 }1310 1311 $page.on('click', 'button[name="filikod_save_alt"]', doSaveAlt);1312 1313 $page.on('submit', '.filikod-alt-row-form', function(e) {1314 1315 e.preventDefault();1316 1317 e.stopPropagation();1318 1319 });1320 1321 }1322 1323 1324 1325 /**1326 1327 * Fonction utilitaire pour afficher des notifications1328 1329 */1330 1331 function showNotice(message, type) {1332 1333 type = type || 'success';1334 1335 var noticeClass = type === 'success' ? 'notice-success' : 'notice-error';1336 1337 1338 1339 var notice = $('<div class="notice ' + noticeClass + ' is-dismissible"><p>' + message + '</p></div>');1340 1341 $('.wrap').first().prepend(notice);1342 1343 1344 1345 // Auto-dismiss après 5 secondes1346 1347 setTimeout(function() {1348 1349 notice.fadeOut(function() {1350 1351 $(this).remove();1352 1353 });1354 1355 687 }, 5000); 1356 1357 } 1358 1359 1360 688 } 689 1361 690 })(jQuery); 1362 691 1363 1364 -
filikod/trunk/filikod.php
r3468088 r3477168 9 9 * Description: Scan your media library, audit ALT text quality, and fix missing ALT text in minutes. Bulk manage ALT attributes to improve accessibility and image SEO. 10 10 11 * Version: 1.0. 611 * Version: 1.0.7 12 12 13 13 * Author: Filikod … … 45 45 // Définir les constantes du plugin 46 46 47 define('FILIKOD_VERSION', '1.0. 6');47 define('FILIKOD_VERSION', '1.0.7'); 48 48 49 49 define('FILIKOD_PLUGIN_URL', plugin_dir_url(__FILE__)); -
filikod/trunk/includes/accessibility/class-filikod-accessibility.php
r3462774 r3477168 1 1 <?php 2 2 3 4 5 3 /** 6 4 7 8 9 5 * Classe Accessibility - Gère l'accessibilité des images 10 6 11 12 13 7 * 14 8 15 16 17 9 * Cette classe permet de : 18 10 19 20 21 11 * 1. Générer automatiquement des textes alternatifs (ALT) à partir du nom de fichier 22 12 23 24 25 13 * 2. Supprimer l'attribut title des images 26 14 27 28 29 15 * 3. Nettoyer les caractères spéciaux dans les textes ALT pour améliorer le SEO 30 16 31 32 33 17 * 4. Traiter les images existantes et futures 34 18 35 36 37 19 * 5. Respecter les textes ALT existants (ne pas les modifier sauf pour le nettoyage) 38 20 39 40 41 21 */ 42 22 43 23 44 24 45 46 47 48 49 25 if (!defined('ABSPATH')) { 50 26 51 52 53 27 exit; 54 28 55 56 57 29 } 58 30 59 31 60 32 61 62 63 64 65 33 class Filikod_Accessibility { 66 34 67 68 69 70 71 72 73 /** 74 75 35 36 37 /** 76 38 77 39 * Instance du plugin principal 78 40 79 80 81 */ 82 83 41 */ 84 42 85 43 private $plugin; 86 44 87 88 89 90 91 92 93 /** 94 95 45 46 47 /** 96 48 97 49 * Constructeur - Initialise la classe 98 50 99 100 101 */ 102 103 51 */ 104 52 105 53 public function __construct() { 106 54 107 108 109 55 $this->plugin = filikod(); 110 56 111 112 113 57 $this->init_hooks(); 114 58 115 116 117 } 118 119 120 121 122 123 124 125 /** 126 127 59 } 60 61 62 63 /** 128 64 129 65 * Initialiser les hooks WordPress 130 66 131 132 133 * 134 135 67 * 136 68 137 69 * Les hooks utilisés : 138 70 139 140 141 71 * - 'add_attachment' : Intercepte l'ajout d'une nouvelle image (après insertion dans la base) 142 72 143 144 145 73 * - 'wp_get_attachment_image_attributes' : Filtre les attributs des images dans le contenu 146 74 147 148 149 */ 150 151 75 */ 152 76 153 77 private function init_hooks() { 154 78 155 156 157 79 // Hook pour traiter les nouvelles images après leur insertion 158 80 159 160 161 81 add_action('add_attachment', array($this, 'process_new_attachment'), 10, 1); 162 82 163 164 165 166 167 83 168 84 169 85 // Hook pour filtrer les attributs des images dans le contenu 170 86 171 172 173 87 add_filter('wp_get_attachment_image_attributes', array($this, 'filter_image_attributes'), 10, 3); 174 88 175 176 177 } 178 179 180 181 182 183 184 185 /** 186 187 89 } 90 91 92 93 /** 188 94 189 95 * Traiter une nouvelle image après son insertion 190 96 191 192 193 * 194 195 97 * 196 98 197 99 * Cette fonction est appelée quand une nouvelle image est ajoutée à la bibliothèque média. 198 100 199 200 201 101 * Elle génère le texte ALT si nécessaire et nettoie les caractères spéciaux. 202 102 203 204 205 * 206 207 103 * 208 104 209 105 * @param int $attachment_id L'ID de l'attachment 210 106 211 212 213 */ 214 215 107 */ 216 108 217 109 public function process_new_attachment($attachment_id) { 218 110 219 220 221 111 // Vérifier que c'est une image 222 112 223 224 225 113 if (!wp_attachment_is_image($attachment_id)) { 226 114 227 228 229 115 return; 230 116 231 232 233 } 234 235 236 237 238 239 240 241 // Générer le texte ALT si activé 242 243 244 117 } 118 119 120 121 // 1) Générer le texte ALT si activé (priorité : titre de l’attachment puis nom de fichier) 245 122 if (get_option('filikod_auto_alt', 'no') === 'yes') { 246 247 248 249 123 $this->generate_alt_text($attachment_id); 250 251 252 253 } 254 255 256 257 258 259 260 261 // Nettoyer les caractères spéciaux dans le texte ALT si activé 262 263 264 124 } 125 126 // 2) Nettoyer les caractères spéciaux dans le texte ALT si activé 265 127 if (get_option('filikod_clean_alt_special_chars', 'no') === 'yes') { 266 267 268 269 128 $this->clean_alt_special_chars($attachment_id); 270 271 272 273 } 274 275 276 277 } 278 279 280 281 282 283 284 285 /** 286 287 129 } 130 131 // 3) Supprimer le titre (post_title) si activé, après utilisation pour l’ALT 132 if (get_option('filikod_remove_title', 'no') === 'yes') { 133 $this->clear_attachment_title($attachment_id); 134 } 135 } 136 137 138 139 /** 288 140 289 141 * Filtrer les attributs des images dans le contenu 290 142 291 292 293 * 294 295 143 * 296 144 297 145 * Cette fonction supprime l'attribut title des images 298 146 299 300 301 147 * quand elles sont affichées dans le contenu. 302 148 303 304 305 * 306 307 149 * 308 150 309 151 * @param array $attr Les attributs de l'image 310 152 311 312 313 153 * @param object $attachment L'objet attachment 314 154 315 316 317 155 * @param string|array $size La taille de l'image 318 156 319 320 321 157 * @return array Les attributs modifiés 322 158 323 324 325 */ 326 327 159 */ 328 160 329 161 public function filter_image_attributes($attr, $attachment, $size) { 330 162 331 332 333 163 // Supprimer l'attribut title si l'option est activée 334 164 335 336 337 165 if (get_option('filikod_remove_title', 'no') === 'yes') { 338 166 339 340 341 167 unset($attr['title']); 342 168 343 344 345 } 346 347 348 349 350 351 169 } 170 171 352 172 353 173 return $attr; 354 174 355 356 357 } 358 359 360 361 362 363 364 365 /** 366 367 175 } 176 177 178 179 /** 368 180 369 181 * Générer le texte alternatif à partir du nom de fichier 370 182 371 372 373 * 374 375 183 * 376 184 377 185 * Cette fonction : 378 186 379 380 381 187 * 1. Récupère le nom de fichier de l'image 382 188 383 384 385 189 * 2. Extrait le nom sans extension 386 190 387 388 389 191 * 3. Nettoie et formate le texte (remplace les tirets/underscores par des espaces) 390 192 391 392 393 193 * 4. Met en forme (première lettre en majuscule) 394 194 395 396 397 195 * 5. Sauvegarde uniquement si l'image n'a pas déjà un texte ALT 398 196 399 400 401 * 402 403 197 * 404 198 405 199 * @param int $attachment_id L'ID de l'attachment 406 200 407 408 409 */ 410 411 201 */ 412 202 413 203 private function generate_alt_text($attachment_id) { 414 204 415 416 417 205 // Récupérer le texte ALT actuel 418 206 419 420 421 207 $current_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true); 422 208 423 424 425 426 427 209 428 210 429 211 // Si l'image a déjà un texte ALT, ne pas le modifier 430 431 432 433 212 if (!empty($current_alt)) { 434 435 436 437 213 return; 438 439 440 441 } 442 443 444 445 446 447 448 449 // Récupérer le nom de fichier 450 451 452 214 } 215 216 // Priorité : titre de l’attachment (souvent renseigné à l’upload), sinon nom de fichier 217 $post = get_post($attachment_id); 218 $title = ( $post && ! empty( $post->post_title ) ) ? trim( $post->post_title ) : ''; 453 219 $file_path = get_attached_file($attachment_id); 454 455 456 457 458 459 460 461 if (!$file_path) { 462 463 464 220 $filename_without_ext = ''; 221 if ($file_path) { 222 $filename = basename($file_path); 223 $filename_without_ext = pathinfo($filename, PATHINFO_FILENAME); 224 } 225 if ( $title !== '' ) { 226 $alt_text = $title; 227 } elseif ( $filename_without_ext !== '' ) { 228 $alt_text = $filename_without_ext; 229 } else { 465 230 return; 466 467 468 469 } 470 471 472 473 474 475 476 477 // Extraire le nom de fichier sans extension 478 479 480 481 $filename = basename($file_path); 482 483 484 485 $filename_without_ext = pathinfo($filename, PATHINFO_FILENAME); 486 487 488 489 490 491 492 493 // Utiliser le nom de fichier tel quel comme texte ALT 494 495 496 497 // On garde les tirets et underscores pour que l'utilisateur puisse les nettoyer avec l'option dédiée 498 499 500 501 $alt_text = $filename_without_ext; 502 503 504 505 506 507 231 } 232 233 508 234 509 235 // Si l'option de nettoyage des caractères spéciaux est activée, l'appliquer maintenant 510 236 511 512 513 237 if (get_option('filikod_clean_alt_special_chars', 'no') === 'yes') { 514 238 515 516 517 239 // Remplacer les tirets, underscores, points par des espaces pour la lisibilité 518 240 519 520 521 241 $alt_text = str_replace(array('-', '_', '.'), ' ', $alt_text); 522 242 523 524 525 243 526 244 527 528 529 245 // Supprimer les espaces multiples 530 246 531 532 533 247 $alt_text = preg_replace('/\s+/', ' ', $alt_text); 534 248 535 536 537 } 538 539 540 541 542 543 249 } 250 251 544 252 545 253 // Mettre en forme : première lettre en majuscule, reste en minuscule 546 254 547 548 549 255 $alt_text = ucfirst(strtolower(trim($alt_text))); 550 256 551 552 553 554 555 257 556 258 557 259 // Si le texte est vide après nettoyage, utiliser un texte par défaut 558 260 559 560 561 261 if (empty($alt_text)) { 562 262 563 564 565 263 $alt_text = __('Image', 'filikod'); 566 264 567 568 569 } 570 571 572 573 574 575 265 } 266 267 576 268 577 269 // Sauvegarder le texte ALT 578 270 579 580 581 271 update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt_text); 582 583 272 if (class_exists('Filikod_Alt_Audit')) { 584 585 273 Filikod_Alt_Audit::invalidate_cache(); 586 587 } 588 589 } 590 591 592 593 594 595 596 597 /** 598 599 600 274 } 275 } 276 277 /** 278 * Supprime le titre (post_title) de l’attachment pour que l’attribut HTML title ne soit pas renseigné à l’affichage. 279 * 280 * @param int $attachment_id L’ID de l’attachment 281 */ 282 private function clear_attachment_title($attachment_id) { 283 $post = get_post($attachment_id); 284 if ( ! $post || empty( $post->post_title ) ) { 285 return; 286 } 287 wp_update_post(array( 288 'ID' => $attachment_id, 289 'post_title' => '', 290 )); 291 } 292 293 /** 601 294 * Nettoyer les caractères spéciaux du texte ALT 602 295 603 604 605 * 606 607 296 * 608 297 609 298 * Cette fonction supprime les caractères spéciaux (slash, anti-slash, tiret, etc.) 610 299 611 612 613 300 * du texte ALT pour améliorer le SEO. 614 301 615 616 617 * 618 619 302 * 620 303 621 304 * @param int $attachment_id L'ID de l'attachment 622 305 623 624 625 */ 626 627 306 */ 628 307 629 308 private function clean_alt_special_chars($attachment_id) { 630 309 631 632 633 310 // Récupérer le texte ALT actuel 634 311 635 636 637 312 $current_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true); 638 313 639 640 641 642 643 314 644 315 645 316 // Si l'image n'a pas de texte ALT, ne rien faire 646 317 647 648 649 318 if (empty($current_alt)) { 650 319 651 652 653 320 return; 654 321 655 656 657 } 658 659 660 661 662 663 322 } 323 324 664 325 665 326 // Liste des caractères spéciaux à supprimer 666 327 667 668 669 328 // Slash (/), Anti-slash (\), Tirets (-), Underscores (_), et autres caractères non-alphanumériques 670 329 671 672 673 330 $special_chars = array('/', '\\', '-', '_', '|', '~', '`', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '+', '=', '{', '}', '[', ']', ':', ';', '"', "'", '<', '>', ',', '.', '?'); 674 331 675 676 677 678 679 332 680 333 681 334 // Remplacer les caractères spéciaux par des espaces 682 335 683 684 685 336 $cleaned_alt = str_replace($special_chars, ' ', $current_alt); 686 337 687 688 689 690 691 338 692 339 693 340 // Supprimer les espaces multiples 694 341 695 696 697 342 $cleaned_alt = preg_replace('/\s+/', ' ', $cleaned_alt); 698 343 699 700 701 702 703 344 704 345 705 346 // Supprimer les espaces en début et fin 706 347 707 708 709 348 $cleaned_alt = trim($cleaned_alt); 710 349 711 712 713 714 715 350 716 351 717 352 // Si le texte est vide après nettoyage, utiliser le texte original 718 353 719 720 721 354 if (empty($cleaned_alt)) { 722 355 723 724 725 356 $cleaned_alt = $current_alt; 726 357 727 728 729 } 730 731 732 733 734 735 358 } 359 360 736 361 737 362 // Sauvegarder uniquement si le texte a changé 738 363 739 740 741 364 if ($cleaned_alt !== $current_alt) { 742 365 743 744 745 366 update_post_meta($attachment_id, '_wp_attachment_image_alt', $cleaned_alt); 746 747 367 if (class_exists('Filikod_Alt_Audit')) { 748 749 368 Filikod_Alt_Audit::invalidate_cache(); 750 751 369 } 752 753 } 754 755 } 756 757 758 759 760 761 762 763 /** 764 765 370 } 371 } 372 373 374 375 /** 766 376 767 377 * Obtenir le nombre total d'images à traiter 768 378 769 770 771 * 772 773 379 * 774 380 775 381 * @return int Le nombre total d'images 776 382 777 778 779 */ 780 781 383 */ 782 384 783 385 public function get_total_images_count() { 784 386 785 786 787 387 global $wpdb; 788 388 789 790 791 792 793 389 794 390 795 391 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for batch processing existing images, caching not applicable for dynamic counts 796 392 797 798 799 393 return (int) $wpdb->get_var( 800 394 801 802 803 395 $wpdb->prepare( 804 396 805 806 807 397 "SELECT COUNT(*) 808 398 809 810 811 399 FROM {$wpdb->posts} 812 400 813 814 815 401 WHERE post_type = 'attachment' 816 402 817 818 819 403 AND post_status = 'inherit' 820 404 821 822 823 405 AND post_mime_type LIKE %s", 824 406 825 826 827 407 'image/%' 828 408 829 830 831 409 ) 832 410 833 834 835 411 ); 836 412 837 838 839 } 840 841 842 843 844 845 846 847 /** 848 849 413 } 414 415 416 417 /** 850 418 851 419 * Traiter un batch d'images existantes pour l'accessibilité 852 420 853 854 855 * 856 857 421 * 858 422 859 423 * @param int $offset L'offset pour le batch 860 424 861 862 863 425 * @param int $batch_size La taille du batch 864 426 865 866 867 427 * @param int $total_processed Le nombre total d'images déjà traitées (pour cumul) 868 428 869 870 871 429 * @param int $total_skipped Le nombre total d'images ignorées (pour cumul) 872 430 873 874 875 431 * @return array Résultat du traitement du batch 876 432 877 878 879 */ 880 881 433 */ 882 434 883 435 public function process_existing_images_batch($offset = 0, $batch_size = 50, $total_processed = 0, $total_skipped = 0) { 884 436 885 886 887 437 global $wpdb; 888 438 889 890 891 892 893 439 894 440 895 441 $auto_alt_enabled = get_option('filikod_auto_alt', 'no') === 'yes'; 896 897 898 899 442 $clean_chars_enabled = get_option('filikod_clean_alt_special_chars', 'no') === 'yes'; 900 901 902 903 904 905 443 $remove_title_enabled = get_option('filikod_remove_title', 'no') === 'yes'; 906 444 907 445 // Si aucune option n'est activée, ne rien faire 908 909 910 911 if (!$auto_alt_enabled && !$clean_chars_enabled) { 912 913 914 446 if ( ! $auto_alt_enabled && ! $clean_chars_enabled && ! $remove_title_enabled ) { 915 447 return array( 916 917 918 919 448 'processed' => 0, 920 921 922 923 449 'skipped' => 0, 924 925 926 927 450 'total_processed' => $total_processed, 928 929 930 931 451 'total_skipped' => $total_skipped, 932 933 934 935 452 'finished' => true 936 937 938 939 453 ); 940 941 942 943 } 944 945 946 947 948 949 454 } 455 456 950 457 951 458 // Récupérer un batch d'images 952 459 953 954 955 460 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for batch processing existing images, caching not applicable for batch operations 956 461 957 958 959 462 $image_ids = $wpdb->get_col( 960 463 961 962 963 464 $wpdb->prepare( 964 465 965 966 967 466 "SELECT ID 968 467 969 970 971 468 FROM {$wpdb->posts} 972 469 973 974 975 470 WHERE post_type = 'attachment' 976 471 977 978 979 472 AND post_status = 'inherit' 980 473 981 982 983 474 AND post_mime_type LIKE %s 984 475 985 986 987 476 LIMIT %d OFFSET %d", 988 477 989 990 991 478 'image/%', 992 479 993 994 995 480 $batch_size, 996 481 997 998 999 482 $offset 1000 483 1001 1002 1003 484 ) 1004 485 1005 1006 1007 486 ); 1008 487 1009 1010 1011 1012 1013 488 1014 489 1015 490 if (empty($image_ids)) { 1016 491 1017 1018 1019 492 return array( 1020 493 1021 1022 1023 494 'processed' => 0, 1024 495 1025 1026 1027 496 'skipped' => 0, 1028 497 1029 1030 1031 498 'total_processed' => $total_processed, 1032 499 1033 1034 1035 500 'total_skipped' => $total_skipped, 1036 501 1037 1038 1039 502 'finished' => true 1040 503 1041 1042 1043 504 ); 1044 505 1045 1046 1047 } 1048 1049 1050 1051 1052 1053 506 } 507 508 1054 509 1055 510 $batch_processed = 0; 1056 511 1057 1058 1059 512 $batch_skipped = 0; 1060 513 1061 1062 1063 1064 1065 514 1066 515 1067 516 // Traiter chaque image du batch 1068 517 1069 1070 1071 518 foreach ($image_ids as $attachment_id) { 1072 519 1073 1074 1075 520 $was_modified = false; 1076 521 1077 1078 1079 522 1080 523 1081 1082 1083 524 // Générer ALT si activé et si l'image n'en a pas 1084 525 1085 1086 1087 526 if ($auto_alt_enabled) { 1088 527 1089 1090 1091 528 $old_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true); 1092 529 1093 1094 1095 530 if (empty($old_alt)) { 1096 531 1097 1098 1099 532 $this->generate_alt_text($attachment_id); 1100 533 1101 1102 1103 534 $new_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true); 1104 535 1105 1106 1107 536 if ($new_alt !== $old_alt) { 1108 537 1109 1110 1111 538 $was_modified = true; 1112 539 1113 1114 1115 540 } 1116 541 1117 1118 1119 542 } 1120 543 1121 1122 1123 544 } 1124 545 1125 1126 1127 546 1128 547 1129 1130 1131 548 // Nettoyer les caractères spéciaux si activé 1132 1133 1134 1135 549 if ($clean_chars_enabled) { 1136 1137 1138 1139 550 $old_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true); 1140 1141 1142 1143 551 if (!empty($old_alt)) { 1144 1145 1146 1147 552 $this->clean_alt_special_chars($attachment_id); 1148 1149 1150 1151 553 $new_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true); 1152 1153 1154 1155 554 if ($new_alt !== $old_alt) { 1156 1157 1158 1159 555 $was_modified = true; 1160 1161 1162 1163 556 } 1164 1165 1166 1167 557 } 1168 1169 1170 1171 558 } 1172 559 1173 1174 1175 1176 1177 560 // Supprimer le titre (post_title) si activé — après ALT et nettoyage (même ordre qu’à l’upload) 561 if ($remove_title_enabled) { 562 $post = get_post($attachment_id); 563 if ( $post && ! empty( $post->post_title ) ) { 564 $this->clear_attachment_title($attachment_id); 565 $was_modified = true; 566 } 567 } 1178 568 1179 569 if ($was_modified) { 1180 570 1181 1182 1183 571 $batch_processed++; 1184 572 1185 1186 1187 573 } else { 1188 574 1189 1190 1191 575 $batch_skipped++; 1192 576 1193 1194 1195 577 } 1196 578 1197 1198 1199 } 1200 1201 1202 1203 1204 1205 579 } 580 581 1206 582 1207 583 // Cumuler les totaux 1208 584 1209 1210 1211 585 $total_processed += $batch_processed; 1212 586 1213 1214 1215 587 $total_skipped += $batch_skipped; 1216 588 1217 1218 1219 1220 1221 589 1222 590 1223 591 return array( 1224 592 1225 1226 1227 593 'processed' => $batch_processed, 1228 594 1229 1230 1231 595 'skipped' => $batch_skipped, 1232 596 1233 1234 1235 597 'total_processed' => $total_processed, 1236 598 1237 1238 1239 599 'total_skipped' => $total_skipped, 1240 600 1241 1242 1243 601 'finished' => false 1244 602 1245 1246 1247 603 ); 1248 604 1249 1250 1251 } 1252 1253 1254 1255 1256 1257 605 } 606 607 1258 608 1259 609 } … … 1261 611 1262 612 1263 1264 1265 1266 -
filikod/trunk/includes/class-filikod-alt-audit.php
r3468088 r3477168 479 479 } 480 480 } 481 // Onglet Duplicated : tri alphabétique par ALT pour regrouper les doublons. 482 if ( $status === 'duplicate' && ! empty( $filtered_ids ) ) { 483 usort( $filtered_ids, function ( $a, $b ) use ( $items ) { 484 $alt_a = isset( $items[ $a ]['alt_raw'] ) ? (string) $items[ $a ]['alt_raw'] : ''; 485 $alt_b = isset( $items[ $b ]['alt_raw'] ) ? (string) $items[ $b ]['alt_raw'] : ''; 486 return strcasecmp( $alt_a, $alt_b ); 487 } ); 488 } 481 489 $total = count( $filtered_ids ); 482 490 $per_page = max( 1, min( 50, (int) $per_page ) ); -
filikod/trunk/includes/dashboard/class-filikod-dashboard.php
r3462774 r3477168 1 1 <?php 2 3 2 /** 4 5 3 * Classe Dashboard - Gère la page principale du dashboard 6 7 4 */ 8 5 9 10 11 6 if (!defined('ABSPATH')) { 12 13 7 exit; 14 15 8 } 16 9 17 18 19 10 class Filikod_Dashboard { 20 21 22 11 23 12 private $plugin; 24 25 26 13 27 14 public function __construct() { 28 29 15 $this->plugin = filikod(); 30 31 16 add_action( 'wp_ajax_filikod_save_alt', array( $this, 'ajax_save_alt' ) ); 32 33 } 34 35 36 37 /** 38 17 } 18 19 /** 39 20 * Afficher la page du dashboard 40 41 */ 42 21 */ 43 22 public function display_dashboard_page() { 44 45 23 // Vérifier les permissions 46 47 24 if (!current_user_can('manage_options')) { 48 49 25 wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'filikod')); 50 51 } 52 53 54 26 } 27 55 28 // Inclure la vue du dashboard 56 57 29 include FILIKOD_PLUGIN_PATH . 'admin/views/dashboard.php'; 58 59 } 60 61 62 63 /** 64 30 } 31 32 /** 65 33 * Sauvegarde AJAX de l’ALT (Option B). Retourne new_status et counts pour mettre à jour le tableau. 66 67 */ 68 34 */ 69 35 public function ajax_save_alt() { 70 71 36 check_ajax_referer( 'filikod_save_alt', 'nonce' ); 72 73 37 if ( ! current_user_can( 'manage_options' ) ) { 74 75 38 wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'filikod' ) ) ); 76 77 } 78 39 } 79 40 $attachment_id = isset( $_POST['attachment_id'] ) ? (int) $_POST['attachment_id'] : 0; 80 81 41 if ( ! $attachment_id ) { 82 83 42 wp_send_json_error( array( 'message' => __( 'Invalid attachment.', 'filikod' ) ) ); 84 85 } 86 43 } 87 44 $new_alt = isset( $_POST['filikod_alt_value'] ) ? sanitize_text_field( wp_unslash( $_POST['filikod_alt_value'] ) ) : ''; 88 89 45 $new_alt = trim( $new_alt ); 90 91 46 update_post_meta( $attachment_id, '_wp_attachment_image_alt', $new_alt ); 92 93 47 if ( class_exists( 'Filikod_Alt_Audit' ) ) { 94 95 48 Filikod_Alt_Audit::invalidate_cache(); 96 97 } 98 49 } 99 50 $audit = class_exists( 'Filikod_Alt_Audit' ) ? Filikod_Alt_Audit::get_cached_audit() : array(); 100 101 51 $items = isset( $audit['items'] ) ? $audit['items'] : array(); 102 103 52 $counts = isset( $audit['counts'] ) ? $audit['counts'] : array( 'missing' => 0, 'generic' => 0, 'too_short' => 0, 'duplicate' => 0 ); 104 105 53 $new_status = isset( $items[ $attachment_id ]['status'] ) ? $items[ $attachment_id ]['status'] : ( $new_alt === '' ? 'missing' : 'correct' ); 106 107 54 wp_send_json_success( array( 108 109 55 'new_status' => $new_status, 110 111 56 'new_alt' => $new_alt, 112 113 57 'counts' => $counts, 114 115 58 ) ); 116 117 } 118 119 120 121 /** 122 59 } 60 61 /** 123 62 * Afficher la page ALT Audit (sauvegarde via AJAX, pas de POST/redirect). 124 125 */ 126 63 */ 127 64 public function display_alt_audit_page() { 128 129 65 if ( ! current_user_can( 'manage_options' ) ) { 130 131 66 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'filikod' ) ); 132 133 } 134 67 } 135 68 include FILIKOD_PLUGIN_PATH . 'admin/views/alt-audit.php'; 136 137 } 138 139 140 141 /** 142 69 } 70 71 /** 143 72 * Obtenir le nombre total d'images dans la bibliothèque média 144 145 * 146 73 * 147 74 * Cette fonction compte toutes les images (attachments de type image) 148 149 75 * présentes dans la bibliothèque média WordPress. 150 151 76 * Utilise une requête optimisée avec $wpdb pour de meilleures performances. 152 153 * 154 77 * 155 78 * @return int Le nombre d'images 156 157 */ 158 79 */ 159 80 public function get_total_images_count() { 160 161 81 global $wpdb; 162 163 164 82 165 83 // Requête SQL optimisée pour compter uniquement les images 166 167 84 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for dashboard statistics, caching not applicable for real-time counts 168 169 85 $count = $wpdb->get_var( 170 171 86 $wpdb->prepare( 172 173 87 "SELECT COUNT(*) 174 175 88 FROM {$wpdb->posts} 176 177 89 WHERE post_type = 'attachment' 178 179 90 AND post_status = 'inherit' 180 181 91 AND post_mime_type LIKE %s", 182 183 92 'image/%' 184 185 93 ) 186 187 ); 188 189 190 94 ); 95 191 96 return (int) $count; 192 193 } 194 195 196 197 /** 198 97 } 98 99 /** 199 100 * Obtenir le nombre d'images avec texte alternatif (ALT) 200 201 * 202 101 * 203 102 * Cette fonction compte toutes les images qui ont un texte ALT défini. 204 205 103 * Utilise une requête SQL optimisée avec JOIN pour de meilleures performances. 206 207 * 208 104 * 209 105 * @return int Le nombre d'images avec ALT 210 211 */ 212 106 */ 213 107 public function get_images_with_alt_count() { 214 215 108 global $wpdb; 216 217 218 109 219 110 // Requête SQL optimisée avec JOIN pour compter les images avec ALT 220 221 111 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for dashboard statistics, caching not applicable for real-time counts 222 223 112 $count = $wpdb->get_var( 224 225 113 $wpdb->prepare( 226 227 114 "SELECT COUNT(DISTINCT p.ID) 228 229 115 FROM {$wpdb->posts} p 230 231 116 INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id 232 233 117 WHERE p.post_type = 'attachment' 234 235 118 AND p.post_status = 'inherit' 236 237 119 AND p.post_mime_type LIKE %s 238 239 120 AND pm.meta_key = '_wp_attachment_image_alt' 240 241 121 AND pm.meta_value != '' 242 243 122 AND pm.meta_value IS NOT NULL", 244 245 123 'image/%' 246 247 124 ) 248 249 ); 250 251 252 125 ); 126 253 127 return (int) $count; 254 255 } 256 257 258 259 260 261 /** 262 128 } 129 130 131 /** 263 132 * Obtenir le nombre d'images sans texte alternatif (ALT) 264 265 133 * 266 267 134 * Compte toutes les images qui n'ont pas de meta _wp_attachment_image_alt 268 269 135 * ou dont la valeur est vide. 270 271 136 * 272 273 137 * @return int Le nombre d'images sans ALT 274 275 */ 276 138 */ 277 139 public function get_images_without_alt_count() { 278 279 140 global $wpdb; 280 141 281 282 283 142 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 284 285 143 $count = $wpdb->get_var( 286 287 144 $wpdb->prepare( 288 289 145 "SELECT COUNT(DISTINCT p.ID) 290 291 146 FROM {$wpdb->posts} p 292 293 147 LEFT JOIN {$wpdb->postmeta} pm 294 295 148 ON p.ID = pm.post_id 296 297 149 AND pm.meta_key = '_wp_attachment_image_alt' 298 299 150 WHERE p.post_type = 'attachment' 300 301 151 AND p.post_status = 'inherit' 302 303 152 AND p.post_mime_type LIKE %s 304 305 153 AND (pm.meta_value IS NULL OR pm.meta_value = '')", 306 307 154 'image/%' 308 309 155 ) 310 311 ); 312 313 156 ); 314 157 315 158 return (int) $count; 316 317 } 318 319 159 } 320 160 321 161 /** 322 323 162 * Obtenir le pourcentage d’images avec ALT 324 325 163 * 326 327 164 * @return int Pourcentage d’images avec ALT 328 329 165 * 330 331 166 * @return array 332 333 */ 334 167 */ 335 168 public function get_alt_audit_data() { 336 337 169 if ( ! class_exists( 'Filikod_Alt_Audit' ) ) { 338 339 170 return array( 340 341 171 'global_score' => 0, 342 343 172 'global_score_precise' => 0.0, 344 345 173 'counts' => array( 346 347 174 'missing' => 0, 348 349 175 'generic' => 0, 350 351 176 'too_short' => 0, 352 353 177 'duplicate' => 0, 354 355 178 'correct' => 0, 356 357 179 ), 358 359 180 ); 360 361 } 362 181 } 363 182 $audit = Filikod_Alt_Audit::get_cached_audit(); 364 365 183 $score = (int) $audit['global_score']; 366 367 184 $score_precise = isset( $audit['global_score_precise'] ) ? (float) $audit['global_score_precise'] : (float) $score; 368 369 185 return array( 370 371 186 'global_score' => $score, 372 373 187 'global_score_precise' => $score_precise, 374 375 188 'counts' => array( 376 377 189 'missing' => (int) ( isset( $audit['counts']['missing'] ) ? $audit['counts']['missing'] : 0 ), 378 379 190 'generic' => (int) ( isset( $audit['counts']['generic'] ) ? $audit['counts']['generic'] : 0 ), 380 381 191 'too_short' => (int) ( isset( $audit['counts']['too_short'] ) ? $audit['counts']['too_short'] : 0 ), 382 383 192 'duplicate' => (int) ( isset( $audit['counts']['duplicate'] ) ? $audit['counts']['duplicate'] : 0 ), 384 385 193 'correct' => (int) ( isset( $audit['counts']['correct'] ) ? $audit['counts']['correct'] : 0 ), 386 387 194 ), 388 389 ); 390 391 } 392 393 394 395 /** 396 195 ); 196 } 197 198 /** 397 199 * Obtenir le pourcentage d'images avec ALT 398 399 200 * 400 401 201 * @return int Pourcentage d'images avec ALT 402 403 */ 404 202 */ 405 203 public function get_alt_coverage_percentage() { 406 407 204 $total = $this->get_total_images_count(); 408 205 409 410 411 206 if ( $total === 0 ) { 412 413 207 return 0; 414 415 } 416 417 208 } 418 209 419 210 $with_alt = $this->get_images_with_alt_count(); 420 211 421 422 423 212 $percentage = ( $with_alt / $total ) * 100; 424 213 425 426 427 214 return (int) round( $percentage ); 428 429 } 430 431 432 433 434 435 436 437 /** 438 215 } 216 217 218 219 /** 439 220 * Obtenir le poids total de toutes les images 440 441 * 442 221 * 443 222 * Cette fonction calcule la taille totale de tous les fichiers images 444 445 223 * dans la bibliothèque média (en octets, puis converti en format lisible). 446 447 224 * Utilise une requête optimisée avec pagination pour éviter les timeouts. 448 449 * 450 225 * 451 226 * @return array Tableau avec 'bytes' (taille en octets) et 'formatted' (taille formatée) 452 453 */ 454 227 */ 455 228 public function get_total_images_size() { 456 457 229 global $wpdb; 458 459 460 230 461 231 // Récupérer les IDs des images par batch pour éviter les timeouts 462 463 232 $total_size = 0; 464 465 233 $batch_size = 100; 466 467 234 $offset = 0; 468 469 470 235 471 236 while (true) { 472 473 237 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for dashboard statistics, caching not applicable for batch operations 474 475 238 $image_ids = $wpdb->get_col( 476 477 239 $wpdb->prepare( 478 479 240 "SELECT ID 480 481 241 FROM {$wpdb->posts} 482 483 242 WHERE post_type = 'attachment' 484 485 243 AND post_status = 'inherit' 486 487 244 AND post_mime_type LIKE %s 488 489 245 LIMIT %d OFFSET %d", 490 491 246 'image/%', 492 493 247 $batch_size, 494 495 248 $offset 496 497 249 ) 498 499 250 ); 500 501 502 251 503 252 if ( empty( $image_ids ) ) { 504 505 253 break; 506 507 } 508 509 510 254 } 255 511 256 // Calculer la taille pour ce batch 512 513 257 foreach ( $image_ids as $image_id ) { 514 515 258 $file_path = get_attached_file( $image_id ); 516 517 259 if ( $file_path && file_exists( $file_path ) ) { 518 519 260 $file_size = filesize( $file_path ); 520 521 261 if ( $file_size !== false ) { 522 523 262 $total_size += $file_size; 524 525 263 } 526 527 264 } 528 529 } 530 531 532 265 } 266 533 267 $offset += $batch_size; 534 535 536 268 537 269 // Limite de sécurité pour éviter les boucles infinies 538 539 270 if ( $offset > 10000 ) { 540 541 271 break; 542 543 } 544 545 } 546 547 548 272 } 273 } 274 549 275 return array( 550 551 276 'bytes' => $total_size, 552 553 277 'formatted' => $this->format_file_size( $total_size ), 554 555 ); 556 557 } 558 559 560 561 /** 562 278 ); 279 } 280 281 /** 563 282 * Obtenir le nombre d'images optimisées 564 565 * 566 283 * 567 284 * Cette fonction compte toutes les images qui ont été optimisées 568 569 285 * (qui ont la métadonnée filikod_resized). 570 571 * 572 286 * 573 287 * @return int Le nombre d'images optimisées 574 575 */ 576 288 */ 577 289 public function get_optimized_images_count() { 578 579 290 global $wpdb; 580 581 582 291 583 292 $count = 0; 584 585 293 $batch_size = 100; 586 587 294 $offset = 0; 588 589 590 295 591 296 while ( true ) { 592 593 297 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for dashboard statistics, caching not applicable for batch operations 594 595 298 $image_ids = $wpdb->get_col( 596 597 299 $wpdb->prepare( 598 599 300 "SELECT ID 600 601 301 FROM {$wpdb->posts} 602 603 302 WHERE post_type = 'attachment' 604 605 303 AND post_status = 'inherit' 606 607 304 AND post_mime_type LIKE %s 608 609 305 LIMIT %d OFFSET %d", 610 611 306 'image/%', 612 613 307 $batch_size, 614 615 308 $offset 616 617 309 ) 618 619 310 ); 620 621 622 311 623 312 if ( empty( $image_ids ) ) { 624 625 313 break; 626 627 } 628 629 630 314 } 315 631 316 foreach ( $image_ids as $image_id ) { 632 633 317 $image_meta = wp_get_attachment_metadata( $image_id ); 634 635 318 636 637 319 // Vérifier aussi directement dans la base de données si les métadonnées ne sont pas trouvées 638 639 320 if ( ! is_array( $image_meta ) || ! isset( $image_meta['filikod_resized'] ) ) { 640 641 321 // Essayer de récupérer directement depuis la base de données 642 643 322 $meta_value = get_post_meta( $image_id, '_wp_attachment_metadata', true ); 644 645 323 if ( is_array( $meta_value ) && isset( $meta_value['filikod_resized'] ) ) { 646 647 324 $image_meta = $meta_value; 648 649 325 } 650 651 326 } 652 653 327 654 655 328 if ( is_array( $image_meta ) && isset( $image_meta['filikod_resized'] ) && is_array( $image_meta['filikod_resized'] ) ) { 656 657 329 $count++; 658 659 330 } 660 661 } 662 663 664 331 } 332 665 333 $offset += $batch_size; 666 667 668 334 669 335 if ( $offset > 10000 ) { 670 671 336 break; 672 673 } 674 675 } 676 677 678 337 } 338 } 339 679 340 return $count; 680 681 } 682 683 684 685 /** 686 341 } 342 343 /** 687 344 * Obtenir le poids total original des images 688 689 * 690 345 * 691 346 * Cette fonction calcule la taille totale originale de toutes les images. 692 693 347 * Pour les images optimisées, utilise la taille originale stockée dans les métadonnées. 694 695 348 * Pour les autres, utilise la taille actuelle du fichier. 696 697 * 698 349 * 699 350 * @return array Tableau avec 'bytes' (taille en octets) et 'formatted' (taille formatée) 700 701 */ 702 351 */ 703 352 public function get_original_total_size() { 704 705 353 global $wpdb; 706 707 708 354 709 355 $total_size = 0; 710 711 356 $batch_size = 100; 712 713 357 $offset = 0; 714 715 716 358 717 359 while ( true ) { 718 719 360 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for dashboard statistics, caching not applicable for batch operations 720 721 361 $image_ids = $wpdb->get_col( 722 723 362 $wpdb->prepare( 724 725 363 "SELECT ID 726 727 364 FROM {$wpdb->posts} 728 729 365 WHERE post_type = 'attachment' 730 731 366 AND post_status = 'inherit' 732 733 367 AND post_mime_type LIKE %s 734 735 368 LIMIT %d OFFSET %d", 736 737 369 'image/%', 738 739 370 $batch_size, 740 741 371 $offset 742 743 372 ) 744 745 373 ); 746 747 748 374 749 375 if ( empty( $image_ids ) ) { 750 751 376 break; 752 753 } 754 755 756 377 } 378 757 379 foreach ( $image_ids as $image_id ) { 758 759 380 $image_meta = wp_get_attachment_metadata( $image_id ); 760 761 381 762 763 382 // Vérifier aussi directement dans la base de données si les métadonnées ne sont pas trouvées 764 765 383 if ( ! is_array( $image_meta ) || ! isset( $image_meta['filikod_resized'] ) ) { 766 767 384 // Essayer de récupérer directement depuis la base de données 768 769 385 $meta_value = get_post_meta( $image_id, '_wp_attachment_metadata', true ); 770 771 386 if ( is_array( $meta_value ) && isset( $meta_value['filikod_resized'] ) ) { 772 773 387 $image_meta = $meta_value; 774 775 388 } 776 777 389 } 778 779 390 780 781 391 // Si l'image a été optimisée, utiliser la taille originale stockée 782 783 392 if ( is_array( $image_meta ) && isset( $image_meta['filikod_resized']['original_size'] ) ) { 784 785 393 $original_size = (int) $image_meta['filikod_resized']['original_size']; 786 787 394 if ( $original_size > 0 ) { 788 789 395 $total_size += $original_size; 790 791 396 } else { 792 793 397 // Si la taille originale n'est pas valide, utiliser la taille actuelle 794 795 398 $file_path = get_attached_file( $image_id ); 796 797 399 if ( $file_path && file_exists( $file_path ) ) { 798 799 400 $file_size = filesize( $file_path ); 800 801 401 if ( $file_size !== false ) { 802 803 402 $total_size += $file_size; 804 805 403 } 806 807 404 } 808 809 405 } 810 811 406 } else { 812 813 407 // Sinon, utiliser la taille actuelle du fichier 814 815 408 $file_path = get_attached_file( $image_id ); 816 817 409 if ( $file_path && file_exists( $file_path ) ) { 818 819 410 $file_size = filesize( $file_path ); 820 821 411 if ( $file_size !== false ) { 822 823 412 $total_size += $file_size; 824 825 413 } 826 827 414 } 828 829 415 } 830 831 } 832 833 834 416 } 417 835 418 $offset += $batch_size; 836 837 838 419 839 420 if ( $offset > 10000 ) { 840 841 421 break; 842 843 } 844 845 } 846 847 848 422 } 423 } 424 849 425 return array( 850 851 426 'bytes' => $total_size, 852 853 427 'formatted' => $this->format_file_size( $total_size ), 854 855 ); 856 857 } 858 859 860 861 /** 862 428 ); 429 } 430 431 /** 863 432 * Obtenir le poids total des images après optimisation 864 865 * 866 433 * 867 434 * Cette fonction calcule la taille totale actuelle de tous les fichiers images. 868 869 * 870 435 * 871 436 * @return array Tableau avec 'bytes' (taille en octets) et 'formatted' (taille formatée) 872 873 */ 874 437 */ 875 438 public function get_optimized_total_size() { 876 877 439 // Utilise la même méthode que get_total_images_size car elle retourne déjà la taille actuelle 878 879 440 return $this->get_total_images_size(); 880 881 } 882 883 884 885 /** 886 441 } 442 443 /** 887 444 * Obtenir le poids moyen par image à partir du total et du nombre d'images. 888 889 445 * 890 891 446 * @param int $total_bytes Taille totale en octets. 892 893 447 * @param int $total_count Nombre d'images. 894 895 448 * @return array{bytes: int, formatted: string} 896 897 */ 898 449 */ 899 450 public function get_average_image_size_from( $total_bytes, $total_count ) { 900 901 451 $count = max( 1, (int) $total_count ); 902 903 452 $avg_bytes = (int) round( (int) $total_bytes / $count ); 904 905 453 return array( 906 907 454 'bytes' => $avg_bytes, 908 909 455 'formatted' => $this->format_file_size( $avg_bytes ), 910 911 ); 912 913 } 914 915 916 917 /** 918 456 ); 457 } 458 459 /** 919 460 * Obtenir le pourcentage d'images optimisées 920 921 * 922 461 * 923 462 * @return float Le pourcentage d'images optimisées (0-100) 924 925 */ 926 463 */ 927 464 public function get_optimization_percentage() { 928 929 465 $total_images = $this->get_total_images_count(); 930 931 932 466 933 467 if ( $total_images === 0 ) { 934 935 468 return 0; 936 937 } 938 939 940 469 } 470 941 471 $optimized_images = $this->get_optimized_images_count(); 942 943 944 472 945 473 return round( ( $optimized_images / $total_images ) * 100, 1 ); 946 947 } 948 949 950 951 /** 952 474 } 475 476 /** 953 477 * Formater la taille d'un fichier en format lisible 954 955 * 956 478 * 957 479 * Convertit les octets en KB, MB, GB selon la taille. 958 959 * 960 480 * 961 481 * @param int $bytes La taille en octets 962 963 482 * @return string La taille formatée (ex: "1.5 MB") 964 965 */ 966 483 */ 967 484 private function format_file_size( $bytes ) { 968 969 485 if ( $bytes >= 1073741824 ) { 970 971 486 // Gigabytes 972 973 487 return number_format_i18n( $bytes / 1073741824, 2 ) . ' GB'; 974 975 488 } elseif ( $bytes >= 1048576 ) { 976 977 489 // Megabytes 978 979 490 return number_format_i18n( $bytes / 1048576, 2 ) . ' MB'; 980 981 491 } elseif ( $bytes >= 1024 ) { 982 983 492 // Kilobytes 984 985 493 return number_format_i18n( $bytes / 1024, 2 ) . ' KB'; 986 987 494 } else { 988 989 495 // Bytes 990 991 496 return number_format_i18n( $bytes, 0 ) . ' ' . __( 'bytes', 'filikod' ); 992 993 } 994 995 } 996 497 } 498 } 997 499 } 998 500 999 1000 -
filikod/trunk/readme.txt
r3468111 r3477168 2 2 Contributors: filikod 3 3 Plugin URI: https://filikod.com/ 4 Tags: alt text, a lt audit, bulk alt text, image alt, accessibility4 Tags: alt text, accessibility, image seo, alt audit, media library 5 5 Requires at least: 5.8 6 6 Tested up to: 6.9 7 7 Requires PHP: 7.4 8 Stable tag: 1.0. 68 Stable tag: 1.0.7 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 11 12 Scan your media library, see your ALT Quality Score, and fix missing ALT text in minutes.12 Audit every image in your media library, get an ALT Quality Score, and fix missing or weak ALT text in bulk. No AI. No external API. Full control. 13 13 14 14 == Description == 15 15 16 Most WordPress sites have broken ALT text. 17 Install Filikod, run the audit, fix what matters first. 18 19 Not because people don’t care, because it’s invisible until it’s too late. 20 21 Your media library quietly accumulates: 22 • Images with missing ALT attributes 23 • Generic ALT like “image”, “photo” or “logo” 24 • Duplicated ALT reused everywhere 25 • ALT descriptions too short to be useful 26 27 That hurts accessibility (screen readers) and image SEO (search engines). 28 29 Filikod gives you a clear ALT audit and the tools to fix your library at scale directly inside WordPress. 30 31 No external API. 32 No complex setup. 33 No black-box rewriting. You stay in control. 34 35 Just visibility, control, and bulk actions. 36 37 Learn more & documentation: 38 [filikod.com](https://filikod.com/) 39 40 --- 41 42 = ALT Text Audit & Quality Score = 43 44 Filikod scans your entire media library and detects: 45 • Missing ALT attributes 46 • Generic ALT descriptions 47 • Too short ALT text 48 • Duplicated ALT values 49 50 You get an ALT Quality Score (0–100%) plus a clear breakdown of what to fix first. 51 52 This turns “ALT text” from a vague SEO task into a measurable system you can improve. 53 54 --- 55 56 = Bulk ALT Management (fast, practical) = 16 Most WordPress sites have broken ALT text, and most site owners have no way to measure it. 17 18 Missing, generic, duplicated or too-short ALT text quietly hurts your accessibility score and your image SEO. The problem stays invisible until it is too late. 19 20 **Filikod scans your entire media library, gives you an ALT Quality Score from 0 to 100%, and gives you the tools to fix everything in bulk, directly inside WordPress.** 21 22 No AI black box. No external API. No credits to buy. No complex setup. 23 You write the ALT text. You stay in control. 24 25 [Learn more and read the documentation on filikod.com](https://filikod.com/) 26 27 --- 28 29 = ALT Text Audit and Quality Score = 30 31 Filikod scans your entire media library and instantly flags: 32 33 * **Missing ALT**: images with no ALT attribute at all 34 * **Generic ALT**: meaningless values like "image", "photo", "logo", "thumbnail", "untitled" 35 * **Too Short ALT**: descriptions too brief to be useful for accessibility or SEO 36 * **Duplicated ALT**: the same ALT text reused across multiple images 37 38 You get a clear **ALT Quality Score from 0 to 100%** plus a breakdown by issue type, so you always know exactly what to fix first. 39 40 This turns ALT text from a vague, invisible problem into a measurable system you can actually improve, like a health score for your media library. 41 42 --- 43 44 = Bulk ALT Management = 57 45 58 46 Editing ALT text one image at a time is the reason most people never finish. 59 47 60 48 Filikod lets you: 61 • Filter images by issue type (missing / generic / short / duplicate) 62 • Edit ALT text inline 63 • Save instantly 64 • Fix dozens (or hundreds) of images in minutes 65 66 Built for real media libraries, not perfect demos. 67 68 --- 69 70 = Context-Based Editing (write better ALT) = 71 72 Good ALT text needs context. 73 74 Filikod shows where each image is used (post or page), so you can jump to the content and write relevant ALT instead of guesswork. 49 50 * Filter images by issue type (missing, generic, short, duplicated) 51 * Edit ALT text inline, directly from the audit view 52 * Save instantly without leaving the page 53 * Fix dozens or hundreds of images in minutes 54 55 Built for real media libraries with thousands of images, not tidy demos with five. 56 57 --- 58 59 = Context-Based Editing = 60 61 Good ALT text requires context. What is this image actually about on this page? 62 63 Filikod shows you **where each image is used** (which post or page) so you can jump directly to the content and write accurate, relevant ALT text. No guesswork. 75 64 76 65 --- … … 78 67 = Controlled Automation (optional) = 79 68 80 Filikod includes basic automation tools: 81 • Generate ALT from filename (optional) 82 • Clean special characters 83 • Bulk process existing images 84 85 Automation supports your workflow. 86 Control always remains yours. 87 88 --- 89 90 = What Filikod is NOT = 91 92 Filikod is not an image compression/WebP/CDN optimizer like Smush, Imagify or ShortPixel. 93 It focuses on ALT text health and media library hygiene visibility, consistency and control. 69 Filikod includes optional automation tools for teams who need to process large volumes: 70 71 * Generate ALT from filename (only applied to images that have no ALT yet) 72 * Remove special characters from ALT text (slashes, dashes, underscores) 73 * Remove title attribute from images (reduces redundancy, improves accessibility) 74 * Bulk process your entire existing media library in one click 75 76 Execution order is always enforced: generate ALT from title then filename, clean special characters, then remove title. Predictable. Consistent. Always yours to control. 77 78 --- 79 80 = How Filikod Compares = 81 82 Most ALT text plugins do one thing: auto-generate ALT on upload, often using AI and a paid credit system. 83 84 Filikod is an ALT audit and bulk management system. It works on your existing library, gives you a measurable quality score, and lets you fix issues at scale with full editorial control. 85 86 | Feature | Image optimizer | Filename-based plugin | AI ALT generator | Filikod | 87 |---|---|---|---|---| 88 | Auto-generates ALT | Sometimes | Yes | Yes | Yes (optional) | 89 | ALT Quality Score | No | No | No | Yes | 90 | Detects missing ALT | No | No | No | Yes | 91 | Detects generic ALT | No | No | No | Yes | 92 | Detects duplicated ALT | No | No | No | Yes | 93 | Bulk ALT filtering | No | No | No | Yes | 94 | Works on existing media | No | No | Partial | Yes | 95 | No external API or credits | Yes | Yes | No | Yes | 96 | Manual editorial control | Limited | Limited | No | Yes | 97 98 If you want AI to write your ALT text automatically, Filikod is not that. 99 If you want full visibility and control over your ALT text quality at scale, Filikod is exactly that. 100 101 --- 102 103 = Who is Filikod for? = 104 105 Filikod is built for: 106 107 * **SEO professionals** who want ALT text treated as a measurable ranking signal, not an afterthought 108 * **Accessibility-focused teams** who need a structured audit to meet WCAG guidelines 109 * **Content managers** with large media libraries who need to fix issues fast, in bulk 110 * **Agencies** managing multiple WordPress sites who need a repeatable quality process 111 * **Anyone who wants control** over their ALT text, without depending on AI or external services 94 112 95 113 --- … … 97 115 = Compatibility = 98 116 99 •Works with any theme100 • Compatible with Elementor, Divi, Gutenberg, WPBakery and most majorbuilders101 • Single site &multisite supported102 • No external services required 117 * Works with any theme 118 * Compatible with Elementor, Divi, Gutenberg, WPBakery and most major page builders 119 * Single site and multisite supported 120 * No external services required, runs entirely inside WordPress 103 121 104 122 --- … … 106 124 == Installation == 107 125 108 1. Upload the plugin to `/wp-content/plugins/` (or install from Plugins → Add New)126 1. Install from **Plugins > Add New** (search "Filikod") or upload the plugin zip manually 109 127 2. Activate Filikod 110 3. Go to Filikod → Dashboard111 4. Review your ALT Quality Score112 5. Fix issues using the bulk tools128 3. Go to **Filikod > Dashboard** to see your ALT Quality Score 129 4. Go to **Filikod > ALT Audit** to find and fix issues by type 130 5. Configure optional automation in **Filikod > Settings** 113 131 114 132 --- … … 117 135 118 136 = Does Filikod generate ALT text automatically? = 119 Yes, Filikod includes optional filename-based automation and bulk processing tools. 120 Its core strength is ALT audit + structured management so you keep full control. 121 122 = Does it work on existing images already in my media library? = 123 Yes. Filikod scans your entire media library, including images uploaded before installation. 124 125 = Does Filikod modify image files? = 126 No. Filikod updates ALT attributes stored in WordPress. Your original image files remain untouched. 137 138 Yes, but it is optional and filename-based, not AI. Filikod generates ALT from the image filename only for images that have no ALT text yet. Its core strength is structured ALT audit and bulk management, so you always stay in control of your content. 139 140 = Does Filikod use AI to write ALT text? = 141 142 No. Filikod does not use AI or any external API. ALT generation is based on the filename. This is intentional: AI-generated ALT text can be inaccurate, generic, or brand-inconsistent. Filikod gives you visibility and tools to write accurate ALT yourself, or to review and correct what automation produces. 143 144 = How is Filikod different from Image Attributes Pro or BIALTY? = 145 146 Image Attributes Pro and similar filename-based plugins set ALT text automatically from the filename. They do not audit your existing library, detect generic or duplicated ALT, or give you a quality score. Filikod gives you a structured audit across your entire media library and the tools to fix issues in bulk, including images uploaded before you installed the plugin. 147 148 = Does it work on images already in my media library? = 149 150 Yes. Filikod scans and processes your entire existing media library, including images uploaded before installation. You can run a bulk process from the Settings page at any time. 151 152 = Does Filikod modify my image files? = 153 154 No. Filikod only updates ALT attributes stored in WordPress. Your original image files are never touched or modified. 155 156 = What is the ALT Quality Score? = 157 158 It is a score from 0 to 100% that reflects the overall ALT text health of your media library. It takes into account the proportion of images with missing, generic, too-short, and duplicated ALT attributes. It gives you a single measurable number you can track and improve over time, like an SEO or accessibility health metric. 127 159 128 160 = Can I bulk edit ALT text? = 129 Yes. You can filter images by issue type and edit ALT text inline from the audit interface. 130 131 = Is it compatible with Elementor / Divi / WPBakery / Gutenberg? = 132 Yes. Filikod works independently of your theme or builder and is compatible with Elementor, Divi, Gutenberg, WPBakery and most major page builders. 133 134 = Does it work on multisite? = 135 Yes. Filikod supports WordPress multisite installations. 136 137 = Does Filikod use AI or external APIs? = 138 No. Filikod runs natively inside WordPress and does not require any external API. 161 162 Yes. The ALT Audit page lets you filter images by issue type (missing, generic, short, duplicated) and edit ALT text inline. Changes save instantly without reloading the page. 163 164 = Is it compatible with Elementor, Divi, WPBakery, and Gutenberg? = 165 166 Yes. Filikod manages ALT attributes directly in the WordPress media library, independently from your theme or page builder. Compatibility is universal. 167 168 = Does it work on multisite installations? = 169 170 Yes. Filikod supports WordPress multisite. 171 172 = Is Filikod an image optimizer like Smush or Imagify? = 173 174 No. Filikod does not compress images, convert to WebP, or serve images via CDN. It focuses entirely on ALT text quality: the audit, the score, and the tools to fix issues at scale. You can use a dedicated image optimizer alongside Filikod without any conflict. 139 175 140 176 = Where can I find documentation? = 141 Documentation and updates are available here: 142 [filikod.com](https://filikod.com/) 177 178 Full documentation is available at [filikod.com](https://filikod.com/). 179 143 180 144 181 --- … … 146 183 == Screenshots == 147 184 148 1. ALT Quality Score dashboard overview149 2. Missing ALT detection tab150 3. Duplicated ALT detection tab151 4. Inline bulk ALT editing interface185 1. **Dashboard**: ALT Quality Score (0 to 100%) with a donut gauge, full issue breakdown (missing, generic, short, duplicated), and media library overview showing total images, total size, and average size 186 2. **ALT Audit: Missing tab**: All images without ALT text listed with thumbnail, filename, context (which post or page it is used in), and an inline edit field to fix each one immediately. 187 3. **ALT Audit: Duplicated tab**: Images grouped alphabetically by ALT value so duplicate text is visually grouped. The list auto-refreshes after each save so resolved duplicates disappear immediately. 188 4. **Settings: Accessibility / SEO**: Toggle automatic ALT generation from filename, title attribute removal, and special character cleaning. Bulk-process all existing images from the same screen. 152 189 153 190 --- 154 191 155 192 == Changelog == 193 194 = 1.0.7 = 195 • Accessibility: "Remove Title Attribute" now works for new uploads (clears post_title after use); execution order enforced: generate ALT (from title then filename) → clean special chars → remove title (same order in bulk process) 196 • ALT Audit page: Duplicated tab lists media sorted alphabetically by ALT so duplicates are grouped; saving an ALT in Duplicated tab reloads the list so media that are no longer duplicated disappear 197 • Security & coding standards: escaped outputs (menu icon via wp_localize_script; pagination printf), all variables in ALT audit view prefixed with filikod_ (PrefixAllGlobals) 156 198 157 199 = 1.0.6 = … … 196 238 == Upgrade Notice == 197 239 240 = 1.0.7 = 241 Extended generic ALT detection, logo+domain exception, "Remove Title" fix for new uploads, Duplicated tab sorted by ALT with list refresh on save, removed Clear/Export CSV, security and coding standards fixes. 242 198 243 = 1.0.6 = 199 244 Positioning and documentation update focused on ALT audit & bulk management. and Improves “Generic ALT” detection.
Note: See TracChangeset
for help on using the changeset viewer.