Plugin Directory

Changeset 3397460


Ignore:
Timestamp:
11/17/2025 05:44:07 PM (4 months ago)
Author:
redshape
Message:

1.1.0

Location:
redshape-easy-labels
Files:
25 added
8 edited

Legend:

Unmodified
Added
Removed
  • redshape-easy-labels/trunk/assets/css/admin.css

    r3397353 r3397460  
    13241324    margin: 0;
    13251325}
     1326
     1327/* ========================================
     1328   QUICK CREATE LABEL MODAL - v1.1.0
     1329   ======================================== */
     1330
     1331/* Modal overlay */
     1332.redshape-modal {
     1333    position: fixed;
     1334    top: 0;
     1335    left: 0;
     1336    width: 100%;
     1337    height: 100%;
     1338    z-index: 999999;
     1339    display: none;
     1340}
     1341
     1342.redshape-modal-overlay {
     1343    position: absolute;
     1344    top: 0;
     1345    left: 0;
     1346    width: 100%;
     1347    height: 100%;
     1348    background: rgba(0, 0, 0, 0.7);
     1349    -webkit-backdrop-filter: blur(2px);
     1350    backdrop-filter: blur(2px);
     1351}
     1352
     1353/* Modal content */
     1354.redshape-modal-content {
     1355    position: absolute;
     1356    top: 50%;
     1357    left: 50%;
     1358    transform: translate(-50%, -50%);
     1359    background: #fff;
     1360    border-radius: 8px;
     1361    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
     1362    width: 90%;
     1363    max-width: 500px;
     1364    max-height: 90vh;
     1365    overflow-y: auto;
     1366    animation: modalSlideIn 0.2s ease-out;
     1367}
     1368
     1369@keyframes modalSlideIn {
     1370    from {
     1371        opacity: 0;
     1372        transform: translate(-50%, -48%);
     1373    }
     1374    to {
     1375        opacity: 1;
     1376        transform: translate(-50%, -50%);
     1377    }
     1378}
     1379
     1380/* Modal header */
     1381.redshape-modal-header {
     1382    display: flex;
     1383    align-items: center;
     1384    justify-content: space-between;
     1385    padding: 20px 24px;
     1386    border-bottom: 1px solid #e5e5e5;
     1387    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
     1388}
     1389
     1390.redshape-modal-header h2 {
     1391    margin: 0;
     1392    font-size: 20px;
     1393    font-weight: 600;
     1394    color: #1d2327;
     1395}
     1396
     1397.redshape-modal-close {
     1398    background: none;
     1399    border: none;
     1400    font-size: 28px;
     1401    line-height: 1;
     1402    color: #646970;
     1403    cursor: pointer;
     1404    padding: 0;
     1405    width: 32px;
     1406    height: 32px;
     1407    display: flex;
     1408    align-items: center;
     1409    justify-content: center;
     1410    border-radius: 4px;
     1411    transition: all 0.2s ease;
     1412}
     1413
     1414.redshape-modal-close:hover {
     1415    background: #f0f0f1;
     1416    color: #1d2327;
     1417}
     1418
     1419/* Modal body */
     1420.redshape-modal-body {
     1421    padding: 24px;
     1422}
     1423
     1424/* Form fields */
     1425.redshape-form-field {
     1426    margin-bottom: 20px;
     1427}
     1428
     1429.redshape-form-field label {
     1430    display: block;
     1431    margin-bottom: 8px;
     1432    font-weight: 600;
     1433    color: #1d2327;
     1434    font-size: 14px;
     1435}
     1436
     1437.redshape-form-field input[type="text"] {
     1438    width: 100%;
     1439    padding: 10px 12px;
     1440    border: 1px solid #dcdcde;
     1441    border-radius: 4px;
     1442    font-size: 14px;
     1443    transition: all 0.2s ease;
     1444}
     1445
     1446.redshape-form-field input[type="text"]:focus {
     1447    border-color: #2271b1;
     1448    outline: none;
     1449    box-shadow: 0 0 0 3px rgba(34, 113, 177, 0.1);
     1450}
     1451
     1452/* Color picker */
     1453.redshape-color-picker {
     1454    display: flex;
     1455    align-items: center;
     1456    gap: 12px;
     1457}
     1458
     1459.redshape-color-picker input[type="color"] {
     1460    width: 60px;
     1461    height: 40px;
     1462    border: 1px solid #dcdcde;
     1463    border-radius: 4px;
     1464    cursor: pointer;
     1465    padding: 0;
     1466}
     1467
     1468.redshape-color-picker .color-preview {
     1469    width: 40px;
     1470    height: 40px;
     1471    border-radius: 20px;
     1472    border: 3px solid #fff;
     1473    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
     1474    transition: transform 0.2s ease;
     1475}
     1476
     1477.redshape-color-picker input[type="color"]:hover + .color-preview {
     1478    transform: scale(1.1);
     1479}
     1480
     1481/* Modal info */
     1482.redshape-modal-info {
     1483    display: flex;
     1484    align-items: flex-start;
     1485    gap: 10px;
     1486    padding: 12px;
     1487    background: #e7f5fe;
     1488    border-left: 4px solid #2271b1;
     1489    border-radius: 4px;
     1490    margin-top: 20px;
     1491}
     1492
     1493.redshape-modal-info .dashicons {
     1494    color: #2271b1;
     1495    flex-shrink: 0;
     1496    margin-top: 2px;
     1497}
     1498
     1499.redshape-modal-info p {
     1500    margin: 0;
     1501    font-size: 13px;
     1502    color: #1d2327;
     1503    line-height: 1.5;
     1504}
     1505
     1506/* Modal footer */
     1507.redshape-modal-footer {
     1508    display: flex;
     1509    align-items: center;
     1510    justify-content: flex-end;
     1511    gap: 12px;
     1512    padding: 16px 24px;
     1513    border-top: 1px solid #e5e5e5;
     1514    background: #f8f9fa;
     1515}
     1516
     1517.redshape-modal-footer .button {
     1518    padding: 8px 16px;
     1519    font-size: 14px;
     1520    height: auto;
     1521    line-height: 1.4;
     1522}
     1523
     1524/* Create new label option in dropdown */
     1525.create-new-label-option {
     1526    cursor: pointer !important;
     1527    border-bottom: 2px solid #ddd !important;
     1528    margin-bottom: 8px !important;
     1529    padding: 8px 10px !important;
     1530    background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%) !important;
     1531    border-radius: 4px 4px 0 0 !important;
     1532    transition: all 0.2s ease !important;
     1533    position: relative !important;
     1534}
     1535
     1536.create-new-label-option:hover {
     1537    background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%) !important;
     1538    transform: translateY(-1px) !important;
     1539    box-shadow: 0 2px 8px rgba(0, 163, 42, 0.2) !important;
     1540}
     1541
     1542.create-new-label-option:active {
     1543    transform: translateY(0) !important;
     1544}
     1545
     1546.create-new-label-option .label-color-preview {
     1547    display: flex !important;
     1548    align-items: center !important;
     1549    justify-content: center !important;
     1550    width: 18px !important;
     1551    height: 18px !important;
     1552    border-radius: 50% !important;
     1553    box-shadow: 0 2px 4px rgba(0, 163, 42, 0.3) !important;
     1554}
     1555
     1556.create-new-label-option .label-name {
     1557    font-weight: 600 !important;
     1558    color: #16a34a !important;
     1559    font-size: 13px !important;
     1560}
     1561
     1562/* Responsive */
     1563@media screen and (max-width: 782px) {
     1564    .redshape-modal-content {
     1565        width: 95%;
     1566        max-width: none;
     1567    }
     1568   
     1569    .redshape-modal-header,
     1570    .redshape-modal-body,
     1571    .redshape-modal-footer {
     1572        padding: 16px;
     1573    }
     1574   
     1575    .redshape-modal-footer {
     1576        flex-direction: column;
     1577        gap: 8px;
     1578    }
     1579   
     1580    .redshape-modal-footer .button {
     1581        width: 100%;
     1582    }
     1583}
     1584
  • redshape-easy-labels/trunk/assets/js/admin.js

    r3397353 r3397460  
    250250       
    251251        // Event listener for dropdowns (inline, column and metabox)
    252         $(document).on('click.contentlabels', '.labels-dropdown-item', function(e) {
     252        // Exclude "Create New Label" option from normal label click handling
     253        $(document).on('click.contentlabels', '.labels-dropdown-item:not(.create-new-label-option)', function(e) {
    253254            handleDropdownItemClick.call(this, e);
    254255        });
     
    296297            dropdown.removeClass('show');
    297298        } else {
    298             // Load available labels if needed
    299             var needsLoading = dropdown.children().length === 0;
     299            // ✅ Load available labels if needed OR if marked as invalidated
     300            var needsLoading = dropdown.children().length === 0 || dropdown.data('invalidated') === true;
    300301           
    301302            // Position dropdown BEFORE showing it to avoid visual jump
     
    304305           
    305306            if (needsLoading) {
     307                // Clear invalidated flag
     308                dropdown.data('invalidated', false);
    306309                loadLabelsInDropdown(dropdown, postId);
    307310                // Wait for content to load, then position and show
     
    553556                    var currentLabels = getCurrentLabels(wrapper);
    554557                   
     558                    // ✅ ALWAYS add "Create New Label" option at the top, even if no labels exist
     559                    html += '<div class="labels-dropdown-items-container">';
     560                    html += '<div class="create-new-label-option labels-dropdown-item" data-label-name="create new label">' +
     561                           '<span class="label-name" style="font-weight: 600; color: #00a32a;">' + __('+ Create New Label') + '</span>' +
     562                           '</div>';
     563                   
    555564                    if (response.data.length === 0) {
    556                         html = '<div style="padding: 10px; text-align: center; color: #666;">' + __('No labels available') + '</div>';
     565                        // No labels exist yet, show message
     566                        html += '<div style="padding: 10px; text-align: center; color: #999; font-size: 12px; font-style: italic; border-top: 1px solid #ddd; margin-top: 4px; padding-top: 8px;">' + __('No labels available. Create your first label above!') + '</div>';
     567                        html += '</div>'; // Close items container
    557568                    } else {
    558569                        // ✅ NEW: Add search field
    559                         html += '<div class="labels-search-box" style="padding: 8px; border-bottom: 1px solid #ddd; background: #f9f9f9;">' +
     570                        html = '<div class="labels-search-box" style="padding: 8px; border-bottom: 1px solid #ddd; background: #f9f9f9;">' +
    560571                               '<input type="text" class="labels-search-input" placeholder="' + __('Search labels...') + '" ' +
    561572                               'style="width: 100%; padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; box-sizing: border-box;">' +
    562                                '</div>';
     573                               '</div>' + html; // Prepend search to existing HTML
    563574                       
    564                         // Container for items (to be filtered)
    565                         html += '<div class="labels-dropdown-items-container">';
     575                        // Container for items already started above
    566576                       
    567577                        var hasAvailableLabels = false;
     
    584594                        html += '</div>'; // Close items container
    585595                       
    586                         // If all labels are already assigned, show a message
     596                        // If all labels are already assigned, show a message (but keep create option)
    587597                        if (!hasAvailableLabels) {
    588                             html = '<div style="padding: 10px; text-align: center; color: #666;">' + __('All labels are already assigned') + '</div>';
     598                            html += '<div style="padding: 10px; text-align: center; color: #999; font-size: 12px; font-style: italic;">' + __('All labels are already assigned') + '</div>';
    589599                        }
    590600                    }
     
    806816            if (existingLabel.length === 0) {
    807817                if (isMetabox) {
    808                     // For metabox use dedicated function
    809                     addLabelToMetabox(labelId, labelName, labelColor, wrapper);
     818                    // For metabox use dedicated function - pass true to skip counter update (already done by main call)
     819                    addLabelToMetabox(labelId, labelName, labelColor, wrapper, true);
    810820                } else {
    811821                    // For table and inline use normal badge
     
    843853
    844854    // Function to update filter counters in real time
     855    // ✅ DEBOUNCE TIMER per evitare chiamate multiple
     856    var filterCounterUpdateTimer = null;
     857   
    845858    function updateFilterCounter(labelId, increment) {
    846         // Reload all counts from server to have accurate data and updated percentages
    847         updateAllFilterCounts();
     859        // ✅ Cancella il timer precedente se esiste
     860        if (filterCounterUpdateTimer) {
     861            clearTimeout(filterCounterUpdateTimer);
     862        }
     863       
     864        // ✅ Imposta nuovo timer - chiama updateAllFilterCounts UNA SOLA VOLTA dopo 300ms
     865        filterCounterUpdateTimer = setTimeout(function() {
     866            updateAllFilterCounts();
     867            filterCounterUpdateTimer = null;
     868        }, 300);
    848869    }
    849870   
     
    10281049   
    10291050    // Specific function to add labels to meta box
    1030     function addLabelToMetabox(labelId, labelName, labelColor, wrapper) {
     1051    function addLabelToMetabox(labelId, labelName, labelColor, wrapper, skipCounterUpdate) {
    10311052        // Check if label already exists (only check badges, not dropdown items)
    10321053        var existingBadge = wrapper.find('.content-label-badge[data-label-id="' + labelId + '"]');
     
    10511072        var postId = wrapper.data('post-id') || wrapper.find('.metabox-add-btn').data('post-id');
    10521073       
    1053         // Save to database via AJAX
     1074        // ✅ If skipCounterUpdate is true, it means we're being called from sync
     1075        // The AJAX was already done by the main call, so just update UI
     1076        if (skipCounterUpdate) {
     1077            // Just create and insert badge without AJAX
     1078            var badge = $('<span>')
     1079                .addClass('content-label-badge')
     1080                .attr('data-label-id', labelId)
     1081                .attr('data-post-id', postId)
     1082                .attr('data-label-name', labelName)
     1083                .attr('data-label-color', labelColor)
     1084                .css('background-color', labelColor)
     1085                .text(labelName);
     1086           
     1087            var addBtn = wrapper.find('.metabox-add-btn');
     1088            addBtn.before(badge);
     1089           
     1090            wrapper.find('.labels-dropdown-item[data-label-id="' + labelId + '"]').addClass('selected');
     1091            updateMetaboxHiddenField(wrapper);
     1092           
     1093            var globalDropdownId = 'labels-dropdown-' + postId;
     1094            $('#' + globalDropdownId).removeClass('show').empty();
     1095            wrapper.find('.labels-dropdown').removeClass('show').empty();
     1096            return;
     1097        }
     1098       
     1099        // Direct call from user action: do AJAX to save to database
    10541100        wrapper.addClass('loading');
    10551101        safeAjax({
     
    10951141                   
    10961142                    // Update counter in quick filters
    1097                     updateFilterCounter(labelId, 1);
     1143                    // BUT ONLY if not called from sync (to avoid double counting)
     1144                    if (!skipCounterUpdate) {
     1145                        updateFilterCounter(labelId, 1);
     1146                    }
    10981147                   
    10991148                    showMessage(__('Label added successfully'), 'success');
     
    15081557            initFilterDragAndDrop();
    15091558        }, 500);
     1559    });
     1560   
     1561    /**
     1562     * ========================================
     1563     * QUICK CREATE LABEL - v1.1.0
     1564     * ========================================
     1565     * Universal modal for creating labels from content
     1566     * Works in both post list and meta box
     1567     */
     1568   
     1569    // Create modal HTML (will be added to DOM once)
     1570    function createQuickLabelModal() {
     1571        if ($('#quick-create-label-modal').length > 0) {
     1572            return; // Modal already exists
     1573        }
     1574       
     1575        const modalHtml = `
     1576            <div id="quick-create-label-modal" class="redshape-modal" style="display: none;">
     1577                <div class="redshape-modal-overlay"></div>
     1578                <div class="redshape-modal-content">
     1579                    <div class="redshape-modal-header">
     1580                        <h2>${__('Create New Label')}</h2>
     1581                        <button type="button" class="redshape-modal-close">&times;</button>
     1582                    </div>
     1583                    <div class="redshape-modal-body">
     1584                        <div class="redshape-form-field">
     1585                            <label for="quick-label-name">${__('Label Name')}</label>
     1586                            <input type="text" id="quick-label-name" class="widefat" placeholder="${__('Enter label name...')}" />
     1587                        </div>
     1588                        <div class="redshape-form-field">
     1589                            <label for="quick-label-color">${__('Label Color')}</label>
     1590                            <div class="redshape-color-picker">
     1591                                <input type="color" id="quick-label-color" value="#007cba" />
     1592                                <span class="color-preview" style="background-color: #007cba;"></span>
     1593                            </div>
     1594                        </div>
     1595                        <div class="redshape-modal-info">
     1596                            <span class="dashicons dashicons-info"></span>
     1597                            <p>${__('The label will be created and immediately available for all content types.')}</p>
     1598                        </div>
     1599                    </div>
     1600                    <div class="redshape-modal-footer">
     1601                        <button type="button" class="button button-secondary redshape-modal-cancel">${__('Cancel')}</button>
     1602                        <button type="button" class="button button-primary" id="quick-create-label-btn">${__('Create Label')}</button>
     1603                        <button type="button" class="button button-primary" id="quick-create-assign-label-btn" style="display: none;">${__('Create and Assign')}</button>
     1604                    </div>
     1605                </div>
     1606            </div>
     1607        `;
     1608       
     1609        $('body').append(modalHtml);
     1610       
     1611        // Update color preview on color change
     1612        $('#quick-label-color').on('input', function() {
     1613            $(this).siblings('.color-preview').css('background-color', $(this).val());
     1614        });
     1615       
     1616        // Close modal on overlay click
     1617        $('.redshape-modal-overlay, .redshape-modal-close, .redshape-modal-cancel').on('click', function() {
     1618            closeQuickLabelModal();
     1619        });
     1620       
     1621        // Enter key submits form
     1622        $('#quick-label-name').on('keypress', function(e) {
     1623            if (e.which === 13) {
     1624                e.preventDefault();
     1625                $('#quick-create-label-btn:visible, #quick-create-assign-label-btn:visible').trigger('click');
     1626            }
     1627        });
     1628    }
     1629   
     1630    // Open modal
     1631    function openQuickLabelModal(postId) {
     1632        createQuickLabelModal();
     1633       
     1634        const $modal = $('#quick-create-label-modal');
     1635       
     1636        // Reset form
     1637        $('#quick-label-name').val('');
     1638        $('#quick-label-color').val('#007cba');
     1639        $('.color-preview').css('background-color', '#007cba');
     1640       
     1641        // Show appropriate button based on context
     1642        if (postId && postId > 0) {
     1643            // Meta box context - show "Create and Assign"
     1644            $('#quick-create-label-btn').hide();
     1645            $('#quick-create-assign-label-btn').show().data('post-id', postId);
     1646        } else {
     1647            // Post list context - show "Create Label"
     1648            $('#quick-create-label-btn').show();
     1649            $('#quick-create-assign-label-btn').hide();
     1650        }
     1651       
     1652        // Show modal with animation
     1653        $modal.fadeIn(200);
     1654        $('#quick-label-name').focus();
     1655    }
     1656   
     1657    // Close modal
     1658    function closeQuickLabelModal() {
     1659        $('#quick-create-label-modal').fadeOut(200);
     1660       
     1661        // ✅ Invalidate the dropdown that opened this modal
     1662        if (window.quickLabelSourceDropdown) {
     1663            window.quickLabelSourceDropdown.data('invalidated', true);
     1664            window.quickLabelSourceDropdown = null;
     1665        }
     1666       
     1667        // ✅ Also close and invalidate any other open dropdowns
     1668        $('.labels-dropdown.show').each(function() {
     1669            $(this).removeClass('show').data('invalidated', true);
     1670        });
     1671    }
     1672   
     1673    // Handle label creation (simple create)
     1674    $(document).on('click', '#quick-create-label-btn', function() {
     1675        const $btn = $(this);
     1676        const labelName = $('#quick-label-name').val().trim();
     1677        const labelColor = $('#quick-label-color').val();
     1678       
     1679        if (!labelName) {
     1680            showMessage(__('Il nome dell\'etichetta è obbligatorio'), 'error');
     1681            $('#quick-label-name').focus();
     1682            return;
     1683        }
     1684       
     1685        // Disable button and show loading
     1686        $btn.prop('disabled', true).text(__('Creating label...'));
     1687       
     1688        safeAjax({
     1689            url: redshapeEasylabelsAjax.ajax_url,
     1690            type: 'POST',
     1691            data: {
     1692                action: 'redshape_easylabels_quick_create_label',
     1693                nonce: redshapeEasylabelsAjax.nonce,
     1694                label_name: labelName,
     1695                label_color: labelColor,
     1696                auto_assign: false
     1697            },
     1698            success: function(response) {
     1699                if (response.success) {
     1700                    showMessage(__('Label created successfully!'), 'success');
     1701                    closeQuickLabelModal();
     1702                   
     1703                    // Reload page to show new label
     1704                    setTimeout(function() {
     1705                        window.location.reload();
     1706                    }, 800);
     1707                } else {
     1708                    showMessage(response.data || __('Failed to create label'), 'error');
     1709                    $btn.prop('disabled', false).text(__('Create Label'));
     1710                }
     1711            },
     1712            error: function() {
     1713                $btn.prop('disabled', false).text(__('Create Label'));
     1714            }
     1715        });
     1716    });
     1717   
     1718    // Handle label creation with auto-assign
     1719    $(document).on('click', '#quick-create-assign-label-btn', function() {
     1720        const $btn = $(this);
     1721        const postId = $btn.data('post-id');
     1722        const labelName = $('#quick-label-name').val().trim();
     1723        const labelColor = $('#quick-label-color').val();
     1724       
     1725        if (!labelName) {
     1726            showMessage(__('Il nome dell\'etichetta è obbligatorio'), 'error');
     1727            $('#quick-label-name').focus();
     1728            return;
     1729        }
     1730       
     1731        if (!postId || postId <= 0) {
     1732            showMessage(__('Error occurred'), 'error');
     1733            return;
     1734        }
     1735       
     1736        // Disable button and show loading
     1737        $btn.prop('disabled', true).text(__('Creating label...'));
     1738       
     1739        safeAjax({
     1740            url: redshapeEasylabelsAjax.ajax_url,
     1741            type: 'POST',
     1742            data: {
     1743                action: 'redshape_easylabels_quick_create_label',
     1744                nonce: redshapeEasylabelsAjax.nonce,
     1745                label_name: labelName,
     1746                label_color: labelColor,
     1747                post_id: postId,
     1748                auto_assign: true
     1749            },
     1750            success: function(response) {
     1751                if (response.success) {
     1752                    showMessage(__('Label created successfully!'), 'success');
     1753                    closeQuickLabelModal();
     1754                   
     1755                    const label = response.data.label;
     1756                    const $wrapper = $('.metabox-labels[data-post-id="' + postId + '"]');
     1757                   
     1758                    if ($wrapper.length > 0 && response.data.assigned) {
     1759                        // Meta box context: the backend already assigned it, just update UI
     1760                        const $badge = $('<span>')
     1761                            .addClass('content-label-badge')
     1762                            .attr('data-label-id', label.id)
     1763                            .attr('data-post-id', postId)
     1764                            .attr('data-label-name', label.name)
     1765                            .attr('data-label-color', label.color)
     1766                            .css('background-color', label.color)
     1767                            .text(label.name);
     1768                       
     1769                        $wrapper.find('.metabox-add-btn').before($badge);
     1770                       
     1771                        // Update hidden field for form consistency
     1772                        updateMetaboxHiddenField($wrapper);
     1773                       
     1774                        // Clear dropdown to force reload
     1775                        const globalDropdownId = 'labels-dropdown-' + postId;
     1776                        $('#' + globalDropdownId).removeClass('show').empty();
     1777                        $wrapper.find('.labels-dropdown').removeClass('show').empty();
     1778                       
     1779                        // ✅ Update filter counter after Quick Create
     1780                        if (response.data.assigned) {
     1781                            updateFilterCounter(label.id, 1);
     1782                        }
     1783                       
     1784                    } else {
     1785                        // Table context: reload to show the new label
     1786                        setTimeout(function() {
     1787                            window.location.reload();
     1788                        }, 800);
     1789                    }
     1790                } else {
     1791                    showMessage(response.data || __('Failed to create label'), 'error');
     1792                    $btn.prop('disabled', false).text(__('Create and Assign'));
     1793                }
     1794            },
     1795            error: function() {
     1796                $btn.prop('disabled', false).text(__('Create and Assign'));
     1797            }
     1798        });
     1799    });
     1800   
     1801    // Handle click on "Create New Label" option
     1802    $(document).on('click', '.create-new-label-option', function(e) {
     1803        e.preventDefault();
     1804        e.stopPropagation();
     1805       
     1806        // Get postId from dropdown (not wrapper, since dropdown is now global)
     1807        const $dropdown = $(this).closest('.labels-dropdown');
     1808        const postId = $dropdown.data('post-id');
     1809       
     1810        // ✅ Save reference to this dropdown so we can invalidate it if modal is closed without creating
     1811        window.quickLabelSourceDropdown = $dropdown;
     1812       
     1813        // Close dropdown (just remove show class, don't use .hide())
     1814        $dropdown.removeClass('show');
     1815       
     1816        // Open modal
     1817        openQuickLabelModal(postId);
    15101818    });
    15111819   
  • redshape-easy-labels/trunk/includes/admin-page.php

    r3397353 r3397460  
    1818// Administrator is now selectable like other roles
    1919// For Menu Access, admin should not be selectable
    20 // Get all public post types
     20
     21// Get plugin language to load correct translations for post types
     22$redshape_easylabels_plugin_locale = Redshape_Easylabels_I18n::get_current_locale();
     23$redshape_easylabels_original_locale = get_locale();
     24
     25// Temporarily switch to plugin locale if different from WordPress locale
     26if ($redshape_easylabels_plugin_locale !== $redshape_easylabels_original_locale) {
     27    switch_to_locale($redshape_easylabels_plugin_locale);
     28}
     29
     30// Get all public post types (now with correct translations)
    2131$redshape_easylabels_post_types = get_post_types(array('public' => true), 'objects');
    2232unset($redshape_easylabels_post_types['attachment']); // Remove attachments
     33
     34// Restore original locale if it was changed
     35if ($redshape_easylabels_plugin_locale !== $redshape_easylabels_original_locale) {
     36    restore_previous_locale();
     37}
    2338
    2439// Determine active tab
     
    301316                               
    302317                                foreach ($redshape_easylabels_enabled_post_types as $redshape_easylabels_post_type_key):
    303                                     $redshape_easylabels_post_type_obj = get_post_type_object($redshape_easylabels_post_type_key);
    304                                     if ($redshape_easylabels_post_type_obj):
     318                                    // Use already loaded post types with correct translations
     319                                    if (isset($redshape_easylabels_post_types[$redshape_easylabels_post_type_key])):
     320                                        $redshape_easylabels_post_type_obj = $redshape_easylabels_post_types[$redshape_easylabels_post_type_key];
    305321                                ?>
    306322                                    <label class="post-type-checkbox">
     
    309325                                               value="<?php echo esc_attr($redshape_easylabels_post_type_key); ?>"
    310326                                               <?php checked(empty($redshape_easylabels_label_post_types) || in_array($redshape_easylabels_post_type_key, $redshape_easylabels_label_post_types)); ?> />
    311                                         <span class="post-type-name"><?php echo esc_html($redshape_easylabels_post_type_obj->labels->name); ?></span>
     327                                        <span class="post-type-name"><?php echo esc_html(Redshape_Easylabels_I18n::get_post_type_name($redshape_easylabels_post_type_key)); ?></span>
    312328                                    </label>
    313329                                <?php
     
    824840                                        <?php endif; ?>
    825841                                    </span>
    826                                     <h4 class="post-type-name"><?php echo esc_html($redshape_easylabels_post_type_obj->labels->name); ?></h4>
     842                                    <h4 class="post-type-name"><?php echo esc_html(Redshape_Easylabels_I18n::get_post_type_name($redshape_easylabels_post_type_key)); ?></h4>
    827843                                </div>
    828844                               
  • redshape-easy-labels/trunk/includes/class-redshape-easylabels-cache.php

    r3390533 r3397460  
    5555            // Salva in cache
    5656            wp_cache_set($cache_key, $count, self::CACHE_GROUP, self::CACHE_DURATION);
    57         } else {
    58             // Cache hit
    5957        }
    6058       
     
    7371        $enabled_post_types = isset($options['enabled_post_types']) ? $options['enabled_post_types'] : array('post');
    7472       
    75         // Invalida cache per questa label con tutti i post types
    76         $cache_key = self::generate_cache_key($label_id, $enabled_post_types);
    77         $deleted = wp_cache_delete($cache_key, self::CACHE_GROUP);
    78        
    79         // Invalida anche la cache generica (senza post types specifici)
     73        $deleted_count = 0;
     74       
     75        // ✅ SOLUZIONE ROBUSTA: Invece di solo eliminare, ricalcola immediatamente e aggiorna la cache
     76        // Questo funziona anche con cache persistenti come Redis/Memcached
     77        foreach ($enabled_post_types as $post_type) {
     78            // Elimina la vecchia cache
     79            $cache_key = self::generate_cache_key($label_id, array($post_type));
     80            wp_cache_delete($cache_key, self::CACHE_GROUP);
     81           
     82            // Ricalcola immediatamente e salva il nuovo valore
     83            $new_count = self::calculate_label_count($label_id, array($post_type));
     84            wp_cache_set($cache_key, $new_count, self::CACHE_GROUP, self::CACHE_DURATION);
     85            $deleted_count++;
     86        }
     87       
     88        // Invalida cache per tutti i post types insieme
     89        $all_types_cache_key = self::generate_cache_key($label_id, $enabled_post_types);
     90        wp_cache_delete($all_types_cache_key, self::CACHE_GROUP);
     91        $all_count = self::calculate_label_count($label_id, $enabled_post_types);
     92        wp_cache_set($all_types_cache_key, $all_count, self::CACHE_GROUP, self::CACHE_DURATION);
     93       
     94        // Invalida cache generica
    8095        $generic_cache_key = self::generate_cache_key($label_id, array());
    8196        wp_cache_delete($generic_cache_key, self::CACHE_GROUP);
    82        
    83         return $deleted;
     97        $generic_count = self::calculate_label_count($label_id, array());
     98        wp_cache_set($generic_cache_key, $generic_count, self::CACHE_GROUP, self::CACHE_DURATION);
     99       
     100        return $deleted_count > 0;
    84101    }
    85102   
  • redshape-easy-labels/trunk/includes/class-redshape-easylabels-i18n.php

    r3390533 r3397460  
    264264            'Select at least one element before continuing' => 'Seleziona almeno un elemento prima di continuare',
    265265            'No labels available for this content type' => 'Nessuna etichetta disponibile per questo tipo di contenuto',
     266           
     267            // Quick Create Label (v1.1.0)
     268            '+ Create New Label' => '+ Crea Nuova Etichetta',
     269            'Create New Label' => 'Crea Nuova Etichetta',
     270            'Label Name' => 'Nome Etichetta',
     271            'Enter label name...' => 'Inserisci nome etichetta...',
     272            'Label Color' => 'Colore Etichetta',
     273            'Create Label' => 'Crea Etichetta',
     274            'Create and Assign' => 'Crea e Assegna',
     275            'Creating label...' => 'Creazione etichetta...',
     276            'Label created successfully!' => 'Etichetta creata con successo!',
     277            'Failed to create label' => 'Impossibile creare l\'etichetta',
     278            'Il nome dell\'etichetta è obbligatorio' => 'Il nome dell\'etichetta è obbligatorio',
     279            'Esiste già un\'etichetta con questo nome' => 'Esiste già un\'etichetta con questo nome',
     280            'Non hai i permessi per creare etichette' => 'Non hai i permessi per creare etichette',
     281            'Etichetta creata con successo' => 'Etichetta creata con successo',
     282            'The label will be created and immediately available for all content types.' => 'L\'etichetta verrà creata e sarà immediatamente disponibile per tutti i tipi di contenuto.',
    266283        ),
    267284       
     
    399416            'Solid' => 'Solid',
    400417            'Double' => 'Double',
     418           
     419            // Quick Create Label (v1.1.0)
     420            '+ Create New Label' => '+ Create New Label',
     421            'Create New Label' => 'Create New Label',
     422            'Label Name' => 'Label Name',
     423            'Enter label name...' => 'Enter label name...',
     424            'Label Color' => 'Label Color',
     425            'Create Label' => 'Create Label',
     426            'Create and Assign' => 'Create and Assign',
     427            'Creating label...' => 'Creating label...',
     428            'Label created successfully!' => 'Label created successfully!',
     429            'Failed to create label' => 'Failed to create label',
     430            'Il nome dell\'etichetta è obbligatorio' => 'Label name is required',
     431            'Esiste già un\'etichetta con questo nome' => 'A label with this name already exists',
     432            'Non hai i permessi per creare etichette' => 'You don\'t have permission to create labels',
     433            'Etichetta creata con successo' => 'Label created successfully',
     434            'The label will be created and immediately available for all content types.' => 'The label will be created and immediately available for all content types.',
    401435           
    402436            // Permissions
     
    490524            'You do not have permission to view labels and notes' => 'You do not have permission to view labels and notes',
    491525           
     526            // General messages
     527            'Delete' => 'Delete',
     528            'Edit' => 'Edit',
     529            'Apply' => 'Apply',
     530            'All' => 'All',
     531            'None' => 'None',
     532            'Select' => 'Select',
     533            'Close' => 'Close',
     534            'Label' => 'Label',
     535            'No Label' => 'No Label',
     536            'Select label' => 'Select label',
     537            'Add new label' => 'Add new label',
     538            'Label color' => 'Label color',
     539            'No labels available' => 'No labels available',
     540            'Nessuna etichetta disponibile per questo tipo di contenuto' => 'No labels available for this content type',
     541            'All labels are already assigned' => 'All labels are already assigned',
     542            'Remove Label' => 'Remove Label',
     543            'Are you sure you want to remove this label?' => 'Are you sure you want to remove this label?',
     544            'Remove' => 'Remove',
     545            'Clear Note' => 'Clear Note',
     546            'Are you sure you want to clear this note?' => 'Are you sure you want to clear this note?',
     547            'Clear' => 'Clear',
     548            'Label added successfully' => 'Label added successfully',
     549            'Label removed successfully' => 'Label removed successfully',
     550            'Label updated successfully' => 'Label updated successfully',
     551            'Notes' => 'Notes',
     552            'Internal Notes' => 'Internal Notes',
     553            'Add note' => 'Add note',
     554            'Save note' => 'Save note',
     555            'Clear note' => 'Clear note',
     556            'Note saved' => 'Note saved',
     557            'Note cleared' => 'Note cleared',
     558            'Quick filter' => 'Quick filter',
     559            'Filter by label' => 'Filter by label',
     560            'Show all' => 'Show all',
     561            'No items found' => 'No items found',
     562            'Are you sure?' => 'Are you sure?',
     563            'This action cannot be undone' => 'This action cannot be undone',
     564            'Error occurred' => 'An error occurred',
     565            'Loading...' => 'Loading...',
     566            'Please wait...' => 'Please wait...',
     567            'You do not have permission' => 'You do not have permission',
     568            'Insufficient permissions' => 'Insufficient permissions',
     569            'Access denied' => 'Access denied',
     570            'Apply label to selected' => 'Apply label to selected',
     571            'items updated' => 'items updated',
     572            'No items selected' => 'No items selected',
     573            'Select at least one item' => 'Select at least one item',
     574            'Search labels...' => 'Search labels...',
     575           
    492576            // Bulk actions
    493577            'Apply label' => 'Apply label',
    494             'Select label' => 'Select label',
    495             'Apply' => 'Apply',
    496578            '%d label applied.' => '%d label applied.',
    497579            '%d labels applied.' => '%d labels applied.',
     
    735817            'Write your internal notes here...' => 'Écrivez vos notes internes ici...',
    736818            'You do not have permission to view labels and notes' => 'Vous n\'avez pas la permission de voir les étiquettes et les notes',
     819           
     820            // Quick Create Label (v1.1.0)
     821            '+ Create New Label' => '+ Créer nouvelle étiquette',
     822            'Create New Label' => 'Créer nouvelle étiquette',
     823            'Label Name' => 'Nom de l\'étiquette',
     824            'Enter label name...' => 'Entrez le nom de l\'étiquette...',
     825            'Label Color' => 'Couleur de l\'étiquette',
     826            'Create Label' => 'Créer l\'étiquette',
     827            'Create and Assign' => 'Créer et assigner',
     828            'Creating label...' => 'Création de l\'étiquette...',
     829            'Label created successfully!' => 'Étiquette créée avec succès !',
     830            'Failed to create label' => 'Échec de la création de l\'étiquette',
     831            'Il nome dell\'etichetta è obbligatorio' => 'Le nom de l\'étiquette est obligatoire',
     832            'Esiste già un\'etichetta con questo nome' => 'Une étiquette avec ce nom existe déjà',
     833            'Non hai i permessi per creare etichette' => 'Vous n\'avez pas la permission de créer des étiquettes',
     834            'Etichetta creata con successo' => 'Étiquette créée avec succès',
     835            'The label will be created and immediately available for all content types.' => 'L\'étiquette sera créée et immédiatement disponible pour tous les types de contenu.',
    737836        ),
    738837       
     
    9701069            'Write your internal notes here...' => 'Schreiben Sie hier Ihre internen Notizen...',
    9711070            'You do not have permission to view labels and notes' => 'Sie haben keine Berechtigung, Labels und Notizen anzuzeigen',
     1071           
     1072            // Quick Create Label (v1.1.0)
     1073            '+ Create New Label' => '+ Neues Label erstellen',
     1074            'Create New Label' => 'Neues Label erstellen',
     1075            'Label Name' => 'Label-Name',
     1076            'Enter label name...' => 'Label-Name eingeben...',
     1077            'Label Color' => 'Label-Farbe',
     1078            'Create Label' => 'Label erstellen',
     1079            'Create and Assign' => 'Erstellen und zuweisen',
     1080            'Creating label...' => 'Label wird erstellt...',
     1081            'Label created successfully!' => 'Label erfolgreich erstellt!',
     1082            'Failed to create label' => 'Fehler beim Erstellen des Labels',
     1083            'Il nome dell\'etichetta è obbligatorio' => 'Der Label-Name ist erforderlich',
     1084            'Esiste già un\'etichetta con questo nome' => 'Ein Label mit diesem Namen existiert bereits',
     1085            'Non hai i permessi per creare etichette' => 'Sie haben keine Berechtigung zum Erstellen von Labels',
     1086            'Etichetta creata con successo' => 'Label erfolgreich erstellt',
     1087            'The label will be created and immediately available for all content types.' => 'Das Label wird erstellt und ist sofort für alle Inhaltstypen verfügbar.',
    9721088        ),
    9731089       
     
    12051321            'Write your internal notes here...' => 'Escribe tus notas internas aquí...',
    12061322            'You do not have permission to view labels and notes' => 'No tienes permiso para ver etiquetas y notas',
     1323           
     1324            // Quick Create Label (v1.1.0)
     1325            '+ Create New Label' => '+ Crear nueva etiqueta',
     1326            'Create New Label' => 'Crear nueva etiqueta',
     1327            'Label Name' => 'Nombre de etiqueta',
     1328            'Enter label name...' => 'Ingresa el nombre de la etiqueta...',
     1329            'Label Color' => 'Color de etiqueta',
     1330            'Create Label' => 'Crear etiqueta',
     1331            'Create and Assign' => 'Crear y asignar',
     1332            'Creating label...' => 'Creando etiqueta...',
     1333            'Label created successfully!' => '¡Etiqueta creada con éxito!',
     1334            'Failed to create label' => 'Error al crear la etiqueta',
     1335            'Il nome dell\'etichetta è obbligatorio' => 'El nombre de la etiqueta es obligatorio',
     1336            'Esiste già un\'etichetta con questo nome' => 'Ya existe una etiqueta con este nombre',
     1337            'Non hai i permessi per creare etichette' => 'No tienes permiso para crear etiquetas',
     1338            'Etichetta creata con successo' => 'Etiqueta creada con éxito',
     1339            'The label will be created and immediately available for all content types.' => 'La etiqueta se creará y estará disponible inmediatamente para todos los tipos de contenido.',
    12071340        ),
    12081341       
     
    14301563            'You do not have permission to view labels and notes' => 'У вас нет разрешения на просмотр меток и заметок',
    14311564           
     1565            // Quick Create Label (v1.1.0)
     1566            '+ Create New Label' => '+ Создать новую метку',
     1567            'Create New Label' => 'Создать новую метку',
     1568            'Label Name' => 'Название метки',
     1569            'Enter label name...' => 'Введите название метки...',
     1570            'Label Color' => 'Цвет метки',
     1571            'Create Label' => 'Создать метку',
     1572            'Create and Assign' => 'Создать и назначить',
     1573            'Creating label...' => 'Создание метки...',
     1574            'Label created successfully!' => 'Метка успешно создана!',
     1575            'Failed to create label' => 'Ошибка создания метки',
     1576            'Il nome dell\'etichetta è obbligatorio' => 'Название метки обязательно',
     1577            'Esiste già un\'etichetta con questo nome' => 'Метка с таким названием уже существует',
     1578            'Non hai i permessi per creare etichette' => 'У вас нет разрешения на создание меток',
     1579            'Etichetta creata con successo' => 'Метка успешно создана',
     1580            'The label will be created and immediately available for all content types.' => 'Метка будет создана и немедленно станет доступна для всех типов контента.',
     1581           
    14321582            // Default Labels Management
    14331583            'Default Labels Management' => 'Управление метками по умолчанию',
     
    16651815            'You do not have permission to view labels and notes' => '您没有权限查看标签和备注',
    16661816           
     1817            // Quick Create Label (v1.1.0)
     1818            '+ Create New Label' => '+ 创建新标签',
     1819            'Create New Label' => '创建新标签',
     1820            'Label Name' => '标签名称',
     1821            'Enter label name...' => '输入标签名称...',
     1822            'Label Color' => '标签颜色',
     1823            'Create Label' => '创建标签',
     1824            'Create and Assign' => '创建并分配',
     1825            'Creating label...' => '创建标签中...',
     1826            'Label created successfully!' => '标签创建成功!',
     1827            'Failed to create label' => '创建标签失败',
     1828            'Il nome dell\'etichetta è obbligatorio' => '标签名称为必填项',
     1829            'Esiste già un\'etichetta con questo nome' => '已存在同名标签',
     1830            'Non hai i permessi per creare etichette' => '您没有创建标签的权限',
     1831            'Etichetta creata con successo' => '标签创建成功',
     1832            'The label will be created and immediately available for all content types.' => '标签将被创建并立即可用于所有内容类型。',
     1833           
    16671834            // Default Labels Management
    16681835            'Default Labels Management' => '默认标签管理',
     
    19002067            'You do not have permission to view labels and notes' => 'ラベルとメモを表示する権限がありません',
    19012068           
     2069            // Quick Create Label (v1.1.0)
     2070            '+ Create New Label' => '+ 新しいラベルを作成',
     2071            'Create New Label' => '新しいラベルを作成',
     2072            'Label Name' => 'ラベル名',
     2073            'Enter label name...' => 'ラベル名を入力...',
     2074            'Label Color' => 'ラベルの色',
     2075            'Create Label' => 'ラベルを作成',
     2076            'Create and Assign' => '作成して割り当てる',
     2077            'Creating label...' => 'ラベルを作成中...',
     2078            'Label created successfully!' => 'ラベルが正常に作成されました!',
     2079            'Failed to create label' => 'ラベルの作成に失敗しました',
     2080            'Il nome dell\'etichetta è obbligatorio' => 'ラベル名は必須です',
     2081            'Esiste già un\'etichetta con questo nome' => 'この名前のラベルは既に存在します',
     2082            'Non hai i permessi per creare etichette' => 'ラベルを作成する権限がありません',
     2083            'Etichetta creata con successo' => 'ラベルが正常に作成されました',
     2084            'The label will be created and immediately available for all content types.' => 'ラベルが作成され、すべてのコンテンツタイプですぐに利用できるようになります。',
     2085           
    19022086            // Default Labels Management
    19032087            'Default Labels Management' => 'デフォルトラベル管理',
     
    21352319            'You do not have permission to view labels and notes' => '라벨과 메모를 볼 권한이 없습니다',
    21362320           
     2321            // Quick Create Label (v1.1.0)
     2322            '+ Create New Label' => '+ 새 라벨 만들기',
     2323            'Create New Label' => '새 라벨 만들기',
     2324            'Label Name' => '라벨 이름',
     2325            'Enter label name...' => '라벨 이름 입력...',
     2326            'Label Color' => '라벨 색상',
     2327            'Create Label' => '라벨 만들기',
     2328            'Create and Assign' => '만들기 및 할당',
     2329            'Creating label...' => '라벨 만드는 중...',
     2330            'Label created successfully!' => '라벨이 성공적으로 생성되었습니다!',
     2331            'Failed to create label' => '라벨 생성 실패',
     2332            'Il nome dell\'etichetta è obbligatorio' => '라벨 이름은 필수입니다',
     2333            'Esiste già un\'etichetta con questo nome' => '이 이름의 라벨이 이미 존재합니다',
     2334            'Non hai i permessi per creare etichette' => '라벨을 만들 권한이 없습니다',
     2335            'Etichetta creata con successo' => '라벨이 성공적으로 생성되었습니다',
     2336            'The label will be created and immediately available for all content types.' => '라벨이 생성되어 모든 콘텐츠 유형에서 즉시 사용할 수 있습니다.',
     2337           
    21372338            // Default Labels Management
    21382339            'Default Labels Management' => '기본 라벨 관리',
     
    23712572            'Write your internal notes here...' => 'अपने आंतरिक नोट्स यहां लिखें...',
    23722573            'You do not have permission to view labels and notes' => 'आपके पास लेबल और नोट्स देखने की अनुमति नहीं है',
     2574           
     2575            // Quick Create Label (v1.1.0)
     2576            '+ Create New Label' => '+ नया लेबल बनाएं',
     2577            'Create New Label' => 'नया लेबल बनाएं',
     2578            'Label Name' => 'लेबल नाम',
     2579            'Enter label name...' => 'लेबल नाम दर्ज करें...',
     2580            'Label Color' => 'लेबल रंग',
     2581            'Create Label' => 'लेबल बनाएं',
     2582            'Create and Assign' => 'बनाएं और असाइन करें',
     2583            'Creating label...' => 'लेबल बनाया जा रहा है...',
     2584            'Label created successfully!' => 'लेबल सफलतापूर्वक बनाया गया!',
     2585            'Failed to create label' => 'लेबल बनाने में विफल',
     2586            'Il nome dell\'etichetta è obbligatorio' => 'लेबल नाम आवश्यक है',
     2587            'Esiste già un\'etichetta con questo nome' => 'इस नाम का लेबल पहले से मौजूद है',
     2588            'Non hai i permessi per creare etichette' => 'आपके पास लेबल बनाने की अनुमति नहीं है',
     2589            'Etichetta creata con successo' => 'लेबल सफलतापूर्वक बनाया गया',
     2590            'The label will be created and immediately available for all content types.' => 'लेबल बनाया जाएगा और सभी सामग्री प्रकारों के लिए तुरंत उपलब्ध होगा।',
    23732591           
    23742592            // Default Labels Management
     
    24662684        return self::translate($string);
    24672685    }
     2686   
     2687    /**
     2688     * Ottiene i nomi tradotti dei post types di WordPress
     2689     *
     2690     * @param string $post_type_key La chiave del post type (es: 'post', 'page')
     2691     * @param string $locale Il locale (es: 'it_IT', 'en_US')
     2692     * @return string Il nome tradotto del post type
     2693     */
     2694    public static function get_post_type_name($post_type_key, $locale = null) {
     2695        if ($locale === null) {
     2696            $locale = self::get_current_locale();
     2697        }
     2698       
     2699        // Mappatura dei post types più comuni
     2700        $post_type_names = array(
     2701            'it_IT' => array(
     2702                'post' => 'Articoli',
     2703                'page' => 'Pagine',
     2704                'attachment' => 'Media',
     2705                'product' => 'Prodotti',
     2706                'shop_order' => 'Ordini',
     2707                'shop_coupon' => 'Coupon',
     2708            ),
     2709            'en_US' => array(
     2710                'post' => 'Posts',
     2711                'page' => 'Pages',
     2712                'attachment' => 'Media',
     2713                'product' => 'Products',
     2714                'shop_order' => 'Orders',
     2715                'shop_coupon' => 'Coupons',
     2716            ),
     2717            'fr_FR' => array(
     2718                'post' => 'Articles',
     2719                'page' => 'Pages',
     2720                'attachment' => 'Médias',
     2721                'product' => 'Produits',
     2722                'shop_order' => 'Commandes',
     2723                'shop_coupon' => 'Coupons',
     2724            ),
     2725            'de_DE' => array(
     2726                'post' => 'Beiträge',
     2727                'page' => 'Seiten',
     2728                'attachment' => 'Medien',
     2729                'product' => 'Produkte',
     2730                'shop_order' => 'Bestellungen',
     2731                'shop_coupon' => 'Gutscheine',
     2732            ),
     2733            'es_ES' => array(
     2734                'post' => 'Entradas',
     2735                'page' => 'Páginas',
     2736                'attachment' => 'Medios',
     2737                'product' => 'Productos',
     2738                'shop_order' => 'Pedidos',
     2739                'shop_coupon' => 'Cupones',
     2740            ),
     2741            'ru_RU' => array(
     2742                'post' => 'Записи',
     2743                'page' => 'Страницы',
     2744                'attachment' => 'Медиафайлы',
     2745                'product' => 'Товары',
     2746                'shop_order' => 'Заказы',
     2747                'shop_coupon' => 'Купоны',
     2748            ),
     2749            'zh_CN' => array(
     2750                'post' => '文章',
     2751                'page' => '页面',
     2752                'attachment' => '媒体',
     2753                'product' => '产品',
     2754                'shop_order' => '订单',
     2755                'shop_coupon' => '优惠券',
     2756            ),
     2757            'ja_JP' => array(
     2758                'post' => '投稿',
     2759                'page' => '固定ページ',
     2760                'attachment' => 'メディア',
     2761                'product' => '商品',
     2762                'shop_order' => '注文',
     2763                'shop_coupon' => 'クーポン',
     2764            ),
     2765            'ko_KR' => array(
     2766                'post' => '글',
     2767                'page' => '페이지',
     2768                'attachment' => '미디어',
     2769                'product' => '제품',
     2770                'shop_order' => '주문',
     2771                'shop_coupon' => '쿠폰',
     2772            ),
     2773            'hi_IN' => array(
     2774                'post' => 'पोस्ट',
     2775                'page' => 'पृष्ठ',
     2776                'attachment' => 'मीडिया',
     2777                'product' => 'उत्पाद',
     2778                'shop_order' => 'आदेश',
     2779                'shop_coupon' => 'कूपन',
     2780            ),
     2781        );
     2782       
     2783        // Se esiste una traduzione per questa lingua e questo post type, la restituisce
     2784        if (isset($post_type_names[$locale][$post_type_key])) {
     2785            return $post_type_names[$locale][$post_type_key];
     2786        }
     2787       
     2788        // Altrimenti, prova a prendere il nome dall'oggetto post type di WordPress
     2789        $post_type_obj = get_post_type_object($post_type_key);
     2790        if ($post_type_obj && isset($post_type_obj->labels->name)) {
     2791            return $post_type_obj->labels->name;
     2792        }
     2793       
     2794        // Fallback: restituisce la chiave
     2795        return $post_type_key;
     2796    }
    24682797}
  • redshape-easy-labels/trunk/includes/class-redshape-easylabels.php

    r3397353 r3397460  
    4444        add_action('wp_ajax_redshape_easylabels_export_settings', array($this, 'ajax_export_settings')); // NEW: Export
    4545        add_action('wp_ajax_redshape_easylabels_import_settings', array($this, 'ajax_import_settings')); // NEW: Import
     46        add_action('wp_ajax_redshape_easylabels_quick_create_label', array($this, 'ajax_quick_create_label')); // NEW: Quick Create Label
    4647       
    4748        // Hook for cache invalidation
     
    21002101   
    21012102    /**
     2103     * AJAX per creare rapidamente una nuova label dal contenuto
     2104     * @since 1.1.0
     2105     */
     2106    public function ajax_quick_create_label() {
     2107        check_ajax_referer('redshape_easylabels_nonce', 'nonce');
     2108       
     2109        // Verifica permessi - solo chi può modificare le label può crearle
     2110        if (!$this->can_user_view_labels()) {
     2111            wp_send_json_error(redshape_easylabels_cl__('Non hai i permessi per creare etichette'));
     2112        }
     2113       
     2114        // Get and sanitize input
     2115        $label_name = isset($_POST['label_name']) ? sanitize_text_field(wp_unslash($_POST['label_name'])) : '';
     2116        $label_color = isset($_POST['label_color']) ? sanitize_hex_color(wp_unslash($_POST['label_color'])) : '#007cba';
     2117        $post_id = isset($_POST['post_id']) ? intval(wp_unslash($_POST['post_id'])) : 0;
     2118        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Boolean value sanitized below
     2119        $auto_assign_raw = isset($_POST['auto_assign']) ? wp_unslash($_POST['auto_assign']) : false;
     2120        $auto_assign = ($auto_assign_raw === true || $auto_assign_raw === 'true' || $auto_assign_raw === 1 || $auto_assign_raw === '1');
     2121       
     2122        // Validate label name
     2123        if (empty($label_name)) {
     2124            wp_send_json_error(redshape_easylabels_cl__('Il nome dell\'etichetta è obbligatorio'));
     2125        }
     2126       
     2127        // Generate label key from name (same logic as settings page)
     2128        $label_key = sanitize_title($label_name);
     2129        $label_key = str_replace('-', '_', $label_key);
     2130       
     2131        // Get current labels
     2132        $options = get_option('redshape_easylabels_options', array());
     2133        $labels = isset($options['default_labels']) ? $options['default_labels'] : array();
     2134       
     2135        // Check if label key already exists
     2136        if (isset($labels[$label_key])) {
     2137            wp_send_json_error(redshape_easylabels_cl__('Esiste già un\'etichetta con questo nome'));
     2138        }
     2139       
     2140        // Create new label
     2141        $labels[$label_key] = array(
     2142            'name' => $label_name,
     2143            'color' => $label_color,
     2144            'post_types' => array() // Empty = available for all post types
     2145        );
     2146       
     2147        // Save updated labels
     2148        $options['default_labels'] = $labels;
     2149        update_option('redshape_easylabels_options', $options);
     2150       
     2151        // Auto-assign to post if requested
     2152        $assigned = false;
     2153        if ($auto_assign && $post_id > 0) {
     2154            // Get current labels
     2155            $current_labels = get_post_meta($post_id, '_content_labels', true);
     2156            if (!is_array($current_labels)) {
     2157                $current_labels = array();
     2158            }
     2159           
     2160            // Add new label if not already present
     2161            if (!in_array($label_key, $current_labels)) {
     2162                $current_labels[] = $label_key;
     2163                $result = update_post_meta($post_id, '_content_labels', $current_labels);
     2164                $assigned = ($result !== false);
     2165               
     2166                // ✅ INVALIDA CACHE per questa label quando viene assegnata
     2167                if ($assigned) {
     2168                    Redshape_Easylabels_Cache::invalidate_label($label_key);
     2169                }
     2170            } else {
     2171                $assigned = true; // Already assigned
     2172            }
     2173        }
     2174       
     2175        // Return success with new label data
     2176        wp_send_json_success(array(
     2177            'message' => redshape_easylabels_cl__('Etichetta creata con successo'),
     2178            'label' => array(
     2179                'id' => $label_key,
     2180                'name' => $label_name,
     2181                'color' => $label_color
     2182            ),
     2183            'assigned' => $assigned
     2184        ));
     2185    }
     2186   
     2187    /**
    21022188     * NUOVO: AJAX per ottenere i conteggi aggiornati dei filtri con percentuali
    21032189     */
     
    21252211        $labels = $this->get_labels_for_post_type($post_type);
    21262212       
    2127         // Prepara i dati con percentuali usando la cache
     2213        // Prepara i dati con percentuali SENZA CACHE per avere dati sempre freschi
    21282214        $counts_data = array();
    21292215        foreach ($labels as $key => $label) {
    2130             // ✅ USA CACHE per ottenere il conteggio
    2131             $count = Redshape_Easylabels_Cache::get_label_count($key, array($post_type));
     2216            // ✅ CALCOLA DIRETTAMENTE DAL DATABASE senza cache per evitare problemi sui siti live
     2217            $count = $this->calculate_label_count_direct($key, $post_type);
    21322218           
    21332219            $percentage = null;
     
    21662252            'total' => $total_posts_count
    21672253        ));
     2254    }
     2255   
     2256    /**
     2257     * Calcola il conteggio diretto dal database senza cache
     2258     * Usato per i contatori real-time per evitare problemi con cache persistenti
     2259     */
     2260    private function calculate_label_count_direct($label_id, $post_type) {
     2261        global $wpdb;
     2262       
     2263        // Query diretta al database SENZA CACHE - intenzionale per real-time counters
     2264        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     2265        $count = $wpdb->get_var($wpdb->prepare(
     2266            "SELECT COUNT(DISTINCT p.ID)
     2267            FROM {$wpdb->posts} p
     2268            INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
     2269            WHERE p.post_type = %s
     2270            AND p.post_status IN ('publish', 'draft', 'private', 'pending')
     2271            AND pm.meta_key = '_content_labels'
     2272            AND pm.meta_value LIKE %s",
     2273            $post_type,
     2274            '%"' . $wpdb->esc_like($label_id) . '"%'
     2275        ));
     2276        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     2277       
     2278        return (int) $count;
    21682279    }
    21692280   
  • redshape-easy-labels/trunk/readme.txt

    r3397353 r3397460  
    55Tested up to: 6.8
    66Requires PHP: 7.0
    7 Stable tag: 1.0.1
     7Stable tag: 1.1.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    8181== Changelog ==
    8282
     83= 1.1.0 =
     84* New: Quick Create Label - Create new labels directly from content editor without going to settings
     85* New: Universal modal for label creation works in both post list and meta box
     86* New: Quick Create button always visible, even when no labels exist yet
     87* New: Post type names translation - Post types (Posts, Pages, Products, etc.) now display in plugin's language for all 10 supported languages
     88* Enhancement: Streamlined workflow for content organization
     89* Enhancement: Auto-assign option when creating labels from content
     90* Enhancement: Improved multilingual support with custom post type name mapping
     91* Enhancement: Direct database queries for real-time counters to ensure accuracy on production sites
     92* Enhancement: Debounced counter updates to prevent multiple simultaneous requests
     93* Fix: Post type names now consistent with plugin language settings in all admin pages
     94* Fix: Real-time counter accuracy on live sites with persistent cache systems (Redis, Memcached)
     95* Fix: Counter increments now properly synchronized across multiple UI contexts (table, inline, metabox)
     96* Fix: Dropdown state properly restored after closing Quick Create modal without creating label
     97* Fix: PHPCS compliance for intentional direct database queries in counter calculations
     98
    8399= 1.0.1 =
    84100* Fix: Added index.php files to all directories to prevent directory listing on misconfigured servers
     
    92108* Fix: Notes auto-save - each note field now has independent timeout to prevent data loss when editing multiple notes
    93109* Fix: Notes persistence - all pending notes are now saved before page unload to prevent loss of unsaved changes
    94 * Fix: Compact inline labels - fixed oval shape issue, now display as perfect circles
    95 * Improvement: Removed debug console.log statements for cleaner production code
    96 * Improvement: Removed unused debug endpoint ajax_debug_post_types
    97 * Improvement: Standardized nonce verification across all AJAX endpoints for consistency
    98 * Improvement: Added clarifying comments to distinguish updateMetaboxHiddenField functions
    99110* Security: Enhanced directory protection following WordPress.org best practices
    100111
  • redshape-easy-labels/trunk/redshape-easy-labels.php

    r3397353 r3397460  
    33 * Plugin Name: REDSHAPE Easy Labels
    44 * Description: Colored labels and internal notes system for posts and pages, visible only in backend for content organization. Supports 10 languages (IT, EN, FR, DE, ES, RU, ZH, JA, KO, HI).
    5  * Version: 1.0.1
     5 * Version: 1.1.0
    66 * Author: REDSHAPE
    77 * Author URI: https://redshape.it
     
    1818
    1919// Define plugin constants
    20 define('REDSHAPE_EASYLABELS_VERSION', '1.0.1');
     20define('REDSHAPE_EASYLABELS_VERSION', '1.1.0');
    2121define('REDSHAPE_EASYLABELS_PLUGIN_URL', plugin_dir_url(__FILE__));
    2222define('REDSHAPE_EASYLABELS_PLUGIN_PATH', plugin_dir_path(__FILE__));
Note: See TracChangeset for help on using the changeset viewer.