Working with the product, the catalog.element component and trade offers in Bitrix
The last notes
All English-language materials have been translated fully automatically using the Google service
Section navigation:
- Working with infoblock elements
- Work with the trade catalog (price\availability in warehouses\remains\reserves)
- Working with product properties
- Working with kits and kits
- Working with discounts
- Working with taxes
- Working with a template
- Exchange with 1C
- Complex ORM selection to get all the necessary product data
- Check for detail page in header
- Typical queries:
- Get all elements from a section and nested subsections
Product types
Product - the most common simple product is added, without any additional features. This is the type of product that is most commonly used, and is currently used everywhere.
Trade offers - This product (as a product that stores a list of offers) has no leftovers, no price or other attributes that a regular product has. This item is not a commodity. In fact, it contains a list of "trade offers", which are goods (offers).
A bundle is a list of products tied to the main product that the store wants to recommend for purchase. It is products that can be added to the set: trade offers and / or simple products. You cannot add products with offers.
Bundle is a list of products that make up the required bundle of the main product. The kit itself does not have a physical remainder, its remainder depends on the goods that are included in this kit. It is the products that can be added to the kit: trade offers and / or simple products. You cannot add products with offers.
Working with infoblock elements
Adding an infoblock element to the site
//We initialize the corresponding module
CModule::IncludeModule("iblock");
// Array with the properties of the infoblock element, where the keys of the array are the id of the required property
// (but you can use the CODE parameter)
// You also need to remember that for lists and fields with binding to elements or references
// as a value we specify the id of a list item, reference or object
$PROP = array(
'342'=>'',
'315'=>''
);
// Create a class object for work
$el = new CIBlockElement;
// Fill the array with data
$arLoadProductArray = Array(
"MODIFIED_BY" => 1, // specify user ID
"IBLOCK_SECTION" => $IBLOCK_SECTION_ID, //for linking to many sections
//"IBLOCK_SECTION_ID" => $IBLOCK_SECTION_ID, //for a single partition
"IBLOCK_ID" => 20,
"IBLOCK_TYPE" => 'aspro_next_catalog',
"PROPERTY_VALUES" => $PROP,
"NAME" => $NAME,
"ACTIVE" => "Y",
"CODE" => $CODE,
//"PREVIEW_TEXT" => "text for the list of items",
//"DETAIL_TEXT" => "text for detailed view",
//"DETAIL_PICTURE" => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/image.gif")
);
$res = $el->Add($arLoadProductArray);
if($res>0){
//Added successfully
//$ res stores the id of the added record
}else{
// Add failed
//Displaying an error message
echo $el->LAST_ERROR;
}
Turning an infoblock element into a product
/ * We make the added product simple * /
$productFileds = array(
"ID" => $res, //ID added infoblock element
"VAT_ID" => 1, //set the vat type (set in the admin panel)
"VAT_INCLUDED" => "Y", //VAT is included in the price
"TYPE " => \Bitrix\Catalog\ProductTable::TYPE_PRODUCT //Item type
);
// Possible values of the product type:
//const TYPE_PRODUCT = 1;
//const TYPE_SET = 2;
//const TYPE_SKU = 3;
//const TYPE_OFFER = 4;
//const TYPE_FREE_OFFER = 5;
//const TYPE_EMPTY_SKU = 6;
if(CCatalogProduct::Add($productFileds)){
// Information block element turned into a product
}else{
//An error has occurred
}
Getting the list of properties of an infoblock element using D7
$res = \Bitrix\Iblock\ElementTable::getList(array(
"select" => array("ID", "*"),
"filter" => array("IBLOCK_ID" => $IBLOCK_ID, "ID" => $ELEMENT_ID),
"order" => array("ID" => "ASC")
));
while ($arItem = $res->fetch()) {
$propRes = \Bitrix\Iblock\ElementPropertyTable::getList(array(
"select" => array("ID", "*"),
"filter" => array("IBLOCK_ELEMENT_ID" => $arItem["ID"],),
"order" => array("ID" => "ASC")
));
while($prop = $propRes->Fetch())
$arItem["PROPERTIES"][$prop["IBLOCK_PROPERTY_ID"]] = $prop;
}
Bitrix get element ID by its symbolic code
CIBlockFindTools::GetElementID($ELEMENT_ID, $ELEMENT_CODE, $SECTION_ID, $SECTION_CODE, $arFilter)
// $ section_id - ID of the section that contains the element
// $ section_code - symbolic code of the section in which the element lies
// $ arFilter - an array of additional properties for filtering
<? // example of use
$objFindTools = new CIBlockFindTools();
$elementID = $objFindTools->GetElementID(false, "super_element", false, "super_section", array("IBLOCK_ID" => 1));
// the method returns the ID of the element if it finds it, and 0 if the element is not found.
Work with the trade catalog (price\availability in warehouses\remains\reserves)
Getting a list of price types using d7
$rsPrices = \Bitrix\Catalog\GroupTable::getList();
while($arPrice = $rsPrices->fetch()){
$PRICE_IDS[] = $arPrice['ID'];
}
Get the price and quantity of goods using D7
$dbPrice = \Bitrix\Catalog\Model\Price::getList([
"filter" => array(
"PRODUCT_ID" => $id,
"CATALOG_GROUP_ID" => 1
)]);
if ($arPrice = $dbPrice->fetch()) {
$price = $arPrice['PRICE'];
}
Add or update product price
$arFieldsPrice = Array(
"PRODUCT_ID" => $ID, // ID of the added product
"CATALOG_GROUP_ID" => 1, //Price type ID
"PRICE" => $item['price'], // price value
"CURRENCY" => !$currency ? "RUB" : $currency, //currency
);
// See if the price has been set for this product
$dbPrice = \Bitrix\Catalog\Model\Price::getList([
"filter" => array(
"PRODUCT_ID" => $item['ID'],
"CATALOG_GROUP_ID" => 1
)
]);
if ($arPrice = $dbPrice->fetch()) {
//If the price is set, then we update
$result = \Bitrix\Catalog\Model\Price::update($arPrice["ID"], $arFieldsPrice);
if ($result->isSuccess()){
echo "Updated the price of a product for a catalog item " . $item['ID'] . " Цена " . $item['price'] . PHP_EOL;
} else {
echo "Error updating the price of a product for a catalog item " . $item['ID'] . " Mistake" . $result->getErrorMessages() . PHP_EOL;
}
}else{
//If there is no price, then add
$result = \Bitrix\Catalog\Model\Price::add($arFieldsPrice);
if ($result->isSuccess()){
echo "Added the price of a product for a catalog item " . $item['ID'] . " Price " . $item['price'] . PHP_EOL;
} else {
echo "Error adding the price of a product for a catalog item " . $item['ID'] . " Mistake " . $result->getErrorMessages() . PHP_EOL;
}
}
Add quantity in warehouses to the product
$arFields = Array(
"PRODUCT_ID" => $id, //Item ID
"STORE_ID" => $storeId, //Warehouse ID
"AMOUNT" => $amount, //amount
);
CCatalogStoreProduct::Add($arFields);
You can read more about working with warehouses here Working with warehouses and the number of products in Bitrix D7
Updating the number of goods in warehouses
$rs = CCatalogStoreProduct::GetList(false, array(
'PRODUCT_ID'=> $id, // Product ID
'STORE_ID' => $storeId //Warehouse ID
));
while($ar_fields = $rs->GetNext())
{
// Update the value of the stock balance from the value of the quantitative accounting balance
$arFields = Array(
"PRODUCT_ID" => $id, //Item ID
"STORE_ID" => $storeId, //Warehouse ID
"AMOUNT" => $amount, //amount
);
CCatalogStoreProduct::Update($ar_fields['ID'], $arFields);
}
Updating inventory with D7
$existProduct = \Bitrix\Catalog\Model\Product::getCacheItem($arFields['ID'],true);
if(!empty($existProduct)){
\Bitrix\Catalog\Model\Product::update(intval($arFields['ID']),$arFields);
} else {
\Bitrix\Catalog\Model\Product::add($arFields);
}
Add or update the total quantity of goods (the "Available quantity" parameter)
CCatalogProduct::Update(
$ID, //ID of the added or updated item
array(
"QUANTITY" => $amount, //Quantity of goods
)
)
Adding an item to the reserve
$provider = new \Bitrix\Catalog\Product\CatalogProvider;
$resReserve = $provider->reserve(array(
$productId => ["PRODUCT_ID" => $PRODUCT_ID, "QUANTITY" => 10]
));
Reserve withdrawal
$provider = new \Bitrix\Catalog\Product\CatalogProvider;
$resReserve = $provider->reserve(array(
$productId => ["PRODUCT_ID" => $PRODUCT_ID, "QUANTITY" => -10 ]
));
Or more briefly:
\Bitrix\Catalog\Product\CatalogProvider::ReserveProduct(array(
$productId => ["PRODUCT_ID" => $PRODUCT_ID, "QUANTITY" => -10 ]
))
Get measure
and ratio
of the product
\Bitrix\Catalog\ProductTable::getCurrentRatioWithMeasure($arResult['ID'])
Changing measure
\CCatalogProduct::Update(13194, array('MEASURE' => 6));
Changing ratio
$db_measure = CCatalogMeasureRatio::getList(array(), $arFilter = array('PRODUCT_ID' => 13194), false, false);
while ($ar_measure = $db_measure->Fetch()) {
$new_measure = CCatalogMeasureRatio::update($ar_measure['ID'], array("RATIO" => 5));
}
Working with product properties
Getting the value of an individual product property in Bitrix using D7
class Product
{
private $id;
public function __construct($id)
{
if (empty($id)) {
throw new \Bitrix\Main\ArgumentNullException('id');
}
$this->id = $id;
}
public function getFields()
{
return \Bitrix\Iblock\ElementTable::getById($this->id)->fetch();
}
public function getProperty($code)
{
$fields = $this->getFields();
if ($fields) {
$iblock = \Bitrix\Iblock\Iblock::wakeUp($fields['IBLOCK_ID']);
$element = $iblock->getEntityDataClass()::getByPrimary($this->id, ['select' => [$code]])->fetchObject();
$property = $element->__call('get', [$code]);
}
return $property ? $property->getValue() : null;
}
}
Documentation Concept and architecture
Get the value of an individual property of a product or TP (old method)
$arFilter = Array("IBLOCK_ID"=>$kitOffer['IBLOCK_ID'], "ID"=>$kitOffer['ID']);
$arSelect = Array("SORT");
$res = CIBlockElement::GetList(Array(), $arFilter,false,false,$arSelect);
if ($ob = $res->GetNextElement()){;
$arFields = $ob->GetFields();
}
We receive goods in Bitrix using D7
//The filter is standard, you know how to use it
$elementIterator = \Bitrix\Iblock\ElementTable::getList([
'select' => [
'ID',
],
'filter' => [
'=IBLOCK_ID' => 20,
'!=ID' => array(1,2,3),//Array of product IDs to be skipped
]
]);
$elems = $elementIterator->fetchAll();
foreach ($elems as $element) {
// create a class object for work
$obElement = new CIBlockElement();
// update the element and make it inactive
$obElement->Update($element['ID'], Array("ACTIVE" => 'N'));
}
Get all the properties of the infoblock element
CIBlockElement::GetByID($arResult['ID'])->GetNextElement()->GetProperties()
Display of all product properties
CCatalogProduct::GetByIDEx($arElement['ID'])
Alternatively, you can use negative and positive sorting of properties. We can mark the properties needed for displaying with a positive sorting number, and unnecessary ones with a negative one, then the script for displaying properties will turn out to be quite compact
Go to the file result_modifier.php
and add to the very bottom:
//==============================================//
// Show all properties in DISPLAY_PROPERTIES //
//==============================================//
$arResult["DISPLAY_PROPERTIES"] = array();
foreach ($arResult["PROPERTIES"] as $pid => &$arProp)
{
// We do not display properties with sorting less than 0 for viewing (they will be service ones for us)
if ($arProp["SORT"] < 0)
continue;
if((is_array($arProp["VALUE"]) && count($arProp["VALUE"])>0) ||
(!is_array($arProp["VALUE"]) && strlen($arProp["VALUE"])>0))
{
$arResult["DISPLAY_PROPERTIES"][$pid] = CIBlockFormatProperties::GetDisplayValue($arResult, $arProp);
}
}
The solution was suggested by Left Ivan from the Bitrix forum
Getting the values of a product property of the list type
$property_enums = CIBlockPropertyEnum::GetList(Array("DEF"=>"DESC", "SORT"=>"ASC"), Array("IBLOCK_ID"=>15, "CODE"=>"BREND_INTERNAL"));
while($enum_fields = $property_enums->GetNext()){
echo $enum_fields["ID"]." - ".$enum_fields["VALUE"]."<br>";
}
Add a new value to a property of the list type
$ibpenum = new CIBlockPropertyEnum;
if($PropID = $ibpenum->Add(Array('PROPERTY_ID'=>$PROPERTY_ID, 'VALUE'=>'New Enum 1')))
echo 'New ID:'.$PropID;
or add a property to D7
\Bitrix\Main\Loader::includeModule('iblock');
$property = \CIBlockProperty::GetList(
[],
[
'IBLOCK_ID' => $iblockId,
'CODE' => $code'
]
)->Fetch();
$ibpenum = new \CIBlockPropertyEnum();
$valueId = $ibpenum->Add([
'PROPERTY_ID' => $property['ID'],
'VALUE' => $newValueText,
'XML_ID' => $newValueXmlId,
]);
if ((int) $valueId < 0) {
throw new \Exception('Unable to add a value');
}
Remove value from property of type list
CIBlockPropertyEnum::Delete(ID);
or remove the property on D7
if (! \Bitrix\Main\Loader::includeModule('iblock')) {
throw new \Bitrix\Main\LoaderException('Unable to load IBLOCK module');
}
$property = \CIBlockProperty::GetList([], ['IBLOCK_ID' => $iblockId, 'CODE' => $propertyCode])->Fetch();
if (! $property) {
throw new \Exception('No such property');
}
$query = \CIBlockPropertyEnum::GetList(
[],
["IBLOCK_ID" => $iblockId, "XML_ID" => 6, "PROPERTY_ID" => $property['ID']]
);
$value = $query->GetNext();
if (! $value) {
throw new \Exception('No such value');
}
$delete = \CIBlockPropertyEnum::delete($value['ID']);
if (! $delete) {
throw new \Exception('Error while deleting the property value');
}
Working with kits and kits
Getting the composition of sets and kits for the parent product
$arProducts = CCatalogProductSet::GetList(
array(), array( "TYPE" => array(1,2), "OWNER_ID" => $productID), false, false, array()
);
while($item = $arSets->Fetch() ){
if($item["OWNER_ID"]!=$item["ITEM_ID"] ){
$arComplects[] = $item["ITEM_ID"];
}
}
We get goods adjacent to the desired one in a set or set
\Bitrix\Main\Loader::includeModule('catalog');
$rItems = CCatalogProductSet::GetList(
array(),
array(
array(
'LOGIC' => 'OR',
'TYPE' => CCatalogProductSet::TYPE_GROUP,
'TYPE' => CCatalogProductSet::TYPE_SET
),
'ITEM_ID' => $arResult['ID']),
false,
false,
array('SET_ID', 'OWNER_ID', 'ITEM_ID', 'TYPE')
);
while ($item = $rItems->Fetch()) {
$arComplect[] = $item;
}
Get the required properties by the id array of trade offers
function getInform($arrOffersIDs){
$arrProductsIDs = array();
foreach($arrOffersIDs as $intElementID){
$mxResult = CCatalogSku::GetProductInfo(
$intElementID, 4
);
if (is_array($mxResult))
{
$arrProductsIDs[] = $mxResult["ID"];
}
}
return CCatalogSKU::getOffersList(
$arrProductsIDs,
$iblockID = 4,
$skuFilter = array('ID'=>$arrOffersIDs),
$fields = array('ID','NAME','DETAIL_PICTURE'),
$propertyFilter = array() );
}
print_r(getInform(array(45,87,98)));
Getting the required properties of all trade offers
function getInform($arrOffersIDs){
$arrProductsIDs = array();
foreach($arrOffersIDs as $intElementID){
$mxResult = CCatalogSku::GetProductInfo(
$intElementID,
4 //ID каталога с товаром
);
if (is_array($mxResult))
{
$arrProductsIDs[] = $mxResult["ID"];
}
}
return CCatalogSKU::getOffersList(
$arrProductsIDs,
$iblockID = 4, //ID каталога с товаром
$skuFilter = array(),
$fields = array('NAME','ID','DETAIL_PICTURE','PREVIEW_PICTURE', "DETAIL_PAGE_URL", "CML2_MANUFACTURER", "DETAIL_TEXT"),//Получаемые свойства
$propertyFilter = array() );
}
print_r(getInform(array(45,87,98)));
Working with discounts
We receive discounts and rules for working with the cart that were applied to the product
In the file result_modifier.php
of the sale.basket.basket
component, we get a list of discounts applied to the product
/** @var \Bitrix\Sale\BasketBase $basket */
$basket = (\Bitrix\Sale\Basket\Storage::getInstance(
\Bitrix\Sale\Fuser::getId(),
\Bitrix\Main\Context::getCurrent()->getSite()))
->getOrderableBasket();
$order = $basket->getOrder();
$discountApplyResults = $order->getDiscount()->getApplyResult(false);
Auto-generating rules for working with the cart, adding a coupon and applying it
Generation code
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
use Bitrix\Sale\Internals;
CModule::IncludeModule("catalog");
CModule::IncludeModule("iblock");
CModule::IncludeModule("sale");
global $APPLICATION;
$unixStart = strtotime(date("d.m.Y H:i:s"));
$unixEnd = $unixStart+43200; //12 часов
$xcount = 0;
$discountValue = rand(1,10); //Размер случайной скидки от 1 до 10 процентов
$Actions["CLASS_ID"] = "CondGroup";
$Actions["DATA"]["All"] = "AND";
$Actions["CLASS_ID"] = "CondGroup";
$Actions["CHILDREN"][0]["CLASS_ID"] = "ActSaleBsktGrp";
$Actions["CHILDREN"][0]["DATA"]["Type"] = "Discount";
$Actions["CHILDREN"][0]["DATA"]["Value"] = $discountValue;
$Actions["CHILDREN"][0]["DATA"]["Unit"] = "Perc";
$Actions["CHILDREN"][0]["DATA"]["All"] = "OR";
$DbParentEl = CIBlockElement::GetList(array(),array("SECTION_ID"=>array(10,11)),false,false,array("ID"));
while($ParentId = $DbParentEl->Fetch()){
// Array of products to which the discount will be applied
$Actions["CHILDREN"][0]["CHILDREN"][$xcount]["CLASS_ID"] = "CondIBElement";
$Actions["CHILDREN"][0]["CHILDREN"][$xcount]["DATA"]["logic"] = "Equal";
$Actions["CHILDREN"][0]["CHILDREN"][$xcount]["DATA"]["value"] = $ParentId["ID"];
$xcount++;
}
$Conditions["CLASS_ID"] = "CondGroup";
$Conditions["DATA"]["All"] = "AND";
$Conditions["DATA"]["True"] = "True";
$Conditions["CHILDREN"] = "";
// Array for creating the rule
$arFields = array(
"LID"=>"s1",
"NAME"=>$discountValue."% Скидки ".date("d.m.y"),
"CURRENCY"=>"RUB",
"ACTIVE"=>"Y",
"USER_GROUPS"=>array(1),
"ACTIVE_FROM"=>ConvertTimeStamp($unixStart, "FULL"),
"ACTIVE_TO"=>ConvertTimeStamp($unixEnd, "FULL"),
"CONDITIONS"=>$Conditions,
'ACTIONS' => $Actions
);
$ID = CSaleDiscount::Add($arFields); //Create a cart rule
$res = $ID>0;
if ($res) {
$codeCoupon = CatalogGenerateCoupon(); // Coupon generation
$fields["DISCOUNT_ID"] = $ID;
$fields["COUPON"] = $codeCoupon;
$fields["ACTIVE"] = "Y";
$fields["TYPE"] = 2;
$fields["MAX_USE"] = 0;
$dd = Internals\DiscountCouponTable::add($fields); //Create a coupon for this rule
if (!$dd->isSuccess())
{
$err = $dd->getErrorMessages();
}else{
echo 'Купон на скидку: '.$codeCoupon;
}
}else{
$ex = $APPLICATION->GetException();
echo 'Ошибка: '.$ex->GetString();
}
Call code
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
use Bitrix\Main\Loader;
use Bitrix\Sale\DiscountCouponsManager;
CModule::IncludeModule("sale");
$check = DiscountCouponsManager::isExist($_POST['coupon']);
if($check["ID"]>0){
DiscountCouponsManager::add($_POST['coupon']);
echo "ok";
}else{
echo 'There is no such coupon ';
}
Original solution
Working with taxes
To check the box "VAT is included in the price" when adding a product, use the OnProductAdd
event. But don’t forget that the event is outdated, for it to work you need to enable “Enable support for outdated events” in the trading catalog settings
AddEventHandler("catalog", "OnProductAdd", "OnProductAdd");
function OnProductAdd($ID, $FIELDS)
{
CCatalogProduct::Update($ID, ["VAT_INCLUDED"=>'Y']);
}
To mass change the value “VAT is included in the price” you can run an sql query
UPDATE b_catalog_product SET VAT_INCLUDED = 'Y', VAT_ID='1' WHERE VAT_INCLUDED = 'N'
Working with the template
Changing additional fields of trade offers in the product card "on the fly" using the example of PREVIEW_TEXT and DETAIL_TEXT
Go to result_modifier.php
and add:
foreach ($arResult['OFFERS'] as $pid => &$arProp)
{
$arResult['JS_OFFERS'][$pid]['PREVIEW_TEXT'] = $arProp['PREVIEW_TEXT'];//Add sentences to the resulting js array PREVIEW_TEXT
$arResult['JS_OFFERS'][$pid]['DETAIL_TEXT'] = $arProp['DETAIL_TEXT'];//We also add DETAIL_TEXT
}
First, we prepare containers where we will display new information in the template template.php
. In my case, it is assumed that PREVIEW_TEXT
will be loaded into a block with the offerShortDescription
class, DETAIL_TEXT
into a block with the offerFullDescription
class. Add blocks to the required place of the template.
<div class="offerShortDescription"><?=$actualItem['PREVIEW_TEXT']?></div>
<div class="offerFullDescription"><?=$actualItem['DETAIL_TEXT']?></div>
Go to the script.js
file, look for the changeInfo
function (~ 2463) and after the condition if (this.obSkuProps)
(~ 2553) add:
if(this.offers[index].PREVIEW_TEXT && this.offers[index].DETAIL_TEXT){
$('.offerShortDescription').html(this.offers[index].PREVIEW_TEXT);
$('.offerFullDescription').html(this.offers[index].DETAIL_TEXT);
}
Do not forget to handle cases when the description fields for trade offers are not filled. I suggest you do it yourself. Personally, I just added via result_modifier.php
to each trade offer additional fields in the description of the original product. This is not very pretty, but quite a working solution.
Remove the slide change when hovering over the preview in the slider
Go to script.js
and comment these lines (they appear twice on ~ 552 and ~ 607)
BX.bind(this.product.slider.ITEMS[j], 'mouseenter', BX.delegate(this.onSliderControlHover, this));
BX.bind(this.product.slider.ITEMS[j], 'mouseleave', BX.delegate(this.onSliderControlLeave, this));
The solution was applied on the Business v18.5.0 edition
Changing the active trade offer
Go to the file \bitrix\components\bitrix\catalog.element\templates\.default\result_modifier.php
We are looking for the line foreach ($ arResult ['OFFERS'] as $ keyOffer => $ arOffer)
- somewhere around line 405.
There is a line $ intSelected = $ keyOffer;
- this is $ intSelected
this is the ordinal (!) number of the trade offer selected by default. That is, the key of the element of the TP array, which are displayed for a particular product.
For example, I had a task to make active by default the offer ID of which was in $ _GET ['TP_ID']
.
Into the loop foreach ($ arResult ['OFFERS'] as $ keyOffer => $ arOffer)
insert:
if ($_GET['TP_ID']) {
if ($arOffer['ID']==$_GET['TP_ID']) {
$intSelected =$keyOffer;
}
}
Exchange with 1C
Crutch for recalculating balances and quantity of goods when updating from 1C
It will be useful if your reserves are not updated when importing from 1C, and your Bitrix edition does not have a button to clear reserves
//When you start importing from 1C, set the session variable
AddEventHandler(
'catalog',
'OnBeforeCatalogImport1C',
function ()
{
$_SESSION["1C_UPDATE"] = true;
}
);
//When updating the product, we check that the session variable is set and modify the data
AddEventHandler(
'catalog',
'OnProductUpdate',
function ($id, $arFields)
{
if (isset($_SESSION["1C_UPDATE"]) && $_SESSION["1C_UPDATE"])
{
$_SESSION["1C_UPDATE"] = false; //fight against looping so that it does not trigger itself. Since when calling CCatalogProduct :: Update, OnProductUpdat will be triggered again
CCatalogProduct::Update(
$id,
array(
'QUANTITY' => $arFields['QUANTITY'] + $add /*new quantity of goods*/,
'QUANTITY_RESERVE' => $arFields['QUANTITY_RESERVE'] + $add /*new quantity of reserved items*/
)
);
$_SESSION["1C_UPDATE"] = true;
}
}
);
//At the end of import from 1C, set the session variable
AddEventHandler(
'catalog',
'OnSuccessCatalogImport1C',
function ()
{
$_SESSION["1C_UPDATE"] = false;
}
);
I describe additional solutions to emerging exchange problems here Bitrix: exchange with 1C
Complex ORM selection to get all the necessary product data
Quite often there is a task to speed up a particular component, display products "as in the catalog" or get all the necessary data for a product element. This can be done both with the help of ordinary components and with the help of ORM.
Sharing an example of a complex case ORM query. On the client's site, a trifling selection of 20 products based on catalog.section
significantly loaded the system and worked out in an average of 1.5s. By combining all the selections into one query, I was able to speed up the selection to 0.4-0.5s without caching, and with caching of results up to 0.1s
This ORM query outputs:
- ordinary goods and goods with TP.
- the request receives several types of prices, including the base price
- query gets products from section including subsections
- query sorts products in random RAND order
- the request correctly generates the URL
- in this query, product properties are stored in a common table (important)
- the query summarizes all values of stock balances
$IBLOCK_ID = 29;
//Properties in the general table
$entityPropsSingle = \Bitrix\Main\Entity\Base::compileEntity(
'PROPS_COMMON',
[
'IBLOCK_ELEMENT_ID' => ['data_type' => 'integer'],
'IBLOCK_PROPERTY_ID' => ['data_type' => 'integer'],
'VALUE' => ['data_type' => 'string'] // there are also columns like VALUE_TYPE, VALUE_ENUM, VALUE_NUM, DESCRIPTION, but we don't need them here, so we omit
],
[
'table_name' => 'b_iblock_element_property', // general table with properties
]
);
//Get 20 random products in the section, including nested ones
$elements = \Bitrix\Iblock\ElementTable::getList([
'filter' => [
'SECTION.ID' => $arResult['SECTION']['PATH'][0]['ID'], // The section in which we are looking for including subsections.
'==LINK.ADDITIONAL_PROPERTY_ID' => NULL, // Basic binding of elements to groups.
'ACTIVE' => 'Y', // active elements.
'!=ID' => [$arResult['ID']], //We exclude an unnecessary element from the selection
'>base_price.PRICE' => 0, // Exclude products with zero base price
'=PROPS_335.IBLOCK_PROPERTY_ID' => 335, // id properties article
'=PRODUCT.AVAILABLE' => 'Y', // Available for purchase
//'>PRODUCT.QUANTITY' => 0, // There are leftovers
//'!=PRODUCT.TYPE' => 1, // Product with trade offers
],
'select' => [
'ID',
'NAME',
'IBLOCK_ID', //Url construction element
'IBLOCK_SECTION_ID', //Url construction element
'CODE', //Url construction element
"QUANTITY" => "PRODUCT.QUANTITY", //Quantity
//'QUANTITY_STORE', //Quantity in warehouses
"TYPE" => "PRODUCT.TYPE", //Product type
'DETAIL_PAGE_URL' => 'IBLOCK.DETAIL_PAGE_URL', //url mask
"PRICE_7" => "base_price.PRICE", //base price
"PRICE_8" => "prop_PRICE_8.PRICE", //Wholesale price
'ART_NUMBER' => 'PROPS_335.VALUE', //SKU property with ID 335
'AVAILABLE' => 'PRODUCT.AVAILABLE', //Availability
],
'order' => ['RAND' => 'ASC'],
//'group' => [ 'ID' ], //Grouping is not available with the specified sorting
'limit' => 20,
'runtime' => [
//Get section element
'LINK' => [
'data_type' => \Bitrix\Iblock\SectionElementTable::class,
'reference' => [
'=this.ID' => 'ref.IBLOCK_ELEMENT_ID',
],
'join_type' => 'inner',
],
//Get the parent section
'PARENT' => [
'data_type' => \Bitrix\Iblock\SectionTable::class,
'reference' => [
'=this.LINK.IBLOCK_SECTION_ID' => 'ref.ID',
],
'join_type' => 'inner',
],
//We connect the product table
'PRODUCT' => [
'data_type' => \Bitrix\Catalog\ProductTable::class,
'reference' => [
'=this.ID' => 'ref.ID',
],
'join_type' => 'left'
],
//Connecting the partition table
'SECTION' => [
'data_type' => \Bitrix\Iblock\SectionTable::class,
'reference' => [
'=this.PARENT.IBLOCK_ID' => 'ref.IBLOCK_ID',
'<=ref.LEFT_MARGIN' => 'this.PARENT.LEFT_MARGIN',
'>=ref.RIGHT_MARGIN' => 'this.PARENT.RIGHT_MARGIN',
],
'join_type' => 'inner',
],
//Get the value of the base price with ID 7
'base_price' => [
'data_type' => '\Bitrix\Catalog\PriceTable',
'reference' => [
'=this.ID' => 'ref.PRODUCT_ID'
],
'join_type' => 'inner'
],
//Get the value of the wholesale price with ID 8
'prop_PRICE_8' => [
'data_type' => '\Bitrix\Catalog\PriceTable',
'reference' => [
'=this.ID' => 'ref.PRODUCT_ID'
],
'join_type' => 'inner'
],
//Get property value 335 (Article)
'PROPS_335' => [
'data_type' => $entityPropsSingle->getDataClass(),
'reference' => [
'=this.ID' => 'ref.IBLOCK_ELEMENT_ID'
],
'join_type' => 'inner'
],
//We connect the table of warehouses
/*'STORE' => [
'data_type' => \Bitrix\Catalog\StoreProductTable::class,
'reference' => [
'=this.ID' => 'ref.PRODUCT_ID'
],
'join_type' => 'inner'
],
//Sum the balances in all available warehouses
'QUANTITY_STORE' => [
'data_type' => 'integer',
'expression' => ['sum(%s)', 'STORE.AMOUNT']
],*/
//Define a custom sort order "Random"
'RAND'=>array('data_type' => 'float', 'expression' => array('RAND()')),
],
'cache' => array( // Request cache. Reset can be done by \Bitrix\Iblock\ElementTable::getEntity()->cleanCache();
'ttl' => 86400, // Cache lifetime
'cache_joins' => true // Whether to cache selections with JOIN
),
]);
$elements = $elements->fetchAll();
foreach ($elements as &$element){
$element['DETAIL_PAGE_URL'] = \CIBlock::ReplaceDetailUrl($element['DETAIL_PAGE_URL'], $element, false, 'E');
$element['HAVE_OFFERS'] = $element['TYPE'] != 1 ? true : false;
$element['QUANTITY'] = $element['QUANTITY'] > $element['QUANTITY_STORE'] ? $element['QUANTITY'] : $element['QUANTITY_STORE'];
}
For properties in a separate table, you can use another declaration entityPropsSingle
//Properties in a separate table
$entityPropsSingle = \Bitrix\Main\Entity\Base::compileEntity(
'PROPS_SINGLE_IB29',
[
'IBLOCK_ELEMENT_ID' => ['data_type' => 'integer'],
'PROPERTY_335' => ['data_type' => 'integer'],
],
[
'table_name' => 'b_iblock_element_prop_s29',
]
);
Where the property is stored in an infoblock with ID 29
and the property itself has ID 335
Check for detail page in header
Bad example as it adds an extra database query on each hit, use as a last resort
public static function checkIsDetail() {
global $APPLICATION;
$IBLOCK_ID = 24;
if(!\CModule::IncludeModule("iblock")) return false;
$url = $APPLICATION->GetCurPage();
$code = array_pop(array_filter(explode( '/', $url)));
$rsSections = \CIBlockSection::GetList([], ['IBLOCK_ID' => $IBLOCK_ID, '=CODE' => $code]);
return ($rsSections->Fetch() !== false);
}
$is_section_page = self::checkIsDetail();
if(\CSite::InDir('/catalog/') && $APPLICATION->GetCurPage() !== '/catalog/' && $is_section_page){
//Detail page
}
Get all elements from a section and nested subsections
$SECTION_ID = 5;
$IBLOCK_ID = 8;
$section = \Bitrix\Iblock\SectionTable::getByPrimary($SECTION_ID, [
'filter' => ['IBLOCK_ID' => $IBLOCK_ID],
'select' => ['LEFT_MARGIN', 'RIGHT_MARGIN'],
])->fetch();
$arItems = \Bitrix\Iblock\ElementTable::getList([
'select' => ['ID', 'NAME', 'IBLOCK_ID'],
'filter' => [
'IBLOCK_ID' => $IBLOCK_ID,
'>=IBLOCK_SECTION.LEFT_MARGIN' => $section['LEFT_MARGIN'],
'<=IBLOCK_SECTION.RIGHT_MARGIN' => $section['RIGHT_MARGIN'],
],
]);
or so
$SECTION_ID = 5;
$IBLOCK_ID = 8;
$SECTION_IDS = getSubsections($SECTION_ID, $IBLOCK_ID);
function getSubsections(int $SECTION_ID, int $IBLOCK_ID): array
{
$SECTION_IDS = [];
$connection = \Bitrix\Main\Application::getConnection();
$sql = sprintf('SELECT cs.ID FROM %1$s AS ps
INNER JOIN %1$s AS cs
ON ps.LEFT_MARGIN <= cs.LEFT_MARGIN AND ps.RIGHT_MARGIN >= cs.RIGHT_MARGIN AND ps.IBLOCK_ID = cs.IBLOCK_ID
WHERE ps.ID = %2$d AND ps.IBLOCK_ID = %3$d',
\Bitrix\Iblock\SectionTable::getTableName(),
$SECTION_ID,
$IBLOCK_ID
);
$result = $connection->query($sql);
while ($section = $result->fetch()) {
$SECTION_IDS[] = $section['ID'];
}
return $SECTION_IDS;
}
$arItems = \Bitrix\Iblock\ElementTable::getList([
'select' => ['ID', 'NAME', 'IBLOCK_ID'],
'filter' => [
'IBLOCK_ID' => $IBLOCK_ID,
'IBLOCK_SECTION_ID' => $SECTION_IDS,
],
]);
Worth reading:
Comments