-
-
Notifications
You must be signed in to change notification settings - Fork 227
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Centralizes all basic product-related querying and overriding. (Not including attributes or pricing.) Closes #6367 The easiest override/notifier hook is now `NOTIFY_GET_PRODUCT_OBJECT_DETAILS`
- Loading branch information
Showing
27 changed files
with
706 additions
and
389 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,306 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
/** | ||
* @copyright Copyright 2003-2024 Zen Cart Development Team | ||
* @license http://www.zen-cart.com/license/2_0.txt GNU Public License V2.0 | ||
* @version $Id: DrByte New in 2.0.0 $ | ||
* | ||
* @var language $lng | ||
* @var queryFactory $db | ||
*/ | ||
|
||
use Zencart\Traits\NotifierManager; | ||
|
||
class Product | ||
{ | ||
use NotifierManager; | ||
|
||
protected array $data; | ||
protected array $languages; | ||
|
||
/** @deprecated use ->get('property') or ->getData() */ | ||
public array $fields; | ||
|
||
/** @deprecated use !exists() */ | ||
public bool $EOF = true; | ||
|
||
public function __construct(protected ?int $product_id = null) | ||
{ | ||
$this->initLanguages(); | ||
|
||
if ($this->product_id !== null) { | ||
$this->data = $this->loadProductDetails($this->product_id); | ||
|
||
// set some backward compatibility properties | ||
$this->fields = $this->data; | ||
$this->EOF = empty($this->data); | ||
} | ||
} | ||
|
||
public function forLanguage(?int $language_id): self | ||
{ | ||
$this->data = $this->getDataForLanguage($language_id); | ||
$this->fields = $this->data; | ||
|
||
return $this; | ||
} | ||
|
||
public function withDefaultLanguage(): self | ||
{ | ||
$this->data = $this->getDataForLanguage(); | ||
$this->fields = $this->data; | ||
|
||
return $this; | ||
} | ||
|
||
public function getData(): ?array | ||
{ | ||
return $this->data; | ||
} | ||
|
||
public function get(string $name) | ||
{ | ||
return $this->data[$name] ?? $this->data['lang'][$this->languages[(int)$_SESSION['languages_id']]] ?? null; | ||
} | ||
|
||
/** | ||
* Same as getData(), but for specific language only | ||
*/ | ||
public function getDataForLanguage(?int $language_id = null): ?array | ||
{ | ||
if (empty($language_id)) { | ||
$language_id = (int)$_SESSION['languages_id']; | ||
} | ||
$data = $this->data; | ||
|
||
// strip all languages except specified one, and merge into parent array instead of sub-array | ||
foreach($data['lang'][$this->languages[$language_id]] as $key => $value) { | ||
$data[$key] = $value; | ||
} | ||
unset($data['lang']); | ||
|
||
return $data; | ||
} | ||
|
||
public function getId(): ?int | ||
{ | ||
return $this->product_id; | ||
} | ||
|
||
public function exists(): bool | ||
{ | ||
return !empty($this->product_id); | ||
} | ||
public function isValid(): bool | ||
{ | ||
return !empty($this->data); | ||
} | ||
|
||
public function isLinked(): bool | ||
{ | ||
return ($this->data['linked_categories_count'] ?? 0) > 0; | ||
} | ||
|
||
public function isVirtual(): bool | ||
{ | ||
return ($this->data['products_virtual'] ?? 0) === '1'; | ||
} | ||
|
||
public function isAlwaysFreeShipping(): bool | ||
{ | ||
return ($this->data['product_is_always_free_shipping'] ?? '') === '1'; | ||
} | ||
|
||
public function isGiftVoucher(): bool | ||
{ | ||
return str_starts_with($this->data['products_model'] ?? '', 'GIFT'); | ||
} | ||
|
||
public function allowsAddToCart(): bool | ||
{ | ||
if (empty($this->data)) { | ||
return false; | ||
} | ||
|
||
$allow_add_to_cart = ($this->data['allow_add_to_cart'] ?? 'N') !== 'N'; | ||
|
||
if ($allow_add_to_cart && $this->isGiftVoucher()) { | ||
// if GV feature disabled, can't allow GV's to be added to cart | ||
if (!defined('MODULE_ORDER_TOTAL_GV_STATUS') || MODULE_ORDER_TOTAL_GV_STATUS !== 'true') { | ||
$allow_add_to_cart = false; | ||
} | ||
} | ||
|
||
$this->notify('NOTIFY_GET_PRODUCT_ALLOW_ADD_TO_CART', $this->product_id, $allow_add_to_cart, $this->data); | ||
|
||
// test for boolean and for 'Y', since observer might try to return 'Y' | ||
return in_array($allow_add_to_cart, [true, 'Y'], true); | ||
} | ||
|
||
public function getProductQuantity(): int|float | ||
{ | ||
$quantity = $this->data['products_quantity'] ?? '0'; | ||
$this->notify('NOTIFY_GET_PRODUCT_QUANTITY', $this->product_id, $quantity); | ||
return zen_str_to_numeric((string)$quantity); | ||
} | ||
|
||
public function getTypeHandler(): string | ||
{ | ||
return ($this->data['type_handler'] ?? 'product'); | ||
} | ||
|
||
public function getInfoPage(): string | ||
{ | ||
return $this->getTypeHandler() . '_info'; | ||
} | ||
|
||
public function hasPriceQuantityDiscounts(): bool | ||
{ | ||
if (empty($this->data)) { | ||
return false; | ||
} | ||
|
||
global $db; | ||
$sql = "SELECT products_id FROM " . TABLE_PRODUCTS_DISCOUNT_QUANTITY . " WHERE products_id=" . (int)$this->product_id; | ||
$results = $db->Execute($sql, 1); | ||
return !$results->EOF; | ||
} | ||
|
||
public function hasPriceSpecials() | ||
{ | ||
if (empty($this->data)) { | ||
return false; | ||
} | ||
|
||
global $db; | ||
$sql = "SELECT products_id FROM " . TABLE_SPECIALS . " WHERE products_id=" . (int)$this->product_id; | ||
$results = $db->Execute($sql, 1); | ||
return !$results->EOF; | ||
} | ||
|
||
public function priceIsByAttribute(): bool | ||
{ | ||
return ($this->data['products_priced_by_attribute'] ?? '0') === '1'; | ||
} | ||
|
||
public function priceIsFree(): bool | ||
{ | ||
return ($this->data['product_is_free'] ?? '0') === '1'; | ||
} | ||
|
||
public function priceIsCall(): bool | ||
{ | ||
return ($this->data['product_is_call'] ?? '0') === '1'; | ||
} | ||
|
||
public function __get(string $name) | ||
{ | ||
return $this->get($name); | ||
} | ||
|
||
protected function loadProductDetails(int $product_id, ?int $language_id = null): array | ||
{ | ||
global $db; | ||
|
||
$sql = "SELECT p.*, pt.allow_add_to_cart, pt.type_handler, m.manufacturers_name, m.manufacturers_image | ||
FROM " . TABLE_PRODUCTS . " p | ||
LEFT JOIN " . TABLE_PRODUCT_TYPES . " pt ON (p.products_type = pt.type_id) | ||
LEFT JOIN " . TABLE_MANUFACTURERS . " m USING (manufacturers_id) | ||
WHERE p.products_id = " . (int)$product_id; | ||
$product = $db->Execute($sql, 1, true, 900); | ||
|
||
if ($product->EOF) { | ||
return []; | ||
} | ||
|
||
$data = $product->fields; | ||
$data['id'] = $data['products_id']; | ||
$data['product_id'] = $data['products_id']; | ||
$data['info_page'] = $data['type_handler'] . '_info'; | ||
//$data['parent_category_id'] = $data['master_categories_id']; | ||
|
||
/** | ||
* Add $data['lang'][code] = [products_name, products_description, etc] for each language | ||
*/ | ||
$sql = "SELECT pd.* | ||
FROM " . TABLE_PRODUCTS_DESCRIPTION . " pd | ||
WHERE pd.products_id = " . (int)$product_id . " | ||
ORDER BY language_id"; | ||
$pd = $db->Execute($sql, null, true, 900); | ||
foreach ($pd as $result) { | ||
unset($result['products_id']); | ||
$data['lang'][$this->languages[$result['language_id']]] = $result; | ||
} | ||
|
||
// count linked categories | ||
$sql = "SELECT categories_id FROM " . TABLE_PRODUCTS_TO_CATEGORIES . " ptc WHERE products_id=" . (int)$product_id; | ||
$results = $db->Execute($sql, null, true, 900); | ||
$data['linked_categories_count'] = $results->RecordCount(); | ||
$data['linked_categories'] = []; | ||
foreach($results as $result) { | ||
$data['linked_categories'][] = $result['categories_id']; | ||
} | ||
|
||
// get cPath | ||
$categories = []; | ||
zen_get_parent_categories($categories, $data['master_categories_id']); | ||
$categories = array_reverse($categories); | ||
$categories[] = $data['master_categories_id']; | ||
$data['cPath'] = implode('_', $categories); | ||
|
||
|
||
//Allow an observer to modify details | ||
$this->notify('NOTIFY_GET_PRODUCT_OBJECT_DETAILS', $product_id, $data); | ||
|
||
return $data; | ||
} | ||
|
||
protected function initLanguages(): void | ||
{ | ||
global $lng; | ||
|
||
if ($lng === null) { | ||
$lng = new language(); | ||
} | ||
|
||
$this->languages = $lng->get_language_list(); // [1 => 'en', 2 => 'fr'] | ||
} | ||
} | ||
|
||
|
||
/* This class essentially deprecates the following functions (note Notifier hook differences): | ||
zen_get_product_details (er, well, it's now a helper to access this class) | ||
zen_get_products_category_id | ||
zen_products_id_valid | ||
zen_get_products_name | ||
zen_get_products_model | ||
zen_get_products_status | ||
zen_get_product_is_linked | ||
zen_get_products_stock (*) | ||
zen_get_products_manufacturers_name | ||
zen_get_products_manufacturers_image | ||
zen_get_products_manufacturers_id | ||
zen_get_products_url | ||
zen_get_products_description | ||
zen_get_info_page | ||
zen_get_products_type | ||
zen_get_products_image (er, well, must call zen_image yourself) | ||
zen_get_products_virtual | ||
zen_get_products_allow_add_to_cart | ||
zen_get_product_is_always_free_shipping | ||
zen_products_lookup | ||
zen_get_parent_category_id | ||
zen_has_product_discounts | ||
zen_has_product_specials | ||
zen_get_product_path | ||
zen_get_products_price_is_free | ||
zen_get_products_price_is_call | ||
zen_get_products_price_is_priced_by_attributes | ||
zen_get_products_quantity_order_min | ||
zen_get_products_quantity_order_units | ||
zen_get_products_quantity_order_max | ||
zen_get_products_qty_box_status | ||
zen_get_products_quantity_mixed | ||
*/ |
Oops, something went wrong.