Plugin Directory

Changeset 3477168


Ignore:
Timestamp:
03/07/2026 08:46:04 PM (4 weeks ago)
Author:
lyode
Message:

1.0.7
docs: optimize readme for WordPress.org SEO and conversion

  • 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)
  • 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
  • Security & coding standards: escaped outputs (menu icon via wp_localize_script; pagination printf), all variables in ALT audit view prefixed with filikod_ (PrefixAllGlobals)
Location:
filikod
Files:
48 added
6 edited

Legend:

Unmodified
Added
Removed
  • filikod/trunk/assets/js/admin.js

    r3462774 r3477168  
    11/**
    2 
    32 * JavaScript pour l'administration Filikod
    4 
    53 * Gère le système d'onglets sans changement de page
    6 
    74 */
    85
     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];
    9626
    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;
    513632                        }
    514633
    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    }
    516671
    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();
    639686            });
    640 
    641         });
    642 
    643     }
    644 
    645    
    646 
    647     /**
    648 
    649      * Initialiser le traitement des images existantes pour redimensionnement
    650 
    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'avertissement
    676 
    677             progressContainer.show();
    678 
    679             warningMessage.show();
    680 
    681            
    682 
    683             // Désactiver le bouton et afficher le statut
    684 
    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 progression
    694 
    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 page
    712 
    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 batch
    732 
    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: totalSaved
    764 
    765                     },
    766 
    767                     success: function(response) {
    768 
    769                         if (response.success) {
    770 
    771                             var data = response.data;
    772 
    773                            
    774 
    775                             // Mettre à jour les compteurs
    776 
    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 progression
    788 
    789                             // Utiliser le nombre total d'images traitées (processed + skipped) pour une meilleure précision
    790 
    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 progression
    798 
    799                             progressBar.css('width', percentage + '%');
    800 
    801                             progressBar.attr('aria-valuenow', percentage);
    802 
    803                            
    804 
    805                             // Mettre à jour le texte de progression
    806 
    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_formatted
    844 
    845                                 );
    846 
    847                                 statusMessage.text(finalMessage);
    848 
    849                                 showNotice(finalMessage, 'success');
    850 
    851                                
    852 
    853                                 // Masquer le conteneur de progression et le message d'avertissement
    854 
    855                                 progressContainer.hide();
    856 
    857                                 warningMessage.hide();
    858 
    859                                
    860 
    861                                 button.prop('disabled', false);
    862 
    863                             } else {
    864 
    865                                 // Continuer avec le batch suivant
    866 
    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'images
    928 
    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.nonce
    940 
    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 progression
    952 
    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 traitement
    966 
    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înes
    1030 
    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 à 1
    1048 
    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 maximum
    1082 
    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 champ
    1096 
    1097         function updateMaxWidthField() {
    1098 
    1099             if (checkbox.is(':checked')) {
    1100 
    1101                 // Activer le champ
    1102 
    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 champ
    1118 
    1119                 maxWidthCard.css({
    1120 
    1121                     'opacity': '0.6',
    1122 
    1123                     'pointer-events': 'auto' // Garder pointer-events pour que le CSS fonctionne
    1124 
    1125                 });
    1126 
    1127                 maxWidthInput.prop('disabled', true);
    1128 
    1129             }
    1130 
    1131         }
    1132 
    1133        
    1134 
    1135         // Initialiser l'état au chargement
    1136 
    1137         updateMaxWidthField();
    1138 
    1139        
    1140 
    1141         // Mettre à jour lors du changement de la checkbox
    1142 
    1143         checkbox.on('change', function() {
    1144 
    1145             updateMaxWidthField();
    1146 
    1147         });
    1148 
    1149        
    1150 
    1151         // Mettre à jour aussi lors du clic sur le toggle label
    1152 
    1153         $('.filikod-toggle-label[for="filikod_auto_resize_enabled"]').on('click', function() {
    1154 
    1155             // Petit délai pour laisser le temps à la checkbox de changer
    1156 
    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 onglets
    1172 
    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: altValue
    1238 
    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 notifications
    1328 
    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 secondes
    1346 
    1347         setTimeout(function() {
    1348 
    1349             notice.fadeOut(function() {
    1350 
    1351                 $(this).remove();
    1352 
    1353             });
    1354 
    1355687        }, 5000);
    1356 
    1357     }
    1358 
    1359    
    1360 
     688    }
     689   
    1361690})(jQuery);
    1362691
    1363 
    1364 
  • filikod/trunk/filikod.php

    r3468088 r3477168  
    99 * 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.
    1010
    11  * Version: 1.0.6
     11 * Version: 1.0.7
    1212
    1313 * Author: Filikod
     
    4545// Définir les constantes du plugin
    4646
    47 define('FILIKOD_VERSION', '1.0.6');
     47define('FILIKOD_VERSION', '1.0.7');
    4848
    4949define('FILIKOD_PLUGIN_URL', plugin_dir_url(__FILE__));
  • filikod/trunk/includes/accessibility/class-filikod-accessibility.php

    r3462774 r3477168  
    11<?php
    22
    3 
    4 
    53/**
    64
    7 
    8 
    95 * Classe Accessibility - Gère l'accessibilité des images
    106
    11 
    12 
    137 *
    148
    15 
    16 
    179 * Cette classe permet de :
    1810
    19 
    20 
    2111 * 1. Générer automatiquement des textes alternatifs (ALT) à partir du nom de fichier
    2212
    23 
    24 
    2513 * 2. Supprimer l'attribut title des images
    2614
    27 
    28 
    2915 * 3. Nettoyer les caractères spéciaux dans les textes ALT pour améliorer le SEO
    3016
    31 
    32 
    3317 * 4. Traiter les images existantes et futures
    3418
    35 
    36 
    3719 * 5. Respecter les textes ALT existants (ne pas les modifier sauf pour le nettoyage)
    3820
    39 
    40 
    4121 */
    4222
    4323
    4424
    45 
    46 
    47 
    48 
    4925if (!defined('ABSPATH')) {
    5026
    51 
    52 
    5327    exit;
    5428
    55 
    56 
    5729}
    5830
    5931
    6032
    61 
    62 
    63 
    64 
    6533class Filikod_Accessibility {
    6634
    67 
    68 
    69    
    70 
    71 
    72 
    73     /**
    74 
    75 
     35   
     36
     37    /**
    7638
    7739     * Instance du plugin principal
    7840
    79 
    80 
    81      */
    82 
    83 
     41     */
    8442
    8543    private $plugin;
    8644
    87 
    88 
    89    
    90 
    91 
    92 
    93     /**
    94 
    95 
     45   
     46
     47    /**
    9648
    9749     * Constructeur - Initialise la classe
    9850
    99 
    100 
    101      */
    102 
    103 
     51     */
    10452
    10553    public function __construct() {
    10654
    107 
    108 
    10955        $this->plugin = filikod();
    11056
    111 
    112 
    11357        $this->init_hooks();
    11458
    115 
    116 
    117     }
    118 
    119 
    120 
    121    
    122 
    123 
    124 
    125     /**
    126 
    127 
     59    }
     60
     61   
     62
     63    /**
    12864
    12965     * Initialiser les hooks WordPress
    13066
    131 
    132 
    133      *
    134 
    135 
     67     *
    13668
    13769     * Les hooks utilisés :
    13870
    139 
    140 
    14171     * - 'add_attachment' : Intercepte l'ajout d'une nouvelle image (après insertion dans la base)
    14272
    143 
    144 
    14573     * - 'wp_get_attachment_image_attributes' : Filtre les attributs des images dans le contenu
    14674
    147 
    148 
    149      */
    150 
    151 
     75     */
    15276
    15377    private function init_hooks() {
    15478
    155 
    156 
    15779        // Hook pour traiter les nouvelles images après leur insertion
    15880
    159 
    160 
    16181        add_action('add_attachment', array($this, 'process_new_attachment'), 10, 1);
    16282
    163 
    164 
    165        
    166 
    167 
     83       
    16884
    16985        // Hook pour filtrer les attributs des images dans le contenu
    17086
    171 
    172 
    17387        add_filter('wp_get_attachment_image_attributes', array($this, 'filter_image_attributes'), 10, 3);
    17488
    175 
    176 
    177     }
    178 
    179 
    180 
    181    
    182 
    183 
    184 
    185     /**
    186 
    187 
     89    }
     90
     91   
     92
     93    /**
    18894
    18995     * Traiter une nouvelle image après son insertion
    19096
    191 
    192 
    193      *
    194 
    195 
     97     *
    19698
    19799     * Cette fonction est appelée quand une nouvelle image est ajoutée à la bibliothèque média.
    198100
    199 
    200 
    201101     * Elle génère le texte ALT si nécessaire et nettoie les caractères spéciaux.
    202102
    203 
    204 
    205      *
    206 
    207 
     103     *
    208104
    209105     * @param int $attachment_id L'ID de l'attachment
    210106
    211 
    212 
    213      */
    214 
    215 
     107     */
    216108
    217109    public function process_new_attachment($attachment_id) {
    218110
    219 
    220 
    221111        // Vérifier que c'est une image
    222112
    223 
    224 
    225113        if (!wp_attachment_is_image($attachment_id)) {
    226114
    227 
    228 
    229115            return;
    230116
    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)
    245122        if (get_option('filikod_auto_alt', 'no') === 'yes') {
    246 
    247 
    248 
    249123            $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é
    265127        if (get_option('filikod_clean_alt_special_chars', 'no') === 'yes') {
    266 
    267 
    268 
    269128            $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    /**
    288140
    289141     * Filtrer les attributs des images dans le contenu
    290142
    291 
    292 
    293      *
    294 
    295 
     143     *
    296144
    297145     * Cette fonction supprime l'attribut title des images
    298146
    299 
    300 
    301147     * quand elles sont affichées dans le contenu.
    302148
    303 
    304 
    305      *
    306 
    307 
     149     *
    308150
    309151     * @param array $attr Les attributs de l'image
    310152
    311 
    312 
    313153     * @param object $attachment L'objet attachment
    314154
    315 
    316 
    317155     * @param string|array $size La taille de l'image
    318156
    319 
    320 
    321157     * @return array Les attributs modifiés
    322158
    323 
    324 
    325      */
    326 
    327 
     159     */
    328160
    329161    public function filter_image_attributes($attr, $attachment, $size) {
    330162
    331 
    332 
    333163        // Supprimer l'attribut title si l'option est activée
    334164
    335 
    336 
    337165        if (get_option('filikod_remove_title', 'no') === 'yes') {
    338166
    339 
    340 
    341167            unset($attr['title']);
    342168
    343 
    344 
    345         }
    346 
    347 
    348 
    349        
    350 
    351 
     169        }
     170
     171       
    352172
    353173        return $attr;
    354174
    355 
    356 
    357     }
    358 
    359 
    360 
    361    
    362 
    363 
    364 
    365     /**
    366 
    367 
     175    }
     176
     177   
     178
     179    /**
    368180
    369181     * Générer le texte alternatif à partir du nom de fichier
    370182
    371 
    372 
    373      *
    374 
    375 
     183     *
    376184
    377185     * Cette fonction :
    378186
    379 
    380 
    381187     * 1. Récupère le nom de fichier de l'image
    382188
    383 
    384 
    385189     * 2. Extrait le nom sans extension
    386190
    387 
    388 
    389191     * 3. Nettoie et formate le texte (remplace les tirets/underscores par des espaces)
    390192
    391 
    392 
    393193     * 4. Met en forme (première lettre en majuscule)
    394194
    395 
    396 
    397195     * 5. Sauvegarde uniquement si l'image n'a pas déjà un texte ALT
    398196
    399 
    400 
    401      *
    402 
    403 
     197     *
    404198
    405199     * @param int $attachment_id L'ID de l'attachment
    406200
    407 
    408 
    409      */
    410 
    411 
     201     */
    412202
    413203    private function generate_alt_text($attachment_id) {
    414204
    415 
    416 
    417205        // Récupérer le texte ALT actuel
    418206
    419 
    420 
    421207        $current_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
    422208
    423 
    424 
    425        
    426 
    427 
     209       
    428210
    429211        // Si l'image a déjà un texte ALT, ne pas le modifier
    430 
    431 
    432 
    433212        if (!empty($current_alt)) {
    434 
    435 
    436 
    437213            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 ) : '';
    453219        $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 {
    465230            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       
    508234
    509235        // Si l'option de nettoyage des caractères spéciaux est activée, l'appliquer maintenant
    510236
    511 
    512 
    513237        if (get_option('filikod_clean_alt_special_chars', 'no') === 'yes') {
    514238
    515 
    516 
    517239            // Remplacer les tirets, underscores, points par des espaces pour la lisibilité
    518240
    519 
    520 
    521241            $alt_text = str_replace(array('-', '_', '.'), ' ', $alt_text);
    522242
    523 
    524 
    525243           
    526244
    527 
    528 
    529245            // Supprimer les espaces multiples
    530246
    531 
    532 
    533247            $alt_text = preg_replace('/\s+/', ' ', $alt_text);
    534248
    535 
    536 
    537         }
    538 
    539 
    540 
    541        
    542 
    543 
     249        }
     250
     251       
    544252
    545253        // Mettre en forme : première lettre en majuscule, reste en minuscule
    546254
    547 
    548 
    549255        $alt_text = ucfirst(strtolower(trim($alt_text)));
    550256
    551 
    552 
    553        
    554 
    555 
     257       
    556258
    557259        // Si le texte est vide après nettoyage, utiliser un texte par défaut
    558260
    559 
    560 
    561261        if (empty($alt_text)) {
    562262
    563 
    564 
    565263            $alt_text = __('Image', 'filikod');
    566264
    567 
    568 
    569         }
    570 
    571 
    572 
    573        
    574 
    575 
     265        }
     266
     267       
    576268
    577269        // Sauvegarder le texte ALT
    578270
    579 
    580 
    581271        update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt_text);
    582 
    583272        if (class_exists('Filikod_Alt_Audit')) {
    584 
    585273            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    /**
    601294     * Nettoyer les caractères spéciaux du texte ALT
    602295
    603 
    604 
    605      *
    606 
    607 
     296     *
    608297
    609298     * Cette fonction supprime les caractères spéciaux (slash, anti-slash, tiret, etc.)
    610299
    611 
    612 
    613300     * du texte ALT pour améliorer le SEO.
    614301
    615 
    616 
    617      *
    618 
    619 
     302     *
    620303
    621304     * @param int $attachment_id L'ID de l'attachment
    622305
    623 
    624 
    625      */
    626 
    627 
     306     */
    628307
    629308    private function clean_alt_special_chars($attachment_id) {
    630309
    631 
    632 
    633310        // Récupérer le texte ALT actuel
    634311
    635 
    636 
    637312        $current_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
    638313
    639 
    640 
    641        
    642 
    643 
     314       
    644315
    645316        // Si l'image n'a pas de texte ALT, ne rien faire
    646317
    647 
    648 
    649318        if (empty($current_alt)) {
    650319
    651 
    652 
    653320            return;
    654321
    655 
    656 
    657         }
    658 
    659 
    660 
    661        
    662 
    663 
     322        }
     323
     324       
    664325
    665326        // Liste des caractères spéciaux à supprimer
    666327
    667 
    668 
    669328        // Slash (/), Anti-slash (\), Tirets (-), Underscores (_), et autres caractères non-alphanumériques
    670329
    671 
    672 
    673330        $special_chars = array('/', '\\', '-', '_', '|', '~', '`', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '+', '=', '{', '}', '[', ']', ':', ';', '"', "'", '<', '>', ',', '.', '?');
    674331
    675 
    676 
    677        
    678 
    679 
     332       
    680333
    681334        // Remplacer les caractères spéciaux par des espaces
    682335
    683 
    684 
    685336        $cleaned_alt = str_replace($special_chars, ' ', $current_alt);
    686337
    687 
    688 
    689        
    690 
    691 
     338       
    692339
    693340        // Supprimer les espaces multiples
    694341
    695 
    696 
    697342        $cleaned_alt = preg_replace('/\s+/', ' ', $cleaned_alt);
    698343
    699 
    700 
    701        
    702 
    703 
     344       
    704345
    705346        // Supprimer les espaces en début et fin
    706347
    707 
    708 
    709348        $cleaned_alt = trim($cleaned_alt);
    710349
    711 
    712 
    713        
    714 
    715 
     350       
    716351
    717352        // Si le texte est vide après nettoyage, utiliser le texte original
    718353
    719 
    720 
    721354        if (empty($cleaned_alt)) {
    722355
    723 
    724 
    725356            $cleaned_alt = $current_alt;
    726357
    727 
    728 
    729         }
    730 
    731 
    732 
    733        
    734 
    735 
     358        }
     359
     360       
    736361
    737362        // Sauvegarder uniquement si le texte a changé
    738363
    739 
    740 
    741364        if ($cleaned_alt !== $current_alt) {
    742365
    743 
    744 
    745366            update_post_meta($attachment_id, '_wp_attachment_image_alt', $cleaned_alt);
    746 
    747367            if (class_exists('Filikod_Alt_Audit')) {
    748 
    749368                Filikod_Alt_Audit::invalidate_cache();
    750 
    751369            }
    752 
    753         }
    754 
    755     }
    756 
    757 
    758 
    759    
    760 
    761 
    762 
    763     /**
    764 
    765 
     370        }
     371    }
     372
     373   
     374
     375    /**
    766376
    767377     * Obtenir le nombre total d'images à traiter
    768378
    769 
    770 
    771      *
    772 
    773 
     379     *
    774380
    775381     * @return int Le nombre total d'images
    776382
    777 
    778 
    779      */
    780 
    781 
     383     */
    782384
    783385    public function get_total_images_count() {
    784386
    785 
    786 
    787387        global $wpdb;
    788388
    789 
    790 
    791        
    792 
    793 
     389       
    794390
    795391        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for batch processing existing images, caching not applicable for dynamic counts
    796392
    797 
    798 
    799393        return (int) $wpdb->get_var(
    800394
    801 
    802 
    803395            $wpdb->prepare(
    804396
    805 
    806 
    807397                "SELECT COUNT(*)
    808398
    809 
    810 
    811399                FROM {$wpdb->posts}
    812400
    813 
    814 
    815401                WHERE post_type = 'attachment'
    816402
    817 
    818 
    819403                AND post_status = 'inherit'
    820404
    821 
    822 
    823405                AND post_mime_type LIKE %s",
    824406
    825 
    826 
    827407                'image/%'
    828408
    829 
    830 
    831409            )
    832410
    833 
    834 
    835411        );
    836412
    837 
    838 
    839     }
    840 
    841 
    842 
    843    
    844 
    845 
    846 
    847     /**
    848 
    849 
     413    }
     414
     415   
     416
     417    /**
    850418
    851419     * Traiter un batch d'images existantes pour l'accessibilité
    852420
    853 
    854 
    855      *
    856 
    857 
     421     *
    858422
    859423     * @param int $offset L'offset pour le batch
    860424
    861 
    862 
    863425     * @param int $batch_size La taille du batch
    864426
    865 
    866 
    867427     * @param int $total_processed Le nombre total d'images déjà traitées (pour cumul)
    868428
    869 
    870 
    871429     * @param int $total_skipped Le nombre total d'images ignorées (pour cumul)
    872430
    873 
    874 
    875431     * @return array Résultat du traitement du batch
    876432
    877 
    878 
    879      */
    880 
    881 
     433     */
    882434
    883435    public function process_existing_images_batch($offset = 0, $batch_size = 50, $total_processed = 0, $total_skipped = 0) {
    884436
    885 
    886 
    887437        global $wpdb;
    888438
    889 
    890 
    891        
    892 
    893 
     439       
    894440
    895441        $auto_alt_enabled = get_option('filikod_auto_alt', 'no') === 'yes';
    896 
    897 
    898 
    899442        $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';
    906444
    907445        // 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 ) {
    915447            return array(
    916 
    917 
    918 
    919448                'processed' => 0,
    920 
    921 
    922 
    923449                'skipped' => 0,
    924 
    925 
    926 
    927450                'total_processed' => $total_processed,
    928 
    929 
    930 
    931451                'total_skipped' => $total_skipped,
    932 
    933 
    934 
    935452                'finished' => true
    936 
    937 
    938 
    939453            );
    940 
    941 
    942 
    943         }
    944 
    945 
    946 
    947        
    948 
    949 
     454        }
     455
     456       
    950457
    951458        // Récupérer un batch d'images
    952459
    953 
    954 
    955460        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for batch processing existing images, caching not applicable for batch operations
    956461
    957 
    958 
    959462        $image_ids = $wpdb->get_col(
    960463
    961 
    962 
    963464            $wpdb->prepare(
    964465
    965 
    966 
    967466                "SELECT ID
    968467
    969 
    970 
    971468                FROM {$wpdb->posts}
    972469
    973 
    974 
    975470                WHERE post_type = 'attachment'
    976471
    977 
    978 
    979472                AND post_status = 'inherit'
    980473
    981 
    982 
    983474                AND post_mime_type LIKE %s
    984475
    985 
    986 
    987476                LIMIT %d OFFSET %d",
    988477
    989 
    990 
    991478                'image/%',
    992479
    993 
    994 
    995480                $batch_size,
    996481
    997 
    998 
    999482                $offset
    1000483
    1001 
    1002 
    1003484            )
    1004485
    1005 
    1006 
    1007486        );
    1008487
    1009 
    1010 
    1011        
    1012 
    1013 
     488       
    1014489
    1015490        if (empty($image_ids)) {
    1016491
    1017 
    1018 
    1019492            return array(
    1020493
    1021 
    1022 
    1023494                'processed' => 0,
    1024495
    1025 
    1026 
    1027496                'skipped' => 0,
    1028497
    1029 
    1030 
    1031498                'total_processed' => $total_processed,
    1032499
    1033 
    1034 
    1035500                'total_skipped' => $total_skipped,
    1036501
    1037 
    1038 
    1039502                'finished' => true
    1040503
    1041 
    1042 
    1043504            );
    1044505
    1045 
    1046 
    1047         }
    1048 
    1049 
    1050 
    1051        
    1052 
    1053 
     506        }
     507
     508       
    1054509
    1055510        $batch_processed = 0;
    1056511
    1057 
    1058 
    1059512        $batch_skipped = 0;
    1060513
    1061 
    1062 
    1063        
    1064 
    1065 
     514       
    1066515
    1067516        // Traiter chaque image du batch
    1068517
    1069 
    1070 
    1071518        foreach ($image_ids as $attachment_id) {
    1072519
    1073 
    1074 
    1075520            $was_modified = false;
    1076521
    1077 
    1078 
    1079522           
    1080523
    1081 
    1082 
    1083524            // Générer ALT si activé et si l'image n'en a pas
    1084525
    1085 
    1086 
    1087526            if ($auto_alt_enabled) {
    1088527
    1089 
    1090 
    1091528                $old_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
    1092529
    1093 
    1094 
    1095530                if (empty($old_alt)) {
    1096531
    1097 
    1098 
    1099532                    $this->generate_alt_text($attachment_id);
    1100533
    1101 
    1102 
    1103534                    $new_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
    1104535
    1105 
    1106 
    1107536                    if ($new_alt !== $old_alt) {
    1108537
    1109 
    1110 
    1111538                        $was_modified = true;
    1112539
    1113 
    1114 
    1115540                    }
    1116541
    1117 
    1118 
    1119542                }
    1120543
    1121 
    1122 
    1123544            }
    1124545
    1125 
    1126 
    1127546           
    1128547
    1129 
    1130 
    1131548            // Nettoyer les caractères spéciaux si activé
    1132 
    1133 
    1134 
    1135549            if ($clean_chars_enabled) {
    1136 
    1137 
    1138 
    1139550                $old_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
    1140 
    1141 
    1142 
    1143551                if (!empty($old_alt)) {
    1144 
    1145 
    1146 
    1147552                    $this->clean_alt_special_chars($attachment_id);
    1148 
    1149 
    1150 
    1151553                    $new_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
    1152 
    1153 
    1154 
    1155554                    if ($new_alt !== $old_alt) {
    1156 
    1157 
    1158 
    1159555                        $was_modified = true;
    1160 
    1161 
    1162 
    1163556                    }
    1164 
    1165 
    1166 
    1167557                }
    1168 
    1169 
    1170 
    1171558            }
    1172559
    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            }
    1178568
    1179569            if ($was_modified) {
    1180570
    1181 
    1182 
    1183571                $batch_processed++;
    1184572
    1185 
    1186 
    1187573            } else {
    1188574
    1189 
    1190 
    1191575                $batch_skipped++;
    1192576
    1193 
    1194 
    1195577            }
    1196578
    1197 
    1198 
    1199         }
    1200 
    1201 
    1202 
    1203        
    1204 
    1205 
     579        }
     580
     581       
    1206582
    1207583        // Cumuler les totaux
    1208584
    1209 
    1210 
    1211585        $total_processed += $batch_processed;
    1212586
    1213 
    1214 
    1215587        $total_skipped += $batch_skipped;
    1216588
    1217 
    1218 
    1219        
    1220 
    1221 
     589       
    1222590
    1223591        return array(
    1224592
    1225 
    1226 
    1227593            'processed' => $batch_processed,
    1228594
    1229 
    1230 
    1231595            'skipped' => $batch_skipped,
    1232596
    1233 
    1234 
    1235597            'total_processed' => $total_processed,
    1236598
    1237 
    1238 
    1239599            'total_skipped' => $total_skipped,
    1240600
    1241 
    1242 
    1243601            'finished' => false
    1244602
    1245 
    1246 
    1247603        );
    1248604
    1249 
    1250 
    1251     }
    1252 
    1253 
    1254 
    1255    
    1256 
    1257 
     605    }
     606
     607   
    1258608
    1259609}
     
    1261611
    1262612
    1263 
    1264 
    1265 
    1266 
  • filikod/trunk/includes/class-filikod-alt-audit.php

    r3468088 r3477168  
    479479            }
    480480        }
     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        }
    481489        $total       = count( $filtered_ids );
    482490        $per_page    = max( 1, min( 50, (int) $per_page ) );
  • filikod/trunk/includes/dashboard/class-filikod-dashboard.php

    r3462774 r3477168  
    11<?php
    2 
    32/**
    4 
    53 * Classe Dashboard - Gère la page principale du dashboard
    6 
    74 */
    85
    9 
    10 
    116if (!defined('ABSPATH')) {
    12 
    137    exit;
    14 
    158}
    169
    17 
    18 
    1910class Filikod_Dashboard {
    20 
    21    
    22 
     11   
    2312    private $plugin;
    24 
    25    
    26 
     13   
    2714    public function __construct() {
    28 
    2915        $this->plugin = filikod();
    30 
    3116        add_action( 'wp_ajax_filikod_save_alt', array( $this, 'ajax_save_alt' ) );
    32 
    33     }
    34 
    35    
    36 
    37     /**
    38 
     17    }
     18   
     19    /**
    3920     * Afficher la page du dashboard
    40 
    41      */
    42 
     21     */
    4322    public function display_dashboard_page() {
    44 
    4523        // Vérifier les permissions
    46 
    4724        if (!current_user_can('manage_options')) {
    48 
    4925            wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'filikod'));
    50 
    51         }
    52 
    53        
    54 
     26        }
     27       
    5528        // Inclure la vue du dashboard
    56 
    5729        include FILIKOD_PLUGIN_PATH . 'admin/views/dashboard.php';
    58 
    59     }
    60 
    61 
    62 
    63     /**
    64 
     30    }
     31
     32    /**
    6533     * Sauvegarde AJAX de l’ALT (Option B). Retourne new_status et counts pour mettre à jour le tableau.
    66 
    67      */
    68 
     34     */
    6935    public function ajax_save_alt() {
    70 
    7136        check_ajax_referer( 'filikod_save_alt', 'nonce' );
    72 
    7337        if ( ! current_user_can( 'manage_options' ) ) {
    74 
    7538            wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'filikod' ) ) );
    76 
    77         }
    78 
     39        }
    7940        $attachment_id = isset( $_POST['attachment_id'] ) ? (int) $_POST['attachment_id'] : 0;
    80 
    8141        if ( ! $attachment_id ) {
    82 
    8342            wp_send_json_error( array( 'message' => __( 'Invalid attachment.', 'filikod' ) ) );
    84 
    85         }
    86 
     43        }
    8744        $new_alt = isset( $_POST['filikod_alt_value'] ) ? sanitize_text_field( wp_unslash( $_POST['filikod_alt_value'] ) ) : '';
    88 
    8945        $new_alt = trim( $new_alt );
    90 
    9146        update_post_meta( $attachment_id, '_wp_attachment_image_alt', $new_alt );
    92 
    9347        if ( class_exists( 'Filikod_Alt_Audit' ) ) {
    94 
    9548            Filikod_Alt_Audit::invalidate_cache();
    96 
    97         }
    98 
     49        }
    9950        $audit   = class_exists( 'Filikod_Alt_Audit' ) ? Filikod_Alt_Audit::get_cached_audit() : array();
    100 
    10151        $items   = isset( $audit['items'] ) ? $audit['items'] : array();
    102 
    10352        $counts  = isset( $audit['counts'] ) ? $audit['counts'] : array( 'missing' => 0, 'generic' => 0, 'too_short' => 0, 'duplicate' => 0 );
    104 
    10553        $new_status = isset( $items[ $attachment_id ]['status'] ) ? $items[ $attachment_id ]['status'] : ( $new_alt === '' ? 'missing' : 'correct' );
    106 
    10754        wp_send_json_success( array(
    108 
    10955            'new_status' => $new_status,
    110 
    11156            'new_alt'    => $new_alt,
    112 
    11357            'counts'     => $counts,
    114 
    11558        ) );
    116 
    117     }
    118 
    119 
    120 
    121     /**
    122 
     59    }
     60
     61    /**
    12362     * Afficher la page ALT Audit (sauvegarde via AJAX, pas de POST/redirect).
    124 
    125      */
    126 
     63     */
    12764    public function display_alt_audit_page() {
    128 
    12965        if ( ! current_user_can( 'manage_options' ) ) {
    130 
    13166            wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'filikod' ) );
    132 
    133         }
    134 
     67        }
    13568        include FILIKOD_PLUGIN_PATH . 'admin/views/alt-audit.php';
    136 
    137     }
    138 
    139    
    140 
    141     /**
    142 
     69    }
     70
     71    /**
    14372     * Obtenir le nombre total d'images dans la bibliothèque média
    144 
    145      *
    146 
     73     *
    14774     * Cette fonction compte toutes les images (attachments de type image)
    148 
    14975     * présentes dans la bibliothèque média WordPress.
    150 
    15176     * Utilise une requête optimisée avec $wpdb pour de meilleures performances.
    152 
    153      *
    154 
     77     *
    15578     * @return int Le nombre d'images
    156 
    157      */
    158 
     79     */
    15980    public function get_total_images_count() {
    160 
    16181        global $wpdb;
    162 
    163        
    164 
     82       
    16583        // Requête SQL optimisée pour compter uniquement les images
    166 
    16784        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for dashboard statistics, caching not applicable for real-time counts
    168 
    16985        $count = $wpdb->get_var(
    170 
    17186            $wpdb->prepare(
    172 
    17387                "SELECT COUNT(*)
    174 
    17588                FROM {$wpdb->posts}
    176 
    17789                WHERE post_type = 'attachment'
    178 
    17990                AND post_status = 'inherit'
    180 
    18191                AND post_mime_type LIKE %s",
    182 
    18392                'image/%'
    184 
    18593            )
    186 
    187         );
    188 
    189        
    190 
     94        );
     95       
    19196        return (int) $count;
    192 
    193     }
    194 
    195    
    196 
    197     /**
    198 
     97    }
     98   
     99    /**
    199100     * Obtenir le nombre d'images avec texte alternatif (ALT)
    200 
    201      *
    202 
     101     *
    203102     * Cette fonction compte toutes les images qui ont un texte ALT défini.
    204 
    205103     * Utilise une requête SQL optimisée avec JOIN pour de meilleures performances.
    206 
    207      *
    208 
     104     *
    209105     * @return int Le nombre d'images avec ALT
    210 
    211      */
    212 
     106     */
    213107    public function get_images_with_alt_count() {
    214 
    215108        global $wpdb;
    216 
    217        
    218 
     109       
    219110        // Requête SQL optimisée avec JOIN pour compter les images avec ALT
    220 
    221111        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for dashboard statistics, caching not applicable for real-time counts
    222 
    223112        $count = $wpdb->get_var(
    224 
    225113            $wpdb->prepare(
    226 
    227114                "SELECT COUNT(DISTINCT p.ID)
    228 
    229115                FROM {$wpdb->posts} p
    230 
    231116                INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
    232 
    233117                WHERE p.post_type = 'attachment'
    234 
    235118                AND p.post_status = 'inherit'
    236 
    237119                AND p.post_mime_type LIKE %s
    238 
    239120                AND pm.meta_key = '_wp_attachment_image_alt'
    240 
    241121                AND pm.meta_value != ''
    242 
    243122                AND pm.meta_value IS NOT NULL",
    244 
    245123                'image/%'
    246 
    247124            )
    248 
    249         );
    250 
    251        
    252 
     125        );
     126       
    253127        return (int) $count;
    254 
    255     }
    256 
    257 
    258 
    259 
    260 
    261     /**
    262 
     128    }
     129
     130
     131    /**
    263132     * Obtenir le nombre d'images sans texte alternatif (ALT)
    264 
    265133     *
    266 
    267134     * Compte toutes les images qui n'ont pas de meta _wp_attachment_image_alt
    268 
    269135     * ou dont la valeur est vide.
    270 
    271136     *
    272 
    273137     * @return int Le nombre d'images sans ALT
    274 
    275      */
    276 
     138     */
    277139    public function get_images_without_alt_count() {
    278 
    279140        global $wpdb;
    280141
    281 
    282 
    283142        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    284 
    285143        $count = $wpdb->get_var(
    286 
    287144            $wpdb->prepare(
    288 
    289145                "SELECT COUNT(DISTINCT p.ID)
    290 
    291146                FROM {$wpdb->posts} p
    292 
    293147                LEFT JOIN {$wpdb->postmeta} pm
    294 
    295148                    ON p.ID = pm.post_id
    296 
    297149                    AND pm.meta_key = '_wp_attachment_image_alt'
    298 
    299150                WHERE p.post_type = 'attachment'
    300 
    301151                AND p.post_status = 'inherit'
    302 
    303152                AND p.post_mime_type LIKE %s
    304 
    305153                AND (pm.meta_value IS NULL OR pm.meta_value = '')",
    306 
    307154                'image/%'
    308 
    309155            )
    310 
    311         );
    312 
    313 
     156        );
    314157
    315158        return (int) $count;
    316 
    317     }
    318 
    319 
     159    }
    320160
    321161/**
    322 
    323162 * Obtenir le pourcentage d’images avec ALT
    324 
    325163 *
    326 
    327164 * @return int Pourcentage d’images avec ALT
    328 
    329165     *
    330 
    331166     * @return array
    332 
    333      */
    334 
     167     */
    335168    public function get_alt_audit_data() {
    336 
    337169        if ( ! class_exists( 'Filikod_Alt_Audit' ) ) {
    338 
    339170            return array(
    340 
    341171                'global_score'         => 0,
    342 
    343172                'global_score_precise' => 0.0,
    344 
    345173                'counts'               => array(
    346 
    347174                    'missing'   => 0,
    348 
    349175                    'generic'   => 0,
    350 
    351176                    'too_short' => 0,
    352 
    353177                    'duplicate' => 0,
    354 
    355178                    'correct'   => 0,
    356 
    357179                ),
    358 
    359180            );
    360 
    361         }
    362 
     181        }
    363182        $audit = Filikod_Alt_Audit::get_cached_audit();
    364 
    365183        $score = (int) $audit['global_score'];
    366 
    367184        $score_precise = isset( $audit['global_score_precise'] ) ? (float) $audit['global_score_precise'] : (float) $score;
    368 
    369185        return array(
    370 
    371186            'global_score'         => $score,
    372 
    373187            'global_score_precise' => $score_precise,
    374 
    375188            'counts'                => array(
    376 
    377189                'missing'   => (int) ( isset( $audit['counts']['missing'] ) ? $audit['counts']['missing'] : 0 ),
    378 
    379190                'generic'   => (int) ( isset( $audit['counts']['generic'] ) ? $audit['counts']['generic'] : 0 ),
    380 
    381191                'too_short' => (int) ( isset( $audit['counts']['too_short'] ) ? $audit['counts']['too_short'] : 0 ),
    382 
    383192                'duplicate' => (int) ( isset( $audit['counts']['duplicate'] ) ? $audit['counts']['duplicate'] : 0 ),
    384 
    385193                'correct'   => (int) ( isset( $audit['counts']['correct'] ) ? $audit['counts']['correct'] : 0 ),
    386 
    387194            ),
    388 
    389         );
    390 
    391     }
    392 
    393 
    394 
    395     /**
    396 
     195        );
     196    }
     197
     198    /**
    397199     * Obtenir le pourcentage d'images avec ALT
    398 
    399200     *
    400 
    401201     * @return int Pourcentage d'images avec ALT
    402 
    403      */
    404 
     202     */
    405203    public function get_alt_coverage_percentage() {
    406 
    407204        $total = $this->get_total_images_count();
    408205
    409 
    410 
    411206        if ( $total === 0 ) {
    412 
    413207            return 0;
    414 
    415         }
    416 
    417 
     208        }
    418209
    419210        $with_alt = $this->get_images_with_alt_count();
    420211
    421 
    422 
    423212        $percentage = ( $with_alt / $total ) * 100;
    424213
    425 
    426 
    427214        return (int) round( $percentage );
    428 
    429     }
    430 
    431 
    432 
    433 
    434 
    435    
    436 
    437     /**
    438 
     215    }
     216
     217
     218   
     219    /**
    439220     * Obtenir le poids total de toutes les images
    440 
    441      *
    442 
     221     *
    443222     * Cette fonction calcule la taille totale de tous les fichiers images
    444 
    445223     * dans la bibliothèque média (en octets, puis converti en format lisible).
    446 
    447224     * Utilise une requête optimisée avec pagination pour éviter les timeouts.
    448 
    449      *
    450 
     225     *
    451226     * @return array Tableau avec 'bytes' (taille en octets) et 'formatted' (taille formatée)
    452 
    453      */
    454 
     227     */
    455228    public function get_total_images_size() {
    456 
    457229        global $wpdb;
    458 
    459        
    460 
     230       
    461231        // Récupérer les IDs des images par batch pour éviter les timeouts
    462 
    463232        $total_size = 0;
    464 
    465233        $batch_size = 100;
    466 
    467234        $offset = 0;
    468 
    469        
    470 
     235       
    471236        while (true) {
    472 
    473237            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for dashboard statistics, caching not applicable for batch operations
    474 
    475238            $image_ids = $wpdb->get_col(
    476 
    477239                $wpdb->prepare(
    478 
    479240                    "SELECT ID
    480 
    481241                    FROM {$wpdb->posts}
    482 
    483242                    WHERE post_type = 'attachment'
    484 
    485243                    AND post_status = 'inherit'
    486 
    487244                    AND post_mime_type LIKE %s
    488 
    489245                    LIMIT %d OFFSET %d",
    490 
    491246                    'image/%',
    492 
    493247                    $batch_size,
    494 
    495248                    $offset
    496 
    497249                )
    498 
    499250            );
    500 
    501            
    502 
     251           
    503252            if ( empty( $image_ids ) ) {
    504 
    505253                break;
    506 
    507             }
    508 
    509            
    510 
     254            }
     255           
    511256            // Calculer la taille pour ce batch
    512 
    513257            foreach ( $image_ids as $image_id ) {
    514 
    515258                $file_path = get_attached_file( $image_id );
    516 
    517259                if ( $file_path && file_exists( $file_path ) ) {
    518 
    519260                    $file_size = filesize( $file_path );
    520 
    521261                    if ( $file_size !== false ) {
    522 
    523262                        $total_size += $file_size;
    524 
    525263                    }
    526 
    527264                }
    528 
    529             }
    530 
    531            
    532 
     265            }
     266           
    533267            $offset += $batch_size;
    534 
    535            
    536 
     268           
    537269            // Limite de sécurité pour éviter les boucles infinies
    538 
    539270            if ( $offset > 10000 ) {
    540 
    541271                break;
    542 
    543             }
    544 
    545         }
    546 
    547        
    548 
     272            }
     273        }
     274       
    549275        return array(
    550 
    551276            'bytes'    => $total_size,
    552 
    553277            'formatted' => $this->format_file_size( $total_size ),
    554 
    555         );
    556 
    557     }
    558 
    559    
    560 
    561     /**
    562 
     278        );
     279    }
     280   
     281    /**
    563282     * Obtenir le nombre d'images optimisées
    564 
    565      *
    566 
     283     *
    567284     * Cette fonction compte toutes les images qui ont été optimisées
    568 
    569285     * (qui ont la métadonnée filikod_resized).
    570 
    571      *
    572 
     286     *
    573287     * @return int Le nombre d'images optimisées
    574 
    575      */
    576 
     288     */
    577289    public function get_optimized_images_count() {
    578 
    579290        global $wpdb;
    580 
    581        
    582 
     291       
    583292        $count = 0;
    584 
    585293        $batch_size = 100;
    586 
    587294        $offset = 0;
    588 
    589        
    590 
     295       
    591296        while ( true ) {
    592 
    593297            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for dashboard statistics, caching not applicable for batch operations
    594 
    595298            $image_ids = $wpdb->get_col(
    596 
    597299                $wpdb->prepare(
    598 
    599300                    "SELECT ID
    600 
    601301                    FROM {$wpdb->posts}
    602 
    603302                    WHERE post_type = 'attachment'
    604 
    605303                    AND post_status = 'inherit'
    606 
    607304                    AND post_mime_type LIKE %s
    608 
    609305                    LIMIT %d OFFSET %d",
    610 
    611306                    'image/%',
    612 
    613307                    $batch_size,
    614 
    615308                    $offset
    616 
    617309                )
    618 
    619310            );
    620 
    621            
    622 
     311           
    623312            if ( empty( $image_ids ) ) {
    624 
    625313                break;
    626 
    627             }
    628 
    629            
    630 
     314            }
     315           
    631316            foreach ( $image_ids as $image_id ) {
    632 
    633317                $image_meta = wp_get_attachment_metadata( $image_id );
    634 
    635318               
    636 
    637319                // Vérifier aussi directement dans la base de données si les métadonnées ne sont pas trouvées
    638 
    639320                if ( ! is_array( $image_meta ) || ! isset( $image_meta['filikod_resized'] ) ) {
    640 
    641321                    // Essayer de récupérer directement depuis la base de données
    642 
    643322                    $meta_value = get_post_meta( $image_id, '_wp_attachment_metadata', true );
    644 
    645323                    if ( is_array( $meta_value ) && isset( $meta_value['filikod_resized'] ) ) {
    646 
    647324                        $image_meta = $meta_value;
    648 
    649325                    }
    650 
    651326                }
    652 
    653327               
    654 
    655328                if ( is_array( $image_meta ) && isset( $image_meta['filikod_resized'] ) && is_array( $image_meta['filikod_resized'] ) ) {
    656 
    657329                    $count++;
    658 
    659330                }
    660 
    661             }
    662 
    663            
    664 
     331            }
     332           
    665333            $offset += $batch_size;
    666 
    667            
    668 
     334           
    669335            if ( $offset > 10000 ) {
    670 
    671336                break;
    672 
    673             }
    674 
    675         }
    676 
    677        
    678 
     337            }
     338        }
     339       
    679340        return $count;
    680 
    681     }
    682 
    683    
    684 
    685     /**
    686 
     341    }
     342   
     343    /**
    687344     * Obtenir le poids total original des images
    688 
    689      *
    690 
     345     *
    691346     * Cette fonction calcule la taille totale originale de toutes les images.
    692 
    693347     * Pour les images optimisées, utilise la taille originale stockée dans les métadonnées.
    694 
    695348     * Pour les autres, utilise la taille actuelle du fichier.
    696 
    697      *
    698 
     349     *
    699350     * @return array Tableau avec 'bytes' (taille en octets) et 'formatted' (taille formatée)
    700 
    701      */
    702 
     351     */
    703352    public function get_original_total_size() {
    704 
    705353        global $wpdb;
    706 
    707        
    708 
     354       
    709355        $total_size = 0;
    710 
    711356        $batch_size = 100;
    712 
    713357        $offset = 0;
    714 
    715        
    716 
     358       
    717359        while ( true ) {
    718 
    719360            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for dashboard statistics, caching not applicable for batch operations
    720 
    721361            $image_ids = $wpdb->get_col(
    722 
    723362                $wpdb->prepare(
    724 
    725363                    "SELECT ID
    726 
    727364                    FROM {$wpdb->posts}
    728 
    729365                    WHERE post_type = 'attachment'
    730 
    731366                    AND post_status = 'inherit'
    732 
    733367                    AND post_mime_type LIKE %s
    734 
    735368                    LIMIT %d OFFSET %d",
    736 
    737369                    'image/%',
    738 
    739370                    $batch_size,
    740 
    741371                    $offset
    742 
    743372                )
    744 
    745373            );
    746 
    747            
    748 
     374           
    749375            if ( empty( $image_ids ) ) {
    750 
    751376                break;
    752 
    753             }
    754 
    755            
    756 
     377            }
     378           
    757379            foreach ( $image_ids as $image_id ) {
    758 
    759380                $image_meta = wp_get_attachment_metadata( $image_id );
    760 
    761381               
    762 
    763382                // Vérifier aussi directement dans la base de données si les métadonnées ne sont pas trouvées
    764 
    765383                if ( ! is_array( $image_meta ) || ! isset( $image_meta['filikod_resized'] ) ) {
    766 
    767384                    // Essayer de récupérer directement depuis la base de données
    768 
    769385                    $meta_value = get_post_meta( $image_id, '_wp_attachment_metadata', true );
    770 
    771386                    if ( is_array( $meta_value ) && isset( $meta_value['filikod_resized'] ) ) {
    772 
    773387                        $image_meta = $meta_value;
    774 
    775388                    }
    776 
    777389                }
    778 
    779390               
    780 
    781391                // Si l'image a été optimisée, utiliser la taille originale stockée
    782 
    783392                if ( is_array( $image_meta ) && isset( $image_meta['filikod_resized']['original_size'] ) ) {
    784 
    785393                    $original_size = (int) $image_meta['filikod_resized']['original_size'];
    786 
    787394                    if ( $original_size > 0 ) {
    788 
    789395                        $total_size += $original_size;
    790 
    791396                    } else {
    792 
    793397                        // Si la taille originale n'est pas valide, utiliser la taille actuelle
    794 
    795398                        $file_path = get_attached_file( $image_id );
    796 
    797399                        if ( $file_path && file_exists( $file_path ) ) {
    798 
    799400                            $file_size = filesize( $file_path );
    800 
    801401                            if ( $file_size !== false ) {
    802 
    803402                                $total_size += $file_size;
    804 
    805403                            }
    806 
    807404                        }
    808 
    809405                    }
    810 
    811406                } else {
    812 
    813407                    // Sinon, utiliser la taille actuelle du fichier
    814 
    815408                    $file_path = get_attached_file( $image_id );
    816 
    817409                    if ( $file_path && file_exists( $file_path ) ) {
    818 
    819410                        $file_size = filesize( $file_path );
    820 
    821411                        if ( $file_size !== false ) {
    822 
    823412                            $total_size += $file_size;
    824 
    825413                        }
    826 
    827414                    }
    828 
    829415                }
    830 
    831             }
    832 
    833            
    834 
     416            }
     417           
    835418            $offset += $batch_size;
    836 
    837            
    838 
     419           
    839420            if ( $offset > 10000 ) {
    840 
    841421                break;
    842 
    843             }
    844 
    845         }
    846 
    847        
    848 
     422            }
     423        }
     424       
    849425        return array(
    850 
    851426            'bytes'    => $total_size,
    852 
    853427            'formatted' => $this->format_file_size( $total_size ),
    854 
    855         );
    856 
    857     }
    858 
    859    
    860 
    861     /**
    862 
     428        );
     429    }
     430   
     431    /**
    863432     * Obtenir le poids total des images après optimisation
    864 
    865      *
    866 
     433     *
    867434     * Cette fonction calcule la taille totale actuelle de tous les fichiers images.
    868 
    869      *
    870 
     435     *
    871436     * @return array Tableau avec 'bytes' (taille en octets) et 'formatted' (taille formatée)
    872 
    873      */
    874 
     437     */
    875438    public function get_optimized_total_size() {
    876 
    877439        // Utilise la même méthode que get_total_images_size car elle retourne déjà la taille actuelle
    878 
    879440        return $this->get_total_images_size();
    880 
    881     }
    882 
    883 
    884 
    885     /**
    886 
     441    }
     442
     443    /**
    887444     * Obtenir le poids moyen par image à partir du total et du nombre d'images.
    888 
    889445     *
    890 
    891446     * @param int $total_bytes Taille totale en octets.
    892 
    893447     * @param int $total_count Nombre d'images.
    894 
    895448     * @return array{bytes: int, formatted: string}
    896 
    897      */
    898 
     449     */
    899450    public function get_average_image_size_from( $total_bytes, $total_count ) {
    900 
    901451        $count     = max( 1, (int) $total_count );
    902 
    903452        $avg_bytes = (int) round( (int) $total_bytes / $count );
    904 
    905453        return array(
    906 
    907454            'bytes'    => $avg_bytes,
    908 
    909455            'formatted' => $this->format_file_size( $avg_bytes ),
    910 
    911         );
    912 
    913     }
    914 
    915 
    916 
    917     /**
    918 
     456        );
     457    }
     458
     459    /**
    919460     * Obtenir le pourcentage d'images optimisées
    920 
    921      *
    922 
     461     *
    923462     * @return float Le pourcentage d'images optimisées (0-100)
    924 
    925      */
    926 
     463     */
    927464    public function get_optimization_percentage() {
    928 
    929465        $total_images = $this->get_total_images_count();
    930 
    931        
    932 
     466       
    933467        if ( $total_images === 0 ) {
    934 
    935468            return 0;
    936 
    937         }
    938 
    939        
    940 
     469        }
     470       
    941471        $optimized_images = $this->get_optimized_images_count();
    942 
    943        
    944 
     472       
    945473        return round( ( $optimized_images / $total_images ) * 100, 1 );
    946 
    947     }
    948 
    949    
    950 
    951     /**
    952 
     474    }
     475   
     476    /**
    953477     * Formater la taille d'un fichier en format lisible
    954 
    955      *
    956 
     478     *
    957479     * Convertit les octets en KB, MB, GB selon la taille.
    958 
    959      *
    960 
     480     *
    961481     * @param int $bytes La taille en octets
    962 
    963482     * @return string La taille formatée (ex: "1.5 MB")
    964 
    965      */
    966 
     483     */
    967484    private function format_file_size( $bytes ) {
    968 
    969485        if ( $bytes >= 1073741824 ) {
    970 
    971486            // Gigabytes
    972 
    973487            return number_format_i18n( $bytes / 1073741824, 2 ) . ' GB';
    974 
    975488        } elseif ( $bytes >= 1048576 ) {
    976 
    977489            // Megabytes
    978 
    979490            return number_format_i18n( $bytes / 1048576, 2 ) . ' MB';
    980 
    981491        } elseif ( $bytes >= 1024 ) {
    982 
    983492            // Kilobytes
    984 
    985493            return number_format_i18n( $bytes / 1024, 2 ) . ' KB';
    986 
    987494        } else {
    988 
    989495            // Bytes
    990 
    991496            return number_format_i18n( $bytes, 0 ) . ' ' . __( 'bytes', 'filikod' );
    992 
    993         }
    994 
    995     }
    996 
     497        }
     498    }
    997499}
    998500
    999 
    1000 
  • filikod/trunk/readme.txt

    r3468111 r3477168  
    22Contributors: filikod
    33Plugin URI: https://filikod.com/
    4 Tags: alt text, alt audit, bulk alt text, image alt, accessibility
     4Tags: alt text, accessibility, image seo, alt audit, media library
    55Requires at least: 5.8
    66Tested up to: 6.9
    77Requires PHP: 7.4
    8 Stable tag: 1.0.6
     8Stable tag: 1.0.7
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1111
    12 Scan your media library, see your ALT Quality Score, and fix missing ALT text in minutes.
     12Audit 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.
    1313
    1414== Description ==
    1515
    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) =
     16Most WordPress sites have broken ALT text, and most site owners have no way to measure it.
     17
     18Missing, 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
     22No AI black box. No external API. No credits to buy. No complex setup.
     23You 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
     31Filikod 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
     38You 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
     40This 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 =
    5745
    5846Editing ALT text one image at a time is the reason most people never finish.
    5947
    6048Filikod 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
     55Built for real media libraries with thousands of images, not tidy demos with five.
     56
     57---
     58
     59= Context-Based Editing =
     60
     61Good ALT text requires context. What is this image actually about on this page?
     62
     63Filikod 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.
    7564
    7665---
     
    7867= Controlled Automation (optional) =
    7968
    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.
     69Filikod 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
     76Execution 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
     82Most ALT text plugins do one thing: auto-generate ALT on upload, often using AI and a paid credit system.
     83
     84Filikod 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
     98If you want AI to write your ALT text automatically, Filikod is not that.
     99If 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
     105Filikod 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
    94112
    95113---
     
    97115= Compatibility =
    98116
    99  Works with any theme
    100 • Compatible with Elementor, Divi, Gutenberg, WPBakery and most major builders
    101 • Single site & multisite supported
    102 • 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
    103121
    104122---
     
    106124== Installation ==
    107125
    108 1. Upload the plugin to `/wp-content/plugins/` (or install from Plugins → Add New)
     1261. Install from **Plugins > Add New** (search "Filikod") or upload the plugin zip manually
    1091272. Activate Filikod
    110 3. Go to Filikod → Dashboard
    111 4. Review your ALT Quality Score
    112 5. Fix issues using the bulk tools
     1283. Go to **Filikod > Dashboard** to see your ALT Quality Score
     1294. Go to **Filikod > ALT Audit** to find and fix issues by type
     1305. Configure optional automation in **Filikod > Settings**
    113131
    114132---
     
    117135
    118136= 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
     138Yes, 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
     142No. 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
     146Image 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
     150Yes. 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
     154No. 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
     158It 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.
    127159
    128160= 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
     162Yes. 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
     166Yes. 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
     170Yes. Filikod supports WordPress multisite.
     171
     172= Is Filikod an image optimizer like Smush or Imagify? =
     173
     174No. 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.
    139175
    140176= Where can I find documentation? =
    141 Documentation and updates are available here:
    142 [filikod.com](https://filikod.com/)
     177
     178Full documentation is available at [filikod.com](https://filikod.com/).
     179
    143180
    144181---
     
    146183== Screenshots ==
    147184
    148 1. ALT Quality Score dashboard overview
    149 2. Missing ALT detection tab
    150 3. Duplicated ALT detection tab
    151 4. Inline bulk ALT editing interface
     1851.  **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
     1862. **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.
     1873. **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.
     1884. **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.
    152189
    153190---
    154191
    155192== 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)
    156198
    157199= 1.0.6 =
     
    196238== Upgrade Notice ==
    197239
     240= 1.0.7 =
     241Extended 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
    198243= 1.0.6 =
    199244Positioning and documentation update focused on ALT audit & bulk management. and Improves “Generic ALT” detection.
Note: See TracChangeset for help on using the changeset viewer.