AlkantarClanX12

Your IP : 216.73.217.24


Current Path : /www/capitalgmcbuickregina_830/public/wp-content/plugins/leadbox/lib/
Upload File :
Current File : /www/capitalgmcbuickregina_830/public/wp-content/plugins/leadbox/lib/inventory.php

<?php

use function Roots\app;
use function Roots\view;

/**
 * Get inventory data (uses LB_Inventory_Manager for request-level caching)
 *
 * This function now delegates to LB_Inventory_Manager which implements
 * request-level caching to prevent reading inventory.json multiple times
 * during a single page load.
 *
 * Performance impact:
 * - Before: 4-720 disk reads per page (59.25GB/day on model pages)
 * - After: 1 disk read per page (60MB/day)
 * - Savings: 99.9% reduction in redundant file I/O
 *
 * Note: This is request-level caching only. Each new page load reads fresh data.
 *
 * @return array|null Inventory data
 */
function get_inventory()
{
    return LB_Inventory_Manager::getInstance()->get_inventory();
}

function leadbox_render_inventory($options)
{
	$options = shortcode_atts(array(
		'year' => 'all',
		'make' => 'all',
		'model' => 'all',
		'trim' => 'all',
		'location' => 'all',
		'price' => 'all',
		'mileage' => 'all',
		'type' => 'all',
		'status' => 'all',
		'tags' => 'all',
		'stock' => 'all',
		'filter' => 'false',
		'slick' => 'false',
		'limit' => '0',
		'sort_by' => 'year',
		'sort_by_condition' => 'desc',
		'sort_by_secondary' => 'none',
		'sort_by_condition_secondary' => 'desc',
		'sort_secondary' => '',
    'sort_makes' => array(),
		'sort_models' => array(),
		'sort_locations' => array(),
		'sort_stocks' => array(),
		'flags' => array(),
		'layout' => 'normal',
		'vehicle_card' => '',
    'exclude_tags' => array(),
    'listing_image_group' => '', // LBT-1462: selected image-group id ('' = global config)
	), $options, 'inventory');

	// Process sort_secondary if it comes from a manual shortcode (format: "year desc")
	if (!empty($options['sort_secondary'])) {
		$sort_secondary_parts = explode(' ', $options['sort_secondary']);
		$options['sort_by_secondary'] = isset($sort_secondary_parts[0]) ? $sort_secondary_parts[0] : 'none';
		$options['sort_by_condition_secondary'] = isset($sort_secondary_parts[1]) ? $sort_secondary_parts[1] : 'desc';
	}
	// Otherwise, sort_by_secondary and sort_by_condition_secondary come directly from block attributes


	$json_path = wp_get_upload_dir()['basedir'] . '/data/inventory';
	$idiom_path = makeUrl($json_path);
    if (file_exists($idiom_path)) {
        $data = json_decode(file_get_contents($idiom_path), true);
		return parse_inventory($data['vehicles'], $options);
    } 	
	return parse_inventory(array(), $options);
}

function parse_inventory($vehicles, $options)
{
	$sort_by = normalize_sort_by_key($options['sort_by']);
	$sort_by_condition = $options['sort_by_condition'];
	$sort_by_secondary = normalize_sort_by_key($options['sort_by_secondary']);
	$sort_by_condition_secondary = $options['sort_by_condition_secondary'];
	$limit = (int) $options['limit'];

	// LBT-1453: sort_stocks is a promotion list, NOT a filter. Vehicles whose stock is
	// in the list are placed first (in list order); the rest of the inventory follows,
	// sorted by the secondary sort. The block's `stock` attribute keeps its own
	// semantics — if the dealer set one, filtering still applies to it.
	$sort_stocks = sort_stocks_to_csv($options['sort_stocks'] ?? array());
	$stock_list = $options['stock'] ?? 'all';
	if ($sort_by === 'stocknumber_custom' && $sort_stocks !== '') {
		$stock_list = $sort_stocks;
	}

	$vehicles = filter_vehicles($vehicles, $options);
	$vehicles = sort_vehicles($vehicles, $sort_by, $sort_by_condition, $sort_by_secondary, $sort_by_condition_secondary, $stock_list);
	$vehicles = limit_vehicles($vehicles, $limit);
	$data = json_encode(['vehicles' => $vehicles]);

	return view('partials.inventory', ['data' => $data, 'options' => $options])->render();
}

function sort_stocks_to_csv($value)
{
	if (is_string($value)) {
		return trim($value);
	}
	if (is_array($value)) {
		return implode(',', array_filter(array_map('trim', $value), function ($s) {
			return $s !== '';
		}));
	}
	return '';
}

function normalize_sort_by_key($value)
{
	if (!is_string($value) || $value === '' || $value === 'none') {
		return $value;
	}
	$key = strtolower($value);
	$aliases = [
		'stocknumbers' => 'stocknumber',
		'stocknumbers (custom)' => 'stocknumber_custom',
		'stocknumber (custom)' => 'stocknumber_custom',
	];
	return $aliases[$key] ?? $key;
}

function parse_range($value, $evaluateHyphen = false)
{
	$range = [];

	if ($value != 'all') {
		if ($evaluateHyphen && strpos($value, '-') !== false) {
			$range = explode('-', $value);
			$range = range($range[0], $range[1]);
		} elseif (strpos($value, ',') !== false) {
			$range = explode(',', $value);
		} else {
			$range = array($value);
		}
	}

	return $range;
}

function filter_vehicles($vehicles, $options)
{
	if($options && isset($options)){
	$_vehicles = [];
	$_stockWildcard = [];

  $options['location'] = $options['location'] ?? 'all';
	$yearrange = parse_range($options['year'], true);
	$pricerange = parse_range($options['price'], true);
	$mileagerange = parse_range($options['mileage'], true);
	$statusrange = parse_range($options['status']);
	$makerange = parse_range($options['make']);
	$modelrange = parse_range($options['model']);
	$trimrange = parse_range($options['trim']);
	$typerange = parse_range($options['type']);
	$stockrange = parse_range($options['stock']);
	$tagsrange = parse_range($options['tags']);
	$flags = parse_range($options['flags']);
	$locationrange = parse_range($options['location']);


	foreach($stockrange as $stockvalue){
		if(strpos($stockvalue, '*') !== false){
			$_stockWildcard[] = str_replace("*","",$stockvalue);
		}
	}

		foreach ($vehicles as $vehicle) {
			$matcher = new VehicleMatcher($vehicle, $options,$_stockWildcard);

			$is_match = $matcher->match('year', $yearrange) &&
				$matcher->match('make', $makerange) &&
				$matcher->match('model', $modelrange) &&
				$matcher->match('trim', $trimrange) &&
				$matcher->match('price', $pricerange) &&
				$matcher->match('mileage', $mileagerange) &&
				$matcher->match('type', $typerange) &&
				$matcher->match('condition', $statusrange, 'status') &&
				$matcher->match('stocknumber', $stockrange, 'stock') &&
	            $matcher->match_any('tags', $tagsrange) &&
	            $matcher->match('location', $locationrange) &&
				$matcher->match_flags($flags);

			if ($is_match) {
				$_vehicles[] = $vehicle;
			}
		}

		return $_vehicles;
	}else
		return $vehicles;
	
}

function sort_vehicles(&$vehicles, $sort_by, $sort_by_condition, $sort_by_secondary = 'none', $sort_by_condition_secondary = 'desc', $stock_list = 'all')
{
	// LBT-1453: custom stock-number order — promote vehicles whose stock is in the
	// dealer-defined list to the top (in list order), then append the rest of the
	// inventory sorted by the secondary sort (fallback: natural stocknumber asc).
	if ($sort_by === 'stocknumber_custom' && is_string($stock_list) && $stock_list !== 'all' && $stock_list !== '') {
		$ordered_stocks = array_values(array_filter(array_map('trim', explode(',', $stock_list)), function ($s) {
			return $s !== '' && strpos($s, '*') === false;
		}));
		$position = array_flip($ordered_stocks);

		$in_list = array();
		$out_list = array();
		foreach (array_values($vehicles) as $vehicle) {
			$stock = $vehicle['stocknumber'] ?? '';
			if (isset($position[$stock])) {
				$in_list[] = $vehicle;
			} else {
				$out_list[] = $vehicle;
			}
		}

		usort($in_list, function ($v1, $v2) use ($position, $sort_by_condition) {
			$p1 = $position[$v1['stocknumber']];
			$p2 = $position[$v2['stocknumber']];
			if ($p1 === $p2) {
				return 0;
			}
			$cmp = ($p1 < $p2) ? -1 : 1;
			return $sort_by_condition === 'desc' ? -$cmp : $cmp;
		});

		// Fallback for the "rest" when the dealer didn't set a secondary sort.
		$rest_field = ($sort_by_secondary !== 'none' && $sort_by_secondary !== '') ? $sort_by_secondary : 'stocknumber';
		$rest_direction = ($sort_by_secondary !== 'none' && $sort_by_secondary !== '') ? $sort_by_condition_secondary : 'asc';
		$rest_is_natural = in_array($rest_field, ['stocknumber', 'vin'], true);
		usort($out_list, function ($v1, $v2) use ($rest_field, $rest_direction, $rest_is_natural) {
			return compare_vehicle_field($v1, $v2, $rest_field, $rest_direction, $rest_is_natural);
		});

		// usort (not uasort) + array_merge to reindex sequentially. uasort preserves
		// the original keys, which would cause json_encode to emit an object whose
		// numeric-key insertion order is not honored by JS engines (Object.values
		// reorders numeric keys ascending), defeating the custom order.
		$vehicles = array_merge($in_list, $out_list);

		return $vehicles;
	}

	$is_natural_primary = in_array($sort_by, ['stocknumber', 'vin'], true);
	$is_natural_secondary = in_array($sort_by_secondary, ['stocknumber', 'vin'], true);

	$vehicles = array_values($vehicles);
	usort($vehicles, function ($v1, $v2) use ($sort_by, $sort_by_condition, $sort_by_secondary, $sort_by_condition_secondary, $is_natural_primary, $is_natural_secondary) {

		$primary = compare_vehicle_field($v1, $v2, $sort_by, $sort_by_condition, $is_natural_primary);
		if ($primary !== 0) {
			return $primary;
		}

		if ($sort_by_secondary !== 'none') {
			return compare_vehicle_field($v1, $v2, $sort_by_secondary, $sort_by_condition_secondary, $is_natural_secondary);
		}

		return 0;
	});

	return $vehicles;
}

function compare_vehicle_field($v1, $v2, $field, $direction, $natural)
{
	if ($natural) {
		$a = isset($v1[$field]) ? (string) $v1[$field] : '';
		$b = isset($v2[$field]) ? (string) $v2[$field] : '';
		// Empty values always sink to the end regardless of direction.
		if ($a === '' && $b !== '') return 1;
		if ($a !== '' && $b === '') return -1;
		$cmp = strnatcasecmp($a, $b);
		if ($cmp === 0) return 0;
		return $direction === 'asc' ? ($cmp < 0 ? -1 : 1) : ($cmp < 0 ? 1 : -1);
	}

	$a = array_key_exists($field, $v1) ? (int) $v1[$field] : 9999999;
	$b = array_key_exists($field, $v2) ? (int) $v2[$field] : 9999999;
	if ($a === $b) return 0;
	return $direction === 'asc' ? ($a < $b ? -1 : 1) : ($a < $b ? 1 : -1);
}

function limit_vehicles($vehicles, $limit)
{
	if ($limit > 0) {
		return array_slice($vehicles, 0, $limit);
	}
	return $vehicles;
}

class VehicleMatcher
{
	function __construct($vehicle, $options, $_stockWildcard)
	{
		$this->vehicle = $vehicle;
		$this->options = $options;
		$this->stockWildcard = $_stockWildcard;
	}

	public function match(string $property, array $values, string $propertyOptions = null)
	{
	
		$propertyOptions = $propertyOptions ?? $property;
		$value = (isset($this->vehicle) && isset($this->vehicle[$property])) ? $this->vehicle[$property] : "";
		if($propertyOptions == "stock"){
			if(sizeof($this->stockWildcard)>0){
						foreach($this->stockWildcard as $wildcard){
							if(strpos($value, $wildcard)!==false){
							return true;
							}
						}
			}
	}
		return $this->options[$propertyOptions] == 'all' || in_array($value, $values);
	}

	public function match_any(string $property, array $values, string $delimiter = ',', string $propertyOptions = null)
	{
		$propertyOptions = $propertyOptions ?? $property;

		if ($this->options[$propertyOptions] == 'all') {
			return true;
		}

		$arrayValue = explode($delimiter, $this->vehicle[$property] ?? '');

		foreach ($arrayValue as $value) {
			if (in_array(trim($value), $values)) {
				return true;
			}
		}

		return false;
	}

	public function match_flags(array $flags)
	{
		if (count($flags) === 0)
		{
			return true;
		}

		if (count($flags) === 1 && $flags[0]=="none") {
			return true;
		}

		foreach($flags as $flag)
		{
			if ((!isset($this->vehicle[$flag]) || !$this->vehicle[$flag]) && $flag != 'excludeincoming' && $flag != 'excludesalepending' && $flag != 'excludedemo' && $flag != 'noprice')
			{
				return false;
			}

			if ($flag == 'excludeincoming' && (isset($this->vehicle['incoming']) && $this->vehicle['incoming']))
			{
				return false;
			}

			if ($flag == 'excludesalepending' && (isset($this->vehicle['salepending']) && $this->vehicle['salepending']))
			{
				return false;
			}

			if ($flag == 'excludedemo' && (isset($this->vehicle['demo']) && $this->vehicle['demo']))
			{
				return false;
			}

      if ($flag == 'noprice' && (isset($this->vehicle['price']) && $this->vehicle['price'] > 0 )) {
        return false;
      }

		}

		return true;
	}
}