Чиним ошибку 400 Bad Request с помощью mod_rpaf у BitrixEnv

, Михаил

Воспользуемся замечательнейшим модулем https://github.com/gnif/mod_rpaf который протестирован и успешно эксплуатируется в на многих серверах

Собираем модуль:

yum groupinstall "Development Tools"
yum install httpd-devel
wget -O /tmp/mod_rpaf.c https://raw.githubusercontent.com/gnif/mod_rpaf/stable/mod_rpaf.c
apxs -c -i /tmp/mod_rpaf.c

Далее создаём /etc/httpd/bx/custom/rpaf.conf

LoadModule              rpaf_module modules/mod_rpaf.so
RPAF_Enable             On
RPAF_ProxyIPs           127.0.0.1 Ваш.IP.Сервера
RPAF_SetHostName        On
RPAF_SetHTTPS           On
RPAF_SetPort            On
RPAF_ForbidIfNotProxy   Off

И не забываем выключить remoteip в файле /etc/httpd/conf.modules.d/00-base.conf и удалить файл /etc/httpd/bx/conf/mod_rpaf.conf

На выходе получим

[root@divasoft ~]# apachectl -M | grep -E 'remoteip|rpaf'
 rpaf_module (shared)

Перезапускаем httpd


В файле /etc/nginx/bx/site_enabled/ssl.s1.conf меняем строки

proxy_set_header   Host   $host:443;
proxy_set_header   HTTPS   YES;

На

proxy_set_header   Host   $host;
proxy_set_header   X-Forwarded-Proto   $scheme;
proxy_set_header   X-Forwarded-Port   $server_port;

В файле /etc/nginx/bx/site_enabled/s1.conf меняем строки

proxy_set_header   Host   $host:80;

На

proxy_set_header   Host   $host;
proxy_set_header   X-Forwarded-Proto   $scheme;
proxy_set_header   X-Forwarded-Port   $server_port;

Перезапускаем nginx

Итоговый блок будет выглядеть как

proxy_set_header   X-Real-IP   $remote_addr;
proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
proxy_set_header   Host   $host;
proxy_set_header   X-Forwarded-Proto   $scheme;
proxy_set_header   X-Forwarded-Port   $server_port;

Приём заказов в 1С из Битрикс с нескольких сайтов

, Михаил

Существует проблема с выгрузкой заказов с 2 разных сайтов.

Если сайты являются копиями друг друга, т.е. сначала был сделан сайт 1, затем скопирован и развернут с базой, и на его основе с незначительными доработками был сделан сайт 2. Для 1 сайта уже была подключены импорты заказов из Битрикс, и импорт номенклатуры в Битрикс. Для второго сайта сделали идентичные обмены, также путем копирования обменов, и изменения параметров отбора и древа групп в обмене товарами, и без изменений обмена заказами, кроме url адреса. При включении на автомате обоих импортов заказов происходит проблема дублирования заказа. Со 2 сайта приходит заказ, и полностью удаляет документы старого заказа в 1С, полученного с первого сайта. При сравнении и проверке заказов выяснилось, что заказы с идентичными ID.

Самый быстрый - меняем стартовое значение поля ID
Оно int(11), значит у нас есть в запасе от (-2147483648 до 2147483647)

Для первого сайта оставляем всё как есть.
Для второго:
ALTER TABLE `b_sale_order` AUTO_INCREMENT = 100000000; Для третьего:
ALTER TABLE `b_sale_order` AUTO_INCREMENT = 200000000;

Для пользователя - задействуем нумератор заказов, что бы не боялся больших цифр.

Чиним fastdownload у nginx в bitrixenv для Яндекс Cloud Storage

, Михаил

Решил все загружаемые файлы в наш Битрикс24 отправить в Яндекс.Облако

Но файлы просто не скачивались, после долгой отладки нашёл как отдаются файлы, копнул глубже... ядро Битрикс, используя заголовок x-accel-redirect, делает всю эту магию с внешними хранилищами.
Находим секцию в /etc/nginx/bx/conf/bitrix_general.conf

# Use nginx to return static content from s3 cloud storage
# /upload/bx_cloud_upload/...amazonaws.com/
location ^~ /upload/bx_cloud_upload/ {

И туда добавляем

    location ~ ^/upload/bx_cloud_upload/(http[s]?)\.([^/:\s]+)\.storage\.yandexcloud\.net/([^\s]+)$ {
        internal;
        resolver 8.8.8.8;
        proxy_method GET;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Server $host;
        #proxy_max_temp_file_size 0;
        proxy_pass $1://$2.storage.yandexcloud.net/$3;
    }

Как в Битриксе, на странице оформления заказа, раскрыть все блоки

, Михаил

Берём за основу стандартный .default шаблон компонента sale.order.ajax, работаем в файле order_ajax.js

  1. Включаем редактирование блоков «Регион» и «Пользователь»: комментируем строки /*if (this.activeSectionId !== this.regionBlockNode.id) this.editFadeRegionContent(this.regionBlockNode.querySelector('.bx-soa-section-content')); if (this.activeSectionId != this.propsBlockNode.id) this.editFadePropsContent(this.propsBlockNode.querySelector('.bx-soa-section-content'));*/
  2. Удаляем кнопки «Далее» и «Назад»: комментируем строки /*node.appendChild( BX.create('DIV', { props: {className: 'row bx-soa-more'}, children: [ BX.create('DIV', { props: {className: 'bx-soa-more-btn col-xs-12'}, children: buttons }) ] }) );*/
  3. Все блоки раскрываем: меняем строку var active = section.id == this.activeSectionId На строку var active = true,
  4. Удаляем обработчики при клике на заголовки: комментируем строки /*BX.unbindAll(titleNode); if (this.result.SHOW_AUTH) { BX.bind(titleNode, 'click', BX.delegate(function(){ this.animateScrollTo(this.authBlockNode); this.addAnimationEffect(this.authBlockNode, 'bx-step-good'); }, this)); } else { BX.bind(titleNode, 'click', BX.proxy(this.showByClick, this)); editButton = titleNode.querySelector('.bx-soa-editstep'); editButton && BX.bind(editButton, 'click', BX.proxy(this.showByClick, this)); }*/
  5. Удаляем ссылки «Изменить»: в конец функции editOrder добавляем код var editSteps = this.orderBlockNode.querySelectorAll('.bx-soa-editstep'), i; for (i in editSteps) { if (editSteps.hasOwnProperty(i)) { BX.remove(editSteps[i]); } }

Находим файл из Битрикс24.Диск по ID

, Михаил

Нельзя просто так взять и получить настоящий файл для дальнейшей манипуляции с ним. В api такого нет. ORM d7 в помощь


<?php 
function getRealFileFromDiskById($diskId) {
	    \Bitrix\Main\Loader::includeModule('disk');
	    $resObjects = \Bitrix\Disk\Internals\ObjectTable::getList([
	            'select' => ['NAME''FILE_ID'],
	            'filter' => [
	                '=ID' => $diskId,
	            ]
	    ]);
	    if ($arObject $resObjects->fetch()) {
		        $arObject['PATH'] = CFile::GetPath($arObject['FILE_ID']);
		        $arObject['FULL_PATH'] = $_SERVER['DOCUMENT_ROOT'].$arObject['PATH'];
		        return $arObject;
		}
	    return false;
	}

?>

A.I.Divasoft - Пишем бота для Slack, создаём репозиторий в Bitbucket и оповещаем о коммитах в группе Битрикс24

, Михаил

Продолжаю оптимизировать рабочий процесс. Теперь создание репозитория, привязка оповещений в Битрикс24 из Bitbucket, к выбранной группе становится гораздо быстрее!

A.I.Divasoft - репозиторий

A.I.Divasoft - Пишем бота для Slack, создаём связанный контакт с компанией, генерируем лид/сделку, создаём или находим группу, приглашаем в группу

, Михаил

Куча действий связанных с новым клиентом, за пару кликов - новая функция нашего бота!

A.I.Divasoft - приглашаем клиента

A.I.Divasoft - Пишем бота для Slack, добавляем задачи в Битрикс24

, Михаил

Первым делом научили делать задачу в наш Битрикс24!

Текст задачи может формироваться сразу из сообщения в чате, постановщик - тот кто пишет, ответственный запоминается, как и выбор проектов.

И очень важная фишка - добавляем в наблюдатели к задаче всех пользователей экстранет относящихся к этой группе, либо без наблюдателей.

A.I.Divasoft - ставим задачу в Битрикс 24

Устанавливаем заголовки в элементе/разделе из SEO модуля Битрикс

, Михаил

Добавляем в result_modifier.php


<?php 

// /local/templates/.default/components/bitrix/catalog.section/.default/result_modifier.php
// для элемента используем $ipropValues = new \Bitrix\Iblock\InheritedProperty\ElementValues($IBLOCK_ID,$ELEMENT_ID);
$ipropValues = new \Bitrix\Iblock\InheritedProperty\SectionValues($arResult['IBLOCK_ID'], $arResult['ID']);
$SEO $ipropValues->getValues();
$cp $this->__component;
if (is_object($cp)) {
	    $cp->arResult['SEO'] = $SEO;
	    $cp->SetResultCacheKeys(array('SEO'));
	    $arResult['SEO'] = $cp->arResult['SEO'];
	}
?>

Добавляем в component_epilog.php


<?php 

// /local/templates/.default/components/bitrix/catalog.section/.default/component_epilog.php
global $APPLICATION;
// DIVASOFT
if ($arResult['SEO']['SECTION_META_TITLE']) {
	    $APPLICATION->SetPageProperty("title"$arResult['SEO']['SECTION_META_TITLE']);
	}
if ($arResult['SEO']['SECTION_META_DESCRIPTION']) {
	    $APPLICATION->SetPageProperty("description"$arResult['SEO']['SECTION_META_DESCRIPTION']);
	}
if ($arResult['SEO']['SECTION_META_KEYWORDS']) {
	    $APPLICATION->SetPageProperty("keywords"$arResult['SEO']['SECTION_META_KEYWORDS']);
	}
if ($arResult['SEO']['SECTION_PAGE_TITLE']) {
	    $APPLICATION->SetTitle($arResult['SEO']['SECTION_PAGE_TITLE']);
	}
?>

Обновляем BitrixEnv до 7.3.4

, Михаил

Перед всеми манипуляциями делаем бэкап/снапшот.

Обновляем ядро/модули Bitrix до последних стабильных обновлений или бета-версий.

Потом обязательно добавляем репозиторий, нужен для обновления mysql до версии 5.7, или будете откатываться при потере базы во время обновления

yum install https://repo.percona.com/yum/percona-release-latest.noarch.rpm

Или временный фикс /etc/yum.repos.d/percona-release.repo - отключаем проверку ключа

[percona-release-noarch]
name = Percona-Release YUM repository - noarch
baseurl = http://repo.percona.com/release/...
enabled = 1
gpgcheck = 0
gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Percona

Повышаем версию php с 5.6 до версии 7.1

Задаем пароль root mysql через меню, или вручную в файле /root/.my.cnf

Повышаем версию mysql с 5.5 до версии 5.7

Диверсия на сайте клиентов от сторонних разработчиков

, Михаил

Пишет нам один заказчик, что сайт начал долго открываться.
Заходим - действительно, ~17 секунд отдаётся контент.
Начали анализировать ситуацию, в одном из включаемых файлов видим это:

sleep15.png

sleep(15); - команда которая говорит серверу - подожди просто 15 секунд, потом делай свои дела дальше.

Это даже не смешно, "случайно" такую команду не напишешь, за 15 лет разработки я применил эту команду только 1 раз, и то, в сервисном скрипте, который по крону делает что то.

Так что это 100% диверсия. И те разработчики, если такое сделали, не известно, на что ещё способны. Как минимум 2 бэкдора уже нашёл.

Возвращаем информацию об удаляющихся при обмене с 1С оплате и доставке в админке Битрикса

, Михаил

Продолжение этой проблемы. Когда менеджер работает одновременно в админке и в 1С, и когда в 1С выключена загрузка отгрузок и оплат - Битрикс убирает из заказа эти данные. Возвращаем информацию в заказ, и список заказов.


<?php 

// local/php_interface/init.php
// Добавляем информацию внутри заказа
\Bitrix\Main\EventManager::getInstance()->addEventHandler('sale''onSaleAdminOrderInfoBlockShow', ['DivasoftFixSyncOrderInfo''onSaleAdminOrderInfoBlockShow']);
// Заполняем колонки в списке заказов
\Bitrix\Main\EventManager::getInstance()->addEventHandler("main",  "OnAdminListDisplay", ['DivasoftFixSyncOrderInfo''onAdminListDisplay']);
\Bitrix\Main\EventManager::getInstance()->addEventHandler("main",  "OnAdminSubListDisplay", ['DivasoftFixSyncOrderInfo''onAdminListDisplay']);
class DivasoftFixSyncOrderInfo {
	    static function getSystemDeliveryNameByOrderD7($order) {
		        $shipmentCollection $order->getShipmentCollection();
		        $shipmenName "Не выбрана";
		        $systemShipmentItemCollection $shipmentCollection->getSystemShipment()->getShipmentItemCollection();
		        foreach ($shipmentCollection as $obShipment) {
			            if ($obShipment->isSystem()) {
				                $arShipment $obShipment->getFields()->getValues();
				                $shipmenName $arShipment['DELIVERY_NAME'];
				}
			}
		        return $shipmenName;
		}
	    static function getSystemPaymentNameByOrderD7($order) {
		        // getSystemPayment такого метода нет, запросим информацию по тому что есть
		        $paySystemService = \Bitrix\Sale\PaySystem\Manager::getObjectById($order->getField('PAY_SYSTEM_ID'));
		        $payName $paySystemService->getField("NAME");
		        return $payName;
		}
	    function onSaleAdminOrderInfoBlockShow(\Bitrix\Main\Event $event) {
		        $order $event->getParameter("ORDER");
		        $shipmenName self::getSystemDeliveryNameByOrderD7($order);
		        $payName self::getSystemPaymentNameByOrderD7($order);
		        return new \Bitrix\Main\EventResult(
		            \Bitrix\Main\EventResult::SUCCESS, array(
		            array('TITLE' => 'Доставка:''VALUE' => $shipmenName'ID' => 'dvs_system_shipment'),
		            array('TITLE' => 'Оплата:''VALUE' => $payName'ID' => 'dvs_system_payment'),
		            ), 'sale'
		        );
		}
	    function onAdminListDisplay(&$list) {
		        if ($list->table_id == "tbl_sale_order") {
			            foreach ($list->aRows as &$row) {
				                foreach ($row->aFields as $key => &$val) {
					                    $order false;
					                    if ($key == "DELIVERY") {
						                        if (!$val['view']['value']) {
							                            if (!$order) {
								                                $order = \Bitrix\Sale\Order::load($row->arRes['ID']);
								}
							                            $val['view']['value'] = self::getSystemDeliveryNameByOrderD7($order);
							}
						}
					                    if ($key == "PAY_SYSTEM") {
						                        if (!$val['view']['value']) {
							                            if (!$order) {
								                                $order = \Bitrix\Sale\Order::load($row->arRes['ID']);
								}
							                            $val['view']['value'] = self::getSystemPaymentNameByOrderD7($order);
							}
						}
					}
				}
			}
		}
	}
?>

Опасность архивирования заказов в Битрикс, или почему увеличиваются остатки на складе

, Михаил

Столкнулись с проблемой. Остатки у некоторых товаров сами увеличиваются. Резервирование выключено, заказы не отменяются.

В итоге, обратил внимание на "Архивирование заказов". Долго описывал проблему, в итоге тех.поддержка ответила:

Посмотрели по заказу у отгрузки стоит RESERVED=Y, но сам заказ и отгрузки по нему не отгружались, поэтому перед архивацией произошло разрезервация остатков. Такая же логика и при удалении заказа, если отгрузка не отгружалась, то резервация возвращается обратно. А архивирование по своему поведению совпадает с удалением.
Это штатное поведение функционала резервирования и архивирования.

Штатное поведение Карл! Выключаем архивацию, радуемся правильным остаткам на складе.

Работа с чужим кодом на сайте. Наследие фриланса.

, Михаил

Вечная проблема любого владельца нетипичного сайта - никто не хочет браться за доработку сайтов с большим объемом рукописного кода, поиск и исправление ошибок.

Почему разработчики не любят работать с чужим кодом?

Это нежелание вызвано в основном трудностью в прогнозировании сроков и стоимости работ. Это - то же самое, что сказать таксисту: «Отвези меня в Тридевятое царство». Таксист спрашивает: «Где это?». «Не знаю» - отвечаете вы, и при этом требуете от таксиста озвучить сроки и стоимость.

Разработчики находятся в аналогичном, положении получая рукописный незакомментированный код. Нужно найти ошибки, изучить код, документировать его и только после этого решать задачу. Из-за частых и неизвестных ошибок в коде, при составлении коммерческого предложения, очень высок риск недооценить задачу.

При этом помним, что в России крайне низкое качество php кода. Все высококлассные программисты работают в крупных веб-студиях на высоких зарплатах. На фрилансе, их просто нет.

Кроме того, фрилансеры/студенты разрабатывают сайты на готовых бесплатных фреймворках, которые не обновлялись несколько лет, поэтому самописный код может быть еще и настолько некачественным, что все попытки доработать его чреваты постоянными глюками.

Как дописывать такой код или исправить ошибки на сайте? Увы, логику придется полностью переписывать.

Клиенты обычно не понимают этих трагичных выводов и вытекающих из этого сроков, и расходов. Начинаются споры, жалобы на дороговизну разработки, запросы в другие студии. В итоге находится начинающая веб-студия, которая не учитывает множество моментов, делает маленькую цену, и только усугубляет ситуацию.

По итогу выходит дешевле сделать новый сайт на платформе 1С-Битрикс, чем прикрутить изменения на старый и неизвестный самописный движок, где все доработки, которые сделаны вручную - уже есть в стандартом Битриксе, и клиенту открывается огромный функционал связанный с аналитикой, работой с клиентской базой, товарами, заказами, и прочими фишками, которые появляются при выходе обновлений от 1С-Битрикс.

Мы прошли большой и сложный путь в своих попытках работать с чужим кодом. Тоже ошибались в оценках, попадали в связи с этим на большие гарантийные работы.

Однако, мы можем предложить работу с чужим кодом нашим заказчикам.

Работа с самописным кодом тарифицируется в двойном размере, предварительное изучение кода обязательно, оплата по факту потраченного времени (время работы с кодом фиксируется CRM Битрикс24, клиент приглашается в группу с проектом).

Отлючаем bootstrap.css у 1С-Битрикс

, Михаил

Добавляем в init.php обработчик и у нас нет встроенного бутстрапа, даже если включить объединение стилей - тоже сработает. т.к. файлы ядра не добавляются в единый файл


<?php 
\Bitrix\Main\EventManager::getInstance()->addEventHandler("main""OnEndBufferContent""deleteKernelCss");
function deleteKernelCss(&$content) {
	    global $USER$APPLICATION;
	    if(strpos($APPLICATION->GetCurDir(), "/bitrix/")!==false) return;
	    if($APPLICATION->GetProperty("save_kernel") == "Y") return;
	    $arPatternsToRemove = Array(
	        '/<link.+?href=".+?bitrix\/css\/main\/bootstrap.css[^"]+"[^>]+>/',
	    );
	    $content preg_replace($arPatternsToRemove""$content);
	    $content preg_replace("/\n{2,}/""\n\n"$content);
	}
?>

Запрещаем удаление оплат и отгрузок из заказов Битрикса при синхронизации с 1С

, Михаил

Уже долгое время компания 1С-Битрикс сделала полную синхронизацию заказа 1С и БУС, что черевато ситуацией - удаляются отгрузки и оплаты

Временное решение данной проблемы init.php:


<?php 

use \Bitrix\Main\EventManager;
use \Bitrix\Main\Event;
use \Bitrix\Main\Entity;
use \Bitrix\Sale\Order;
use \Bitrix\Sale\Payment;
use \Bitrix\Sale\PaySystem\Manager;
use \Bitrix\Sale\Shipment;
use \Bitrix\Sale\Helpers\Admin\Blocks\OrderBasketShipment;
$inst EventManager::getInstance();
$inst-> addEventHandler('sale''OnBeforeCollectionDeleteItem''saveInfo');
$inst-> addEventHandler('sale''OnSaleOrderBeforeSaved''reverseInfo');
//Небольшая прослойка, возвращает доступные поля
/**
 * @param array $arValues
 * @param array $allowedFields
 * @return array $result
 */
function checkFields$arValues$allowedFields) {
	   $result = array();
	   foreach ( $arValues as $key => $value ) {
		      if ( in_array$key,$allowedFields ) && !in_array($key, array('ACCOUNT_NUMBER')) ) {
			         $result[$key] = $value;
			}
		}
	   return $result;
	}
function saveInfo(\Bitrix\Main\Event $event ) {
	   /**
	    * @var \Bitrix\Sale\Shipment|\Bitrix\Sale\Payment $entity
	    */
	   if ( $_SESSION['BX_CML2_EXPORT'] ) {
		      $entity $event->getParameter('ENTITY');
		      if ( $entity instanceof Shipment ) {
			         if ( !is_array$_SESSION['BX_CML2_EXPORT']['DELETED_SHIPMENTS'] )  )
			            $_SESSION['BX_CML2_EXPORT']['DELETED_SHIPMENTS'] = array();
			         if ( !$entity->isSystem() )
			            $_SESSION['BX_CML2_EXPORT']['DELETED_SHIPMENTS'][] = checkFields$entity->getFields()->getValues(), Shipment::getAvailableFields() );
			}
		      if ( $entity instanceof Payment ) {
			         if ( !is_array$_SESSION['BX_CML2_EXPORT']['DELETED_PAYMENTS'] )  )
			            $_SESSION['BX_CML2_EXPORT']['DELETED_PAYMENTS'] = array();
			         $_SESSION['BX_CML2_EXPORT']['DELETED_PAYMENTS'][] = checkFields$entity->getFields()->getValues(), Payment::getAvailableFields() );
			}
		}
	   else {
		      return;
		}
	}
function reverseInfo(\Bitrix\Main\Event $event ) {
	   /**
	    * @var \Bitrix\Sale\Order $order
	    * @var \Bitrix\Sale\ShipmentCollection $shipmentCollection
	    * @var \Bitrix\Sale\Shipment $shipment
	    * @var \Bitrix\Sale\PaymentCollection $paymentCollection
	    * @var \Bitrix\Sale\Payment $payment
	    * @var \Bitrix\Sale\PropertyValue $somePropValue
	    * **/
	   if ( $_SESSION['BX_CML2_EXPORT'] ) {
		      $order $event->getParameter("ENTITY");
		      if ( $_SESSION['BX_CML2_EXPORT']['DELETED_SHIPMENTS'] ) {
			         //Вернем отгрузки
			         $shipmentCollection $order->getShipmentCollection();
			         $systemShipmentItemCollection $shipmentCollection->getSystemShipment()->getShipmentItemCollection();
			$products = array();
			         $basket $order->getBasket();
			         if ($basket)
			         {
				            /** @var \Bitrix\Sale\BasketItem $product */
				            $basketItems $basket->getBasketItems();
				            foreach ($basketItems as $product)
				            {
					               $systemShipmentItem $systemShipmentItemCollection->getItemByBasketCode($product->getBasketCode());
					               if ($product->isBundleChild() || !$systemShipmentItem || $systemShipmentItem->getQuantity() <= 0)
					                  continue;
					               $products[] = array(
					                  'AMOUNT' => $product->getQuantity(),
					                  'BASKET_CODE' => $product->getBasketCode()
					               );
					}
				}
			         /** @var \Bitrix\Sale\Shipment $obShipment */
			         /** @var array $shipmentFields */
			         foreach ( $_SESSION['BX_CML2_EXPORT']['DELETED_SHIPMENTS'] as $shipmentFields ) {
				            $fg true;
				            foreach( $shipmentCollection as $obShipment ) {
					               if ($obShipment->isSystem())
					                  continue;
					               $usedFields checkFields($obShipment->getFields()->getValues(), Shipment::getAvailableFields() );
					               if ( countarray_diff_assoc$shipmentFields$usedFields) ) == )
					                  $fg false;
					 //доставка с такими полями уже есть
					}
				            if ( $fg ) {
					               $shipment $shipmentCollection->createItem();
					               $shipment->setFields$shipmentFields );
					               OrderBasketShipment::updateData($order$shipment$products);
					}
				}
			         unset( $_SESSION['BX_CML2_EXPORT']['DELETED_SHIPMENTS'] );
			}
		      if ( $_SESSION['BX_CML2_EXPORT']['DELETED_PAYMENTS'] ) {
			         //Вернем оплаты
			         $paymentCollection $order->getPaymentCollection();
			         /** @var \Bitrix\Sale\Payment $obPayment */
			         /** @var array $paymentFields */
			         foreach ( $_SESSION['BX_CML2_EXPORT']['DELETED_PAYMENTS'] as $paymentFields ) {
				            $fg true;
				            foreach( $paymentCollection as $obPayment ) {
					               $usedFields checkFields$obPayment->getFields()->getValues(), Payment::getAvailableFields() );
					               if ( countarray_diff_assoc$paymentFields$usedFields) ) == )
					                  $fg false;
					 //такая оплата уже есть
					}
				            if ( $fg ) {
					               $payment $paymentCollection->createItem();
					               $payment->setFields$paymentFields );
					}
				}
			         unset( $_SESSION['BX_CML2_EXPORT']['DELETED_PAYMENTS'] );
			}
		      //Проверим сумму заказа
		      $paymentCollection $order->getPaymentCollection();
		      if ( ($sumP $paymentCollection->getSum() ) != ($sumO $order->getPrice() ) ) {
			         $diff $sumO $sumP;
			         $innerPayID Manager::getInnerPaySystemId();
			         foreach ( $paymentCollection as $payment ) {
				            if ( $payment->getPaymentSystemId() != $innerPayID) {
					               $newVal floatval($payment->getField("SUM")) + floatval($diff);
					               $payment->setField("SUM"$newVal);
					}
				}
			}
		}
	}

?>

Типовое задание для интеграции вёрстки интернет магазина

, Михаил

Есть вёрстка (набор html шаблонов) для Интернет магазина. Все скрипты, шрифты, изображения и дополнительные стили лежат на одном уровне с шаблоном, в соответствующих каталогах.

Основной файл стилей называется styles.css, и лежит на первом уровне.
Если используется less, то итоговый файл так же компилируется в один styles.css
Допускается размещение font-awesome.min.css на одном уровне с styles.css

Структура шаблона для интеграции

Предоставляются все типовые страницы, кроме корзины (sale.basket.basket), оформления заказа (sale.order.ajax), истории заказов, детального заказа, отмены заказа. Так же не верстается блок с фильтром (catalog.smart.filter)

Эти компоненты стандартные, необходимо просто закинуть на страницы, наши верстальщики приведут к стилю сайта.

Если это интернет магазин, то редакция малый бизнес. Одна цена. Предусмотреть функционал скидок.
Если продаём одежду, то только одна характеристика(SKU) у товаров - размер

Платёжная система - тип "Наличный расчет (cash)"
Доставка - никаких авто расчётов, только названия и логотипы Транспортных компаний.

Рекапча на сайте от гугла. Подключаем вот так https://divasoft.ru/blog/podklyuchenie-neskolkikh-recaptcha2-v-bitrikse/

Для доп.настроек используем Настройки++

На главной есть форма подписки - ajax добавление подписчика, в т.ч анонимного, без лишних вопросов.

Убираем индикатор ajax загрузки у Битрикс

, Михаил

Добавляем стиль, и блок больше не показывается

div[id^="wait_"] { display: none !important; background: none !important; border: 0 !important; color: #000000; font-family: Verdana, Arial, sans-serif; font-size: 11px; font-style: normal !important; font-variant: normal !important; font-weight: normal; letter-spacing: normal !important; line-height: normal; padding: 0 !important; position: absolute; text-align: center !important; text-indent: 0 !important; width: 0px !important; height: 0px !important; word-spacing: normal !important; z-index: 0; content: ""; }

Важное уведомление от 1С-Битрикс

, Михаил

С 1 января 2018 года будет ограничена поддержка 1С-Битрикс на PHP версии ниже 5.6.
Обратитесь в компанию Дивасофт, мы поможем перейти на подходящую версию PHP!

Рекомендации по выбору виртуального сервера (VPS) для 1С-Битрикс

, Михаил

Мы перепробовали много хостинговых компаний.
Самый выгодный и удобный оказался Flops.

В частности тариф "Оплата за потребление" - зачем платить постоянно за полную мощность, когда есть вариант платить только за потреблённые ресурсы.
Так же есть возможность быстро добавить или убрать оперативную память, добавить или убрать ядра процессоров, расширить место на SSD диске.

Все действия без доплаты, в течении нескольких секунд. Можно сделать в 1 клик клон сервера, потом его быстро удалить. Конечно не amazon, но в РФ это очень интересный вариант!

Скидка и промокод на Битрикс24

, Михаил

Используйте при регистрации промокод divasoft5gb, что бы получить бесплатные 5Gb места в вашем Битрикс24!

Закажите через нас продление Битрикс24, со скидкой 5%!

Динамическое изменение данных в CRM-форме Битрикс24

, Михаил

Если нужно разместить на странице несколько кнопок, которые будут заполнять значения одной CRM-Формы, которые будут приходить по ajax - нужно сделать так:

  • Добавляем на страницу скрытую кнопку с классом, b24-web-form-popup-btn-X, где X = ID Битрикс формы
  • Используем функцию reinitB24Dvsform(), в данном случае на вход идёт значение поля формы LEAD_TITLE, можно вызывать в скрипте, или по событию onclick

<?php 

// JS код
/**
 * Инициализируем настройки формы, ОБЯЗАТЕЛЬНО указываем ref
 */
function initB24CrmDvsForm(b24val) {
	    return {"id":"8","lang":"ru","sec":"y8awav","type":"button","click":"""ref":'https://ACCOUNT.bitrix24.ru/bitrix/js/crm/form_loader.js'"fields": {
			        'values'b24val
			    }};
	}
var b24paramsload initB24CrmDvsForm({});
/**
 * Стандартное добавление скрипта и переменных для скрипта Б24
 */
(function(w,d,u,b){w['Bitrix24FormObject']=b;
	w[b] = w[b] || function(){arguments[0].ref=u;
		        (w[b].forms=w[b].forms||[]).push(arguments[0])};
	        if(w[b]['forms']) return;
	        s=d.createElement('script');
	r=1*new Date();
	s.async=1;
	s.src=u+'?'+r;
	        h=d.getElementsByTagName('script')[0];
	h.parentNode.insertBefore(s,h);
	})(window,document,b24paramsload.ref,'b24form');
b24form(b24paramsload);
/**
 * Реинициализируем форму, частично повторяем функцию init у Bitrix24FormLoader
 */
function reinitB24Dvsform(nVal) {
	    if(!window.Bitrix24FormObject || !window[window.Bitrix24FormObject])
	        return;
	    if(!window[window.Bitrix24FormObject].forms)
	        return;
	    // Уничтожаем форму
	    Bitrix24FormLoader.unload(b24paramsload);
	    // Пересоздаём параметры формы
	    b24paramsload initB24CrmDvsForm({'LEAD_TITLE'nVal});
	    // Инициируем форму с новыми данными
	    Bitrix24FormLoader.params b24paramsload;
	    Bitrix24FormLoader.init();
	    // Открываем попап
	    Bitrix24FormLoader.showPopup(b24paramsload);
	}

?>

Исправляем ошибку 400 Bad Request при включении https у 1С-Битрикс

, Михаил

После включение редиректа на https, в некоторых случаях появляется ошибка 400 Bad Request The plain HTTP request was sent to HTTPS port

Всё происходит из за mod_dir, он берет на себя редирект с папки без слеша на папку с слешом, но он не воспринимает "HTTPS on" как побудитель использования схемы https:// 

Что бы всё это заработало, нужно:

  • В конфигах nginx'a ничего не трогаем
    proxy_set_header       Host       $host:443;
  • В конфиге апача который отвечает за ваш домен
    Если у вас конфигурация многосайтовая - /etc/httpd/bx/conf/bx_ext_site.local.conf
    односайтовая - /etc/httpd/bx/conf/default.conf
    К названию сервера ServerName  site.local  дописываем:
    ServerName  https://site.local 

смысл следующий: http://httpd.apache.org/docs/2.2/mod/core.html#servername

Sometimes, the server runs behind a device that processes SSL, such as a reverse proxy, load balancer or SSL offload appliance. When this is the case, specify the https:// scheme and the port number to which the clients connect in the ServerName directive to make sure that the server generates the correct self-referential URLs.

Так же можно применить вот это решение Чиним ошибку 400 Bad Request с помощью mod_rpaf у BitrixEnv

Интернет Касса в 1С-Битрикс (Соответствие ФЗ-54)

, Михаил

Организации или ИП, деятельность которых связана с наличными расчётами или оплатой при помощи банковских карт, обязательно должны применять зарегистрированный кассовый аппарат. Благодаря нашей инструкции вы сможете осуществить регистрацию быстро и без осложнений. Давайте разберёмся, как она проводится, какие документы для неё потребуются.

  1. Заключить договор с ОФД на передачу данных
  2. Приобрести ККМ со встроеным Фискальным Накопителем
  3. Зарегистрировать ККМ в ФНС
  4. Подключить ККМ к ОФД
  5. Скачать и установить драйвера ККМ на локальный компьютер
    Драйвер для работы кассового аппарата в режиме формирования чека
    Ставится для поддержки виртуальных COM-портов В драйверах содержится программа «Тест драйверов («fptr_t.exe», в каталоге «nt-x86-mingw»)» - требуется для настройки и просмотра портов
    Скачать здесь

    Драйвер для отправки кассой данных в ОФД
    Ставится для работы службы «Ethernet over USB» и настраивается с помощью «DTOintegrator.exe», в каталоге «\Program Files (x86)\ATOL\Drivers8\Bin\». Настраивается в режиме «Администратора»
    Скачать здесь
  6. Скачать (msiexe) и установить приложение 1С-Битрикс.Кассы
  7. Обновить 1С-Битрикс Управление сайтом до версии 17.хх
  8. Установите галочку «Разрешить печать чеков» в настройках платежной системы, если требуется печатать чек.

Обращайтесь в компанию Дивасофт, мы поможем вам с ФЗ-54
ККМ ФЗ-54

ФЗ-54 и что с этим делать клиенту и разработчику

, Михаил

С 1 февраля 2017 вступил в силу новая версия 54-ФЗ «О применении контрольно-кассовой техники». Новый порядок применения ККТ предполагает, что данные о продажах с каждого выбитого чека должны передаваться в налоговую инспекцию через интернет. То есть вместо касс с ЭКЛЗ придется использовать кассы с фискальным накопителем (ФН).

При чем тут интернет-магазины?

Если раньше использование касс, при оплате заказа при помощи банковских карт, электронных денег(электронных средств платежа) в интернет-магазинах (ИМ) не требовалось, то теперь, вне зависимости от того как была произведена оплата, владельцы ИМ будут обязаны пробить чек и предоставить его покупателю как минимум в электронном виде в течении пяти минут. Причем зарегистрировать чек в налоговой надо сразу после поступления денежных средств. Электронный чек должен быть отправлен на e-mail покупателя указанный при получении заказа. Все данные о чеках сохраняются в фискальном накопителе.

Внимание! Если ИМ получает деньги с помощью Киви-кошелька, банковских карт, Вебмани, Yandex.Денег — надо печатать чек.

Под закон не подпадают только банковские переводы. Пример: ИМ выставил счёт клиенту, клиент идёт на почту или в банк, оплачивает. Вот тут ИМ не пробивает чек.

В остальных случаях — выбивает. Это самое большое изменение. Кассовый аппарат регистрируется. Чек выписывается сразу.

Печатать чеки не обязательно, физический чек не нужен, если ИМ не работает с оффлайн-клиентами. Для таких ИМ существую специальные интернет-кассы без печатного блока (например, АТОЛ 42ФС)
atol42fs.jpg

Что надо будет делать клиенту по ФЗ-54?

  1. Нужно приобрести кассу с поддержкой ФЗ-54 или модернизировать старую. Можете порекомендовать клиентам «Атол» или «Штрих-М».(ссылки) Драйверы универсальные, вся линейка «Атол» прекрасно работает с нашим решением. «ШТРИХ-М» тоже будет работать в ближайшее время.
  2. Проверить, возможна ли модернизация текущей кассы или подобрать новую можно на сайте: https://kkt-online.nalog.ru/#_rkkt
  3. Клиент заключает договор с ОФД. Это легко. Клиент регистрирует кассу в ФНС. Подключает её к ОФД. Нужно прописать внутри касс определённые параметры. Обычно это делают сервисные компании по продаже касс.
  4. Нужно установить и настроить драйвер кассы(ссылки). Устанавливаете приложение «1С-Битрикс». Система сама найдёт конкретный магазин, сама заберёт нужные данные, сама получит ключи авторизации.
  5. Нужно настроить кассу в «1С-Битрикс: Управление сайтом». Подключаете кассу внутри сервиса, настраиваете и клиент работает по новому закону.

Когда надо перейти?

  • С 1 февраля 2017 года вступил в силу запрет на регистрацию касс старого образца, а регистрация онлайн-касс стала обязательной. Пока еще работать можно как на кассах с ЭКЛЗ, так и на кассах с ФН.
  • С 1 июля 2017 года кассы с ЭКЛЗ станут вне закона. Работать можно будет только на новых кассах с ФН.
  • С 1 июля 2018 – кассы должны использовать все предприниматели, которые раньше могли их не использовать: ПСН, ЕНВД. ИП, оказывающие услуги должны будут выдавать бланк строгой отчетности.

Что если владелец ИМ не будет использовать новую кассу?

Неприменение ККТ (первый инцидент):

  • для должностных лиц и ИП - 25-50% суммы чека, но не менее 10 000 рублей;
  • для юридических лиц - 75-100% суммы чека, но не менее 30 000 рублей.

Повторный инцидент неприменения ККТ:

  • приостановление деятельности ИП или юр. лица на срок до 90 суток;
  • для должностных лиц - дисквалификация до 2 лет.

Не направление покупателю электронного чека:

  • для должностных лиц и ИП - до 2 000 рублей;
  • для юридических лиц - до 10 000 рублей.

Незаконная работа с кассовым аппаратом старого образца:

  • для должностных лиц и ИП - до 3 000 рублей.
  • для юридических лиц - до 10 000 рублей

Чем поможет «1С-Битрикс»?

Есть бесплатное приложение «1C-Битрикс.Кассы». Оно ставиться на компьютер, и к нему подключается касса.

Приложение работает по принципу сервера:

  • подключается к вашему интернет-магазину;
  • получает от него данные;
  • отправляет их на кассу.

Касса сама не умеет инициировать соединение. Она умеет как принтер — получать данные. Значит нужен посредник, который будет связан с интернет-магазином. Он будет получать данные и печатать на этом принтере. Таким посредником является «1С-Битрикс.Кассы»

Сейчас поддерживаются два типа аппаратов: «Атол» и «Штрих-М». На данный момент все тесты происходят с аппаратами «Атол». Со «Штрих-М» пока есть технические задержки.

Чуть позже разницы не будет, клиент будет выбирать кассу, с которой привычнее работать.

Важно: Приложение работает при наличии активной лицензии «1С-Битрикс»!

Кроме того, к интернет-магазину можно подключить несколько касс, если заказов очень много. Возможно подключение разных юр. лиц, подключение к разным ОФД, поддерживаются все необходимые виды чеков: чек-возврат, чек-аванс, чек с учетом аванса.

Варианты подключения скрипта для Asset::getInstance()->addString() у 1С-Битрикс

, Михаил

Третий аргумент для функции addString() можно подсмотреть только в ядре: /bitrix/modules/main/lib/page/asset.php


<?php 

use Bitrix\Main\Page\Asset;
use Bitrix\Main\Page\AssetLocation;
Asset::getInstance()->addString("<script>***</script>"trueAssetLocation::AFTER_JS);
// AssetLocation::BEFORE_CSS;
// AssetLocation::AFTER_CSS;
// AssetLocation::AFTER_JS_KERNEL
// AssetLocation::AFTER_JS

?>

Подключение CSS и JS файлов в шаблоне компонента 1С-Битрикс

, Михаил

Для оформления и реализации front-end логики компонента, в шаблоне доступны не обязательные файлы

  • style.css — дополнительные стили для шаблона;
  • script.js — дополнительные скрипты для шаблона.

Архитектурно правильный способ - создать component_epilog.php


<?php 

global $APPLICATION;
$APPLICATION->AddHeadScript(SITE_TEMPLATE_PATH ."/js/divasoft.js");
$APPLICATION->SetAdditionalCss(SITE_TEMPLATE_PATH ."/css/divasoft.css");
$APPLICATION->AddHeadString("<link href='http://fonts.googleapis.com/css?family=PT+Sans:400&subset=cyrillic' rel='stylesheet' type='text/css'>");

?>

Или в новом стиле D7:


<?php 

use Bitrix\Main\Page\Asset;
Asset::getInstance()->addJs(SITE_TEMPLATE_PATH "/js/divasoft.js");
Asset::getInstance()->addCss(SITE_TEMPLATE_PATH "/css/divasoft.css");
Asset::getInstance()->addString("<link href='http://fonts.googleapis.com/css?family=PT+Sans:400&subset=cyrillic' rel='stylesheet' type='text/css'>");

?>

И теперь самый простой и правильный способ в template.php


<?php 

$this->addExternalCss("/local/styles.css");
$this->addExternalJS("/local/liba.js");

?>

Ещё один способ, если весь шаблон находится в отложенной функции template.php


<?php 

//we can't use $APPLICATION->SetAdditionalCSS()
$css $APPLICATION->GetCSSArray();
if(!is_array($css) || !in_array("/bitrix/css/main/font-awesome.css"$css)) {
	    $strReturn .= '<link href="'.CUtil::GetAdditionalFileURL("/bitrix/css/main/font-awesome.css").'" type="text/css" rel="stylesheet" />'."\n";
	}

?>

Подключение нескольких recaptcha2 в Битриксе

, Михаил

Есть несколько способов подключения и инициализации рекапч, но самый удобный это тот, который занимает мало строчек кода, и максимально эффективен.

В init.php добавляем код, инициализирующий рекапчу. Код добавляется структурно после подключения всех стилей и скриптов, что исключает ошибку раннего старта рекапчи. Код добавляется именно через addString(), а не через addScript():


<?php 

// Открытый ключ
define("DVS_RC_KEY","123");
// Секретный ключ 
define("DVS_RC_SECRET","321");
use Bitrix\Main\Page\Asset;
use Bitrix\Main\Page\AssetLocation;
if (!defined("DVS_RECAPTCHA")) {
	// Подключаем скрипт рекапчи
	    Asset::getInstance()->addString('<script src="//www.google.com/recaptcha/api.js?onload=divaCaptchaRender&render=explicit" async defer></script>',
	 trueAssetLocation::AFTER_JS);
	// Инициализируем массив рекапч
	    Asset::getInstance()->addString("<script>window.rc = {};
	 var divaCaptchaRender = function () {
		        $('.g-recaptcha').each(function() {
			          window.rc[$(this).attr('id')] = grecaptcha.render( this,{ 'sitekey': '" DVS_RC_KEY "', 'theme': 'light'} );
			});
		};
	</script>"trueAssetLocation::AFTER_JS);
	    define("DVS_RECAPTCHA"true);
	}

?>

Дальше мы можем обращаться из любого компонента к объекту рекапчи, которые находятся в массиве window.rc.

Вставка рекапчи:


<?php 
<div id="recaptchaUID" class="g-recaptcha"></div>
?>

Сброс рекапчи:


<?php 
<script>grecaptcha.reset(window.rc[recaptchaUID]);
</script>
?>

Тем самым не зная точно сколько рекапч будет на странице, мы можем работать с ними по идентификатору дива (в данном случае это recaptchaUID), в котором эта капча инициализировалась по классу g-recaptcha.

Принудительное включение https в ядре 1С-Битрикс

, Михаил

Есть недокументированная возможность заставить Битрикс работать по протоколу https (если не корректно настроен хостинг)

Создаём/редактируем файл bitrix/.settings_extra.php


<?php 

return array (
    "https_request" => array("value" => true),
);
 
?>

Как правильно включить отображение ошибок в 1С-Битрикс

, Михаил

В файле bitrix/.settings.php


<?php 
'exception_handling' => 
  array (
    'value' => 
    array (
      'debug' => true,
      'handled_errors_types' => E_ALL & ~E_NOTICE & ~E_STRICT & ~E_USER_NOTICE & ~E_DEPRECATED,
      'exception_errors_types' => E_ALL & ~E_NOTICE & ~E_WARNING & ~E_STRICT & ~E_USER_WARNING & ~E_USER_NOTICE & ~E_COMPILE_WARNING,
      'ignore_silence' => false,
      'assertion_throws_exception' => true,
      'assertion_error_type' => 256,
      'log' => 
      array (
        'settings' => 
        array (
          'file' => 'bitrix/err.log',
          'log_size' => 1000000,
        ),
      ),
    ),
    'readonly' => false,
  )
?>

Логи будут в файле bitrix/err.log

Как можно увеличить количество хранимых логов обмена с 1С на сайте 1С-Битрикс?

, Михаил

Для этого необходимо доработать компонент catalog.import.1c

Где в файле \bitrix\components\diva\catalog.import.1c\class.php

А именно в функции public function cleanUpDirectory($directoryName) есть условие


<?php 
if ($directoryEntry->isDirectory() && $directoryEntry->getName() === "Reports"{} 
?>

В котором нужно отключить $reportsEntry->delete();

Зачем лечить сайт от вирусов?

, Михаил

Вирусный код на страницах сайта может привести к полной блокировке доступа к сайту со стороны антивирусов или хостинга, а также бану в поисковых системах. Вирусы на сайте вредят посетителям, заражая их компьютеры вредоносным кодом и троянскими программами. Заполняют ваш сайт ссылками на другие сайты, что понижает ваши позиции в поисковых системах. Постоянно рассылают через ваш сайт вредоносные рекламные электронные письма.

Необходимо понимать, что вирусы являются следствием взлома, поэтому в первую очередь нужно найти источник появления вируса на сайте (скорее всего это будет взлом сайта через уязвимости в скриптах или кража пароля от FTP).

Если ваш сайт заразился, то Дивасофт поможем вам. Вылечим. Починим. Обновим.

Федеральный закон ФЗ-54 «О применении контрольно-кассовой техники» для интернет-магазинов на платформе 1С-Битрикс

, Михаил

Компания Дивасофт поможет решить эту задачу, установить нужное оборудование, интегрировать кассу с вашим Интернет-магазином, а так же проконсультирует по всем вопросам.

CMS 1С-Битрикс подружит интернет-магазины с физическими кассами для соблюдения нового 54-ФЗ «О применении контрольно-кассовой техники». Сайты смогут подключать к интернет-магазину несколько параллельно работающих касс для соответствия законодательству. С 1 февраля 2017 года вступит в силу часть поправок к 54-ФЗ, которые изменят порядок работы и приема оплаты в интернет-магазинах. В полную силу закон заработает с 1 июля 2017 года. Каждый интернет-магазин должен иметь кассовый аппарат (ККТ), подключенный к интернету и соединенный с оператором фискальных данных (ОФД).

Касса будет оформлять чек на каждую транзакцию в интернет-магазине. А после - отправлять его в ОФД для регистрации. Чек будет дублироваться клиенту на почту. Электронный вариант чека станет полноценной альтернативой бумажному.

Из ОФД данные будут уходить в Федеральную налоговую службу. Штраф за каждую незарегистрированную покупку в интернет-магазине будет составлять до 100% ее суммы, но не меньше 30 000 рублей, а за неправильную кассу или неотправку электронной версии чека клиенту – 10 000 рублей.

Количество заказов в интернет-магазине распределяется неравномерно – в зависимости от времени суток, сезона, акционных предложений. За одну секунду в интернет-магазин может поступать несколько десятков заказов, а текущее кассовое оборудование позволяет печатать не более 2 чеков за секунду.

К интернет-магазинам на «1С-Битрикс» можно будет подключить несколько кассовых аппаратов параллельно. Эта проблема решена.

Меняем точность округления цены в 1С-Битрикс

, Михаил

Цена/Количество в 1С-Битрикс может округляться до 4 знака. Но что делать, если нам нужна точность до 10 знаков?

Устанавливаем константу /php_interface/init.php define("SALE_VALUE_PRECISION", 10);

В настройках модуля "Интернет-магазин" ставим
Знаков после запятой при выводе количественного значения: Авто

В свойствах таблицы b_sale_basket меняем все длины полей, что содержат 18,4 на 18,10

ALTER TABLE `b_sale_basket` MODIFY `PRICE` decimal(18,10) NOT NULL ;
ALTER TABLE `b_sale_basket` MODIFY `BASE_PRICE` decimal(18,10) NULL DEFAULT NULL  ;
ALTER TABLE `b_sale_basket` MODIFY `QUANTITY` decimal(18,10) NOT NULL DEFAULT "0.0000000000000" ;

Если заказ создаётся через API, а не через админку - то всё хорошо. Если через админку будем менять цену за единицу товара - то всё хорошо, но если "сохранить" без изменений... точность сбросится до 4го знака.

Добавляем ID профиля покупателя в свойство заказа 1С-Битрикс на ядре D7

, Михаил
  1. Создаём поле для хранения идентификатора покупателя SUBUSER_ID
  2. Добавляем обработчик OnBeforeSaleOrderFinalAction, что бы установить наше свойство.

<?php 
\Bitrix\Main\EventManager::getInstance()->addEventHandler('sale''OnBeforeSaleOrderFinalAction''OnBeforeSaleOrderFinalActionHandler');
function OnBeforeSaleOrderFinalActionHandler(\Bitrix\Main\Event $event) {
	    $order $event->getParameter('ENTITY');
	    $userProfileId getContragentID($order->getId());
	    $propertyCollection $order->getpropertyCollection();
	    foreach ($propertyCollection as $property) {
		        if($property->getField('CODE') == 'SUBUSER_ID') {
			            $property->setValue($userProfileId);
			}
		}
	    return new \Bitrix\Main\EventResult(
	        \Bitrix\Main\EventResult::SUCCESS, array(
	            "ENTITY" => $order,
	        )
	    );
	}
?>

  1. Дальше находим ID профиля покупателя по ИНН у Юр.Лиц, и по ФИО(названию профиля) у Физ.Лиц.

<?php 
function getContragentID($orderID) {
	    CModule::IncludeModule("sale");
	    if ($arOrder CSaleOrder::GetByID($orderID)) {
		        $profiles = \Bitrix\Sale\Helpers\Admin\Blocks\OrderBuyer::getBuyerProfilesList($arOrder['USER_ID'],$arOrder['PERSON_TYPE_ID']);
		        unset($profiles[0]);
		        $profiles array_flip($profiles);
		        $filterValue = array('PERSON_TYPE_ID' => $arOrder['PERSON_TYPE_ID'], 'IS_PROFILE_NAME' => 'Y');
		        if ($arOrder['PERSON_TYPE_ID']==2) {
			            $filterValue = array('PERSON_TYPE_ID' => $arOrder['PERSON_TYPE_ID'], 'CODE' => 'INN');
			        }
		        $rsOrderProps CSaleOrderProps::GetList(array(), $filterValue);
		        if ($arOrderProp $rsOrderProps->Fetch()) {
			            $rsProps CSaleOrderPropsValue::GetList(array('SORT' => 'ASC'), array('ORDER_ID' => $orderID'ORDER_PROPS_ID' => $arOrderProp['ID']));
			            if ($arProp $rsProps->Fetch()) {
				                $rsUP CSaleOrderUserPropsValue::GetList(array(), array('ORDER_PROPS_ID' => $arOrderProp['ID'],
				                    'VALUE' => $arProp['VALUE'],
				                    'PROP_PERSON_TYPE_ID' => $arOrder['PERSON_TYPE_ID']));
				                if ($arUP $rsUP->Fetch()) {
					                    if (array_key_exists($arProp['VALUE'], $profiles) && $arOrder['PERSON_TYPE_ID']==1) {
						                        return $profiles[$arProp['VALUE']];
						                    }
					                    return $arUP['USER_PROPS_ID'];
					                } else {
					                    $db_sales CSaleOrderUserProps::GetList(array(),array("USER_ID" => $arOrder['USER_ID'],"PERSON_TYPE_ID"=>$arOrder['PERSON_TYPE_ID'],"=NAME"=>$arProp['VALUE']));
					                    if ($ar_sales $db_sales->Fetch()) {
						                       return $ar_sales["ID"];
						                    }
					                }
				            } 
			        }
		    }
	}
?>

Возвращаем блок "Предложите покупателю" в детальный заказ 1С-Битрикс

, Михаил
Добавляем в /bitrix/php_interface/admin_header.php

<?php 
<? if (stripos($_SERVER['REQUEST_URI'], '/bitrix/admin/sale_order_view.php') !== false) : ?>
    <script type="text/javascript">
        $(function () {
	            $.ajax({  method: "GET", url: "/bitrix/diva/ajaxGetBasket.php", data: {ID: <?= intval($_REQUEST['ID']) ?>}
		}).done(function (msg) {
		                $('.adm-s-result-container').prepend(msg);
		});
	});
    </script>
<?endif;
?>
?>

Создаём обработчик /bitrix/diva/ajaxGetBasket.php

<?php 

define("NO_KEEP_STATISTIC"true);
//define("NOT_CHECK_PERMISSIONS", true);
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_before.php");
$ID intval($_REQUEST['ID']);
if (!$USER->IsAdmin() || $ID == 0) {
	    die();
	}
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/sale/general/admin_tool.php");
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/sale/lang/ru/admin/order_detail.php");
CModule::IncludeModule("iblock");
CModule::IncludeModule("catalog");
CModule::IncludeModule("sale");
$dbOrder CSaleOrder::GetList(
                array("ID" => "DESC"), array("ID" => $ID), falsefalse, array(
            "ID""LID""PERSON_TYPE_ID",
            "PAYED""DATE_PAYED""EMP_PAYED_ID""PAY_VOUCHER_NUM""PAY_VOUCHER_DATE",
            "CANCELED""DATE_CANCELED""EMP_CANCELED_ID""REASON_CANCELED",
            "STATUS_ID""DATE_STATUS""EMP_STATUS_ID""PRICE_DELIVERY",
            "ALLOW_DELIVERY""DATE_ALLOW_DELIVERY""EMP_ALLOW_DELIVERY_ID",
            "DEDUCTED""DATE_DEDUCTED""EMP_DEDUCTED_ID""REASON_UNDO_DEDUCTED",
            "MARKED""DATE_MARKED""EMP_MARKED_ID""REASON_MARKED",
            "PRICE""CURRENCY""DISCOUNT_VALUE""SUM_PAID""USER_ID""PAY_SYSTEM_ID",
            "DELIVERY_ID""DATE_INSERT""DATE_INSERT_FORMAT""DATE_UPDATE""USER_DESCRIPTION",
            "ADDITIONAL_INFO""PS_STATUS""PS_STATUS_CODE""PS_STATUS_DESCRIPTION",
            "PS_STATUS_MESSAGE""PS_SUM""PS_CURRENCY""PS_RESPONSE_DATE""COMMENTS",
            "TAX_VALUE""STAT_GID""RECURRING_ID""AFFILIATE_ID""LOCK_STATUS",
            "USER_LOGIN""USER_NAME""USER_LAST_NAME""USER_EMAIL""DELIVERY_DOC_NUM",
            "DELIVERY_DOC_DATE""STORE_ID""ACCOUNT_NUMBER""TRACKING_NUMBER",
                )
);
if (($arOrder $dbOrder->Fetch())) {
	    ?>
	    <div class="load_product order_summary" style="float: left;
	">
	        <table width="100%" class="itog_header"><tr><td>Предложите покупателю</td></tr></table>
	        <div id="tabs">
	            <?
	            $crmMode false;
	            $displayNone "block";
	            $displayNoneBasket "block";
	            $displayNoneViewed "block";
	            $arFilterRecomendet = array();
	            $arBasketItems = array();
	            $dbBasketTmp CSaleBasket::GetList(array("ID" => "ASC"), array("ORDER_ID" => $arOrder["ID"]), falsefalse, array("ID""PRODUCT_ID"));
	            while ($arBasketTmp $dbBasketTmp->GetNext()) {
		                $arBasketItems[] = $arBasketTmp;
		            }
	            //pr($arBasketItems);
	            foreach ($arBasketItems as $arItem) {
		                if (!CSaleBasketHelper::isSetItem($arItem)) {
			                    $arFilterRecomendet[] = $arItem["PRODUCT_ID"];
			                }
		            }
	            $arRecommendedResult CSaleProduct::GetRecommendetProduct($arOrder["USER_ID"], $arOrder["LID"], $arFilterRecomendet);
	            $recomCnt count($arRecommendedResult);
	            if ($recomCnt 2) {
		                $arTmp = array();
		                $arTmp[] = $arRecommendedResult[0];
		                $arTmp[] = $arRecommendedResult[1];
		                $arRecommendedResult $arTmp;
		            }
	            if ($recomCnt <= 0)
	                $displayNone "none";
	            $arErrors = array();
	            $arFuserItems CSaleUser::GetList(array("USER_ID" => intval($arOrder["USER_ID"])));
	            $arCartWithoutSetItems = array();
	            $arTmpShoppingCart CSaleBasket::DoGetUserShoppingCart($arOrder["LID"], $arOrder["USER_ID"], $arFuserItems["ID"], $arErrors, array());
	            if (is_array($arTmpShoppingCart)) {
		                foreach ($arTmpShoppingCart as $arCartItem) {
			                    if (CSaleBasketHelper::isSetItem($arCartItem))
			                        continue;
			                    $item findPositionsByID($arCartItem["PRODUCT_ID"]);
			                    if ($item['IBLOCK_ID'] != CATALOG_IBLOCK_ID) {
				                        if ($item['PROPS']['CML2_LINK']['VALUE'] != "") {
					                            $item findPositionsByID($item['PROPS']['CML2_LINK']['VALUE']);
					                        }
				                    }
			                    $arCartItem['MASTER'] = $item;
			                    $arCartItem['NAME'] = "[" $item['PROPS']['CML2_ARTICLE']['VALUE'] . "] " $arCartItem['NAME'];
			                    $arCartWithoutSetItems[] = $arCartItem;
			                }
		            }
	            $basketCnt count($arCartWithoutSetItems);
	            if ($basketCnt 2) {
		                $arTmp = array();
		                $arTmp[] = $arCartWithoutSetItems[0];
		                $arTmp[] = $arCartWithoutSetItems[1];
		                $arCartWithoutSetItems $arTmp;
		            }
	            if ($basketCnt <= 0)
	                $displayNoneBasket "none";
	            ///
	            $arViewed = array();
	            $arViewedIds = array();
	            $viewedCount 0;
	            $mapViewed = array();
	            if (CModule::includeModule("catalog")) {
		                $viewedIterator = \Bitrix\Catalog\CatalogViewedProductTable::getList(array(
		                            'order' => array("DATE_VISIT" => "DESC"),
		                            'filter' => array('FUSER_ID' => $arFuserItems["ID"], "SITE_ID" => $arOrder["LID"]),
		                            'select' => array("ID""FUSER_ID""DATE_VISIT""PRODUCT_ID""LID" => "SITE_ID""NAME" => "ELEMENT.NAME""PREVIEW_PICTURE" => "ELEMENT.PREVIEW_PICTURE""DETAIL_PICTURE" => "ELEMENT.DETAIL_PICTURE")
		                ));
		                while ($viewed $viewedIterator->fetch()) {
			                    $viewed['MODULE'] = 'catalog';
			                    $arViewed[$viewedCount] = $viewed;
			                    $arViewedIds[] = $viewed['PRODUCT_ID'];
			                    $mapViewed[$viewed['PRODUCT_ID']] = $viewedCount;
			                    $viewedCount++;
			                }
		                unset($viewedCount);
		                $baseGroup CCatalogGroup::getBaseGroup();
		                if (!empty($arViewedIds)) {
			                    $priceIterator CPrice::getList(
			                                    array(), array("PRODUCT_ID" => $arViewedIds'CATALOG_GROUP_ID' => $baseGroup['ID']), falsefalse, array("PRODUCT_ID""PRICE""CURRENCY"));
			                    while ($productPrice $priceIterator->fetch()) {
				                        if (isset($mapViewed[$productPrice['PRODUCT_ID']])) {
					                            $key $mapViewed[$productPrice['PRODUCT_ID']];
					                            $arViewed[$key]["PRICE"] = $productPrice["PRICE"];
					                            $arViewed[$key]["CURRENCY"] = $productPrice["CURRENCY"];
					                        }
				                    }
			                }
		                $viewedCnt count($arViewed);
		                $arViewed array_slice($arViewed02);
		                if (count($arViewed) <= 0)
		                    $displayNoneViewed "none";
		            }
	            else {
		                $displayNoneViewed "none";
		            }
	            $tabBasket "tabs";
	            $tabViewed "tabs";
	            if ($displayNoneBasket == 'none' && $displayNone == 'none' && $displayNoneViewed == 'block')
	                $tabViewed .= " active";
	            if ($displayNoneBasket == 'block' && $displayNone == 'none')
	                $tabBasket .= " active";
	            ?>
	            <div id="tab_1" style="display:<?= $displayNone ?>"       class="tabs active"     onClick="fTabsSelect('buyer_recmon', this);
	" ><?= GetMessage('SOD_SUBTAB_RECOMENET'?></div>
	            <div id="tab_2" style="display:<?= $displayNoneBasket ?>" class="<?= $tabBasket ?>" onClick="fTabsSelect('buyer_basket', this);
	"><?= GetMessage('SOD_SUBTAB_BASKET'?></div>
	            <div id="tab_3" style="display:<?= $displayNoneViewed ?>" class="<?= $tabViewed ?>" onClick="fTabsSelect('buyer_viewed', this);
	"><?= GetMessage('SOD_SUBTAB_LOOKED'?></div>
	            <?
	            if ($displayNone == 'block') {
		                $displayNoneBasket 'none';
		                $displayNoneViewed 'none';
		            }
	            if ($displayNoneBasket == 'block') {
		                $displayNone 'none';
		                $displayNoneViewed 'none';
		            }
	            if ($displayNoneViewed == 'block') {
		                $displayNone 'none';
		                $displayNoneBasket 'none';
		            }
	            ?>
	            <div id="buyer_recmon" class="tabstext active" style="display:<?= $displayNone ?>">
	                <? echo fGetFormatedProductData($arOrder["USER_ID"], $arOrder["LID"], $arRecommendedResult$recomCnt$arOrder["CURRENCY"], 'recom'$crmMode);
	 ?>
	            </div>
	            <div id="buyer_basket" class="tabstext active" style="display:<?= $displayNoneBasket ?>">
	                if (count($arCartWithoutSetItems) > 0)
	                echo fGetFormatedProductData($arOrder["USER_ID"], $arOrder["LID"], $arCartWithoutSetItems, $basketCnt, $arOrder["CURRENCY"], 'basket', $crmMode);
	                ?>
	            </div>
	            <div id="buyer_viewed" class="tabstext active" style="display:<?= $displayNoneViewed ?>">
	                <?
	                if (count($arViewed) > 0)
	                    echo fGetFormatedProductData($arOrder["USER_ID"], $arOrder["LID"], $arViewed$viewedCnt$arOrder["CURRENCY"], 'viewed'$crmMode);
	                ?>
	            </div>
	        </div>
	        <script type="text/javascript">
	            function fTabsSelect(tabText, el)
	            {
		                BX('tab_1').className = "tabs";
		                BX('tab_2').className = "tabs";
		                BX('tab_3').className = "tabs";
		                BX(el).className = "tabs active";
		                BX(el).className = "tabs active";
		                BX(el).style.display = 'block';
		                BX('buyer_recmon').className = "tabstext";
		                BX('buyer_basket').className = "tabstext";
		                BX('buyer_viewed').className = "tabstext";
		                BX('buyer_recmon').style.display = 'none';
		                BX('buyer_basket').style.display = 'none';
		                BX('buyer_viewed').style.display = 'none';
		                BX(tabText).style.display = 'block';
		                BX(tabText).className = "tabstext active";
		            }
	        </script>
	        <script type="text/javascript">
	            /*
	             * click on recommendet More
	             */
	            function fGetMoreProduct(type)
	            {
		                BX.showWait();
		                productData = <? echo CUtil::PhpToJSObject($arFilterRecomendet);
		 ?>;
		                var userId = '<?= $arOrder["USER_ID"?>';
		                var fUserId = '<?= $arFuserItems["ID"?>';
		                var currency = '<?= $arOrder["CURRENCY"?>';
		                var lid = '<?= $arOrder["LID"?>';
		                BX.ajax.post('/bitrix/admin/sale_order_detail.php', '<?= CUtil::JSEscape(bitrix_sessid_get()) ?>&ORDER_AJAX=Y&type=' + type + '&arProduct=' + productData + '&currency=' + currency + '&LID=' + lid + '&userId=' + userId + '&fUserId=' + fUserId + '&ID=<?= $ID ?>', fGetMoreProductResult);
		            }
	            function fGetMoreProductResult(res)
	            {
		                BX.closeWait();
		                var rs = eval('(' + res + ')');
		                if (rs["ITEMS"].length > 0)
		                {
			                    if (rs["TYPE"] == 'basket')
			                        BX("buyer_basket").innerHTML = rs["ITEMS"];
			                    if (rs["TYPE"] == 'recom')
			                        BX("buyer_recmon").innerHTML = rs["ITEMS"];
			                    if (rs["TYPE"] == 'viewed')
			                        BX("buyer_viewed").innerHTML = rs["ITEMS"];
			                }
		            }
	        </script>    
	    </div>
	    <?
	}
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/epilog_after.php");

?>

SECTION_CODE_PATH не учитывает отсутствие символьного кода родительского раздела (1С-Битрикс)

, Михаил

<?php 
У catalog.section->result_modifier.php
foreach ($arResult["ITEMS"] as &$arItem) {
	   // FIX: SECTION_CODE_PATH
	   $nav CIBlockSection::GetNavChain($arItem['IBLOCK_ID'], $arItem['~IBLOCK_SECTION_ID']);
	   $arNavi=array();
	   while($arNavItem $nav->Fetch()) {
		        $arNavi[]=$arNavItem['CODE'];
		}
	   $arNavi[]=$arItem['CODE'];
	   $arItem['DETAIL_PAGE_URL']="/".implode("/"$arNavi)."/";
	}

?>

Подключаем почтовый сервис для рассылок электронных писем mailgun.com для 1С-Битрикс

, Михаил

Скачиваем библиотеку https://github.com/mailgun/mailgun-php


<?php 
require_once $_SERVER['DOCUMENT_ROOT'].'/bitrix/divasoft/mailgun-php/vendor/autoload.php';
use Mailgun\Mailgun;
function custom_mail($to$subject$message$additionalHeaders ''){
	        $mg = new Mailgun("key-*****");
	        $domain "divasoft.ru";
	/*$mg = new Mailgun('key-*****', null, 'bin.mailgun.net');
	 // TEST
	$mg->setApiVersion('*******');
	*/
	      //Получаем тему письма
	    $elements imap_mime_header_decode($subject);
	    $title =  '';
	    for ($i=0;
	 $i<count($elements);
	 $i++) {
		        $title .= $elements[$i]->text;
		    }
	        preg_match('/From: (.+)\n/i'$additionalHeaders$matches);
	        list(, $from) = $matches;
	        preg_match('/BCC: (.+)\n/i'$additionalHeaders$matches);
	        list(, $bcc) = $matches;
	        preg_match('/CC: (.+)\n/i'$additionalHeaders$matches);
	        list(, $cc) = $matches;
	        preg_match('/Reply-To: (.+)\n/i'$additionalHeaders$matches);
	        list(, $rt) = $matches;
	        $mg->sendMessage($domain, array('from'    => $from, 
	                                        'to'      => $to, 
	                                        'bcc'      => $bcc, 
	                                        'subject' => $subject, 
	                                        'html' => $message, 
	                                        'text'    => strip_tags($message)));
	        // DEBUG
	        /*$result = $mg->get("$domain/log", array('limit' => 25, 
	                                                'skip'  => 0));
	        $logItems = $result->http_response_body->items;
	        foreach($logItems as $logItem){
		            //echo $logItem->message_id . "\n";
		}*/
	        return true;
	}
?>

И волшебный ответ для того, что бы аккаунт прошёл валидацию
"your account is temporarily disabled. business verification please contact support to resolve"

Hello!

What types of emails will you be sending - transactional or marketing?
transactional + marketing
Register info, Order info, Promo mail.

Where do you source your database of email addresses?
CMS 1C-Bitrix (divasoft.ru)

Are all of your email addresses double-opt in?
I do not quite understand, but the mail has.

What is your expected monthly volume of messages?
100-1000, we just start online-store

Have you read our Email Best Practices document? 
Yes!

Can you please give us the URL that your users use to sign up for your email as well as a link to your Terms of Service?
In development, link to register page = https://divasoft.ru/login/?register=yes&backurl=%2F

p.s. этот сервис скрывает ip адрес сервера отправителя

Тонкий пробел в цене для 1С-Битрикс

, Михаил

Если у вас php 5.3, то понадобится обработчик


<?php 
/bitrix/php_interface/init.php
// Установка тонкого пробела в цене для php 5.3
AddEventHandler("currency""CurrencyFormat""thinNbspFormat");
function thinNbspFormat($fSum$strCurrency) {
	    $separator '&#8201;
	';
	    $summ preg_replace('/(?<=\d)\x' bin2hex($separator[0]) . '(?=\d)/'$separatornumber_format($fSum2'.'$separator)).$separator.'руб.';
	    $summ str_replace(".00"""$summ);
	    return $summ
	}
?>

Дальше в Админ-панели, в настройках Валюты->Языковые настройки (/bitrix/admin/currency_edit.php?lang=ru&ID=RUB)

Строка формата для вывода валюты: #&thinsp;руб.

Разделитель тысяч при выводе: Другое значение &thinsp;

Дальше меняем длину одного столбца в SQL таблице: ALTER TABLE `b_catalog_currency_lang` CHANGE `THOUSANDS_SEP` `THOUSANDS_SEP` varchar(10) COLLATE 'utf8_unicode_ci' NULL DEFAULT ' ' AFTER `DEC_POINT`;

И устанавливаем нужное значение разделителя вручную (т.к. в админке ограничение на 5 символов при вводе): UPDATE `b_catalog_currency_lang` SET `THOUSANDS_SEP` = ' ' WHERE `CURRENCY` = 'RUB' AND `LID` = 'ru';

Включаем PDO в BitrixEnv5 (Витруальная машина 1С-Битрикс)

, Михаил

Подключаем модуль PDO

mv /etc/php.d/pdo.ini.disabled /etc/php.d/pdo.ini
mv /etc/php.d/pdo_sqlite.ini.disabled /etc/php.d/pdo_sqlite.ini
mv /etc/php.d/pdo_mysql.ini.disabled /etc/pdo_mysql.d/pdo.ini
mv /etc/php.d/pdo_dblib.ini.disabled /etc/php.d/pdo_dblib.ini

Если получаем ошибку

Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock'

То смотрим в файле /etc/my.cnf где находится сокет

socket = /var/lib/mysqld/mysqld.sock

Добавляем в /etc/php.ini этот путь

pdo_mysql.default_socket=/var/lib/mysqld/mysqld.sock

Разные цены для разных сайтов 1С-Битрикс

, Михаил

В /bitrix/php_interface/init.php


<?php 
global $TYPE_PRICE;
$TYPE_PRICE 3;
 //ID цены на 1м сайте
?>

В /bitrix/php_interface/s2/init.php добавляем обработчик:


<?php 
AddEventHandler("catalog""OnGetOptimalPrice"'OnGetOptimalPriceHandler');
global $TYPE_PRICE;
$TYPE_PRICE 4;
  //ID цены на 2м сайте
function OnGetOptimalPriceHandler($productID$quantity 1$arUserGroups = array(), $renewal "N"$arPrices = array(), $siteID "s2"$arDiscountCoupons false) {
	    CModule::IncludeModule("iblock");
	    Cmodule::IncludeModule('catalog');
	    global $TYPE_PRICE;
	    $db_res CPrice::GetList(array(), array("PRODUCT_ID" => $productID"CATALOG_GROUP_ID" => $TYPE_PRICE));
	    if ($ar_res $db_res->Fetch()) {
		        $price $ar_res['PRICE'];
		        $currency $ar_res['CURRENCY'];
		        $arResult = array(
		            'PRICE' => array(
		                'PRICE' => $price,
		                'CURRENCY' => $currency,
		            )
		        );
		        $arDiscounts CCatalogDiscount::GetDiscount($productID15);
		 // ID Инфоблока с торговыми предложениями (в данном случае)
		        if ($arDiscounts) {
			            foreach ($arDiscounts as $arDiscount) {
				                $arResult['DISCOUNT_LIST'][] = array(
				                    'VALUE_TYPE' => $arDiscount['VALUE_TYPE'],
				                    'VALUE' => $arDiscount['VALUE'],
				                    'CURRENCY' => $arDiscount['CURRENCY']
				                );
				}
			}
		} else {
		        return true;
		}
	    return $arResult;
	}
?>

Ставим статус "Отменен" в заказе 1С-Битрикс

, Михаил

Ставим статус "Отменен" в заказе 1С-Битрикс

Для этого правим:


<?php 
/bitrix/modules/sale/general/order_loader.php
function collectOrderInfo($value) {
	***
	$arOrder["TRAITS"] = array();
	$arOrder["TRAITS"][GetMessage("CC_BSC1_CANCELED")] = $value["#"][GetMessage("CC_BSC1_CANCELED")][0]["#"];
	}

?>

Добавляем SITE_ID в экспорт/импорт заказов 1С-Битрикс

, Михаил

Нам нужно отправлять/принимать/обновлять id сайта, на котором был сделан заказ.

Для этого правим:

Добавляем SITE_ID в выгрузку заказа


<?php 

/bitrix/modules/sale/general/export.php
<<?=GetMessage("SALE_EXPORT_DOCUMENT")?>>
<SITE_ID><?=$arOrder["LID"]?></SITE_ID>

?>

Добавляем SITE_ID в импорт заказа, сохраняя основную функциональность


<?php 
/bitrix/modules/sale/general/order_loader.php
function collectOrderInfo($value) {
	***
	$arOrder["ID"] = $value["#"][GetMessage("CC_BSC1_NUMBER")][0]["#"];
	$arOrder["SITE_ID"] = ($value["#"]["SITE_ID"][0]["#"])?$value["#"]["SITE_ID"][0]["#"]:$this->arParams["SITE_NEW_ORDERS"];
	}

?>


<?php 
/bitrix/modules/sale/general/order_loader.php
if ($orderInfo['SITE_ID']!=$arOrder['SITE_ID']) {
	    $arAditFields["SITE_ID"]=$arOrder['SITE_ID'];
	    $arAditFields["UPDATED_1C"] = "Y";
	}
****
if(count($arAditFields)>0)
    CSaleOrder::Update($orderInfo["ID"], $arAditFields);

?>


<?php 
/bitrix/modules/sale/general/order_loader.php
if(IntVal($arOrder["USER_ID"]) > 0) {
	$orderFields = array( "SITE_ID" => $arOrder["SITE_ID"],
	***
	)
	}

?>

Открытие блога

, Ashe Gentle

Как и всякая хорошая вещь, блог «Дивасофт» начинался с идеи. Чужой. Где-то на просторах интернета мы подсмотрели дизайн с широкими картинками и узким текстом. Отталкиваясь от него, приступили к созданию своего варианта. Долго, очень долго мы вымеряли шаблоны. Придумывали, где разметить дату и подпись, использовать ли теги, как обойтись с картинками, что делать с разделением по страницам. И вот, спустя чуть ли не полгода, блог «Дивасофт» запущен и работает.

И он красивый. Да, вот так вот нескромно с нашей стороны это заявлять, но посудите сами. Чего только стоит исходный код страницы, залюбоваться можно. Разметка всех и вся сделана по стандартам HTML5. Вы только посмотрите на структуру каждого поста!

<article>
    <header>
        <h2><a href="">Открытие блога</a></h2>
        <span><time datetime="2014-05-16T12:00:00+04:00">16 Мая 2014</time>, <em>Ashe Gentle</em></span>
    </header>
    <p>Как и всякая хорошая вещь, блог «Дивасофт» начинался с идеи. Чужой. Где-то на просторах интернета мы подсмотрели дизайн с широкими картинками и узким текстом. Отталкиваясь от него, приступили к созданию своего варианта. Долго, очень долго мы вымеряли шаблоны. Придумывали, где разметить дату и подпись, использовать ли теги, как обойтись с картинками, что делать с разделением по страницам. И вот, спустя чуть ли не полгода, блог «Дивасофт» запущен и работает.</p>
    <h3>Подзаголовок</h3>
    <p>И он красивый. Да, вот так вот нескромно с нашей стороны это заявлять, но посудите сами. Чего только стоит исходный код страницы, залюбоваться можно. Разметка всех и вся сделана по стандартам HTML5. Вы только посмотрите на структуру каждого поста!</p>
    <footer>
        <em><a href="#">Бекэнд</a>, <a href="#">Bitrix</a></em>
    </footer>
</article>

Тут тебе и <article>, и <header>, и <footer>, и <time> и всё-всё-всё на свете. А как замечательно оформлены у нас цитаты:

<blockquote cite="http://divasoft.ru/blog/">
    <p>А как замечательно оформлены у нас цитаты</p>
</blockquote>

Отдельно стоит упомянуть типографику. Вы обратили внимание, что мы используем верные кавычки, принятые в русском языке — «ёлочки»? И, конечно, длинные тире вместо дефисов и минусов, которые, между прочим, не так просто поставить. Приходится использовать либо цифровые коды, либо специальные символы html.

А ещё мы точно продумали расположение изображений. Их может быть четыре рядом, три, два или одно на весь блок записи. И при всём этом отступы между ними будут равными. Пришлось повозиться с полпикселями, появляющимися в разных браузерах. Но и эту проблему мы решили, использовав float: right; для крайнего правого изображения.

Кстати, о полпикселях. Это происходит потому, что мы используем собственную процентную сетку. Которая тоже была разработана путём долгих и кровавых споров. У нас вообще никогда без них не обходится. Сейчас, например, воюем за или против переносов слов.

Всё вышесказанное относилось к вёрстке, но и с натягом вышло не так просто. Изначально планировалось использовать стандартный компонент битрикс — блог. Но когда уже всё было готово и мы приступили к тестированию, возникли непредвиденные проблемы. Оказалось, что начиная с 12 версии, разработчики битрикса запретили использование HTML-редактора в блогах. Наверное, это связано с безопасностью. И наверное, важно. Но нам абсолютно не подходит. Так реализация проекта застопорилась на неопределённое время, пока мы искали пути обхода. Как вариант рассматривали использование другой площадки — wordpress, например. Заточенный именно для блогов, он мог бы стать решением всех проблем. Но оказалось нецелесообразным разбивать единую систему. Поэтому мы сделали блог на инфоблоках.

И увидел Бог всё, что Он создал, и вот, хорошо весьма. И был вечер, и было утро...

Возможно, потом у нас будут комментарии и подписка, и конечно «новое видение» основного сайта. Пока это всё в проекте и в необозримом будущем. Но уже сейчас вы можете читать о жизни компании, её проблемах, решениях, разработках, проектах и о многом другом. Присоединяйтесь!

Стать партнёром битрикс

, Ashe Gentle

Некоторое время назад мы поставили себе цель стать сертифицированным партнёром битрикс. Это дало бы нам дополнительные плюшки и корпоративный портал, к тому же. Битрикс предлагает два способа: набрать 100 и более баллов по продажам их продуктов или пройти сертификацию по определённым курсам. Мы решили пойти по более простому (на наш взгляд) второму пути. И я приступила к изучению и прохождению кучи тестов. День за днём я читала, отвечала, снова читала и получала сертификаты. В результате собралась внушительная стопка.

Сертификаты Битрикс

И вот, когда были сданы все тесты, оставалось дело за малым: рассказать на нашем сайте о продуктах битрикс. Дав себе справедливый отдых, я отложила написание статьи на недельку. Этого времени хватило, чтобы продать нужное количество лицензий и заработать статус «Сертифицированный партнёр» по первому пути.