Files
Commerce/product.php

693 lines
26 KiB
PHP

<?php
// Prevent direct access to file
defined(security_key) or exit;
// Check to make sure the id parameter is specified in the URL
if (isset($_GET['id'])) {
//GET CATALOG DATA FROM CACHE
$all_products = $GLOBALS['cached_catalog'];
// Find the specific product by ID or slug
$product = null;
foreach ($all_products as $prod) {
if ($prod['rowID'] == $_GET['id'] || (isset($prod['url_slug']) && $prod['url_slug'] == $_GET['id'])) {
$product = $prod;
break;
}
}
// Check if the product exists (array is not empty)
if (!$product) {
// Output simple error if the id for the product doesn't exists (array is empty)
http_response_code(404);
exit('Product does not exist!');
}
// Add the HTML meta data (for SEO purposes)
$meta = '
<meta property="og:url" content="' . url('index.php?page=product&id=' . ($product['url_slug'] ? $product['url_slug'] : $product['rowID'])) . '">
<meta property="og:title" content="' . (${$product['productname']} ?? $product['productname']) . '">
';
if (isset($product['full_path'])) {
$meta .= '<meta property="og:image" content="'.img_url.$product['full_path'].'">';
}
//GET RELATED MEDIA
$product_media = ioAPIv2('/v2/products_media/product_id='.$product['rowID'],'',$clientsecret);
$product_media = json_decode($product_media,true);
// If the user clicked the add to cart button
if (isset($_POST['product'])) {
//VALIDATE THE INPUT FOR THE SHOPPING CART
$payload = json_encode($_POST['product'], JSON_UNESCAPED_UNICODE);
$product_to_cart = ioAPIv2('/v2/shopping_cart/',$payload,$clientsecret);
$product_to_cart = json_decode($product_to_cart,true);
// Check if the product exists (array is not empty)
if ($product_to_cart['quantity'] > 0) {
// Product exists in database, now we can create/update the session variable for the cart
if (!isset($_SESSION['cart'])) {
// Shopping cart session variable doesnt exist, create it
$_SESSION['cart'] = [];
}
$cart_product = &get_cart_product($product_to_cart['id'], $product_to_cart['options']);
if ($cart_product) {
// Product exists in cart, update the quanity
$cart_product['quantity'] += $quantity;
} else {
// Product is not in cart, add it
$_SESSION['cart'][] = $product_to_cart;
}
}
// Prevent form resubmission...
header('Location: ' . url('index.php?page=cart'));
exit;
}
} else {
// Output simple error if the id wasn't specified
http_response_code(404);
exit('Product does not exist!');
}
//LINK to products page:
$products_link = url(link_to_collection);
$product_link = url('index.php?page=product&id='.($product['url_slug'] ? $product['url_slug'] : $product['rowID']));
/*Notifier - when 1 user ask for product notification
$notifier = 0;
if (isset($_POST["notifier"])){
$stmt = $pdo->prepare('SELECT * FROM accounts WHERE id = ?');
$stmt->execute([ $_SESSION['account_id'] ]);
$account = $stmt->fetch(PDO::FETCH_ASSOC);
$email = $account['email'];
send_product_notification_email($email, $_POST["product_details"]);
$notifier = 1;
}
*/
$view = template_header((${$product['productname']} ?? $product['productname']), $meta);
if ($error){
$view .='<p class="content-wrapper error">'.$error.'</p>';
}
else {
$view .='
<div class="product content-wrapper">
<div class="product-mobile-header">
<div class="breadcrum">
<a href="'.$products_link.'">'.$breadcrum_products.'</a> <p>/ '.(${$product['productname']} ?? $product['productname']).'</p>
</div>
<h1 class="name">'.(${$product['productname']} ?? $product['productname']).'</h1>
</div>
<div class="product-imgs carousel-container">';
// Determine the main image source
$mainImageSrc = '';
$mainImageAlt = '';
if (isset($_GET['option_id']) && !empty($_GET['option_id']) && $_GET['option_id'] !=''){
foreach ($product['configurations'] as $configuration) {
if (isset($configuration['attributes'])) {
foreach ($configuration['attributes'] as $attribute) {
if ($attribute['attribute_id'] == $_GET['option_id']) {
$mainImageSrc = img_url . ($attribute['alternative_media_full_path'] ?? $attribute['full_path']);
$mainImageAlt = $attribute['alternative_media_title'] ?? $attribute['title'];
break 2;
}
}
}
}
}
// Fallback to product main image if no option_id or not found
if (empty($mainImageSrc) && isset($product['full_path']) && $product['full_path'] != '') {
$mainImageSrc = img_url . $product['full_path'];
$mainImageAlt = ${$product['productname']} ?? $product['productname'];
}
// Render carousel with arrows if we have an image
if (!empty($mainImageSrc)) {
$view .='
<div class="product-img-large carousel-main">
<button class="carousel-arrow left" aria-label="Previous image">&#8592;</button>
<img src="'.$mainImageSrc.'" id="'.$product['rowID'].'" alt="'.$mainImageAlt.'">
<button class="carousel-arrow right" aria-label="Next image">&#8594;</button>
</div>';
}
$view .='
<div class="product-small-imgs carousel-thumbnails">';
// Add the main product image as the first thumbnail
$thumbs = [];
if (isset($_GET['option_id']) && !empty($_GET['option_id']) && $_GET['option_id'] !=''){
foreach ($product['configurations'] as $configuration) {
if (isset($configuration['attributes'])) {
foreach ($configuration['attributes'] as $attribute) {
if ($attribute['attribute_id'] == $_GET['option_id']) {
$mainPath = $attribute['alternative_media_full_path'] ?? $attribute['full_path'];
$mainAltTitle = $attribute['alternative_media_title'] ?? $attribute['title'];
$thumbs[] = ['src' => img_url.$mainPath, 'alt' => $mainAltTitle, 'selected' => true];
break 2;
}
}
}
}
} elseif (isset($product['full_path']) && $product['full_path'] != ''){
$thumbs[] = ['src' => img_url.$product['full_path'], 'alt' => (${$product['productname']} ?? $product['productname']), 'selected' => true];
}
foreach ($product_media as $media){
$thumbs[] = ['src' => img_url.$media['full_path'], 'alt' => '', 'selected' => false];
}
foreach ($thumbs as $i => $thumb) {
$sel = $thumb['selected'] ? ' selected' : '';
$view .= ' <div class="product-img-small'.$sel.'" data-index="'.$i.'">
<img src="'.$thumb['src'].'" width="80" height="80" alt="'.$thumb['alt'].'">
</div>';
}
$view .='
</div>
</div>
<div class="product-wrapper">
<div class="breadcrum">
<a href="'.$products_link.'">'.$breadcrum_products.'</a> <p>/ '.(${$product['productname']} ?? $product['productname']).'</p>
</div>
<h1 class="name">'.(${$product['productname']} ?? $product['productname']).'</h1>
<div class="prices">
<span class="price" data-price="'.$product['price'].'">'.currency_code.''.number_format($product['price'],2).'</span>';
if ($product['rrp'] > 0){
$view .= '<span class="rrp" data-rrp="'.$product['rrp'].'">'.currency_code.''.number_format($product['rrp'],2).'</span>';
}
$view .='</div>
<div class="stock">';
//Stock status
$stock_status = ($product['quantity'] != 0) ? $product_on_stock : $out_of_stock;
$style = ($stock_status == $product_on_stock) ? 'style="color:green;"' : 'style="color:gray;font-weight: lighter;"';
$view .= '<span class="stock_product">
<p '.$style.'> '.$stock_status.' </p>
</span>';
$view .='</div>';
//FREE SHIPMENT INDICATOR
if (free_shipment_indicator){
freeShipment($product['price'],'div');
}
$view .='<form id="product-form" action="" method="post">';
//CHECK FOR OPTIONS ASSIGNED
if(isset($product['configurations'])){
foreach ($product['configurations'] as $configuration) {
//CHECK FOR GROUPS
if (isset($configuration['type']) && $configuration['type'] == 'product'){
$view .= '<input id="product" type="hidden" name="product[option][products][]" value="'.$configuration['assignment'].'">';
}
//CHECK FOR GROUPS
if (isset($configuration['type']) && $configuration['type'] == 'group'){
$view .= '<label for="">'.(${$configuration['assignment_name']} ?? $configuration['assignment_name']).'</label>';
//BASED ON GROUP TYPE CREATE INPUT FORM
switch($configuration['group_type']) {
case 0: //Radiobutton
$output ='';
foreach ($configuration['attributes'] as $attribute){
// Check if this option should be pre-selected
$isChecked = (isset($_GET['option_id']) && $_GET['option_id'] == $attribute['attribute_id']) ? ' checked' : '';
if(isset($attribute['full_path']) && $attribute['full_path'] !=''){
$onclick ='';
//ADD updateOption to change pictures when GROUP is IN configuration
if(isset($product['config_setting']) && $product['config_setting'] == $configuration['assignment']){
$IMG_large_id = img_url.$attribute['alternative_media_full_path']; //URL TO LARGE IMAGE
$onclick = 'onclick="updateOption(\''.$product['rowID'].'\',\''.$IMG_large_id.'\')"';
}
$IMG_small_id = img_url.$attribute['full_path']; //URL TO SMALL IMAGE
$output .= '
<label class="picture_select_label">
<input id="'.$attribute['attribute_id'].'" class="option radio" value="'.$attribute['attribute_id'].'" name="product[option]['.$configuration['assignment'].'][]" type="radio" data-price="'.($attribute['price'] ?? 0).'" data-rrp="'.($attribute['rrp'] ?? 0).'" data-modifier="'.($attribute['price_modifier'] ?? 1).'" '.(($configuration['group_mandatory'] == 1 ) ? ' required' : '').$isChecked.'>
<span class="picture_select"><img '.$onclick.' src="'.$IMG_small_id.'"></span>
</label>';
} else {
$output .= '
<label>
<input id="'.$attribute['attribute_id'].'>" class="option radio" value="'.$attribute['attribute_id'].'" name="product[option]['.$configuration['assignment'].'][]" type="radio" data-price="'.($attribute['price'] ?? 0).'" data-rrp="'.($attribute['rrp'] ?? 0).'" data-modifier="'.($attribute['price_modifier'] ?? 1).'" '.(($configuration['group_mandatory'] == 1 ) ? ' required' : '').$isChecked.'>'.(${$attribute['item_name']} ?? $attribute['item_name']).'
</label>';
}
}
$view .= '<div class="radio-checkbox">'.$output.'</div>';
break;
case 1: //Checkbox
$output ='';
foreach ($configuration['attributes'] as $attribute){
// Check if this option should be pre-selected
$isChecked = (isset($_GET['option_id']) && $_GET['option_id'] == $attribute['attribute_id']) ? ' checked' : '';
if(isset($attribute['full_path']) && $attribute['full_path'] !=''){
$onclick ='';
//ADD updateOption to change pictures when GROUP is IN configuration
if(isset($product['config_setting']) && $product['config_setting'] == $configuration['assignment']){
$IMG_large_id = img_url.$attribute['alternative_media_full_path']; //URL TO LARGE IMAGE
$onclick = 'onclick="updateOption(\''.$product['rowID'].'\',\''.$IMG_large_id.'\')"';
}
$IMG_small_id = img_url.$attribute['full_path']; //URL TO SMALL IMAGE
$output .= '
<label class="picture_select_label">
<input id="'.$attribute['attribute_id'].'>" class="option checkbox" value="'.$attribute['attribute_id'].'" name="product[option]['.$configuration['assignment'].'][]" type="checkbox" data-price="'.($attribute['price'] ?? 0).'" data-rrp="'.($attribute['rrp'] ?? 0).'" data-modifier="'.($attribute['price_modifier'] ?? 1).'" '.(($configuration['group_mandatory'] == 1 ) ? ' required' : '').$isChecked.'>
<span class="picture_select"><img '.$onclick.' src="'.$IMG_small_id.'"></span>
</label>';
} else {
$output .= '
<label>
<input id="'.$attribute['attribute_id'].'>" class="option checkbox" value="'.$attribute['attribute_id'].'" name="product[option]['.$configuration['assignment'].'][]" type="checkbox" data-price="'.($attribute['price'] ?? 0).'" data-rrp="'.($attribute['rrp'] ?? 0).'" data-modifier="'.($attribute['price_modifier'] ?? 1).'" '.(($configuration['group_mandatory'] == 1 ) ? ' required' : '').$isChecked.'>'.(${$attribute['item_name']} ?? $attribute['item_name']).'
</label>';
}
}
$view .= '<div class="radio-checkbox">'.$output.'</div>';
break;
case 2: //Dropdown
// Check if any option is pre-selected via URL
$hasPreselection = isset($_GET['option_id']) && !empty($_GET['option_id']);
$output ='
<select id="'.$configuration['assignment'].'" class="option select" name="product[option]['.$configuration['assignment'].']" '.(($configuration['group_mandatory'] == 1 ) ? ' required' : '').'>
<option value=""'.($hasPreselection ? '' : ' selected').' disabled style="display:none">'.$configuration['assignment_name'].'</option>
';
foreach ($configuration['attributes'] as $attribute){
// Check if this option should be pre-selected
$isSelected = (isset($_GET['option_id']) && $_GET['option_id'] == $attribute['attribute_id']) ? ' selected' : '';
if(isset($attribute['full_path']) && $attribute['full_path'] !=''){
$onclick ='';
//ADD updateOption to change pictures when GROUP is IN configuration
if(isset($product['config_setting']) && $product['config_setting'] == $configuration['assignment']){
$IMG_large_id = img_url.$attribute['alternative_media_full_path']; //URL TO LARGE IMAGE
$onclick = 'onclick="updateOption(\''.$product['rowID'].'\',\''.$IMG_large_id.'\')"';
}
$IMG_small_id = img_url.$attribute['full_path']; //URL TO SMALL IMAGE
$output .= '
<option id="'.$attribute['attribute_id'].'" value="'.$attribute['attribute_id'].'" data-price="'.($attribute['price'] ?? 0).'" data-rrp="'.($attribute['rrp'] ?? 0).'" data-modifier="'.($attribute['price_modifier'] ?? 1).'"'.$isSelected.'>'.(${$attribute['item_name']} ?? $attribute['item_name']).'</option>';
} else {
$output .= '
<option id="'.$attribute['attribute_id'].'" value="'.$attribute['attribute_id'].'" data-price="'.($attribute['price'] ?? 0).'" data-rrp="'.($attribute['rrp'] ?? 0).'" data-modifier="'.($attribute['price_modifier'] ?? 1).'"'.$isSelected.'>'.(${$attribute['item_name']} ?? $attribute['item_name']).'</option>';
}
}
$view .= $output.'</select></div>';
break;
}
}
}
}
$view .='
<label for="quantity">'.$product_quantity.'</label>
<input id="quantity" type="number" name="product[quantity]" value="1" min="1" placeholder="Quantity" required>
<input id="product" type="hidden" name="product[product]" value="'.$product['rowID'].'">
<input id="product" type="hidden" name="product[version]" value="'.($product['version_id'] ?? '').'">
<input type="submit" value="'.$add_to_basket.'" class="btn add-to-basket-sticky">
</form>
<div class="description">
'.(${$product['productdescription']} ?? $product['productdescription']).'
</div>
</div>
</div>';
$view .= '<style>
/* Mobile Header - Hidden on desktop */
.product-mobile-header {
display: none;
}
/* Carousel Container */
.carousel-container {
position: relative;
display: flex;
flex-direction: column;
}
/* Main Image Area */
.carousel-main {
display: flex;
align-items: center;
justify-content: center;
position: relative;
background-color: #f8f8f8;
border-radius: 8px;
padding: 1rem;
overflow: hidden;
min-height: 300px;
}
.carousel-main img {
max-width: 100%;
max-height: 450px;
object-fit: contain;
border-radius: 8px;
transition: opacity 0.3s ease;
user-select: none;
-webkit-user-drag: none;
}
/* Carousel Arrows */
.carousel-arrow {
background: rgba(255, 255, 255, 0);
border: none;
font-size: 1.5rem;
padding: 0.75rem 1rem;
cursor: pointer;
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 10;
border-radius: 50%;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
transition: all 0.2s ease;
color: #1a3a5f;
line-height: 1;
}
.carousel-arrow:hover {
background: #fff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
transform: translateY(-50%) scale(1.1);
}
.carousel-arrow.left { left: 10px; }
.carousel-arrow.right { right: 10px; }
/* Thumbnails Container */
.carousel-thumbnails {
display: flex;
flex-direction: row;
gap: 10px;
justify-content: center;
align-items: center;
margin-top: 15px;
padding: 10px 0;
overflow-x: auto;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
scrollbar-color: #ccc transparent;
}
.carousel-thumbnails::-webkit-scrollbar {
height: 6px;
}
.carousel-thumbnails::-webkit-scrollbar-track {
background: transparent;
}
.carousel-thumbnails::-webkit-scrollbar-thumb {
background-color: #ccc;
border-radius: 3px;
}
/* Thumbnail Items */
.product-img-small {
flex-shrink: 0;
width: 70px;
height: 70px;
border: 2px solid transparent;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
overflow: hidden;
opacity: 0.6;
}
.product-img-small:hover {
border-color: #1a3a5f;
opacity: 0.9;
}
.product-img-small.selected {
border-color: #1a3a5f;
opacity: 1;
box-shadow: 0 2px 8px rgba(26, 58, 95, 0.3);
}
.product-img-small img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* Sticky Add to Basket Button */
.add-to-basket-sticky {
transition: all 0.3s ease;
}
.add-to-basket-sticky.is-sticky {
position: fixed;
left: 0;
right: 0;
bottom: 0;
width: 100%;
z-index: 1000;
border-radius: 0;
margin: 0;
padding: 1rem 2rem;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.15);
}
/* Tablet/Mobile Responsive Styles (< 993px) */
@media (max-width: 992px) {
/* Hide info bar on tablet/mobile */
.top-info-bar,
.info-bar {
display: none !important;
}
/* Show mobile header on tablet/mobile */
.product-mobile-header {
display: block;
order: 0;
padding: 1rem;
background: #fff;
}
.product-mobile-header .breadcrum {
margin-bottom: 0.5rem;
}
.product-mobile-header .name {
font-size: 1.5rem;
margin: 0;
padding: 0;
border-bottom: none;
}
/* Hide original breadcrumb and name on tablet/mobile */
.product-wrapper > .breadcrum,
.product-wrapper > .name {
display: none;
}
/* Reorder product page layout */
.product.content-wrapper {
display: flex;
flex-direction: column;
padding: 0;
margin: 0;
gap: 0;
max-width: 100%;
border-radius: 0;
box-shadow: none;
}
/* Full width carousel */
.carousel-container,
.product-imgs {
order: 1;
width: 100%;
padding: 0;
}
.carousel-main {
border-radius: 0;
padding: 0;
min-height: auto;
max-height: 60vh;
}
.carousel-main img {
max-width: 100%;
max-height: 60vh;
width: 100%;
border-radius: 0;
}
.carousel-arrow {
padding: 0.5rem 0.75rem;
font-size: 1.25rem;
}
.carousel-arrow.left { left: 5px; }
.carousel-arrow.right { right: 5px; }
/* Thumbnails */
.carousel-thumbnails {
margin-top: 10px;
padding: 10px;
justify-content: flex-start;
gap: 8px;
}
.product-img-small {
width: 55px;
height: 55px;
}
/* Product wrapper */
.product-wrapper {
order: 2;
padding: 1rem;
}
/* Breadcrumb - smaller on tablet/mobile */
.breadcrum {
font-size: 0.8rem;
margin-bottom: 0.5rem;
}
/* Always sticky add to basket on tablet/mobile */
.add-to-basket-sticky {
position: fixed !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100% !important;
z-index: 1000 !important;
border-radius: 0 !important;
margin: 0 !important;
padding: 1rem !important;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.15) !important;
}
/* Add padding at bottom for sticky button */
.product.content-wrapper {
padding-bottom: 70px;
}
/* Prices/stock spacing */
.product-wrapper .prices,
.product-wrapper .stock {
margin-bottom: 0.75rem;
}
}
/* Extra small screens */
@media (max-width: 480px) {
.carousel-main img {
max-height: 50vh;
}
.product-img-small {
width: 50px;
height: 50px;
}
.carousel-arrow {
padding: 0.4rem 0.6rem;
font-size: 1rem;
}
}
</style>';
}
$view .= '
<script>
// Push viewContent event to dataLayer
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
"event": "viewContent",
"ecommerce": {
"detail": {
"products": [{
"id": "' . $product['rowID'] . '",
"name": "' . addslashes(${$product['productname']} ?? $product['productname']) . '",
"price": "' . $product['price'] . '",
"category": "' . ($product['category_name'] ?? '') . '"
}]
}
},
"content_type": "product",
"content_ids": ["' . $product['rowID'] . '"],
"content_name": "' . addslashes(${$product['productname']} ?? $product['productname']) . '",
"value": "' . $product['price'] . '",
"currency": "EUR"
});
// Pre-select option from URL
(function() {
//Read urlstring
const queryString = window.location.href;
const option_id = queryString.substring(queryString.lastIndexOf(\'/\') + 1);
const url_slug = "'.($product['url_slug'] ?? $product['rowID']).'";
//Check for option_id
if (option_id != url_slug){
const optionElement = document.getElementById(option_id);
if (optionElement) {
optionElement.checked = true;
}
} else {
// Get all radio buttons
const radioButtons = document.querySelectorAll(\'.picture_select_label input[type="radio"]\');
// Select the first radio button if any exist
if (radioButtons.length > 0) {
radioButtons[0].checked = true;
}
}
})();
</script>';
$view .= template_footer();
//OUTPUT
echo $view;
?>