Server : Apache/2.4.18 (Ubuntu) System : Linux canvaswebdesign 3.13.0-71-generic #114-Ubuntu SMP Tue Dec 1 02:34:22 UTC 2015 x86_64 User : oppastar ( 1041) PHP Version : 7.0.33-0ubuntu0.16.04.15 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority, Directory : /var/www/laciasmara.com/public_html/shop/application/controllers/ |
Upload File : |
<?php defined('BASEPATH') or exit('No direct script access allowed'); class Product extends Public_Controller { public function __construct() { parent::__construct(); $this->load->model('Top_banner_m'); $this->load->model('Review_m'); $this->load->model('customer_m'); $this->load->model('Category_m'); $this->load->model('Statistic_m'); $this->load->model('Footer_m'); $this->load->library('GoogleClient'); $this->load->library('VisitorTracking'); $this->load->library('session'); $loginUrl = $this->googleclient->getLoginUrl(); $this->data_footer['googleUrl'] = $loginUrl; if ($this->session->userdata('site_lang') == 'english') { $this->lang->load('mainpage', 'english'); } else { $this->lang->load('mainpage', 'indonesian'); } } public function get($alias = NULL) { $this->load->helper('url'); $this->visitortracking->trackVisitor(); $referral = $this->uri->segment(3); // UTM data $utm_source = $this->input->get('utm_source'); $utm_medium = $this->input->get('utm_medium'); $utm_campaign = $this->input->get('utm_campaign'); $utm_content = $this->input->get('utm_content'); $isEnglish = $this->session->userdata('site_lang') === 'english'; // Periksa apakah ada parameter UTM di URL saat ini if (!empty($utm_source) || !empty($utm_medium) || !empty($utm_campaign) || !empty($utm_content)) { // Hanya update session jika ada parameter UTM di URL $this->session->set_userdata('data_utm', [ 'utm_source' => $utm_source, 'utm_medium' => $utm_medium, 'utm_campaign' => $utm_campaign, 'utm_content' => $utm_content ]); } $data_utm = $this->session->userdata('data_utm') ?: []; $data['data_utm'] = $data_utm; if (!empty($referral)) { $this->session->set_userdata('referral', $referral); } $ip_address = $this->input->ip_address(); $data_refferal = array( 'ip_address' => $ip_address, 'referral' => $referral, ); $this->db->insert('visits', $data_refferal); $query = "SELECT COUNT(*) as count FROM affiliator_register WHERE referral = ?"; $result = $this->db->query($query, array($referral))->row(); if ($result->count > 0) { $this->session->set_userdata('referral', $referral); } $activeBanners = $this->Top_banner_m->get_active_banners(); $websiteData = $this->db->select('website_icon, browser_title, meta_description') ->from('configuration') ->where('id_configuration', 1) ->get() ->row(); $this->lang->load('mainpage', $this->session->userdata('site_lang') == 'english' ? 'english' : 'indonesian'); $product = $this->db->select('*') ->from('products') ->where(['alias' => $alias, 'product_status' => 1]) ->get() ->row(); // Ambil ID kategori dari produk $category = $this->db->select('id_category') ->from('category_product') ->where('id_product', $product->id_products) ->get() ->row(); $review_aspects = []; if ($category) { $review_aspects = $this->db->select(" ra.id, ra.aspect_name, ra.display_name" . ($isEnglish ? "_en" : "") . " AS display_name, ra.description" . ($isEnglish ? "_en" : "") . " AS description ") ->from('review_aspects ra') ->where('ra.id_category', $category->id_category) ->where('ra.is_active', 1) ->order_by('ra.sort_order', 'asc') ->get() ->result_array(); // Ambil opsi rating foreach ($review_aspects as &$aspect) { $options = $this->db->select(" rating_value, label" . ($isEnglish ? "_en" : "") . " AS label ") ->from('review_rating_options') ->where('aspect_id', $aspect['id']) ->order_by('rating_value', 'asc') ->get() ->result_array(); $aspect['options'] = $options; } } if (!$product) { show_404(); } $product_details = $this->db->select('*') ->from('product_details') ->where('product_id', $product->id_products) ->get() ->result(); $product_image = $this->db->select('*') ->from('product_images') ->where(['product_details_id' => $product_details[0]->id, 'status' => 1, 'priority' => 1]) ->limit(1) ->get() ->row(); // Jika ada gambar, set gambar untuk og_image, jika tidak, gunakan gambar default $image_url = $product_image ? base_url('uploads/product/' . $product_image->image) : base_url('uploads/product/' . $product->image); $this->data_header = [ 'website_icon' => $websiteData->website_icon, 'browser_title' => 'Laciasmara - ' . $product->title, 'meta_description' => $product->meta_description, 'banners' => $activeBanners, 'logo_path' => 'https://storage.googleapis.com/laciasmara-photos/laciaasmara_assets/laciasmara_landing_page/laciasmara_landing_page_logo.webp', 'og_title' => $product->title, 'og_description' => strip_tags($product->description), 'og_image' => $image_url, 'og_url' => current_url(), 'og_type' => 'product' ]; $customer = $this->session->userdata('customer'); $customer_id = $customer['customer_id']; $customer_data = $this->db->where('id_customers', $customer_id)->get('customers')->row_array(); $reseller_id = $customer_data['reseller_id']; $reseller = $this->db->where('id_resellers', $customer_data['reseller_id'])->where('active', 'yes')->get('resellers')->row_array(); foreach ($product_details as &$detail) { $current_price = $detail->discounted_price > 0 ? $detail->discounted_price : $detail->price; $original_price = $detail->discounted_price > 0 ? $detail->price : null; // Check if customer is a reseller and set the MSRP price $is_reseller = false; $msrp_price = null; if (!empty($customer_id)) { if (!empty($reseller_id)) { // Check if reseller is valid if ($reseller) { $is_reseller = true; // Get reseller price for the product $reseller_price = $this->db->where('product_detail_id', $detail->id) ->where('reseller_id', $customer_data['reseller_id']) ->get('resellers_price') ->row(); if ($reseller_price) { $msrp_price = $current_price; $current_price = $reseller_price->price; } } } } $detail->current_price = $current_price; $detail->original_price = $original_price; $detail->msrp_price = $msrp_price; $stock = $this->db->select('stock, stock_keep, stock_reject, stock_sample') ->from('stock') ->where('id_product_detail', $detail->id) ->get() ->row(); if ($stock) { // Hitung stock_sell dan pastikan tidak minus $stock_sell = $stock->stock - $stock->stock_keep - (isset($stock->stock_reject) ? $stock->stock_reject : 0) - (isset($stock->stock_sample) ? $stock->stock_sample : 0); $detail->stock_available = max(0, $stock_sell); } else { $detail->stock_available = 0; } // Ambil Variants $detail->variants = $this->db->select('pd.product_id as product_id, pd.id as product_detail_id, pa.product_attribute, pad.attribute_detail, pad.color_hex, pc.attribute_id, pc.attribute_detail_id, COALESCE(GROUP_CONCAT(DISTINCT CONCAT_WS(": ", pa.product_attribute, pad.attribute_detail) SEPARATOR "; "), "No variants available") AS variants') ->from('product_combination pc') ->join('product_attributes pa', 'pc.attribute_id = pa.id') ->join('product_attributes_detail pad', 'pc.attribute_detail_id = pad.id') ->join('product_details pd', 'pc.product_details_id = pd.id') ->where('pc.product_details_id', $detail->id) ->get() ->result_array(); } usort($product_details, function ($a, $b) { return $b->stock_available - $a->stock_available; }); $initial_product_detail = $product_details[0]; $initial_variant = $initial_product_detail->variants[0] ?? null; $id_detail_products = []; foreach ($product_details as &$detail) { $id_detail_products[] = $detail->id; } $attributes = $this->db->query(" SELECT DISTINCT pa.id, pa.product_attribute, pa.is_color, pa.priority FROM product_attributes pa JOIN product_combination pc ON pa.id = pc.attribute_id JOIN product_details pd ON pc.product_details_id = pd.id WHERE pd.product_id = ? ORDER BY pa.priority ASC ", [$product->id_products])->result(); $variants_list = []; foreach ($attributes as $attr) { // Query untuk mengambil rincian atribut berdasarkan atribut yang ditemukan $variants[$attr->id] = $this->db->query(" SELECT DISTINCT pad.id, pad.attribute_detail, pad.color_hex, pd.product_id as product_id, pd.id as product_detail_id, pd.price, pd.discounted_price, s.stock, s.stock_keep, s.stock_reject, s.stock_sample, (s.stock - s.stock_keep - COALESCE(s.stock_reject, 0) - COALESCE(s.stock_sample, 0)) as stock_available FROM product_attributes_detail pad JOIN product_combination pc ON pad.id = pc.attribute_detail_id JOIN product_details pd ON pc.product_details_id = pd.id LEFT JOIN stock s ON pd.id = s.id_product_detail WHERE pd.product_id = ? AND pc.attribute_id = ? ORDER BY GREATEST((s.stock - s.stock_keep - COALESCE(s.stock_reject, 0) - COALESCE(s.stock_sample, 0)), 0) DESC, pad.attribute_detail ASC ", [$product->id_products, $attr->id])->result(); // Tambahkan ke daftar varian $variants_list[$attr->id] = [ 'attribute' => $attr, 'details' => $variants[$attr->id] ]; } $product_images = $this->db->select('*') ->from('product_images') ->where('product_id', $product->id_products) ->where('product_details_id', $initial_product_detail->id) ->where('status', 1) ->limit(4) ->order_by('priority', 'ASC') ->get() ->result(); $this->db->select(' pr.id, pr.review_date, pr.rating, pr.subject, pr.review, pr.is_verified_purchase, pr.display_name, p.id_products AS product_id, p.title AS product_name, p.alias AS product_alias, p.deleted_at, COUNT(pr.id) AS review_count, AVG(pr.rating) AS average_rating, SUM(CASE WHEN pr.rating = 5 THEN 1 ELSE 0 END) AS rating_5, SUM(CASE WHEN pr.rating = 4 THEN 1 ELSE 0 END) AS rating_4, SUM(CASE WHEN pr.rating = 3 THEN 1 ELSE 0 END) AS rating_3, SUM(CASE WHEN pr.rating = 2 THEN 1 ELSE 0 END) AS rating_2, SUM(CASE WHEN pr.rating = 1 THEN 1 ELSE 0 END) AS rating_1') ->from('product_review pr') ->join('products p', 'p.id_products = pr.product_id') ->where('p.product_status', 1) ->where('p.deleted_at', null) ->where('pr.status', 'approved') ->where('pr.product_id', $product->id_products) ->group_by('pr.product_id'); $reviews = $this->db->get()->row(); // Ambil detail ulasan $this->db->select(' pr.id, pr.product_id, pr.review_date, pr.customer_id, pr.is_verified_purchase, pr.status, pr.rating, pr.subject, pr.review, pr.display_name, '); $this->db->from('product_review pr'); $this->db->where('pr.status', 'approved'); $this->db->where('pr.product_id', $product->id_products); $this->db->order_by('pr.review_date', 'DESC'); $review_details = $this->db->get()->result(); foreach ($review_details as &$review) { $aspect_ratings = $this->db->select(" rar.aspect_id, rar.rating, ra.aspect_name AS aspect_title, ra.display_name" . ($isEnglish ? "_en" : "") . " AS aspect_name, ") ->from('review_aspect_ratings rar') ->join('review_aspects ra', 'rar.aspect_id = ra.id') ->where('rar.review_id', $review->id) ->get() ->result(); $review->aspect_ratings = $aspect_ratings; } // Produk Suggest $product_suggest = $product->product_suggest; if (!empty($product_suggest)) { $suggested_ids = explode(',', $product_suggest); $suggested_products = $this->db->select(' p.id_products, p.title, p.alias, p.brand_id, pd.id AS id_detail, pd.price, pd.discounted_price, pd.sku, COALESCE(variants.variants, "No variants available") AS variants, pi.image, pi_secondary.image AS image_secondary, s.stock, s.stock_keep, s.stock_reject, s.stock_sample, (COALESCE(s.stock, 0) - COALESCE(s.stock_keep, 0) - COALESCE(s.stock_reject, 0) - COALESCE(s.stock_sample, 0)) AS stock_sell, IF(COALESCE(s.stock, 0) - COALESCE(s.stock_keep, 0) - COALESCE(s.stock_reject, 0) - COALESCE(s.stock_sample, 0) <= 0, 1, 0) as sort_order, total_sales.total_sales, total_reviews.total_reviews, rp.price AS reseller_price, rp.min_quantity AS reseller_min_quantity, -- Badges Information (Added) COALESCE(product_badges_data.badges_names, "") AS badges_names, COALESCE(product_badges_data.badges_data, "") AS badges_data, COALESCE(product_badges_data.badges_count, 0) AS badges_count, -- Discount calculations (Added) CASE WHEN pd.discounted_price > 0 AND pd.discounted_price < pd.price THEN (pd.price - pd.discounted_price) ELSE 0 END AS savings_amount, CASE WHEN pd.discounted_price > 0 AND pd.discounted_price < pd.price THEN ROUND(((pd.price - pd.discounted_price) / pd.price) * 100, 0) ELSE 0 END AS discount_percentage ') ->from('products p') ->join('product_details pd', 'p.id_products = pd.product_id', 'left') ->join('product_images pi', 'pd.id = pi.product_details_id AND pi.priority = 1 AND pi.status = 1', 'left') ->join('product_images pi_secondary', 'pd.id = pi_secondary.product_details_id AND pi_secondary.priority = 2 AND pi_secondary.status = 1', 'left') ->join('stock s', 'pd.id = s.id_product_detail', 'left') ->join('(SELECT item_id, SUM(quantity) AS total_sales FROM orders_detail WHERE warehouse_id = 1 GROUP BY item_id) total_sales', 'pd.id = total_sales.item_id', 'left') ->join('(SELECT product_id, COUNT(id) AS total_reviews FROM product_review GROUP BY product_id) total_reviews', 'p.id_products = total_reviews.product_id', 'left') ->join('(SELECT pc.product_details_id, GROUP_CONCAT(DISTINCT CONCAT_WS(": ", pa.product_attribute, pad.attribute_detail) SEPARATOR "; ") AS variants FROM product_combination pc JOIN product_attributes pa ON pc.attribute_id = pa.id JOIN product_attributes_detail pad ON pc.attribute_detail_id = pad.id GROUP BY pc.product_details_id) variants', 'pd.id = variants.product_details_id', 'left') ->join('category_product cp', 'p.id_products = cp.id_product', 'inner') ->join('resellers_price rp', 'pd.id = rp.product_detail_id', 'left') // Product Badges Join (Added) ->join('(SELECT pb.product_id, COUNT(pb.badge_id) AS badges_count, GROUP_CONCAT(b.name ORDER BY pb.priority ASC SEPARATOR ", ") AS badges_names, GROUP_CONCAT( CONCAT( b.id, ":", b.name, ":", b.slug, ":", IFNULL(b.description, ""), ":", IFNULL(b.background_color, "#FF6B6B"), ":", IFNULL(b.text_color, "#FFFFFF"), ":", IFNULL(b.icon, ""), ":", pb.position, ":", pb.priority ) ORDER BY pb.priority ASC SEPARATOR "|" ) AS badges_data FROM product_badges pb INNER JOIN badges b ON pb.badge_id = b.id WHERE pb.is_active = 1 AND b.is_active = 1 AND (pb.start_date IS NULL OR pb.start_date <= NOW()) AND (pb.end_date IS NULL OR pb.end_date >= NOW()) GROUP BY pb.product_id) product_badges_data', 'p.id_products = product_badges_data.product_id', 'left') ->where_in('p.id_products', $suggested_ids) ->where('p.product_status', '1') ->where('p.deleted_at', null) ->having('(COALESCE(s.stock, 0) - COALESCE(s.stock_keep, 0)) > 0') ->group_by('p.id_products') ->order_by('sort_order', 'ASC') ->order_by('pd.id', 'ASC'); $this->db->cache_on(); $suggested_products_data = $this->db->get()->result_array(); $this->db->cache_off(); $suggested_products = $this->prepare_all_products_optimized($suggested_products_data); } else { $suggested_products_data = []; } $is_wishlisted = $this->_check_wishlist_status($product->id_products, $customer_id); $wishlist_count = $this->_check_wishlist_counts($product->id_products); $footer_categories = $this->Footer_m->get_all_categories(); $footer_social_media = $this->Footer_m->get_social_media(); $footer_payment_methods = $this->Footer_m->get_payment_methods(); $footer_asmaradoor = $this->Footer_m->get_asmaradoor(); $footer_bottom = $this->Footer_m->get_footer_bottom(); // Tambahkan ke data $data = compact( 'review_details', 'product', 'product_details', 'id_detail_products', 'initial_product_detail', 'initial_variant', 'product_images', 'reviews', 'referral', 'suggested_products', 'reseller', 'variants_list', 'is_wishlisted', 'wishlist_count', 'footer_categories', 'footer_social_media', 'footer_payment_methods', 'footer_asmaradoor', 'footer_bottom', 'review_aspects' ); $this->data_footer['popular_categories'] = $this->Category_m->get_footer_popular_categories(); $this->data_footer['trending_searches'] = $this->Statistic_m->get_trending_searches(); // echo "<pre>"; // print_r($data); // echo "</pre>"; // exit; $this->session->set_userdata('productpage_to_cart', base_url() . 'product/' . $alias); $this->load->view("themes/3/header_new", $this->data_header); $this->load->view("themes/3/product_new", $data); $this->load->view("themes/3/footer_new", $this->data_footer); } public function index() { $this->visitortracking->trackVisitor(); $activeBanners = $this->Top_banner_m->get_active_banners(); // Fetch website data for header and meta information $websiteData = $this->db->select('website_icon, browser_title, meta_description') ->from('configuration') ->where('id_configuration', 1) ->get() ->row(); $query = $this->get_optimized_product_query(); $this->db->cache_on(); $all_products = $this->db->query($query)->result_array(); $this->db->cache_off(); $formatted_all_products = $this->prepare_all_products_optimized($all_products); $data = [ 'products' => $formatted_all_products, 'max_price' => $this->getMaxPrice() ]; $meta_description = ($this->session->userdata('site_lang') == 'english') ? "Enjoy the best intimate experience with Laci Asmara's curated pleasure. Comfortable, safe, and fun for a more exciting exploration!" : "Nikmati pengalaman intim terbaik dengan piranti asmara pilihan Laci Asmara. Nyaman, aman, dan menyenangkan untuk eksplorasi yang lebih menantang!"; $footer_categories = $this->Footer_m->get_all_categories(); $footer_social_media = $this->Footer_m->get_social_media(); $footer_payment_methods = $this->Footer_m->get_payment_methods(); $footer_asmaradoor = $this->Footer_m->get_asmaradoor(); $footer_bottom = $this->Footer_m->get_footer_bottom(); // Prepare header data $this->data_header = [ 'website_icon' => $websiteData->website_icon, 'browser_title' => $websiteData->browser_title . ' - All Products', 'meta_description' => $meta_description, 'banners' => $activeBanners, 'logo_path' => 'https://storage.googleapis.com/laciasmara-photos/laciaasmara_assets/laciasmara_landing_page/laciasmara_landing_page_logo.webp', 'footer_categories' => $footer_categories, 'footer_social_media' => $footer_social_media, 'footer_payment_methods' => $footer_payment_methods, 'footer_asmaradoor' => $footer_asmaradoor, 'footer_bottom' => $footer_bottom, ]; $this->load->view("themes/3/header_new", $this->data_header); $this->load->view("all-products", $data); $this->load->view("themes/3/footer_new", $this->data_footer); } private function get_optimized_product_query() { return " SELECT p.id_products, p.title, p.alias, p.brand_id, p.created_at, p.best_seller, p.new_arrival, p.popular_product, p.priority, -- Product Details COALESCE(primary_variant.id, first_variant.id) AS id_detail, COALESCE(primary_variant.price, first_variant.price) AS price, COALESCE(primary_variant.discounted_price, first_variant.discounted_price) AS discounted_price, COALESCE(primary_variant.sku, first_variant.sku) AS sku, -- Product Images COALESCE( main_image.image, COALESCE(primary_variant_image.image, first_variant_image.image), p.image ) AS image, COALESCE( main_image_secondary.image, COALESCE(primary_variant_image_secondary.image, first_variant_image_secondary.image), p.image1 ) AS image_secondary, -- Stock Information stock_summary.total_stock, stock_summary.total_stock_keep, stock_summary.total_stock_sell, stock_summary.available_variants, stock_summary.total_variants, -- Current variant stock COALESCE( GREATEST(0, COALESCE(primary_variant_stock.stock, 0) - COALESCE(primary_variant_stock.stock_keep, 0)), GREATEST(0, COALESCE(first_variant_stock.stock, 0) - COALESCE(first_variant_stock.stock_keep, 0)), 0 ) AS current_variant_stock_sell, -- Availability Flags CASE WHEN stock_summary.total_stock_sell <= 0 THEN 1 ELSE 0 END AS is_completely_sold_out, CASE WHEN COALESCE( GREATEST(0, COALESCE(primary_variant_stock.stock, 0) - COALESCE(primary_variant_stock.stock_keep, 0)), GREATEST(0, COALESCE(first_variant_stock.stock, 0) - COALESCE(first_variant_stock.stock_keep, 0)), 0 ) <= 0 THEN 1 ELSE 0 END AS current_variant_out_of_stock, CASE WHEN stock_summary.available_variants > 1 THEN 1 ELSE 0 END AS has_other_variants_available, -- Variants COALESCE( primary_variant_attrs.variants, first_variant_attrs.variants, 'No variants available' ) AS variants, -- Badges Information COALESCE(product_badges_data.badges_names, '') AS badges_names, COALESCE(product_badges_data.badges_data, '') AS badges_data, COALESCE(product_badges_data.badges_count, 0) AS badges_count, -- Additional data COALESCE(total_sales.total_sales, 0) AS total_sales, COALESCE(total_reviews.total_reviews, 0) AS total_reviews, COALESCE(reseller_price.price, 0) AS reseller_price, COALESCE(reseller_price.min_quantity, 0) AS reseller_min_quantity, COALESCE(b.priority, 0) AS brand_priority, -- Discount calculations CASE WHEN COALESCE(primary_variant.discounted_price, first_variant.discounted_price) > 0 AND COALESCE(primary_variant.discounted_price, first_variant.discounted_price) < COALESCE(primary_variant.price, first_variant.price) THEN (COALESCE(primary_variant.price, first_variant.price) - COALESCE(primary_variant.discounted_price, first_variant.discounted_price)) ELSE 0 END AS savings_amount, CASE WHEN COALESCE(primary_variant.discounted_price, first_variant.discounted_price) > 0 AND COALESCE(primary_variant.discounted_price, first_variant.discounted_price) < COALESCE(primary_variant.price, first_variant.price) THEN ROUND(((COALESCE(primary_variant.price, first_variant.price) - COALESCE(primary_variant.discounted_price, first_variant.discounted_price)) / COALESCE(primary_variant.price, first_variant.price)) * 100, 0) ELSE 0 END AS discount_percentage, -- Sort order CASE WHEN stock_summary.total_stock_sell <= 0 THEN 1 ELSE 0 END AS sort_order FROM products p -- Stock Summary LEFT JOIN ( SELECT pd.product_id, COUNT(DISTINCT pd.id) AS total_variants, SUM(COALESCE(s.stock, 0)) AS total_stock, SUM(COALESCE(s.stock_keep, 0)) AS total_stock_keep, SUM(GREATEST(0, COALESCE(s.stock, 0) - COALESCE(s.stock_keep, 0))) AS total_stock_sell, SUM(CASE WHEN GREATEST(0, COALESCE(s.stock, 0) - COALESCE(s.stock_keep, 0)) > 0 THEN 1 ELSE 0 END) AS available_variants FROM product_details pd LEFT JOIN stock s ON pd.id = s.id_product_detail GROUP BY pd.product_id ) stock_summary ON p.id_products = stock_summary.product_id -- Primary Variant LEFT JOIN ( SELECT pd.* FROM product_details pd INNER JOIN ( SELECT product_id, MIN(id) as min_id FROM product_details GROUP BY product_id ) min_pd ON pd.product_id = min_pd.product_id AND pd.id = min_pd.min_id ) primary_variant ON p.id_products = primary_variant.product_id LEFT JOIN ( SELECT pd.* FROM product_details pd LEFT JOIN stock s ON pd.id = s.id_product_detail INNER JOIN ( SELECT pd.product_id, MAX(GREATEST(0, COALESCE(s.stock, 0) - COALESCE(s.stock_keep, 0))) AS max_stock FROM product_details pd LEFT JOIN stock s ON pd.id = s.id_product_detail GROUP BY pd.product_id ) max_stock_pd ON pd.product_id = max_stock_pd.product_id LEFT JOIN stock s2 ON pd.id = s2.id_product_detail WHERE GREATEST(0, COALESCE(s2.stock, 0) - COALESCE(s2.stock_keep, 0)) = max_stock_pd.max_stock ) first_variant ON p.id_products = first_variant.product_id -- Stock for displayed variants LEFT JOIN stock primary_variant_stock ON primary_variant.id = primary_variant_stock.id_product_detail LEFT JOIN stock first_variant_stock ON first_variant.id = first_variant_stock.id_product_detail -- Product Images LEFT JOIN product_images main_image ON p.id_products = main_image.product_id AND main_image.product_details_id = 0 AND main_image.priority = 1 AND main_image.status = '1' LEFT JOIN product_images main_image_secondary ON p.id_products = main_image_secondary.product_id AND main_image_secondary.product_details_id = 0 AND main_image_secondary.priority = 2 AND main_image_secondary.status = '1' LEFT JOIN product_images primary_variant_image ON primary_variant.id = primary_variant_image.product_details_id AND primary_variant_image.priority = 1 AND primary_variant_image.status = '1' LEFT JOIN product_images primary_variant_image_secondary ON primary_variant.id = primary_variant_image_secondary.product_details_id AND primary_variant_image_secondary.priority = 2 AND primary_variant_image_secondary.status = '1' LEFT JOIN product_images first_variant_image ON first_variant.id = first_variant_image.product_details_id AND first_variant_image.priority = 1 AND first_variant_image.status = '1' LEFT JOIN product_images first_variant_image_secondary ON first_variant.id = first_variant_image_secondary.product_details_id AND first_variant_image_secondary.priority = 2 AND first_variant_image_secondary.status = '1' -- Variant Attributes LEFT JOIN ( SELECT pc.product_details_id, GROUP_CONCAT(DISTINCT CONCAT_WS(': ', pa.product_attribute, pad.attribute_detail) SEPARATOR '; ') AS variants FROM product_combination pc JOIN product_attributes pa ON pc.attribute_id = pa.id JOIN product_attributes_detail pad ON pc.attribute_detail_id = pad.id GROUP BY pc.product_details_id ) primary_variant_attrs ON primary_variant.id = primary_variant_attrs.product_details_id LEFT JOIN ( SELECT pc.product_details_id, GROUP_CONCAT(DISTINCT CONCAT_WS(': ', pa.product_attribute, pad.attribute_detail) SEPARATOR '; ') AS variants FROM product_combination pc JOIN product_attributes pa ON pc.attribute_id = pa.id JOIN product_attributes_detail pad ON pc.attribute_detail_id = pad.id GROUP BY pc.product_details_id ) first_variant_attrs ON first_variant.id = first_variant_attrs.product_details_id -- Product Badges (Enhanced but still simple) LEFT JOIN ( SELECT pb.product_id, COUNT(pb.badge_id) AS badges_count, GROUP_CONCAT(b.name ORDER BY pb.priority ASC SEPARATOR ', ') AS badges_names, GROUP_CONCAT( CONCAT( b.id, ':', b.name, ':', b.slug, ':', IFNULL(b.description, ''), ':', IFNULL(b.background_color, '#FF6B6B'), ':', IFNULL(b.text_color, '#FFFFFF'), ':', IFNULL(b.icon, ''), ':', pb.position, ':', pb.priority ) ORDER BY pb.priority ASC SEPARATOR '|' ) AS badges_data FROM product_badges pb INNER JOIN badges b ON pb.badge_id = b.id WHERE pb.is_active = 1 AND b.is_active = 1 AND (pb.start_date IS NULL OR pb.start_date <= NOW()) AND (pb.end_date IS NULL OR pb.end_date >= NOW()) GROUP BY pb.product_id ) product_badges_data ON p.id_products = product_badges_data.product_id -- Additional joins LEFT JOIN ( SELECT item_id, SUM(quantity) AS total_sales FROM orders_detail WHERE warehouse_id = 1 GROUP BY item_id ) total_sales ON COALESCE(primary_variant.id, first_variant.id) = total_sales.item_id LEFT JOIN ( SELECT product_id, COUNT(id) AS total_reviews FROM product_review GROUP BY product_id ) total_reviews ON p.id_products = total_reviews.product_id LEFT JOIN resellers_price reseller_price ON COALESCE(primary_variant.id, first_variant.id) = reseller_price.product_detail_id LEFT JOIN brands b ON p.brand_id = b.id_brands INNER JOIN category_product cp ON p.id_products = cp.id_product WHERE p.product_status = '1' AND p.deleted_at IS NULL GROUP BY p.id_products ORDER BY sort_order ASC, p.best_seller DESC, p.new_arrival DESC, p.popular_product DESC, p.priority DESC, COALESCE(b.priority, 0) DESC, p.created_at DESC "; } /** * Optimized prepare_all_products method */ private function prepare_all_products_optimized($products) { $customer = $this->session->userdata('customer'); $is_reseller = false; $reseller_price_map = []; // Get reseller information (existing code) if (!empty($customer['customer_id'])) { $customer_id = $customer['customer_id']; $customer_data = $this->db->where('id_customers', $customer_id)->get('customers')->row_array(); if (!empty($customer_data['reseller_id'])) { $reseller = $this->db->where('id_resellers', $customer_data['reseller_id'])->get('resellers')->row_array(); if ($reseller) { $is_reseller = true; $reseller_prices = $this->db->where('reseller_id', $reseller['id_resellers'])->get('resellers_price')->result_array(); foreach ($reseller_prices as $price) { $reseller_price_map[$price['product_detail_id']] = $price['price']; } } } } $formatted_products = []; foreach ($products as $product) { // Price calculations $is_discounted = $product['discounted_price'] > 0; $default_price = $product['price']; $discounted_price = $product['discounted_price']; $current_price = $is_discounted ? $discounted_price : $default_price; $msrp_price = null; // Handle reseller pricing if ($is_reseller && isset($reseller_price_map[$product['id_detail']])) { $msrp_price = $current_price; $current_price = $reseller_price_map[$product['id_detail']]; } // Get reviews $review_data = $this->Review_m->get_product_reviews($product['id_products']); $average_rating = isset($review_data['average_rating']) ? round($review_data['average_rating'], 1) : 0; $total_reviews = isset($review_data['total_reviews']) ? $review_data['total_reviews'] : 0; // Parse variants $variants = !empty($product['variants']) ? explode('; ', $product['variants']) : []; $badges = $this->parse_badges_data($product['badges_data'] ?? ''); $badges_names = $product['badges_names'] ?? ''; // Stock calculations - all done in SQL now, just ensure non-negative values $current_variant_stock_sell = max(0, intval($product['current_variant_stock_sell'] ?? 0)); $total_stock_sell = max(0, intval($product['total_stock_sell'] ?? 0)); $formatted_products[] = [ 'id' => $product['id_products'], 'title' => $product['title'], 'id_detail' => $product['id_detail'], 'alias' => $product['alias'], 'sku' => $product['sku'], 'current_price' => $current_price, 'original_price' => $is_discounted ? $default_price : null, 'msrp_price' => $msrp_price, 'image' => $product['image'], 'image_secondary' => $product['image_secondary'], 'stock' => $product['total_stock'], 'stock_sell' => $current_variant_stock_sell, 'total_stock_sell' => $total_stock_sell, 'average_rating' => $average_rating, 'total_reviews' => $total_reviews, 'variants' => $variants, 'is_wishlisted' => $this->_check_wishlist_status($product['id_products'], $customer['customer_id'] ?? null), 'created_at' => $product['created_at'], 'brand_priority' => $product['brand_priority'], 'savings_amount' => $product['savings_amount'], 'is_discounted' => $is_discounted, 'discount_percentage' => $product['discount_percentage'], // Stock flags - now calculated in SQL 'is_completely_sold_out' => (bool) $product['is_completely_sold_out'], 'current_variant_out_of_stock' => (bool) $product['current_variant_out_of_stock'], 'has_other_variants_available' => (bool) $product['has_other_variants_available'], // Additional stock information 'total_variants' => (int) $product['total_variants'], 'available_variants' => (int) $product['available_variants'], // Badges 'badges' => $badges, 'badges_names' => $badges_names, // Simple comma-separated names for quick display 'badges_count' => (int) ($product['badges_count'] ?? 0), 'has_badges' => !empty($badges), // New arrivals flag for badges 'new_arrivals' => ($product['new_arrival'] === 'yes'), 'best_seller' => ($product['best_seller'] === 'yes'), 'popular_product' => ($product['popular_product'] === 'yes'), ]; } return $formatted_products; } private function parse_badges_data($badges_data) { $badges = []; if (empty($badges_data)) { return $badges; } $badges_raw = explode('|', $badges_data); foreach ($badges_raw as $badge_raw) { if (empty($badge_raw)) continue; $badge_parts = explode(':', $badge_raw); if (count($badge_parts) >= 9) { $badges[] = [ 'id' => (int) $badge_parts[0], 'name' => $badge_parts[1], 'slug' => $badge_parts[2], 'description' => $badge_parts[3], 'background_color' => !empty($badge_parts[4]) ? $badge_parts[4] : '#FF6B6B', 'text_color' => !empty($badge_parts[5]) ? $badge_parts[5] : '#FFFFFF', 'icon' => $badge_parts[6], 'position' => $badge_parts[7], 'priority' => (int) $badge_parts[8], // Additional computed properties 'css_class' => 'badge-' . $badge_parts[2], 'has_icon' => !empty($badge_parts[6]), 'style' => sprintf( 'background-color: %s; color: %s;', !empty($badge_parts[4]) ? $badge_parts[4] : '#FF6B6B', !empty($badge_parts[5]) ? $badge_parts[5] : '#FFFFFF' ) ]; } } // Already sorted by priority in SQL, but double check usort($badges, function ($a, $b) { return $a['priority'] - $b['priority']; }); return $badges; } /** * Helper method untuk mengecek ketersediaan stok semua variant produk * @param int $product_id * @return array Informasi lengkap tentang stok variant */ private function get_product_stock_info($product_id) { // Ambil semua variant dari produk ini dengan perhitungan stock_sell yang aman $this->db->select(' s.stock, s.stock_keep, CASE WHEN (s.stock IS NULL OR s.stock_keep IS NULL) THEN 0 WHEN (s.stock - s.stock_keep) < 0 THEN 0 ELSE (s.stock - s.stock_keep) END as stock_sell_safe ') ->from('product_details pd') ->join('stock s', 'pd.id = s.id_product_detail', 'left') ->where('pd.product_id', $product_id); $variants_stock = $this->db->get()->result_array(); // Jika tidak ada data stock, anggap sold out if (empty($variants_stock)) { return [ 'is_completely_sold_out' => true, 'has_other_variants_available' => false, 'has_other_variants_sold_out' => false, 'total_variants' => 0, 'available_variants' => 0 ]; } $available_variants = 0; $sold_out_variants = 0; $total_variants = count($variants_stock); // Hitung variant yang tersedia dan yang habis dengan logika yang aman foreach ($variants_stock as $variant) { $stock = intval($variant['stock'] ?? 0); $stock_keep = intval($variant['stock_keep'] ?? 0); // Pastikan stock_sell tidak bisa negatif $stock_sell_safe = max(0, $stock - $stock_keep); if ($stock_sell_safe > 0) { $available_variants++; } else { $sold_out_variants++; } } return [ 'is_completely_sold_out' => ($available_variants == 0), // Semua variant habis 'has_other_variants_available' => ($available_variants > 1), // Ada variant lain yang tersedia (selain yang ditampilkan) 'has_other_variants_sold_out' => ($sold_out_variants > 0), // Ada variant lain yang habis 'total_variants' => $total_variants, 'available_variants' => $available_variants ]; } private function _check_wishlist_status($product_id, $customer_id) { if (!$customer_id) return false; $exists = $this->db->where([ 'customer_id' => $customer_id, 'product_id' => $product_id ])->get('wishlists')->num_rows(); return $exists > 0; } private function _check_wishlist_counts($product_id) { $count = $this->db->where([ 'product_id' => $product_id ]) ->from('wishlists') ->count_all_results(); return $count; } public function getMaxPrice() { $this->db->select_max('price'); $this->db->from('product_details'); $result = $this->db->get()->row(); return $result ? $result->price : 0; } }