AlkantarClanX12
| Current Path : /www/capitalgmcbuickregina_830/public/wp-content/plugins/leadbox/lib/ |
| 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;
}
}