• Почему сбрасывается выборка товаров в catalog.section?

    @PetrPo
    itemashabanov, комплексный не кешируется и component_epilog не кешируется тоже. Компоненты слайдеров где находятся, в каком файле?
  • Почему сбрасывается выборка товаров в catalog.section?

    @PetrPo
    itemashabanov, ну у тебя видимо компонент в компоненте?
  • Почему сбрасывается выборка товаров в catalog.section?

    @PetrPo
    Это прямо какая-то глобальная дичь, врядли кто-то поймет что ты вообще сделал. Единственное что могу предложить - это в result_modifier добавить нужные данные/свойства в cache keys ссылка и в component_epilog перенести твою логику
  • Как связать данные в битрикс многие-ко-многим с разными типами сущностей Iblock, highload?

    @PetrPo
    sashavol, ну да, видимо про 'reference' => ['this.'.$crmQuoteFieldId => 'ref.ID'] я тупанул

    По твоему вопросу, что-то типа того
    // CRM_QUOTE_ID
    $crmQuoteId = 2;
    // XML_ID UF поля с CRM_QUOTE_ID в HL-блоке
    $crmQuoteFieldId = 'UF_ELEMENT';
    
    // IBLOCK PROPERTY
    $iblockUserProertyCode = 'USER_ID';
    $iblockUserProertyValue = 1;
    
    $result = $entity_data_class::getList(array(
    	'select' => ['ELEMENT_' => 'IBLOCK_ELEMENT', 'IBLOCK_PROPERTIES.CODE', 'IBLOCK_ELEMENT_PROPERTIES.VALUE'],
    	'order' => [],
    	'filter' => [$crmQuoteFieldId => $crmQuoteId, [
    		'LOGIC' => 'AND',
    		['IBLOCK_PROPERTIES.CODE' => $iblockUserProertyCode],
    		['IBLOCK_ELEMENT_PROPERTIES.VALUE' => $iblockUserProertyValue]
    	]],
    	//'group' => ['ELEMENT_DATE_CREATE'],
    	'runtime' => [
    		'IBLOCK_ELEMENT' => [
    			'data_type' => \Bitrix\Iblock\ElementTable::class,
    			'reference' => ['this.'.$crmQuoteFieldId => 'ref.ID'],
    			'join' => 'RIGHT'
    		],
    		'IBLOCK_ELEMENT_PROPERTIES' => [
    			'data_type' => \Bitrix\Iblock\ElementPropertyTable::class,
    			'reference' => ['this.IBLOCK_ELEMENT.ID' => 'ref.IBLOCK_ELEMENT_ID'],
    			'join' => 'RIGHT'
    		],
    		'IBLOCK_PROPERTIES' => [
    			'data_type' => \Bitrix\Iblock\PropertyTable::class,
    			'reference' => ['this.IBLOCK_ELEMENT_PROPERTIES.IBLOCK_PROPERTY_ID' => 'ref.ID'],
    			'join' => 'RIGHT'
    		],
    	]
    ))->fetchAll();
    
    print_r($result);

    1. Закомментил ключ group, если раскоментить то будут уникальные элементы по ID (теоретически))
    2. Стоит проверить скорость работы такого запроса
    3. Эту строку 'this.'.$crmQuoteFieldId => 'ref.ID' в примере я не стал править

    UPD
    Если известен ID свойства, а не его символьный код, то можно избавится от последнего блока в runtime
  • Как через ORM правильно сохранить значения в свойства инфоблока?

    @PetrPo
    tgarl, да, странно. У себя попробовал - норм работает, попробуй измени символьный код свойства, может еще какое-то событие срабатывает и чудесным образом в нем участвует MANAGER )
  • Как через ORM правильно сохранить значения в свойства инфоблока?

    @PetrPo
    Как связана твоя кастомная сущность ElementFeedbackFormTable и инфоблоки?
    В остальном NAME и FIO - это поля описанной сущности, поэтому методы setName и setFio подхватываются с помощью магического метода __call, а как ты создаешь поле MANAGER не понятно
  • Как получить свойства SKU через api битрикс?

    @PetrPo
    Павел Сидоров, да я проверил, взял твой вариант - не работает, поставил скобки - все ок
  • Можно ли сделать авторизацию по номеру телефона?

    @PetrPo
    ANTO, да забыл написать, такой нюанс из формы вместе с телефоном не должны уходить поля USER_LOGIN и
    USER_PASSWORD, если эти 2 поля попадают в $_REQUEST с правильными полями, то где-то в ядре происходит авторизация
  • При удалении DETAIL_PICTURE сервер зависает?

    @PetrPo
    xedema, смотря что ты имеешь ввиду. Через CIBlockElement::Update нельзя, но через sql всегда можно, но не нужно, потому что для файлов есть связь с отдельной таблицей
  • При удалении DETAIL_PICTURE сервер зависает?

    @PetrPo
    Видимо какое-то событие срабатывает. При update и setPropertyValuesEx разные события срабатывают, проверяй те что на update - OnIBlockElementUpdate и OnAfterIBlockElementUpdate
    $eventManager = \Bitrix\Main\EventManager::getInstance();
    $events = $eventManager->findEventHandlers('iblock', 'OnIBlockElementUpdate');
    print_r($events);
  • Можно ли сделать авторизацию по номеру телефона?

    @PetrPo
    ANTO, печально, я сам не делал, но думал что раз есть регистрация то есть и авторизация.

    Тогда руками пилить (компонент system.auth.authorize)
    1. В классе CUser есть 2 метода
    public static function SendPhoneCode($phoneNumber, $smsTemplate, $siteId = null)
    
    public static function VerifyPhoneCode($phoneNumber, $code)


    2. В шаблоне добавляешь input для телефона в стандартную форму авторизации и оборачиваешь в if
    <?if(!isset($arResult["SHOW_SMS_FIELD"]) || $arResult["SHOW_SMS_FIELD"] !== true):?>
    <form //..............
        <input type="text" required name="PHONE_NUMBER">
    </form>
    <?endif;?>


    3. Добавляешь в шаблон блок для ввода кода из смс
    <?if($arResult["SHOW_SMS_FIELD"] == true):?>
    
    	<?CJSCore::Init('phone_auth');?>
    	
    	<form method="post" action="<?=$arResult["AUTH_URL"]?>" name="regform">
    
    		<input type="hidden" name="SIGNED_DATA" value="<?=htmlspecialcharsbx($arResult["SIGNED_DATA"])?>" />
    
    		<div class="bx-authform-formgroup-container">
    			<div class="bx-authform-label-container"><span class="bx-authform-starrequired">*</span><?echo GetMessage("main_register_sms_code")?></div>
    			<div class="bx-authform-input-container">
    				<input type="text" name="SMS_CODE" maxlength="255" value="<?=htmlspecialcharsbx($arResult["SMS_CODE"])?>" autocomplete="off" />
    			</div>
    		</div>
    
    		<div class="bx-authform-formgroup-container">
    			<input type="submit" class="btn btn-primary" name="code_submit_button" value="<?echo GetMessage("main_register_sms_send")?>" />
    		</div>
    
    	</form>
    	<div id="bx_auth_error" style="display:none" class="alert alert-danger"></div>
    
    	<div id="bx_auth_resend"></div>
    
    	<script>
    	new BX.PhoneAuth({
    		containerId: 'bx_auth_resend',
    		errorContainerId: 'bx_auth_error',
    		interval: <?=$arResult["PHONE_CODE_RESEND_INTERVAL"]?>,
    		data:
    			<?=CUtil::PhpToJSObject([
    				'signedData' => $arResult["SIGNED_DATA"],
    			])?>,
    		onError:
    			function(response)
    			{
    				var errorNode = BX('bx_auth_error');
    				errorNode.innerHTML = '';
    				for(var i = 0; i < response.errors.length; i++)
    				{
    					errorNode.innerHTML = errorNode.innerHTML + BX.util.htmlspecialchars(response.errors[i].message) + '<br />';
    				}
    				errorNode.style.display = '';
    			}
    	});
    	</script>
    
    <?endif;?>


    4. Добавляешь обработчик, например в result_modifier (обработчик писал без проверки):
    $arResult["PHONE_CODE_RESEND_INTERVAL"] = \CUser::PHONE_CODE_RESEND_INTERVAL;
    
    $phoneNumber = $_REQUEST['PHONE_NUMBER'] ?? null;
    $signedData = $_REQUEST['SIGNED_DATA'] ?? null;
    
    if(!empty($phoneNumber))
    {
    	$smsTemplate = 'SMS_USER_RESTORE_PASSWORD'; // только указываешь свой шаблон (создаешь сначала в админке)
    	
    	$result = \CUser::SendPhoneCode($phoneNumber, $smsTemplate, SITE_ID);
    	
    	if($result->isSuccess()) {
    		$arResult['SIGNED_DATA'] = \Bitrix\Main\Controller\PhoneAuth::signData([
    			'phoneNumber' => $phoneNumber,
    			'smsTemplate' => $smsTemplate
    		]);
    		
    		$arResult["SHOW_SMS_FIELD"] = true;
    	}
    	else {
    		$arResult['ERROR_MESSAGE'] = $result->getErrorMessages();
    	}
    	
    	
    	if(!empty($signedData)) {
    		$arResult["SIGNED_DATA"] = $signedData;
    		$arResult["SHOW_SMS_FIELD"] = true;
    	}
    	
    	// verify phone code
    	$isNeedVerifySmsCode = $_SERVER["REQUEST_METHOD"] == "POST" && $_REQUEST["code_submit_button"] <> '' && !$USER->IsAuthorized() && !empty($signedData);
    	
    	if($isNeedVerifySmsCode)
    	{
    		$smsCode = $_REQUEST['SMS_CODE'];
    		
    		if(($params = \Bitrix\Main\Controller\PhoneAuth::extractData($signedData)) !== false)
    		{
    			if(($userId = CUser::VerifyPhoneCode($params['phoneNumber'], $smsCode)))
    			{
    				$USER->Authorize($userId);
    			}
    			else
    			{
    				$arResult['ERROR_MESSAGE'] = 'Error verify code'; // здесь сам ошибку придумай
    				$arResult["SHOW_SMS_FIELD"] = true;
    				$arResult["SMS_CODE"] = $smsCode;
    				$arResult["SIGNED_DATA"] = $signedData;
    			}
    		}
    	}
    }


    Вроде так, но могут быть ошибки, если что пиши))
  • Почему автоматически не создается событие при заполнении веб-формы Bitrix?

    @PetrPo
    А если в консоли написать, выведет инфу по форме?
    \Bitrix\Main\Loader::includeModule('form');
    
    $formId = 19;
    $form = CForm::GetByID($formId)->Fetch();
    
    print_r($form);
  • Как избежать спагетти-кода в result_modifier?

    @PetrPo
    Александр Маджугин, полностью согласен и также согласен с суждением
    ничего плохого в процедурном подходе, если все идеи реализованы в виде простых, коротких и минимально функциональных чистых статических методах

    тем более что битрикс так и так заставляет в какой-то момент писать процедурный код, например в событиях
  • Как избежать спагетти-кода в result_modifier?

    @PetrPo
    ragnar_ok, фабрика и фабричный метод - это разные вещи.
    Фабрика - это так скажем некоторая идиома ООП программирования, фабричный метод - это паттерн ООП.
    Эти вещи 99% людей путают (я тоже долго путал), но тем не менее разница достаточна явная:

    Рассмотрим два класса
    1.
    abstract class AbstractComponentFactory implements ComponentFactoryInterface
    {
        public function supports(string $component): bool
        {
            return in_array($component, $this->getSupportedComponents());
        }
    
        abstract protected function getSupportedComponents(): array;
    }


    2.
    final class ComponentFactory extends AbstractComponentFactory
    {
        public function create(string $component): ComponentInterface
        {
            switch ($component) {
                case 'news':
                    return new NewsComponent();
    
                default:
                    throw new InvalidArgumentException(sprintf('No component supports the given name "%s".', $component));
            }
        }
    
        protected function getSupportedComponents(): array
        {
            return ['news'];
        }
    }


    1. В классе AbstractComponentFactory методы supports и getSupportedComponents не совершают никаких действий над объектами, по факту это неоправданно усложненный if
    2. Т.е. для дочернего класса ComponentFactory его родительский класс AbstractComponentFactory - это такое себе непонятное зачем наследование, не несущее в себе логического профита, т.е. остается метод create и использованный в нем оператор switch/case однозначно трактует его как фабрику (не фабричный метод!!!)
    3. Исходя из выше сказанного абстракция - интерфейс ComponentFactoryInterface и абстрактный класс AbstractComponentFactory - это абстракция ради абстракции

    Т.е. в рамках фабрики ничего не изменится, если оставить просто
    final class ComponentFactory
    {
        public function create(string $component): ComponentInterface
        {
            switch ($component) {
                case 'news':
                    return new NewsComponent();
    
                default:
                    throw new InvalidArgumentException(sprintf('No component supports the given name "%s".', $component));
            }
        }
    }


    Остальное выглядит, как неоправданное усложнение для восприятия, возможно в реальном формате разработки какой-то профит от этого есть, но так сходу выглядит бесполезным

    Ну и собственно прилагаю тот самый фабричный метод (после реализации которого, его бесполезность в данном контексте становится очевидной)
    abstract class AbstractComponentFactory
    {
        public function modifyResult(array $result): array
        {
            $component = $this->create();
            
            return $component->modifyResult($result);
        }
    
        abstract protected function create(): ComponentInterface;
    }
    
    class NewsListComponentFactory extends AbstractComponentFactory
    {
        protected function create(): ComponentInterface
        {
            return new NewsListComponent();
        }
    }
    
    class NewsDetailComponentFactory extends AbstractComponentFactory
    {
        protected function create(): ComponentInterface
        {
            return new NewsDetailComponent();
        }
    }
    
    class NewsListComponent implements ComponentInterface
    {
        public function modifyResult(array $result): array
        {
            return [];
        }
    }
    
    class NewsDetailComponent implements ComponentInterface
    {
        public function modifyResult(array $result): array
        {
            return [];
        }
    }
    
    (new NewsListComponentFactory())->modifyResult($arResult);


    UPD
    Добавлю, что все это не отвечает на твой изначальный вопрос "Как избежать спагетти-кода в result_modifier?", а отвечает на вопрос кто? где? как? должен создавать объекты.
    А ответ на твой вопрос - это то что я написал в своем первом комменте или как предложил человек в ответах, что собственно является аналогией моего ответа. Все остальное на твоей совести, помни только что каждый созданный класс усложняет понимание твоих намерений для других программистов
  • Как избежать спагетти-кода в result_modifier?

    @PetrPo
    паттерн "Фабричный метод" - это ООП паттерн, предполагает наличие интерфейса выполняющего какую-то задачу с объектами классов одного типа, но когда заранее неизвестно с каким именно объектом - это определяют его подклассы. Как это клеется с result_modifier остается только догадыватьься, где такую чушь вычитал?

    Делай примерно также как делают битриксоиды, большая часть логики скрыта в модуле, а в result_modifier вызываются нужные методы, например можно посмотреть шаблон catalog.section bootstrap_v4
    $component = $this->getComponent();
    $arParams = $component->applyTemplateModifications();

    Метод applyTemplateModifications определен в модуле iblock - /bitrix/modules/iblock/lib/component/base.php
    Хоть здесь и присваивается возвращаемое знчение в переменную $arParams, но по факту он также модифицирует $arResult

    Т.е. делаешь свой модуль, в котором описываешь свои кастомизации в классах и потом вызываешь методы в result_modifier, будет ли этот код переиспользуемым во всех компонентах или для каждого индивидуально, будет он в ООП стиле написан или это будет несколько методов в процедурном стиле - зависит уже от тебя, но по крайней мере не будет размазано по всему проекту
  • Как вывести из инфоблока определенные разделы и вложенные элементы в виде дерева?

    @PetrPo
    1. Делаешь выборку разделов по ИД - $idSections = array(104,105,106); // ID разделов
    А элементы получаешь все, без ограничений. Я так понимаю элементы тоже нужны только для указанных разделов, тогда в массив $arFilter для CIBlockElement::GetList стоит добавить это ограничение
    $arFilter = array("IBLOCK_ID" => $IBLOCK_ID, "ACTIVE_DATE" => "Y", "ACTIVE" => "Y", 'IBLOCK_SECTION_ID' => $idSections);

    И вот это дело array("nPageSize" => 100) тоже убери, хитрец)) - поставь false

    2. Лично от меня пожелание, если пишешь в процедурном стиле, когда делаешь выборку собирай массив с ключами = ID, например вместо
    while ($sectRes = $dbResSect->GetNext()) {
    	$arSections[] = $sectRes;
    }

    делай
    while ($sectRes = $dbResSect->GetNext()) {
    	$arSections[$sectRes['ID']] = $sectRes;
    }

    дальше поймешь почему....

    3. Все дело в коде под комментариями
    // Находим все разделы элемента и собираем их в массив
    // Собираем  массив из разделов и элементов

    Запросы в цикле - это плохо, больше элементов в цикле = больше запросов

    Итого меняем три блока
    1. Добавляем ID в ключи
    // Получаем разделы и собираем их в массив
        while ($sectRes = $dbResSect->GetNext()) {
            $arSections[$sectRes['ID']] = $sectRes;
        }


    2. Добавляем разделы в фильтр, убираем nPageSize и добавляем ключи = ID
    // Формируем массив элементов по фильтру
    //......
    $arFilter = array("IBLOCK_ID" => $IBLOCK_ID, 'IBLOCK_SECTION_ID' => $idSections, "ACTIVE_DATE" => "Y", "ACTIVE" => "Y");
      $res = CIBlockElement::GetList(array("ID" => "ASC"), $arFilter, false, false, $arSelect);
      while ($el = $res->Fetch()) {
        $arResult["ITEMS"][$el['ID']] = $el;
      }


    3. Меняем проблемный блок, т.е. все что ниже комментария // Находим все разделы элемента и собираем их в массив
    $elementIdsBySectionIds = [];
    $iterator = \Bitrix\Iblock\SectionElementTable::getList([
    	'select' => ['IBLOCK_SECTION_ID', 'IBLOCK_ELEMENT_ID'],
    	'filter' => ['IBLOCK_SECTION_ID' => $idSections]
    ]);
    
    while($row = $iterator->fetch()) {
    	$elementIdsBySectionIds[$row['IBLOCK_SECTION_ID']][$row['IBLOCK_ELEMENT_ID']] = $row['IBLOCK_ELEMENT_ID'];
    }
    
    // здесь как раз пригодятся ключи = ID
    foreach($elementIdsBySectionIds as $sectionId => $elementIds) {
    	foreach($elementIds as $elementId) {
    		if(isset($arResult["ITEMS"][$elementId])) {
    			$arSections[$sectionId]['ITEMS'][] = $arResult["ITEMS"][$elementId];
    		}
    	}
    }
    
    $arResult = $arSections;


    Весь код

    CModule::IncludeModule('iblock'); //Подключаем модуль "Информационные блоки" 
      // Параметры
      $IBLOCK_ID = 7; // ID ифноблока
      $idSections = array(104,105,106); // ID разделов
      
        // Формируем список разделов по фильтру
        $dbResSect = CIBlockSection::GetList(
            array("SORT" => 'asc'), // Параметры сортировки
            array(
                "IBLOCK_ID" => $IBLOCK_ID, // ID инфоблока
                "ID" => $idSections, // Массив разделов
            ),
            false,
            array("ID", "NAME", "UF_ITEMPROP") // Выбираем необходимые поля разделов.
        );
    
        // Получаем разделы и собираем их в массив
        while ($sectRes = $dbResSect->GetNext()) {
            $arSections[$sectRes['ID']] = $sectRes;
        }
    
    
      // Формируем массив элементов по фильтру
      $arResult = [];
      $arSelect = Array(
        "ID",
        "NAME",
        "IBLOCK_ID",
        "IBLOCK_SECTION_ID",
        "PROPERTY_FILE", 
        "PROPERTY_ITEMPROP",
      );
    
      $arFilter = array("IBLOCK_ID" => $IBLOCK_ID, 'IBLOCK_SECTION_ID' => $idSections, "ACTIVE_DATE" => "Y", "ACTIVE" => "Y");
      $res = CIBlockElement::GetList(array("ID" => "ASC"), $arFilter, false, false, $arSelect);
      while ($el = $res->Fetch()) {
        $arResult["ITEMS"][$el['ID']] = $el;
      }
    
    
    $elementIdsBySectionIds = [];
    $iterator = \Bitrix\Iblock\SectionElementTable::getList([
    	'select' => ['IBLOCK_SECTION_ID', 'IBLOCK_ELEMENT_ID'],
    	'filter' => ['IBLOCK_SECTION_ID' => $idSections]
    ]);
    
    while($row = $iterator->fetch()) {
    	$elementIdsBySectionIds[$row['IBLOCK_SECTION_ID']][$row['IBLOCK_ELEMENT_ID']] = $row['IBLOCK_ELEMENT_ID'];
    }
    
    foreach($elementIdsBySectionIds as $sectionId => $elementIds) {
    	foreach($elementIds as $elementId) {
    		if(isset($arResult["ITEMS"][$elementId])) {
    			$arSections[$sectionId]['ITEMS'][] = $arResult["ITEMS"][$elementId];
    		}
    	}
    }
    
    $arResult = $arSections;



    UPD
    На моей выборке, примерно 15 товаров, стало меньше на 53 запроса к БД