Changeset 3718469
- Timestamp:
- 09/29/2026 07:37:37 AM (5 weeks ago)
- Location:
- vendboard-vendor-dashboard-for-woocommerce/trunk
- Files:
-
- 1 added
- 10 edited
-
admin/assets/css/vendor-dashboard.css (modified) (1 diff)
-
admin/assets/js/vendboard-dashboard.js (modified) (1 diff)
-
admin/assets/js/vendboard-product-edit.js (modified) (5 diffs)
-
admin/class-vendboard-branding-settings.php (modified) (3 diffs)
-
admin/class-vendboard-pending-review.php (modified) (3 diffs)
-
admin/class-vendboard-vendor-dashboard.php (modified) (24 diffs)
-
includes/class-vendboard-variations.php (added)
-
languages/vendboard-vendor-dashboard-for-woocommerce-he_IL.mo (modified) ( previous)
-
languages/vendboard-vendor-dashboard-for-woocommerce-he_IL.po (modified) (1 diff)
-
readme.txt (modified) (8 diffs)
-
vendor-dashboard.php (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
vendboard-vendor-dashboard-for-woocommerce/trunk/admin/assets/css/vendor-dashboard.css
r3664998 r3718469 1689 1689 color: #334155; 1690 1690 } 1691 1692 /* ------------------------------------------------------ 1693 * VARIATIONS 1694 * ------------------------------------------------------ */ 1695 .vendboard-variation-row { 1696 border: 1px solid #e2e8f0; 1697 border-radius: 6px; 1698 padding: 14px 16px; 1699 margin-bottom: 12px; 1700 background: #fff; 1701 } 1702 1703 .vendboard-variation-head { 1704 display: flex; 1705 flex-wrap: wrap; 1706 align-items: center; 1707 gap: 8px; 1708 margin-bottom: 12px; 1709 font-size: 15px; 1710 } 1711 1712 .vendboard-variation-fields, 1713 .vendboard-variation-add-fields { 1714 display: grid; 1715 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); 1716 gap: 12px; 1717 align-items: end; 1718 } 1719 1720 .vendboard-variation-field { 1721 display: flex; 1722 flex-direction: column; 1723 gap: 5px; 1724 font-size: 13px; 1725 font-weight: 600; 1726 color: #475569; 1727 text-transform: none; 1728 letter-spacing: 0; 1729 } 1730 1731 .vendboard-variation-field .vendboard-text-input { 1732 padding: 9px 12px; 1733 font-size: 14px; 1734 } 1735 1736 .vendboard-variation-toggle { 1737 justify-content: flex-end; 1738 } 1739 1740 .vendboard-variation-toggle input[type="checkbox"] { 1741 width: 20px; 1742 height: 20px; 1743 margin: 6px 0; 1744 } 1745 1746 .vendboard-variation-add { 1747 margin-top: 18px; 1748 padding: 16px; 1749 border: 2px dashed #cbd5e1; 1750 border-radius: 6px; 1751 background: #f8fafc; 1752 } 1753 1754 .vendboard-variation-add h4 { 1755 margin: 0 0 6px; 1756 font-size: 16px; 1757 color: #334155; 1758 } 1759 1760 .vendboard-variation-add-note { 1761 margin-top: 10px; 1762 color: #64748b; 1763 font-style: italic; 1764 } 1765 1766 .vendboard-variation-review { 1767 width: 100%; 1768 border-collapse: collapse; 1769 font-size: 14px; 1770 } 1771 1772 .vendboard-variation-review td { 1773 padding: 8px 10px; 1774 border-bottom: 1px solid #e2e8f0; 1775 vertical-align: top; 1776 } 1777 1778 .vendboard-variation-review-label { 1779 font-weight: 600; 1780 white-space: nowrap; 1781 } 1782 1783 @media (max-width: 600px) { 1784 .vendboard-variation-fields, 1785 .vendboard-variation-add-fields { 1786 grid-template-columns: 1fr; 1787 } 1788 } 1789 1790 /* ------------------------------------------------------ 1791 * STOCK FILTER 1792 * ------------------------------------------------------ */ 1793 .vendboard-stock-filter { 1794 flex: 0 0 auto; 1795 padding: 12px 14px; 1796 border: 2px solid #e2e8f0; 1797 border-radius: 5px; 1798 font-size: 14px; 1799 background: #f8fafc; 1800 cursor: pointer; 1801 } 1802 1803 .vendboard-stock-filter:focus { 1804 outline: none; 1805 border-color: var(--vendboard-primary, #0000c8); 1806 background: #fff; 1807 } 1808 1809 /* ------------------------------------------------------ 1810 * PRODUCT TYPE + OPTION PICKER 1811 * ------------------------------------------------------ */ 1812 .vendboard-simple-only[hidden], 1813 .vendboard-variable-only[hidden] { 1814 display: none !important; 1815 } 1816 1817 .vendboard-attribute-set { 1818 margin: 0 0 14px; 1819 padding: 12px 16px; 1820 border: 1px solid #e2e8f0; 1821 border-radius: 6px; 1822 background: #fff; 1823 } 1824 1825 .vendboard-attribute-set legend { 1826 padding: 0 6px; 1827 font-weight: 700; 1828 color: #334155; 1829 } 1830 1831 .vendboard-attribute-terms { 1832 display: flex; 1833 flex-wrap: wrap; 1834 gap: 8px 18px; 1835 } 1836 1837 /* Beat the generic ".vendboard-form-group label" rule (big, uppercase, block) */ 1838 .vendboard-form-group .vendboard-attribute-terms label { 1839 display: inline-flex; 1840 align-items: center; 1841 gap: 6px; 1842 margin: 0; 1843 font-size: 14px; 1844 font-weight: 400; 1845 text-transform: none; 1846 letter-spacing: 0; 1847 cursor: pointer; 1848 } 1849 1850 .vendboard-form-group .vendboard-variation-field { 1851 display: flex; 1852 margin: 0; 1853 font-size: 13px; 1854 font-weight: 600; 1855 text-transform: none; 1856 letter-spacing: 0; 1857 } 1858 1859 /* ------------------------------------------------------ 1860 * TEXT DIRECTION 1861 * Follows the dashboard language setting, not the site locale. Hebrew 1862 * themes often force RTL on body or on form fields, so the dashboard 1863 * re-asserts its own direction for everything inside it. Table cells keep 1864 * their own (centred) alignment. 1865 * ------------------------------------------------------ */ 1866 .main-vendboard-container.vendboard-dir-ltr, 1867 .main-vendboard-container.vendboard-dir-ltr * { 1868 direction: ltr; 1869 } 1870 1871 .main-vendboard-container.vendboard-dir-rtl, 1872 .main-vendboard-container.vendboard-dir-rtl * { 1873 direction: rtl; 1874 } 1875 1876 .main-vendboard-container.vendboard-dir-ltr { 1877 text-align: left; 1878 } 1879 1880 .main-vendboard-container.vendboard-dir-rtl { 1881 text-align: right; 1882 } 1883 1884 .main-vendboard-container.vendboard-dir-ltr input, 1885 .main-vendboard-container.vendboard-dir-ltr select, 1886 .main-vendboard-container.vendboard-dir-ltr textarea, 1887 .main-vendboard-container.vendboard-dir-ltr legend, 1888 .main-vendboard-container.vendboard-dir-ltr .vendboard-form-group, 1889 .main-vendboard-container.vendboard-dir-ltr .vendboard-search-summary { 1890 text-align: left; 1891 } 1892 1893 .main-vendboard-container.vendboard-dir-rtl input, 1894 .main-vendboard-container.vendboard-dir-rtl select, 1895 .main-vendboard-container.vendboard-dir-rtl textarea, 1896 .main-vendboard-container.vendboard-dir-rtl legend, 1897 .main-vendboard-container.vendboard-dir-rtl .vendboard-form-group, 1898 .main-vendboard-container.vendboard-dir-rtl .vendboard-search-summary { 1899 text-align: right; 1900 } 1901 1902 .main-vendboard-container .vendboard-search-form { 1903 justify-content: flex-start; 1904 } 1905 1906 /* ------------------------------------------------------ 1907 * SELECT SIZING 1908 * Themes commonly give selects a fixed height, which clips the text once 1909 * our padding is added. Size them by content instead and leave room for 1910 * the dropdown arrow on whichever side it sits. 1911 * ------------------------------------------------------ */ 1912 .main-vendboard-container select { 1913 box-sizing: border-box; 1914 height: auto; 1915 min-height: 44px; 1916 max-width: 100%; 1917 line-height: 1.4; 1918 padding-top: 8px; 1919 padding-bottom: 8px; 1920 padding-inline-start: 12px; 1921 padding-inline-end: 36px; 1922 } 1923 1924 .main-vendboard-container .vendboard-search-form .vendboard-search-input, 1925 .main-vendboard-container .vendboard-search-form .vendboard-stock-filter { 1926 box-sizing: border-box; 1927 height: auto; 1928 min-height: 48px; 1929 margin: 0; 1930 } 1931 1932 .main-vendboard-container .vendboard-search-form .vendboard-stock-filter { 1933 width: auto; 1934 min-width: 200px; 1935 padding-top: 10px; 1936 padding-bottom: 10px; 1937 padding-inline-start: 14px; 1938 padding-inline-end: 40px; 1939 } 1940 1941 .main-vendboard-container .vendboard-variation-field select.vendboard-text-input { 1942 padding-inline-end: 36px; 1943 } 1944 1945 @media (max-width: 600px) { 1946 .main-vendboard-container .vendboard-search-form .vendboard-stock-filter { 1947 flex: 1 1 100%; 1948 min-width: 0; 1949 width: 100%; 1950 } 1951 } 1952 1953 /* ------------------------------------------------------ 1954 * NEW VARIATIONS TABLE 1955 * ------------------------------------------------------ */ 1956 .vendboard-combo-fill-bar { 1957 display: flex; 1958 flex-wrap: wrap; 1959 align-items: flex-end; 1960 gap: 12px; 1961 margin: 12px 0; 1962 } 1963 1964 .vendboard-combo-fill-bar .vendboard-variation-field { 1965 flex: 0 1 180px; 1966 } 1967 1968 .vendboard-combo-scroll { 1969 overflow-x: auto; 1970 } 1971 1972 .vendboard-combo-table { 1973 width: 100%; 1974 min-width: 560px; 1975 border-collapse: collapse; 1976 background: #fff; 1977 font-size: 14px; 1978 } 1979 1980 .vendboard-combo-table th, 1981 .vendboard-combo-table td { 1982 padding: 8px 10px; 1983 border-bottom: 1px solid #e2e8f0; 1984 vertical-align: middle; 1985 text-align: start; 1986 } 1987 1988 .vendboard-combo-table th { 1989 font-weight: 700; 1990 color: #334155; 1991 white-space: nowrap; 1992 } 1993 1994 .vendboard-combo-table .vendboard-text-input { 1995 width: 100%; 1996 min-width: 80px; 1997 padding: 8px 10px; 1998 font-size: 14px; 1999 } 2000 2001 .vendboard-combo-label { 2002 font-weight: 600; 2003 } 2004 2005 .vendboard-combo-table input[type="checkbox"] { 2006 width: 18px; 2007 height: 18px; 2008 } 2009 2010 /* Beat the generic ".vendboard-form-group label" rule */ 2011 .vendboard-form-group .vendboard-combo-all-label { 2012 display: inline-flex; 2013 align-items: center; 2014 gap: 6px; 2015 margin: 0; 2016 font-size: 13px; 2017 font-weight: 700; 2018 text-transform: none; 2019 letter-spacing: 0; 2020 white-space: nowrap; 2021 cursor: pointer; 2022 } 2023 2024 /* ------------------------------------------------------ 2025 * COLLAPSIBLE SECTIONS 2026 * ------------------------------------------------------ */ 2027 .vendboard-attribute-set, 2028 .vendboard-variations-list, 2029 .vendboard-variation-add { 2030 margin: 0 0 14px; 2031 padding: 0; 2032 border: 1px solid #e2e8f0; 2033 border-radius: 6px; 2034 background: #fff; 2035 } 2036 2037 .vendboard-attribute-set > summary, 2038 .vendboard-variations-list > summary, 2039 .vendboard-variation-add > summary { 2040 display: flex; 2041 align-items: center; 2042 gap: 10px; 2043 padding: 12px 16px; 2044 background: #f8fafc; 2045 border-radius: 6px; 2046 cursor: pointer; 2047 list-style: none; 2048 font-size: 15px; 2049 font-weight: 700; 2050 color: #334155; 2051 } 2052 2053 .vendboard-attribute-set[open] > summary, 2054 .vendboard-variations-list[open] > summary, 2055 .vendboard-variation-add[open] > summary { 2056 border-bottom: 1px solid #e2e8f0; 2057 border-radius: 6px 6px 0 0; 2058 } 2059 2060 .vendboard-attribute-set > summary::-webkit-details-marker, 2061 .vendboard-variations-list > summary::-webkit-details-marker, 2062 .vendboard-variation-add > summary::-webkit-details-marker { 2063 display: none; 2064 } 2065 2066 .vendboard-attribute-set > summary::after, 2067 .vendboard-variations-list > summary::after, 2068 .vendboard-variation-add > summary::after { 2069 content: "\25BE"; 2070 margin-inline-start: auto; 2071 font-size: 14px; 2072 color: #64748b; 2073 } 2074 2075 .vendboard-attribute-set[open] > summary::after, 2076 .vendboard-variations-list[open] > summary::after, 2077 .vendboard-variation-add[open] > summary::after { 2078 content: "\25B4"; 2079 } 2080 2081 .vendboard-summary-count { 2082 font-size: 13px; 2083 font-weight: 400; 2084 color: #64748b; 2085 } 2086 2087 .vendboard-attribute-set > .vendboard-attribute-terms { 2088 padding: 14px 16px; 2089 } 2090 2091 .vendboard-variations-list > .vendboard-variation-row { 2092 margin: 14px 16px; 2093 } 2094 2095 .vendboard-variation-add > p, 2096 .vendboard-variation-add > .vendboard-combo-fill-bar, 2097 .vendboard-variation-add > .vendboard-combo-scroll { 2098 margin-inline: 16px; 2099 } 2100 2101 .vendboard-variation-add > .vendboard-combo-scroll { 2102 padding-bottom: 16px; 2103 } -
vendboard-vendor-dashboard-for-woocommerce/trunk/admin/assets/js/vendboard-dashboard.js
r3664998 r3718469 1 1 jQuery(document).ready(function ($) { 2 $('.vendboard-stock-filter').on('change', function () { 3 this.form.submit(); 4 }); 5 2 6 $('#vendboard-onboarding-close').on('click', function () { 3 7 $('.vendboard-onboarding-popup').fadeOut(300); -
vendboard-vendor-dashboard-for-woocommerce/trunk/admin/assets/js/vendboard-product-edit.js
r3663532 r3718469 109 109 } 110 110 111 /* ------------------------------------------------------ 112 * PRODUCT TYPE 113 * A product with options is priced and stocked per variation, so the 114 * single-product fields are hidden and disabled (disabled fields are not 115 * submitted), and the option picker is shown instead. 116 * ------------------------------------------------------ */ 117 var $productType = $('select[name="vendboard_product_type"]'); 118 119 function vendboardApplyProductType() { 120 121 if (!$productType.length) { 122 return; 123 } 124 125 var isVariable = ($productType.val() === 'variable'); 126 127 $('.vendboard-simple-only').each(function () { 128 this.hidden = isVariable; 129 $(this).find(':input').prop('disabled', isVariable); 130 }); 131 132 $('.vendboard-variable-only').each(function () { 133 this.hidden = !isVariable; 134 $(this).find(':input').prop('disabled', !isVariable); 135 }); 136 137 // Re-applies the quantity lock on the status select after re-enabling 138 if (!isVariable && $stockQty.length && $stockStatus.length) { 139 vendboardSyncStockStatus(); 140 } 141 } 142 143 $productType.on('change', vendboardApplyProductType); 144 vendboardApplyProductType(); 145 146 /* ------------------------------------------------------ 147 * NEW VARIATIONS (combination table) 148 * ------------------------------------------------------ */ 149 $(document).on('change', '.vendboard-combo-all', function () { 150 $('.vendboard-combo-pick').prop('checked', this.checked); 151 }); 152 153 // Typing into a row selects it, so filled-in rows are not lost 154 $(document).on('input', '.vendboard-combo-price, .vendboard-combo-stock, .vendboard-combo-sku', function () { 155 if (String(this.value).trim() !== '') { 156 $(this).closest('tr').find('.vendboard-combo-pick').prop('checked', true); 157 } 158 }); 159 160 $(document).on('click', '.vendboard-combo-fill', function () { 161 var price = String($('.vendboard-combo-fill-price').val()).trim(); 162 var stock = String($('.vendboard-combo-fill-stock').val()).trim(); 163 164 $('.vendboard-combo-pick:checked').each(function () { 165 var $row = $(this).closest('tr'); 166 if (price !== '') { 167 $row.find('.vendboard-combo-price').val(price); 168 } 169 if (stock !== '') { 170 $row.find('.vendboard-combo-stock').val(stock); 171 } 172 }); 173 }); 174 111 175 112 176 $('#vendboard-edit-form').on('submit', function (e) { … … 130 194 131 195 // Price (only for new product) 132 if ($('input[name="product_price"] ').length) {133 let priceField = $('input[name="product_price"] ');196 if ($('input[name="product_price"]:not(:disabled)').length) { 197 let priceField = $('input[name="product_price"]:not(:disabled)'); 134 198 let price = priceField.val().trim(); 135 199 … … 145 209 146 210 // Stock quantity (optional, but must be a positive whole number) 147 if ($stockQty.length ) {211 if ($stockQty.length && !$stockQty.prop('disabled')) { 148 212 let stock = String($stockQty.val()).trim(); 149 213 … … 169 233 } 170 234 235 // Removing variations is permanent, so confirm it once 236 var removing = $('.vendboard-variation-remove:checked').length; 237 238 if (removing && !window.confirm(String(vendboardProductData.i18n.confirm_remove).replace('%d', removing))) { 239 return false; 240 } 241 171 242 var formData = new FormData(this); 172 243 173 244 // Disabled fields are skipped by FormData, so send the status explicitly. 174 if ($stockStatus.length && $stockStatus.prop('disabled')) { 245 if ($stockStatus.length && $stockStatus.prop('disabled') && 246 !$stockStatus.closest('.vendboard-simple-only').prop('hidden')) { 175 247 formData.set('product_stock_status', $stockStatus.val()); 176 248 } … … 206 278 msgBox.fadeOut(300, function () { 207 279 $(this).remove(); 208 window.location.href = vendboardProductData.redirect_url; 280 window.location.href = (res.data && res.data.redirect) 281 ? res.data.redirect 282 : vendboardProductData.redirect_url; 209 283 }); 210 284 }, 1500); -
vendboard-vendor-dashboard-for-woocommerce/trunk/admin/class-vendboard-branding-settings.php
r3664998 r3718469 47 47 'sanitize_callback' => 'sanitize_text_field', 48 48 'default' => 'blue', 49 ] 50 ); 51 52 // Attributes vendors may use (empty = all of them) 53 register_setting( 54 'vendboard_branding_settings', 55 'vendboard_vendor_attributes', 56 [ 57 'type' => 'array', 58 'sanitize_callback' => [__CLASS__, 'sanitize_vendor_attributes'], 59 'default' => [], 49 60 ] 50 61 ); … … 67 78 68 79 /** 80 * Keep only real global attribute keys. An empty list means "all of them". 81 */ 82 public static function sanitize_vendor_attributes($value) 83 { 84 if (!is_array($value)) { 85 return []; 86 } 87 88 $valid = function_exists('vendboard_get_global_attribute_choices') 89 ? vendboard_get_global_attribute_choices() 90 : []; 91 92 $out = []; 93 94 foreach ($value as $key) { 95 $key = (string) $key; 96 97 if (isset($valid[$key])) { 98 $out[] = $key; 99 } 100 } 101 102 return $out; 103 } 104 105 /** 69 106 * Predefined themes map 70 107 */ … … 240 277 <?php esc_html_e('Select one of the predefined color themes', 'vendboard-vendor-dashboard-for-woocommerce'); ?> 241 278 </p> 279 </div> 280 281 <!-- =============================== 282 WHICH OPTIONS VENDORS MAY USE 283 =============================== --> 284 <div class="vendboard-color-control"> 285 <label><?php esc_html_e('Options vendors can use', 'vendboard-vendor-dashboard-for-woocommerce'); ?></label> 286 287 <?php 288 $attribute_choices = function_exists('vendboard_get_global_attribute_choices') 289 ? vendboard_get_global_attribute_choices() 290 : []; 291 $allowed_attributes = (array) get_option('vendboard_vendor_attributes', []); 292 $allowed_attributes = array_map('strval', $allowed_attributes); 293 ?> 294 295 <?php if (empty($attribute_choices)): ?> 296 <p class="vendboard-color-desc"> 297 <?php esc_html_e('No product attributes have been set up yet. Add them under Products > Attributes.', 'vendboard-vendor-dashboard-for-woocommerce'); ?> 298 </p> 299 <?php else: ?> 300 <!-- Always submitted, so unticking everything still saves --> 301 <input type="hidden" name="vendboard_vendor_attributes[]" value=""> 302 303 <?php foreach ($attribute_choices as $attribute_key => $attribute_choice): ?> 304 <label style="display:block;margin:6px 0;font-weight:400;"> 305 <input type="checkbox" name="vendboard_vendor_attributes[]" 306 value="<?php echo esc_attr($attribute_key); ?>" 307 <?php checked(empty($allowed_attributes) || in_array((string) $attribute_key, $allowed_attributes, true)); ?>> 308 <?php echo esc_html($attribute_choice['label']); ?> 309 </label> 310 <?php endforeach; ?> 311 312 <p class="vendboard-color-desc"> 313 <?php esc_html_e('Vendors can only choose from the ticked options when setting up a product with options. Ticking all of them is the same as ticking none. Attributes already on a product are never removed.', 'vendboard-vendor-dashboard-for-woocommerce'); ?> 314 </p> 315 <?php endif; ?> 242 316 </div> 243 317 -
vendboard-vendor-dashboard-for-woocommerce/trunk/admin/class-vendboard-pending-review.php
r3666364 r3718469 578 578 </div> 579 579 580 <?php 581 $variation_rows = class_exists('VendBoard_Variations') 582 ? VendBoard_Variations::get_pending_rows($pid) 583 : []; 584 ?> 585 <?php if (!empty($variation_rows)): ?> 586 <!-- VARIATIONS --> 587 <div class="vendboard-comparison-item vendboard-comparison-variations"> 588 <h4 class="vendboard-comparison-label"> 589 <span class="dashicons dashicons-screenoptions"></span> 590 <?php esc_html_e('Variations', 'vendboard-vendor-dashboard-for-woocommerce'); ?> 591 </h4> 592 593 <table class="vendboard-variation-review"> 594 <tbody> 595 <?php foreach ($variation_rows as $row): ?> 596 <tr> 597 <td class="vendboard-variation-review-label"> 598 <?php echo esc_html($row['label']); ?> 599 <?php if (!empty($row['is_new'])): ?> 600 <span class="vendboard-new-product-label"> 601 <?php esc_html_e('New', 'vendboard-vendor-dashboard-for-woocommerce'); ?> 602 </span> 603 <?php endif; ?> 604 </td> 605 <td><?php echo esc_html($row['change']); ?></td> 606 </tr> 607 <?php endforeach; ?> 608 </tbody> 609 </table> 610 </div> 611 <?php endif; ?> 612 580 613 <!-- SKU --> 581 614 <div class="vendboard-comparison-item"> … … 851 884 if ($pending_barcode !== '') { 852 885 vendboard_set_barcode($pid, $pending_barcode); 886 } 887 888 /* ------------------------------ 889 * VARIATIONS 890 * 891 * Enables variations the vendor added and applies staged per-variation 892 * price and SKU values. 893 * ------------------------------ */ 894 if (class_exists('VendBoard_Variations')) { 895 VendBoard_Variations::approve($pid); 853 896 } 854 897 … … 972 1015 } 973 1016 1017 // Staged variation changes are discarded too. Variations the vendor 1018 // added are left in place but stay disabled, so nothing is destroyed. 1019 if (class_exists('VendBoard_Variations')) { 1020 VendBoard_Variations::reject($pid); 1021 } 1022 974 1023 // Save status BEFORE sending response 975 1024 update_post_meta($pid, '_vendboard_last_status', 'rejected'); -
vendboard-vendor-dashboard-for-woocommerce/trunk/admin/class-vendboard-vendor-dashboard.php
r3666364 r3718469 17 17 add_action('admin_init', [__CLASS__, 'redirect_vendor_to_dashboard']); 18 18 add_action('wp', [__CLASS__, 'restrict_vendor_to_dashboard']); 19 20 // Send vendors straight to their dashboard when they log in 21 add_filter('login_redirect', [__CLASS__, 'login_redirect'], 20, 3); 22 add_filter('woocommerce_login_redirect', [__CLASS__, 'woocommerce_login_redirect'], 20, 2); 19 23 20 24 // Dashboard template loader … … 91 95 'saved_successfully' => __('Saved successfully.', 'vendboard-vendor-dashboard-for-woocommerce'), 92 96 'error_occurred' => __('An error occurred. Please try again.', 'vendboard-vendor-dashboard-for-woocommerce'), 97 /* translators: %d: number of variations */ 98 'confirm_remove' => __('Remove %d variation(s)? This cannot be undone.', 'vendboard-vendor-dashboard-for-woocommerce'), 93 99 ] 94 100 ]); … … 120 126 public static function redirect_vendor_to_dashboard() 121 127 { 128 if (!is_admin() || wp_doing_ajax()) { 129 return; 130 } 131 122 132 $user = wp_get_current_user(); 123 if (in_array('vendor', (array) $user->roles, true) && is_admin() && !defined('DOING_AJAX')) { 124 125 // Emergency escape hatch 126 if (isset($_GET['vendboard_no_redirect']) && $_GET['vendboard_no_redirect'] === 'emergency') { 127 return; 128 } 129 130 $screen = function_exists('get_current_screen') ? get_current_screen() : null; 131 132 // Allow plugin admin pages only 133 if ($screen && strpos($screen->id, 'vendor') !== false) 134 return; 135 136 // Check if dashboard page exists 137 if (!self::vendor_dashboard_page_exists()) { 138 return; 139 } 140 141 wp_safe_redirect(site_url('/vendor-dashboard/')); 142 exit; 143 } 133 134 // Someone who is also an administrator keeps wp-admin 135 if (!in_array('vendor', (array) $user->roles, true) || user_can($user, 'manage_options')) { 136 return; 137 } 138 139 /* 140 * Fail closed. Vendors never fall through to wp-admin, where they 141 * could edit products outside the approval workflow. If there is no 142 * published dashboard page, they go to the home page instead. 143 * 144 * There is deliberately no bypass parameter: one would only ever 145 * apply to vendors, since administrators are never redirected. 146 */ 147 wp_safe_redirect(self::vendor_dashboard_url()); 148 exit; 149 } 150 151 /** 152 * Where a vendor should land: their dashboard, or the home page if it is 153 * unavailable. 154 */ 155 private static function vendor_dashboard_url() 156 { 157 return self::vendor_dashboard_page_exists() ? site_url('/vendor-dashboard/') : home_url('/'); 158 } 159 160 /** 161 * Send vendors to their dashboard after logging in through wp-login.php. 162 * 163 * @param string $redirect_to Default destination. 164 * @param string $requested Destination the form asked for. 165 * @param WP_User|WP_Error $user The user logging in. 166 */ 167 public static function login_redirect($redirect_to, $requested = '', $user = null) 168 { 169 if ( 170 $user instanceof WP_User && 171 in_array('vendor', (array) $user->roles, true) && 172 !user_can($user, 'manage_options') 173 ) { 174 return self::vendor_dashboard_url(); 175 } 176 177 return $redirect_to; 178 } 179 180 /** 181 * Same, for the WooCommerce My Account login form. 182 */ 183 public static function woocommerce_login_redirect($redirect, $user = null) 184 { 185 return self::login_redirect($redirect, '', $user); 144 186 } 145 187 … … 180 222 private static function vendor_dashboard_page_exists() 181 223 { 182 $dashboard_page = get_page_by_path('vendor-dashboard');183 return ($dashboard_page && $dashboard_page->post_status === 'publish');224 // Recreates the page if it has gone missing 225 return vendboard_ensure_dashboard_page() > 0; 184 226 } 185 227 … … 240 282 241 283 return in_array((int) $assigned_brand, array_map('intval', $product_brands), true); 284 } 285 286 /** 287 * Text direction of the vendor dashboard. 288 * 289 * Follows the dashboard language setting, not the site locale: a Hebrew 290 * site can run an English dashboard, and vice versa. 291 */ 292 private static function text_direction() 293 { 294 return (get_option('vendboard_dashboard_lang', 'en') === 'he') ? 'rtl' : 'ltr'; 242 295 } 243 296 … … 418 471 $search = isset($_GET['vb_s']) ? trim(sanitize_text_field(wp_unslash($_GET['vb_s']))) : ''; 419 472 $paged = isset($_GET['vb_page']) ? max(1, absint($_GET['vb_page'])) : 1; 473 474 // Stock status filter; anything not a known status is ignored 475 $stock_filter = isset($_GET['vb_stock']) ? sanitize_key(wp_unslash($_GET['vb_stock'])) : ''; 476 $stock_filter = array_key_exists($stock_filter, vendboard_get_stock_statuses()) ? $stock_filter : ''; 420 477 421 478 /** … … 481 538 ]; 482 539 540 if ($stock_filter !== '') { 541 $main_args['meta_query'] = [ 542 'relation' => 'AND', 543 $main_args['meta_query'], 544 [ 545 'key' => '_stock_status', 546 'value' => $stock_filter, 547 'compare' => '=', 548 ], 549 ]; 550 } 551 483 552 $no_search_matches = false; 484 553 … … 588 657 // Selected theme logic moved to enqueue_frontend_assets 589 658 ?> 590 <div class="main-vendboard-container"> 659 <div class="main-vendboard-container vendboard-dir-<?php echo esc_attr(self::text_direction()); ?>" 660 dir="<?php echo esc_attr(self::text_direction()); ?>"> 591 661 <div class="vendboard-container" dir="<?php echo esc_attr($rtl); ?>"> 592 662 … … 635 705 value="<?php echo esc_attr($search); ?>" 636 706 placeholder="<?php esc_attr_e('Search by name, SKU or barcode', 'vendboard-vendor-dashboard-for-woocommerce'); ?>"> 707 <select name="vb_stock" class="vendboard-stock-filter" 708 aria-label="<?php esc_attr_e('Filter by stock status', 'vendboard-vendor-dashboard-for-woocommerce'); ?>"> 709 <option value=""><?php esc_html_e('All stock statuses', 'vendboard-vendor-dashboard-for-woocommerce'); ?></option> 710 <?php foreach (vendboard_get_stock_statuses() as $status_key => $status_label): ?> 711 <option value="<?php echo esc_attr($status_key); ?>" <?php selected($stock_filter, $status_key); ?>> 712 <?php echo esc_html($status_label); ?> 713 </option> 714 <?php endforeach; ?> 715 </select> 637 716 <button type="submit" 638 717 class="vendboard-submit-btn"><?php esc_html_e('Search', 'vendboard-vendor-dashboard-for-woocommerce'); ?></button> 639 <?php if ($search !== '' ): ?>718 <?php if ($search !== '' || $stock_filter !== ''): ?> 640 719 <a class="vendboard-cancel-btn" 641 720 href="<?php echo esc_url(site_url('/vendor-dashboard/')); ?>"><?php esc_html_e('Clear', 'vendboard-vendor-dashboard-for-woocommerce'); ?></a> … … 655 734 ?> 656 735 </p> 736 <?php elseif ($stock_filter !== ''): ?> 737 <p class="vendboard-search-summary"> 738 <?php 739 $found = $query ? (int) $query->found_posts : 0; 740 $stock_labels = vendboard_get_stock_statuses(); 741 printf( 742 /* translators: 1: number of products, 2: stock status such as "Out of stock" */ 743 esc_html(_n('Showing %1$s product: %2$s', 'Showing %1$s products: %2$s', $found, 'vendboard-vendor-dashboard-for-woocommerce')), 744 esc_html(number_format_i18n($found)), 745 esc_html($stock_labels[$stock_filter]) 746 ); 747 ?> 748 </p> 657 749 <?php endif; ?> 658 750 659 751 <?php echo wp_kses_post(self::render_product_table($products, false)); ?> 660 <?php echo wp_kses_post(self::render_pagination($query, $paged, $search )); ?>752 <?php echo wp_kses_post(self::render_pagination($query, $paged, $search, $stock_filter)); ?> 661 753 </div> 662 754 … … 699 791 * Pagination links for the product list, preserving the search term. 700 792 */ 701 private static function render_pagination($query, $paged, $search )793 private static function render_pagination($query, $paged, $search, $stock_filter = '') 702 794 { 703 795 if (!$query || (int) $query->max_num_pages < 2) { … … 709 801 if ($search !== '') { 710 802 $base_args['vb_s'] = $search; 803 } 804 805 if ($stock_filter !== '') { 806 $base_args['vb_stock'] = $stock_filter; 711 807 } 712 808 … … 1019 1115 // Selected theme logic moved to enqueue_frontend_assets 1020 1116 ?> 1021 <div class="main-vendboard-container"> 1117 <div class="main-vendboard-container vendboard-dir-<?php echo esc_attr(self::text_direction()); ?>" 1118 dir="<?php echo esc_attr(self::text_direction()); ?>"> 1022 1119 <div class="vendboard-edit"> 1023 1120 <div class="vendboard-header-section"> … … 1042 1139 <input type="hidden" name="product_id" value="<?php echo esc_attr($pid); ?>"> 1043 1140 <?php wp_nonce_field('vendboard_save_product', 'vendboard_nonce'); ?> 1141 1142 <?php 1143 // Product type + options, only while the product is unapproved 1144 VendBoard_Variations::render_structure_controls($pid); 1145 ?> 1044 1146 1045 1147 … … 1104 1206 <div class="vendboard-form-group"> 1105 1207 <label><?php esc_html_e('Product Price', 'vendboard-vendor-dashboard-for-woocommerce'); ?></label> 1106 <input type="number" name="product_price" class="vendboard-text-input" step="0.01" min="0" 1107 value="<?php echo esc_attr($display_price); ?>" required> 1108 <p class="vendboard-error vendboard-price-error" style="display:none;color:red;margin-top:5px;"> 1109 <?php esc_html_e('Price is required.', 'vendboard-vendor-dashboard-for-woocommerce'); ?> 1110 </p> 1208 <?php if (VendBoard_Variations::is_variable($pid)): ?> 1209 <p class="vendboard-na"> 1210 <?php esc_html_e('This product is priced per variation. Set the prices in the Variations section below.', 'vendboard-vendor-dashboard-for-woocommerce'); ?> 1211 </p> 1212 <?php else: ?> 1213 <div class="vendboard-simple-only"> 1214 <input type="number" name="product_price" class="vendboard-text-input" step="0.01" min="0" 1215 value="<?php echo esc_attr($display_price); ?>" required> 1216 <p class="vendboard-error vendboard-price-error" style="display:none;color:red;margin-top:5px;"> 1217 <?php esc_html_e('Price is required.', 'vendboard-vendor-dashboard-for-woocommerce'); ?> 1218 </p> 1219 <p class="vendboard-stock-live-note"> 1220 <?php esc_html_e('Applies straight away, without admin approval.', 'vendboard-vendor-dashboard-for-woocommerce'); ?> 1221 </p> 1222 </div> 1223 <p class="vendboard-na vendboard-variable-only" hidden> 1224 <?php esc_html_e('Priced per variation. You can set prices after saving, once you have added its variations.', 'vendboard-vendor-dashboard-for-woocommerce'); ?> 1225 </p> 1226 <?php endif; ?> 1111 1227 </div> 1112 1228 … … 1207 1323 </div> 1208 1324 1325 <p class="vendboard-stock-live-note"> 1326 <?php esc_html_e('SKU and barcode changes apply straight away, without admin approval.', 'vendboard-vendor-dashboard-for-woocommerce'); ?> 1327 </p> 1328 1209 1329 <?php 1210 1330 /* ------------------------------------------------------ … … 1217 1337 ?> 1218 1338 1339 <?php if (VendBoard_Variations::is_variable($pid)): ?> 1340 1341 <div class="vendboard-form-group"> 1342 <label><?php esc_html_e('Stock', 'vendboard-vendor-dashboard-for-woocommerce'); ?></label> 1343 <p class="vendboard-na"> 1344 <?php esc_html_e('This product keeps stock per variation. Set the quantities in the Variations section below.', 'vendboard-vendor-dashboard-for-woocommerce'); ?> 1345 </p> 1346 </div> 1347 1348 <?php else: ?> 1349 1350 <div class="vendboard-simple-only"> 1219 1351 <div class="vendboard-form-row"> 1220 1352 … … 1253 1385 1254 1386 </div> 1387 </div> 1388 1389 <?php endif; ?> 1255 1390 1256 1391 <?php … … 1306 1441 </div> 1307 1442 1443 <?php 1444 // Variations panel, only for products that have variations. 1445 // Prints its own escaped markup; see render_panel(). 1446 VendBoard_Variations::render_panel($pid); 1447 ?> 1448 1308 1449 1309 1450 <!-- Short Description --> 1310 1451 <div class="vendboard-form-group"> 1311 <label><?php esc_html_e(' YouTube link /Short Description', 'vendboard-vendor-dashboard-for-woocommerce'); ?></label>1452 <label><?php esc_html_e('Short Description', 'vendboard-vendor-dashboard-for-woocommerce'); ?></label> 1312 1453 1313 1454 <?php … … 1475 1616 } 1476 1617 1618 // New products are created without WooCommerce's default category; 1619 // some themes cannot render a product page without one. 1620 vendboard_ensure_product_category($pid); 1621 1477 1622 /** 1478 1623 * Tracks whether anything that needs admin approval actually changed. … … 1483 1628 1484 1629 /* ------------------------------------------------------ 1485 * PRICE always save pending value 1630 * PRICE, SKU & BARCODE 1631 * 1632 * Applied straight away, like stock. Written through the WooCommerce 1633 * product object rather than raw meta, so an active sale price keeps 1634 * working and SKU uniqueness is enforced. Nothing is written until 1635 * every submitted value has passed validation. 1486 1636 * ------------------------------------------------------ */ 1487 if (isset($_POST['product_price'])) { 1488 1489 $price = floatval($_POST['product_price'] ?? 0); 1490 if ($price < 0) { 1637 $fields_applied = false; 1638 $live_product = function_exists('wc_get_product') ? wc_get_product($pid) : null; 1639 1640 if (isset($_POST['product_price']) && $live_product) { 1641 1642 $raw_price = trim(sanitize_text_field(wp_unslash($_POST['product_price']))); 1643 1644 if ($raw_price === '') { 1645 restore_previous_locale(); 1646 wp_send_json_error(['msg' => __('Price required', 'vendboard-vendor-dashboard-for-woocommerce')]); 1647 } 1648 1649 if (!is_numeric($raw_price)) { 1650 restore_previous_locale(); 1651 wp_send_json_error(['msg' => __('Price must be a number', 'vendboard-vendor-dashboard-for-woocommerce')]); 1652 } 1653 1654 if ((float) $raw_price < 0) { 1655 restore_previous_locale(); 1491 1656 wp_send_json_error(['msg' => __('Price cannot be negative', 'vendboard-vendor-dashboard-for-woocommerce')]); 1492 1657 } 1493 1658 1494 if ($price === '') { 1495 wp_send_json_error(['msg' => __('Price required', 'vendboard-vendor-dashboard-for-woocommerce')]); 1496 } 1497 1498 $live_price = get_post_meta($pid, '_regular_price', true); 1499 if (abs($price - (float) $live_price) > 0.00001) { 1500 $content_changed = true; 1501 } 1502 1503 update_post_meta($pid, '_vendboard_pending_price', $price); 1504 1505 if ($is_new) { 1506 update_post_meta($pid, '_regular_price', $price); 1507 update_post_meta($pid, '_price', $price); 1508 } 1509 } 1510 1511 /* ------------------------------------------------------ 1512 * SKU & BARCODE (pending only) 1513 * ------------------------------------------------------ */ 1514 1515 /* ------------------------------------------------------ 1516 * SKU & BARCODE 1517 * ------------------------------------------------------ */ 1518 1519 if (isset($_POST['product_sku'])) { 1520 1521 $sku = sanitize_text_field(wp_unslash($_POST['product_sku'])); 1522 1523 if ($sku !== (string) get_post_meta($pid, '_sku', true)) { 1524 $content_changed = true; 1525 } 1526 1527 // Always store pending 1528 update_post_meta($pid, '_vendboard_pending_sku', $sku); 1529 1530 // NEW PRODUCT → update live immediately 1531 if ($is_new) { 1532 update_post_meta($pid, '_sku', $sku); 1533 } 1659 $live_price = (string) $live_product->get_regular_price('edit'); 1660 1661 if ($live_price === '' || abs((float) $raw_price - (float) $live_price) > 0.00001) { 1662 $live_product->set_regular_price(wc_format_decimal($raw_price)); 1663 $fields_applied = true; 1664 } 1665 } 1666 1667 if (isset($_POST['product_sku']) && $live_product) { 1668 1669 $sku = trim(sanitize_text_field(wp_unslash($_POST['product_sku']))); 1670 1671 if ($sku !== (string) $live_product->get_sku('edit')) { 1672 try { 1673 $live_product->set_sku($sku); 1674 $fields_applied = true; 1675 } catch (Exception $e) { 1676 restore_previous_locale(); 1677 wp_send_json_error(['msg' => __('That SKU is already in use. Please choose another.', 'vendboard-vendor-dashboard-for-woocommerce')]); 1678 } 1679 } 1680 } 1681 1682 if ($fields_applied) { 1683 $live_product->save(); 1534 1684 } 1535 1685 1536 1686 if (isset($_POST['product_barcode'])) { 1537 1687 1538 $barcode = sanitize_text_field(wp_unslash($_POST['product_barcode']));1688 $barcode = trim(sanitize_text_field(wp_unslash($_POST['product_barcode']))); 1539 1689 1540 1690 if ($barcode !== vendboard_get_barcode($pid)) { 1541 $content_changed = true;1542 }1543 1544 // Always store pending1545 update_post_meta($pid, '_vendboard_pending_barcode', $barcode);1546 1547 // NEW PRODUCT → update live immediately1548 if ($is_new) {1549 1691 vendboard_set_barcode($pid, $barcode); 1692 $fields_applied = true; 1693 } 1694 } 1695 1696 // Values queued for approval by an earlier version are superseded by 1697 // what the vendor has just submitted. 1698 $superseded = [ 1699 'product_price' => '_vendboard_pending_price', 1700 'product_sku' => '_vendboard_pending_sku', 1701 'product_barcode' => '_vendboard_pending_barcode', 1702 ]; 1703 1704 foreach ($superseded as $field => $meta_key) { 1705 if (isset($_POST[$field])) { 1706 delete_post_meta($pid, $meta_key); 1550 1707 } 1551 1708 } … … 1670 1827 } 1671 1828 1829 /* ------------------------------------------------------ 1830 * VARIATIONS 1831 * 1832 * Ownership of $pid is already established above; the class re-checks 1833 * every variation id against it, because those ids arrive over POST. 1834 * 1835 * Runs before the options step, so a vendor can remove variations and 1836 * untick the values they used in the same save. The rows on screen were 1837 * also built from the options as they were before this save. 1838 * ------------------------------------------------------ */ 1839 $variation_result = VendBoard_Variations::save($pid); 1840 1841 if (!empty($variation_result['error'])) { 1842 restore_previous_locale(); 1843 wp_send_json_error(['msg' => $variation_result['error']]); 1844 } 1845 1846 if (!empty($variation_result['content_changed'])) { 1847 $content_changed = true; 1848 } 1849 1850 if (!empty($variation_result['applied'])) { 1851 $publishing_applied = true; 1852 } 1853 1854 /* ------------------------------------------------------ 1855 * PRODUCT TYPE + OPTIONS 1856 * 1857 * The type changes only before the product is first approved; options 1858 * can be edited on any variable product. Neither needs approval: a new 1859 * product is reviewed as a whole anyway. 1860 * ------------------------------------------------------ */ 1861 $structure_result = VendBoard_Variations::save_structure($pid); 1862 1863 if (!empty($structure_result['error'])) { 1864 restore_previous_locale(); 1865 wp_send_json_error(['msg' => $structure_result['error']]); 1866 } 1867 1868 if (!empty($structure_result['changed'])) { 1869 $publishing_applied = true; 1870 } 1871 1672 1872 1673 1873 … … 1897 2097 $msg = __('Changes saved and pending admin review', 'vendboard-vendor-dashboard-for-woocommerce'); 1898 2098 1899 // Stock is live already, so say so rather than implying it is queued 2099 // These are live already, so say so rather than implying they are queued 2100 if ($fields_applied) { 2101 $msg = rtrim($msg, '. ') . '. ' . __('Price, SKU and barcode changes were applied straight away.', 'vendboard-vendor-dashboard-for-woocommerce'); 2102 } 2103 1900 2104 if ($stock_applied) { 1901 $msg .= '' . __('The stock level was updated straight away.', 'vendboard-vendor-dashboard-for-woocommerce');1902 } 1903 } elseif ($stock_applied || $publishing_applied ) {2105 $msg = rtrim($msg, '. ') . '. ' . __('The stock level was updated straight away.', 'vendboard-vendor-dashboard-for-woocommerce'); 2106 } 2107 } elseif ($stock_applied || $publishing_applied || $fields_applied) { 1904 2108 1905 2109 $msg = __('Updated. No admin approval needed.', 'vendboard-vendor-dashboard-for-woocommerce'); … … 1910 2114 1911 2115 2116 $response = ['msg' => $msg]; 2117 2118 if (!empty($variation_result['summary'])) { 2119 $response['msg'] = rtrim($response['msg'], '. ') . '. ' . $variation_result['summary']; 2120 } 2121 2122 // Keep the vendor on the edit screen when there is more to do there: 2123 // adding variations, or setting a price after switching back. 2124 if (!empty($structure_result['changed']) || VendBoard_Variations::is_variable($pid)) { 2125 2126 $response['redirect'] = add_query_arg('id', $pid, site_url('/vendor-dashboard/')); 2127 2128 if (!empty($structure_result['changed']) && VendBoard_Variations::is_variable($pid)) { 2129 $response['msg'] = rtrim($response['msg'], '. ') . '. ' . __('You can now add its variations.', 'vendboard-vendor-dashboard-for-woocommerce'); 2130 } 2131 } 2132 1912 2133 restore_previous_locale(); 1913 2134 1914 wp_send_json_success( ['msg' => $msg]);2135 wp_send_json_success($response); 1915 2136 } 1916 2137 -
vendboard-vendor-dashboard-for-woocommerce/trunk/languages/vendboard-vendor-dashboard-for-woocommerce-he_IL.po
r3664998 r3718469 659 659 msgid "Updated. No admin approval needed." 660 660 msgstr "עודכן. לא נדרש אישור מנהל." 661 662 msgid "Variations" 663 msgstr "וריאציות" 664 665 msgid "Variation" 666 msgstr "וריאציה" 667 668 msgid "Stock and availability apply straight away. Price and SKU changes wait for admin approval." 669 msgstr "מלאי וזמינות נכנסים לתוקף מיד. שינויי מחיר ומק\"ט ממתינים לאישור המנהל." 670 671 msgid "This product has no variations yet." 672 msgstr "למוצר זה אין עדיין וריאציות." 673 674 msgid "Awaiting approval" 675 msgstr "ממתין לאישור" 676 677 msgid "Not for sale" 678 msgstr "לא למכירה" 679 680 msgid "Change pending" 681 msgstr "שינוי ממתין" 682 683 msgid "Price" 684 msgstr "מחיר" 685 686 msgid "For sale" 687 msgstr "למכירה" 688 689 msgid "Once approved" 690 msgstr "לאחר אישור" 691 692 msgid "Add a variation" 693 msgstr "הוספת וריאציה" 694 695 msgid "New variations are not for sale until the store owner approves them." 696 msgstr "וריאציות חדשות אינן למכירה עד שבעל החנות מאשר אותן." 697 698 msgid "Leave the options blank if you do not want to add anything." 699 msgstr "השאירו את האפשרויות ריקות אם אינכם רוצים להוסיף דבר." 700 701 msgid "No variation options have been set up for this product yet. Ask the store owner to add them." 702 msgstr "לא הוגדרו עדיין אפשרויות וריאציה למוצר זה. בקשו מבעל החנות להוסיף אותן." 703 704 msgid "This product is priced per variation. Set the prices in the Variations section below." 705 msgstr "מוצר זה מתומחר לפי וריאציה. הגדירו את המחירים בקטע הווריאציות שלמטה." 706 707 msgid "Variation stock must be a number" 708 msgstr "מלאי הווריאציה חייב להיות מספר" 709 710 msgid "Variation stock cannot be negative" 711 msgstr "מלאי הווריאציה אינו יכול להיות שלילי" 712 713 msgid "Variation price must be a number" 714 msgstr "מחיר הווריאציה חייב להיות מספר" 715 716 msgid "Variation price cannot be negative" 717 msgstr "מחיר הווריאציה אינו יכול להיות שלילי" 718 719 msgid "That variation option is not available for this product." 720 msgstr "אפשרות וריאציה זו אינה זמינה עבור מוצר זה." 721 722 msgid "Please choose every option before adding a variation." 723 msgstr "יש לבחור את כל האפשרויות לפני הוספת וריאציה." 724 725 msgid "That combination already exists on this product." 726 msgstr "שילוב זה כבר קיים במוצר זה." 727 728 msgid "The variation could not be created." 729 msgstr "לא ניתן היה ליצור את הווריאציה." 730 731 msgid "New variation, not for sale until approved" 732 msgstr "וריאציה חדשה, לא למכירה עד לאישור" 733 734 msgid "Filter by stock status" 735 msgstr "סינון לפי סטטוס מלאי" 736 737 msgid "All stock statuses" 738 msgstr "כל סטטוסי המלאי" 739 740 msgid "Product Type" 741 msgstr "סוג מוצר" 742 743 msgid "Single product" 744 msgstr "מוצר בודד" 745 746 msgid "Product with options (sizes, colours and so on)" 747 msgstr "מוצר עם אפשרויות (מידות, צבעים וכדומה)" 748 749 msgid "This product already has variations, so its type can no longer be changed." 750 msgstr "למוצר זה כבר יש וריאציות, ולכן לא ניתן עוד לשנות את סוגו." 751 752 msgid "You can change this until the store owner approves the product." 753 msgstr "ניתן לשנות זאת עד שבעל החנות יאשר את המוצר." 754 755 msgid "Options" 756 msgstr "אפשרויות" 757 758 msgid "The store owner has not set up any options, such as Size or Colour, yet." 759 msgstr "בעל החנות עדיין לא הגדיר אפשרויות, כמו מידה או צבע." 760 761 msgid "Tick the options this product comes in and save. You can then add its variations below." 762 msgstr "סמנו את האפשרויות שבהן המוצר מגיע ושמרו. לאחר מכן תוכלו להוסיף את הווריאציות שלו למטה." 763 764 msgid "This product already has variations, so it cannot be switched back." 765 msgstr "למוצר זה כבר יש וריאציות, ולכן לא ניתן להחזיר אותו למוצר בודד." 766 767 msgid "The product type could not be changed." 768 msgstr "לא ניתן היה לשנות את סוג המוצר." 769 770 msgid "\"%s\" is used by an existing variation, so it cannot be removed." 771 msgstr "\"%s\" נמצא בשימוש בווריאציה קיימת, ולכן לא ניתן להסיר אותו." 772 773 msgid "You can now add its variations." 774 msgstr "כעת ניתן להוסיף את הווריאציות שלו." 775 776 msgid "Priced per variation. You can set prices after saving, once you have added its variations." 777 msgstr "התמחור הוא לפי וריאציה. תוכלו להגדיר מחירים לאחר השמירה, כשתוסיפו את הווריאציות." 778 779 msgid "Tick the options this product comes in above and save, then you can add variations here." 780 msgstr "סמנו למעלה את האפשרויות שבהן המוצר מגיע ושמרו, ואז תוכלו להוסיף כאן וריאציות." 781 782 msgid "Variations are saved with the product and go live when the store owner approves it." 783 msgstr "הווריאציות נשמרות יחד עם המוצר ונכנסות לתוקף כשבעל החנות מאשר אותו." 784 785 msgid "Stock, availability, and adding or removing variations apply straight away. Price and SKU changes to existing variations wait for admin approval." 786 msgstr "מלאי, זמינות והוספה או הסרה של וריאציות נכנסים לתוקף מיד. שינויי מחיר ומק\"ט בווריאציות קיימות ממתינים לאישור המנהל." 787 788 msgid "Remove" 789 msgstr "הסרה" 790 791 msgid "Add variations" 792 msgstr "הוספת וריאציות" 793 794 msgid "Tick the combinations you sell, fill in their details and save." 795 msgstr "סמנו את השילובים שאתם מוכרים, מלאו את הפרטים ושמרו." 796 797 msgid "Showing the first %1$s of %2$s combinations." 798 msgstr "מוצגים %1$s הראשונים מתוך %2$s שילובים." 799 800 msgid "Price for selected" 801 msgstr "מחיר לנבחרים" 802 803 msgid "Stock for selected" 804 msgstr "מלאי לנבחרים" 805 806 msgid "Fill selected rows" 807 msgstr "מילוי השורות שנבחרו" 808 809 msgid "Select all" 810 msgstr "בחירת הכל" 811 812 msgid "Every combination of the chosen options already exists." 813 msgstr "כל השילובים של האפשרויות שנבחרו כבר קיימים." 814 815 msgid "There are too many combinations to list (%s). Untick some option values to narrow them down." 816 msgstr "יש יותר מדי שילובים להצגה (%s). בטלו את הסימון של חלק מהאפשרויות כדי לצמצם." 817 818 msgid "This product already has variations, so a new kind of option cannot be added to it. Remove its variations first, or add values to the options it already uses." 819 msgstr "למוצר זה כבר יש וריאציות, ולכן לא ניתן להוסיף לו סוג אפשרות חדש. הסירו קודם את הווריאציות, או הוסיפו ערכים לאפשרויות הקיימות." 820 821 msgid "Variations added: %s." 822 msgstr "וריאציות שנוספו: %s." 823 824 msgid "Variations removed: %s." 825 msgstr "וריאציות שהוסרו: %s." 826 827 msgid "Some SKUs were already in use and were left blank." 828 msgstr "חלק מהמק\"טים כבר היו בשימוש ונשארו ריקים." 829 830 msgid "%s selected combinations were skipped because the options changed. Please select them again." 831 msgstr "%s שילובים שנבחרו דולגו כי האפשרויות השתנו. יש לבחור אותם שוב." 832 833 msgid "Remove %d variation(s)? This cannot be undone." 834 msgstr "להסיר %d וריאציות? לא ניתן לבטל פעולה זו." 835 836 msgid "Price %1$s, stock %2$s" 837 msgstr "מחיר %1$s, מלאי %2$s" 838 839 msgid "All changes to variations apply straight away, without admin approval." 840 msgstr "כל השינויים בווריאציות נכנסים לתוקף מיד, ללא אישור מנהל." 841 842 msgid "Price required" 843 msgstr "נדרש מחיר" 844 845 msgid "Price must be a number" 846 msgstr "המחיר חייב להיות מספר" 847 848 msgid "Price cannot be negative" 849 msgstr "המחיר אינו יכול להיות שלילי" 850 851 msgid "That SKU is already in use. Please choose another." 852 msgstr "המק\"ט הזה כבר בשימוש. בחרו מק\"ט אחר." 853 854 msgid "Price, SKU and barcode changes were applied straight away." 855 msgstr "שינויי מחיר, מק\"ט וברקוד נכנסו לתוקף מיד." 856 857 msgid "Applies straight away, without admin approval." 858 msgstr "נכנס לתוקף מיד, ללא אישור מנהל." 859 860 msgid "SKU and barcode changes apply straight away, without admin approval." 861 msgstr "שינויי מק\"ט וברקוד נכנסים לתוקף מיד, ללא אישור מנהל." 862 863 msgid "VendBoard: the Vendor Dashboard page (/vendor-dashboard/) is not published, so vendors are sent to the home page instead of their dashboard. Publish the page to fix this." 864 msgstr "VendBoard: עמוד לוח הבקרה לספקים (/vendor-dashboard/) אינו מפורסם, ולכן ספקים מועברים לעמוד הבית במקום ללוח הבקרה שלהם. פרסמו את העמוד כדי לתקן זאת." 865 866 msgid "%1$s of %2$s selected" 867 msgstr "%1$s מתוך %2$s נבחרו" 868 869 msgid "Current variations" 870 msgstr "וריאציות קיימות" 871 872 msgid "%s not created yet" 873 msgstr "%s טרם נוצרו" 874 875 msgid "Options vendors can use" 876 msgstr "אפשרויות שספקים יכולים להשתמש בהן" 877 878 msgid "No product attributes have been set up yet. Add them under Products > Attributes." 879 msgstr "לא הוגדרו עדיין מאפייני מוצר. הוסיפו אותם תחת מוצרים > מאפיינים." 880 881 msgid "Vendors can only choose from the ticked options when setting up a product with options. Ticking all of them is the same as ticking none. Attributes already on a product are never removed." 882 msgstr "ספקים יכולים לבחור רק מהאפשרויות המסומנות בעת הגדרת מוצר עם אפשרויות. סימון כולן זהה לאי-סימון אף אחת. מאפיינים שכבר קיימים במוצר לעולם אינם מוסרים." -
vendboard-vendor-dashboard-for-woocommerce/trunk/readme.txt
r3666364 r3718469 6 6 Requires PHP: 7.4 7 7 Requires at least WooCommerce: 9.4 8 Stable tag: 1. 1.18 Stable tag: 1.2.0 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 19 19 * Add new products 20 20 * Edit product content (title, short and full description) 21 * Change product price, SKU, and barcode 21 * Change product price, SKU, and barcode (applied immediately) 22 22 * Update stock quantity and stock status (applied immediately) 23 23 * Publish or unpublish a product and set its catalog visibility (applied immediately) 24 * Add, remove and edit product variations: stock, price and SKU for each size or colour (applied immediately) 24 25 * Upload product images and videos 25 26 26 All vendor changes are saved as Pending Review, while the store admin keeps full control over what gets published. Stock levels are the one exception: they are applied immediately, because holding a stock figure in a queue while orders keep coming inwould let an approval roll the level back and oversell the product.27 New products and content changes (title, descriptions and images) are saved as Pending Review, while the store admin keeps full control over what gets published. Operational changes apply immediately instead: price, SKU, barcode, stock levels, publishing and catalog visibility, and product variations. Holding a stock figure in a queue while orders keep coming in, for example, would let an approval roll the level back and oversell the product. 27 28 28 29 This plugin is ideal for multi-vendor stores, brands working with external suppliers, and marketplaces that require moderation before publishing product changes. … … 41 42 * Paginated product list that stays fast with large catalogues 42 43 * Clear indicators showing missing or incomplete content 43 * Changes are never published automatically44 * New products and content changes are never published without approval 44 45 45 46 Admin Moderation … … 60 61 Why Stores Choose This Plugin 61 62 * Safe Editing: vendors never risk site integrity 62 * Full Control: allchanges require admin approval63 * Full Control: new products and content changes require admin approval 63 64 * Time Saver: reduces admin workload 64 65 * Vendor-Friendly: simple and intuitive interface … … 68 69 69 70 = What does this plugin do? = 70 This plugin allows vendors to safely add and edit their own WooCommerce products without access to wp-admin. Allchanges require admin approval before publishing.71 This plugin allows vendors to safely add and edit their own WooCommerce products without access to wp-admin. New products and content changes require admin approval before publishing. 71 72 72 73 = Do vendors get access to wp-admin? = … … 93 94 Yes. The dashboard has a search box that matches product name, description, SKU and barcode, and the product list is paginated. Products waiting for approval are always listed in full, above the searchable list, so they never get lost behind a filter. 94 95 96 = Can vendors manage product variations? = 97 Yes. On any product that uses variations, vendors can set the stock, price and SKU for each variation from their dashboard, and can add new variations by choosing from the options you have already set up on that product. 98 99 All variation changes apply immediately, without admin approval: stock, price, SKU, availability, and adding or removing variations. To add variations, vendors tick the combinations they sell in one list, fill in price, stock and SKU, and save them all at once. On a new product everything is reviewed together when you approve the product. 100 101 = Can I choose which options vendors see? = 102 Yes. Under Vendor Dashboard > Branding there is a list of your product attributes, and vendors can only use the ones you tick. Attributes already attached to a product are left alone, so hiding one never changes existing products. The values inside each attribute come from Products > Attributes, so editing or removing a value there changes what vendors can choose. 103 104 = Can vendors create their own attributes? = 105 No, and this is deliberate. Attributes such as Size and Colour are site-wide taxonomies shared by every product and every vendor, so only administrators create them and their values. Vendors choose from the values you have attached to their product, which means one vendor can never introduce a term that shows up in another vendor's products or your shop filters. 106 107 When creating a product, a vendor chooses whether it is a single product or one with options, and ticks which of your attribute values it comes in. They can change the type until you first approve the product; after that it is fixed, because changing it on a live product affects its pricing. Vendors can still tick or untick option values later, to add a new size for example. 108 95 109 = Do stock changes need admin approval? = 96 No, and this is deliberate. Stock is the only field that goes live immediately. Every other field still waits for your approval. Stock isoperational data that your own sales keep changing, so if a vendor's figure sat in the approval queue while orders came in, approving it later would overwrite those sales and leave the store showing more units than actually exist. Applying stock straight away keeps the number honest. Vendors still only ever reach their own brand's products.110 No, and this is deliberate. Stock is operational data that your own sales keep changing, so if a vendor's figure sat in the approval queue while orders came in, approving it later would overwrite those sales and leave the store showing more units than actually exist. Applying stock straight away keeps the number honest. Vendors still only ever reach their own brand's products. 97 111 98 112 = Are vendor changes published automatically? = 99 N o. All vendor changes and new products are saved as Pending Review until approved by an administrator.113 New products, and changes to titles, descriptions and images, are saved as Pending Review until an administrator approves them. Price, SKU, barcode, stock, publishing, visibility and variation changes apply immediately. 100 114 101 115 = How does admin moderation work? = … … 140 154 141 155 == Changelog == 156 157 = 1.2.0 = 158 * The options and variations sections on the vendor edit screen are now collapsible, with counts in each heading, so a product with many options is easier to work through. 159 * Added a setting (Vendor Dashboard > Branding) for choosing which product attributes vendors may use, so attributes that are not relevant to them can be hidden without removing them from the store. 160 * Renamed the "YouTube link / Short Description" field to "Short Description". 161 * Security: vendors are now always kept out of wp-admin. Previously, if the Vendor Dashboard page was missing or unpublished, vendors were let into wp-admin, and a URL parameter documented only in the source code let any vendor skip the redirect. 162 * The Vendor Dashboard page is recreated automatically if it has been deleted, and administrators see a notice if it has been unpublished. 163 * Vendors are taken straight to their dashboard when they log in. 164 * Fixed products created by vendors having no category. WooCommerce files every product under the default category, and without one some themes could not display the product page. Existing products without a category are filed under the default category the first time an administrator opens wp-admin after updating. 165 * A product's price, SKU and barcode now apply immediately, without admin approval. Prices are saved through WooCommerce, so an active sale price is no longer overwritten. New products and changes to titles, descriptions and images still require approval. 166 * Added a stock status filter to the vendor product list, so vendors with large catalogues can show only out of stock (or in stock, or on backorder) products. Works together with search and pagination. 167 * Vendors can now manage product variations from their dashboard: stock, price and SKU for each variation, plus switching individual variations on and off. 168 * Vendors can create many variations at once: every missing combination of the product's options is listed, and vendors tick the ones they sell and fill in price, stock and SKU. New variations go live immediately, without admin approval. 169 * Vendors can remove variations. Removal is permanent and asks for confirmation. 170 * When creating a product, vendors can make it a product with options and tick which of your global attribute values it comes in. The type can be changed until an administrator first approves the product; option values stay editable afterwards. 171 * Vendors cannot create attributes or attribute terms, and cannot change a product's type once it has been approved. Both stay with the administrator. 172 * All variation changes apply immediately, without admin approval: stock, price, SKU and availability. 173 * Fixed the product price field on variable products. It previously saved and was approved but had no effect, because WooCommerce derives a variable product's price from its variations rather than the parent. The field is now replaced with a pointer to the variations section. 142 174 143 175 = 1.1.1 = … … 205 237 == Upgrade Notice == 206 238 239 = 1.2.0 = 240 Security fix: vendors could reach wp-admin if the Vendor Dashboard page was missing or unpublished. Updating is recommended for every site. Please also note a deliberate change to the approval workflow: price, SKU, barcode, stock and variations now apply immediately, while new products and changes to titles, descriptions and images still require admin approval. Vendors can now create and manage product variations. 241 207 242 = 1.1.1 = 208 243 Fixes barcodes being saved under a key the plugin never displayed, and products being falsely flagged for approval. Recommended for anyone using the barcode field. -
vendboard-vendor-dashboard-for-woocommerce/trunk/vendor-dashboard.php
r3666364 r3718469 3 3 * Plugin Name: VendBoard - Vendor Dashboard for WooCommerce 4 4 * Description: Empowers store owners by creating a personal dashboard for vendors to easily manage and edit their own products, with smart brand linking and admin approval before publishing. (Requires WooCommerce 9.4+) 5 * Version: 1. 1.15 * Version: 1.2.0 6 6 * Author: WebSkipper 7 7 * Text Domain: vendboard-vendor-dashboard-for-woocommerce … … 37 37 define('VENDBOARD_PLUGIN_PATH', plugin_dir_path(__FILE__)); 38 38 define('VENDBOARD_PLUGIN_URL', plugin_dir_url(__FILE__)); 39 define('VENDBOARD_PLUGIN_VERSION', '1. 1.1');39 define('VENDBOARD_PLUGIN_VERSION', '1.2.0'); 40 40 41 41 // ----------------------------- … … 46 46 require_once VENDBOARD_PLUGIN_PATH . 'includes/class-vendboard-role.php'; 47 47 require_once VENDBOARD_PLUGIN_PATH . 'includes/class-vendboard-user-brand.php'; 48 require_once VENDBOARD_PLUGIN_PATH . 'includes/class-vendboard-variations.php'; 48 49 49 50 require_once VENDBOARD_PLUGIN_PATH . 'admin/admin-menu.php'; … … 447 448 } 448 449 } 450 451 /** 452 * Helper: Make sure a product is filed under at least one category. 453 * 454 * WooCommerce puts every product it creates into the default product 455 * category ("Uncategorized"). Products created with wp_insert_post() skip 456 * that step, and some themes fail to render a product page that has no 457 * category at all, so the same default is applied here. 458 * 459 * @return bool True if a category was assigned. 460 */ 461 function vendboard_ensure_product_category($product_id) 462 { 463 $default = (int) get_option('default_product_cat', 0); 464 465 if (!$product_id || !$default || !term_exists($default, 'product_cat')) { 466 return false; 467 } 468 469 $current = wp_get_object_terms($product_id, 'product_cat', ['fields' => 'ids']); 470 471 if (is_wp_error($current) || !empty($current)) { 472 return false; 473 } 474 475 wp_set_object_terms($product_id, [$default], 'product_cat'); 476 477 return true; 478 } 479 480 /** 481 * One-time data repairs after an update. 482 * 483 * Runs when an administrator opens wp-admin, so it never slows down vendors 484 * or shoppers, and only once per repair version. 485 */ 486 function vendboard_maybe_upgrade() 487 { 488 if (!current_user_can('manage_options') || !taxonomy_exists('product_cat')) { 489 return; 490 } 491 492 if (version_compare((string) get_option('vendboard_db_version', '0'), '1.2.0', '>=')) { 493 return; 494 } 495 496 // 1.2.0: products created by vendors had no category (see above) 497 $uncategorised = get_posts([ 498 'post_type' => 'product', 499 'post_status' => ['publish', 'draft', 'pending', 'private', 'future'], 500 'posts_per_page' => -1, 501 'fields' => 'ids', 502 'no_found_rows' => true, 503 'tax_query' => [ 504 [ 505 'taxonomy' => 'product_cat', 506 'operator' => 'NOT EXISTS', 507 ], 508 ], 509 ]); 510 511 foreach ($uncategorised as $product_id) { 512 vendboard_ensure_product_category($product_id); 513 } 514 515 update_option('vendboard_db_version', '1.2.0', false); 516 } 517 add_action('admin_init', 'vendboard_maybe_upgrade'); 518 519 /** 520 * Helper: The published vendor dashboard page, created if it is missing. 521 * 522 * Everything vendor-facing lives on the page at /vendor-dashboard/. The page 523 * is normally created on activation, but can be missing on a site where 524 * activation was interrupted, or after it was deleted. A missing page is 525 * recreated. A page the store owner has unpublished is left as it is, and 526 * they are told about it instead (see the admin notice below). 527 * 528 * @return int Page ID, or 0 if there is no published dashboard page. 529 */ 530 function vendboard_ensure_dashboard_page() 531 { 532 $page = get_page_by_path('vendor-dashboard'); 533 534 if ($page) { 535 return ($page->post_status === 'publish') ? (int) $page->ID : 0; 536 } 537 538 $admins = get_users(['role' => 'administrator', 'number' => 1, 'fields' => 'ID']); 539 540 $page_id = wp_insert_post([ 541 'post_title' => 'Vendor Dashboard', 542 'post_name' => 'vendor-dashboard', 543 'post_content' => '[vendboard_dashboard]', 544 'post_status' => 'publish', 545 'post_type' => 'page', 546 'post_author' => $admins ? (int) $admins[0] : 0, 547 'comment_status' => 'closed', 548 ], true); 549 550 if (is_wp_error($page_id) || !$page_id) { 551 return 0; 552 } 553 554 update_option('vendboard_vendor_dashboard_page_id', (int) $page_id); 555 556 // wp_insert_post() may have made the slug unique; the plugin needs this one. 557 return (get_post_field('post_name', $page_id) === 'vendor-dashboard') ? (int) $page_id : 0; 558 } 559 560 /** 561 * Tell administrators when vendors cannot reach their dashboard. 562 */ 563 function vendboard_dashboard_page_notice() 564 { 565 if (!current_user_can('manage_options') || vendboard_ensure_dashboard_page()) { 566 return; 567 } 568 569 echo '<div class="notice notice-error"><p>' . 570 esc_html__('VendBoard: the Vendor Dashboard page (/vendor-dashboard/) is not published, so vendors are sent to the home page instead of their dashboard. Publish the page to fix this.', 'vendboard-vendor-dashboard-for-woocommerce') . 571 '</p></div>'; 572 } 573 add_action('admin_notices', 'vendboard_dashboard_page_notice'); 574 575 /** 576 * Helper: Global attributes, keyed the way the plugin compares them. 577 * 578 * @return array [ key => ['taxonomy' => 'pa_size', 'label' => 'Size'] ] 579 */ 580 function vendboard_get_global_attribute_choices() 581 { 582 $out = []; 583 584 if (!function_exists('wc_get_attribute_taxonomies')) { 585 return $out; 586 } 587 588 foreach ((array) wc_get_attribute_taxonomies() as $tax_obj) { 589 590 $taxonomy = wc_attribute_taxonomy_name($tax_obj->attribute_name); 591 592 if (!taxonomy_exists($taxonomy)) { 593 continue; 594 } 595 596 $out[sanitize_title($taxonomy)] = [ 597 'taxonomy' => $taxonomy, 598 'label' => $tax_obj->attribute_label ? $tax_obj->attribute_label : $tax_obj->attribute_name, 599 ]; 600 } 601 602 return $out; 603 } 604 605 /** 606 * Helper: May vendors use this global attribute? 607 * 608 * The store owner picks the list under Vendor Dashboard > Branding. An empty 609 * setting means every attribute is available, which is the default. 610 */ 611 function vendboard_vendor_attribute_allowed($key) 612 { 613 $allowed = get_option('vendboard_vendor_attributes', []); 614 615 if (!is_array($allowed) || empty($allowed)) { 616 return true; 617 } 618 619 return in_array((string) $key, array_map('strval', $allowed), true); 620 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)