Changeset 3397460
- Timestamp:
- 11/17/2025 05:44:07 PM (4 months ago)
- Location:
- redshape-easy-labels
- Files:
-
- 25 added
- 8 edited
-
tags/1.1.0 (added)
-
tags/1.1.0/assets (added)
-
tags/1.1.0/assets/css (added)
-
tags/1.1.0/assets/css/admin-settings.css (added)
-
tags/1.1.0/assets/css/admin.css (added)
-
tags/1.1.0/assets/css/index.php (added)
-
tags/1.1.0/assets/index.php (added)
-
tags/1.1.0/assets/js (added)
-
tags/1.1.0/assets/js/admin.js (added)
-
tags/1.1.0/assets/js/index.php (added)
-
tags/1.1.0/assets/js/vendor (added)
-
tags/1.1.0/assets/js/vendor/index.php (added)
-
tags/1.1.0/assets/js/vendor/sortable.min.js (added)
-
tags/1.1.0/includes (added)
-
tags/1.1.0/includes/admin-page.php (added)
-
tags/1.1.0/includes/class-redshape-easylabels-cache.php (added)
-
tags/1.1.0/includes/class-redshape-easylabels-content-i18n.php (added)
-
tags/1.1.0/includes/class-redshape-easylabels-i18n.php (added)
-
tags/1.1.0/includes/class-redshape-easylabels.php (added)
-
tags/1.1.0/includes/index.php (added)
-
tags/1.1.0/languages (added)
-
tags/1.1.0/languages/index.php (added)
-
tags/1.1.0/readme.txt (added)
-
tags/1.1.0/redshape-easy-labels.php (added)
-
tags/1.1.0/uninstall.php (added)
-
trunk/assets/css/admin.css (modified) (1 diff)
-
trunk/assets/js/admin.js (modified) (11 diffs)
-
trunk/includes/admin-page.php (modified) (4 diffs)
-
trunk/includes/class-redshape-easylabels-cache.php (modified) (2 diffs)
-
trunk/includes/class-redshape-easylabels-i18n.php (modified) (12 diffs)
-
trunk/includes/class-redshape-easylabels.php (modified) (4 diffs)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/redshape-easy-labels.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
redshape-easy-labels/trunk/assets/css/admin.css
r3397353 r3397460 1324 1324 margin: 0; 1325 1325 } 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 250 250 251 251 // 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) { 253 254 handleDropdownItemClick.call(this, e); 254 255 }); … … 296 297 dropdown.removeClass('show'); 297 298 } else { 298 // Load available labels if needed299 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; 300 301 301 302 // Position dropdown BEFORE showing it to avoid visual jump … … 304 305 305 306 if (needsLoading) { 307 // Clear invalidated flag 308 dropdown.data('invalidated', false); 306 309 loadLabelsInDropdown(dropdown, postId); 307 310 // Wait for content to load, then position and show … … 553 556 var currentLabels = getCurrentLabels(wrapper); 554 557 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 555 564 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 557 568 } else { 558 569 // ✅ 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;">' + 560 571 '<input type="text" class="labels-search-input" placeholder="' + __('Search labels...') + '" ' + 561 572 '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 563 574 564 // Container for items (to be filtered) 565 html += '<div class="labels-dropdown-items-container">'; 575 // Container for items already started above 566 576 567 577 var hasAvailableLabels = false; … … 584 594 html += '</div>'; // Close items container 585 595 586 // If all labels are already assigned, show a message 596 // If all labels are already assigned, show a message (but keep create option) 587 597 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>'; 589 599 } 590 600 } … … 806 816 if (existingLabel.length === 0) { 807 817 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); 810 820 } else { 811 821 // For table and inline use normal badge … … 843 853 844 854 // Function to update filter counters in real time 855 // ✅ DEBOUNCE TIMER per evitare chiamate multiple 856 var filterCounterUpdateTimer = null; 857 845 858 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); 848 869 } 849 870 … … 1028 1049 1029 1050 // Specific function to add labels to meta box 1030 function addLabelToMetabox(labelId, labelName, labelColor, wrapper ) {1051 function addLabelToMetabox(labelId, labelName, labelColor, wrapper, skipCounterUpdate) { 1031 1052 // Check if label already exists (only check badges, not dropdown items) 1032 1053 var existingBadge = wrapper.find('.content-label-badge[data-label-id="' + labelId + '"]'); … … 1051 1072 var postId = wrapper.data('post-id') || wrapper.find('.metabox-add-btn').data('post-id'); 1052 1073 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 1054 1100 wrapper.addClass('loading'); 1055 1101 safeAjax({ … … 1095 1141 1096 1142 // 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 } 1098 1147 1099 1148 showMessage(__('Label added successfully'), 'success'); … … 1508 1557 initFilterDragAndDrop(); 1509 1558 }, 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">×</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); 1510 1818 }); 1511 1819 -
redshape-easy-labels/trunk/includes/admin-page.php
r3397353 r3397460 18 18 // Administrator is now selectable like other roles 19 19 // 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 26 if ($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) 21 31 $redshape_easylabels_post_types = get_post_types(array('public' => true), 'objects'); 22 32 unset($redshape_easylabels_post_types['attachment']); // Remove attachments 33 34 // Restore original locale if it was changed 35 if ($redshape_easylabels_plugin_locale !== $redshape_easylabels_original_locale) { 36 restore_previous_locale(); 37 } 23 38 24 39 // Determine active tab … … 301 316 302 317 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]; 305 321 ?> 306 322 <label class="post-type-checkbox"> … … 309 325 value="<?php echo esc_attr($redshape_easylabels_post_type_key); ?>" 310 326 <?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> 312 328 </label> 313 329 <?php … … 824 840 <?php endif; ?> 825 841 </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> 827 843 </div> 828 844 -
redshape-easy-labels/trunk/includes/class-redshape-easylabels-cache.php
r3390533 r3397460 55 55 // Salva in cache 56 56 wp_cache_set($cache_key, $count, self::CACHE_GROUP, self::CACHE_DURATION); 57 } else {58 // Cache hit59 57 } 60 58 … … 73 71 $enabled_post_types = isset($options['enabled_post_types']) ? $options['enabled_post_types'] : array('post'); 74 72 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 80 95 $generic_cache_key = self::generate_cache_key($label_id, array()); 81 96 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; 84 101 } 85 102 -
redshape-easy-labels/trunk/includes/class-redshape-easylabels-i18n.php
r3390533 r3397460 264 264 'Select at least one element before continuing' => 'Seleziona almeno un elemento prima di continuare', 265 265 '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.', 266 283 ), 267 284 … … 399 416 'Solid' => 'Solid', 400 417 '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.', 401 435 402 436 // Permissions … … 490 524 'You do not have permission to view labels and notes' => 'You do not have permission to view labels and notes', 491 525 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 492 576 // Bulk actions 493 577 'Apply label' => 'Apply label', 494 'Select label' => 'Select label',495 'Apply' => 'Apply',496 578 '%d label applied.' => '%d label applied.', 497 579 '%d labels applied.' => '%d labels applied.', … … 735 817 'Write your internal notes here...' => 'Écrivez vos notes internes ici...', 736 818 '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.', 737 836 ), 738 837 … … 970 1069 'Write your internal notes here...' => 'Schreiben Sie hier Ihre internen Notizen...', 971 1070 '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.', 972 1088 ), 973 1089 … … 1205 1321 'Write your internal notes here...' => 'Escribe tus notas internas aquí...', 1206 1322 '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.', 1207 1340 ), 1208 1341 … … 1430 1563 'You do not have permission to view labels and notes' => 'У вас нет разрешения на просмотр меток и заметок', 1431 1564 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 1432 1582 // Default Labels Management 1433 1583 'Default Labels Management' => 'Управление метками по умолчанию', … … 1665 1815 'You do not have permission to view labels and notes' => '您没有权限查看标签和备注', 1666 1816 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 1667 1834 // Default Labels Management 1668 1835 'Default Labels Management' => '默认标签管理', … … 1900 2067 'You do not have permission to view labels and notes' => 'ラベルとメモを表示する権限がありません', 1901 2068 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 1902 2086 // Default Labels Management 1903 2087 'Default Labels Management' => 'デフォルトラベル管理', … … 2135 2319 'You do not have permission to view labels and notes' => '라벨과 메모를 볼 권한이 없습니다', 2136 2320 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 2137 2338 // Default Labels Management 2138 2339 'Default Labels Management' => '기본 라벨 관리', … … 2371 2572 'Write your internal notes here...' => 'अपने आंतरिक नोट्स यहां लिखें...', 2372 2573 '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.' => 'लेबल बनाया जाएगा और सभी सामग्री प्रकारों के लिए तुरंत उपलब्ध होगा।', 2373 2591 2374 2592 // Default Labels Management … … 2466 2684 return self::translate($string); 2467 2685 } 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 } 2468 2797 } -
redshape-easy-labels/trunk/includes/class-redshape-easylabels.php
r3397353 r3397460 44 44 add_action('wp_ajax_redshape_easylabels_export_settings', array($this, 'ajax_export_settings')); // NEW: Export 45 45 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 46 47 47 48 // Hook for cache invalidation … … 2100 2101 2101 2102 /** 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 /** 2102 2188 * NUOVO: AJAX per ottenere i conteggi aggiornati dei filtri con percentuali 2103 2189 */ … … 2125 2211 $labels = $this->get_labels_for_post_type($post_type); 2126 2212 2127 // Prepara i dati con percentuali usando la cache2213 // Prepara i dati con percentuali SENZA CACHE per avere dati sempre freschi 2128 2214 $counts_data = array(); 2129 2215 foreach ($labels as $key => $label) { 2130 // ✅ USA CACHE per ottenere il conteggio2131 $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); 2132 2218 2133 2219 $percentage = null; … … 2166 2252 'total' => $total_posts_count 2167 2253 )); 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; 2168 2279 } 2169 2280 -
redshape-easy-labels/trunk/readme.txt
r3397353 r3397460 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.0 7 Stable tag: 1. 0.17 Stable tag: 1.1.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 81 81 == Changelog == 82 82 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 83 99 = 1.0.1 = 84 100 * Fix: Added index.php files to all directories to prevent directory listing on misconfigured servers … … 92 108 * Fix: Notes auto-save - each note field now has independent timeout to prevent data loss when editing multiple notes 93 109 * 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 circles95 * Improvement: Removed debug console.log statements for cleaner production code96 * Improvement: Removed unused debug endpoint ajax_debug_post_types97 * Improvement: Standardized nonce verification across all AJAX endpoints for consistency98 * Improvement: Added clarifying comments to distinguish updateMetaboxHiddenField functions99 110 * Security: Enhanced directory protection following WordPress.org best practices 100 111 -
redshape-easy-labels/trunk/redshape-easy-labels.php
r3397353 r3397460 3 3 * Plugin Name: REDSHAPE Easy Labels 4 4 * 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.15 * Version: 1.1.0 6 6 * Author: REDSHAPE 7 7 * Author URI: https://redshape.it … … 18 18 19 19 // Define plugin constants 20 define('REDSHAPE_EASYLABELS_VERSION', '1. 0.1');20 define('REDSHAPE_EASYLABELS_VERSION', '1.1.0'); 21 21 define('REDSHAPE_EASYLABELS_PLUGIN_URL', plugin_dir_url(__FILE__)); 22 22 define('REDSHAPE_EASYLABELS_PLUGIN_PATH', plugin_dir_path(__FILE__));
Note: See TracChangeset
for help on using the changeset viewer.