1(function(){ 2 function $(s,ctx=document){ return ctx.querySelector(s); } 3 function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[m])); } 4 5 // CSRF token helper 6 function getCsrfToken() { 7 const metaTag = document.querySelector('meta[name="csrf-token"]'); 8 return metaTag ? metaTag.getAttribute('content') : ''; 9 } 10 11 function guessPdMeta(){ 12 const name = (window.PD_NAME !== undefined) ? String(window.PD_NAME) : ($('#pd-title')?.textContent || ''); 13 const sku = (function(){ const el=$('#pd-sku'); return el ? el.textContent.trim() : (window.PD_SKU||''); })(); 14 // SECURITY FIX: Remove price from client-side metadata (will be validated server-side) 15 const imgEl = $('#pd-main-img'); 16 const image_url = imgEl ? (imgEl.getAttribute('src') || '') : ''; 17 return { name, sku, image_url }; // price removed for security 18 } 19 20 async function addToCart(productId, qty=1, variantId=0, metaOpt){ 21 const meta = Object.assign(guessPdMeta(), metaOpt||{}); 22 const payload = { 23 product_id: parseInt(productId||0,10) || 0, 24 qty: parseInt(qty||1,10) || 1, 25 variant_id: parseInt(variantId||0,10) || 0, 26 meta 27 }; 28 29 // Check if user is logged in 30 const isLoggedIn = document.querySelector('meta[name="user-logged-in"]')?.content === 'true' || 31 document.body.classList.contains('logged-in') || 32 window.userLoggedIn === true || 33 document.body.dataset.userLoggedIn === 'true'; 34 35 // Skip localStorage untuk logged user - mereka pakai database 36 if (!isLoggedIn && window.guestCart) { 37 try { 38 window.guestCart.add(payload.product_id, payload.qty, payload.variant_id, meta); 39 } catch (e) { 40 console.warn('Failed to add to localStorage cart:', e); 41 } 42 } 43 44 const res = await fetch('/api/cart/add.php', { 45 method: 'POST', 46 headers: { 47 'Content-Type':'application/json', 48 'X-CSRF-Token': getCsrfToken() 49 }, 50 credentials: 'same-origin', 51 body: JSON.stringify({ 52 ...payload, 53 csrf_token: getCsrfToken() 54 }), 55 }); 56 const data = await res.json().catch(()=>({ ok:false, error:'Gagal menambahkan ke keranjang' })); 57 if (!res.ok || !data.ok) { 58 const msg = data && data.error ? data.error : 'Gagal menambahkan ke keranjang'; 59 throw new Error(msg); 60 } 61 62 // Update badge di navbar dengan data dari response 63 try { 64 const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount'); 65 if (el && data.cart && typeof data.cart.items_count === 'number') { 66 el.textContent = String(data.cart.items_count); 67 el.classList.toggle('is-empty', data.cart.items_count <= 0); 68 } 69 } catch(_){} 70 71 // Siarkan event global (dipakai cart-badge.js dan halaman lain) 72 try { 73 document.dispatchEvent(new CustomEvent('cart:updated', { detail: data.cart || {} })); 74 } catch(_){} 75 76 // Update CartBadge dengan data dari response (lebih reliable) 77 try { 78 if (window.CartBadge && window.CartBadge.set) { 79 window.CartBadge.set(data.cart.items_count||0); 80 } 81 } catch(_){} 82 83 // Tambahan: refresh cart counter setelah delay kecil untuk memastikan data tersinkronisasi 84 setTimeout(() => { 85 if (window.updateCartCounter) { 86 window.updateCartCounter(); 87 } 88 }, 100); 89 90 return data; 91 } 92 93 // Function to update cart counter from server 94 async function updateCartCounter(){ 95 try { 96 const res = await fetch('/api/cart/get.php', { credentials:'same-origin' }); 97 const data = await res.json().catch(()=>({ok:false})); 98 if (data.ok && typeof data.items_count === 'number') { 99 const el = document.querySelector('[data-role="cart-badge"]') || document.getElementById('cartCount'); 100 if (el) { 101 el.textContent = String(data.items_count); 102 el.classList.toggle('is-empty', data.items_count <= 0); 103 } 104 // Dispatch event 105 try { 106 document.dispatchEvent(new CustomEvent('cart:updated', { detail: { items_count: data.items_count } })); 107 } catch(_){} 108 } 109 } catch (err) { 110 console.log('Could not update cart counter:', err); 111 } 112 } 113 114 // Function to manually merge guest cart (if needed) 115 async function mergeGuestCart(){ 116 try { 117 const res = await fetch('/api/cart/merge-guest.php', { 118 method: 'POST', 119 headers: { 120 'Content-Type': 'application/json', 121 'X-CSRF-Token': getCsrfToken() 122 }, 123 credentials: 'same-origin', 124 body: JSON.stringify({ csrf_token: getCsrfToken() }) 125 }); 126 const data = await res.json().catch(()=>({ok:false})); 127 128 if (data.ok && data.merged_items > 0) { 129 console.log(`Guest cart merged: ${data.merged_items} items`); 130 // Update cart counter after merge 131 await updateCartCounter(); 132 return data; 133 } 134 return data; 135 } catch (err) { 136 console.log('Could not merge guest cart:', err); 137 return { ok: false, error: err.message }; 138 } 139 } 140 141 window.addToCart = addToCart; 142 window.updateCartCounter = updateCartCounter; 143 window.mergeGuestCart = mergeGuestCart; 144 145 // Optional: auto-bind untuk tombol apapun yang punya data-addtocart 146 document.addEventListener('click', async (e)=>{ 147 const btn = e.target.closest('[data-addtocart]'); 148 if (!btn) return; 149 e.preventDefault(); 150 if (btn.disabled) return; 151 152 const pid = parseInt(btn.getAttribute('data-pid')||'0',10); 153 const vid = parseInt(btn.getAttribute('data-vid')||'0',10);
154 const qty = parseInt(btn.getAttribute('data-qty')||'1',10) || 1; 155 156 try { 157 const data = await addToCart(pid, qty, vid); 158 if (window.showToast) { 159 const meta = guessPdMeta(); 160 window.showToast({ 161 type:'success', 162 title:'Berhasil ditambahkan', 163 message:`<strong>${esc(meta.name)}</strong>${meta.sku?` · <em>SKU: ${esc(meta.sku)}</em>`:''}<br>Qty: ${qty}`, 164 duration: 3800, 165 actions: [ 166 { label:'Lihat Keranjang', onClick: ()=> { window.location.href='/keranjang'; } }, 167 { label:'Lanjut Belanja', variant:'alt', onClick: ()=>{} } 168 ] 169 }); 170 } 171 } catch (err) { 172 if (window.showToast) { 173 window.showToast({ type:'warn', title:'Gagal', message: String(err?.message||'Gagal menambahkan'), duration: 4200 }); 174 } else { 175 console.error(err); 176 } 177 } 178 }); 179})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.